Files
pulse/internal/updates/updater.go
T
rcourtman e1d1ec1d7c Add update readiness checks for v6 upgrades
Surface server upgrade readiness on update plans so v5 agent continuity and agent token scope issues are checked before install.
2026-05-28 16:47:49 +01:00

118 lines
3.6 KiB
Go

package updates
import (
"context"
"fmt"
)
// UpdateRequest represents a request to update
type UpdateRequest struct {
Version string
Channel string
Force bool
DryRun bool
}
// UpdatePlan contains information about how an update will be performed
type UpdatePlan struct {
CanAutoUpdate bool `json:"canAutoUpdate"`
Instructions []string `json:"instructions"`
Prerequisites []string `json:"prerequisites"`
EstimatedTime string `json:"estimatedTime,omitempty"`
RequiresRoot bool `json:"requiresRoot"`
RollbackSupport bool `json:"rollbackSupport"`
DownloadURL string `json:"downloadUrl,omitempty"`
Readiness *UpdateReadiness `json:"readiness,omitempty"`
}
// UpdateReadiness is the operator-facing preflight summary attached to an
// update plan. It is advisory unless a check is explicitly "blocked".
type UpdateReadiness struct {
Status string `json:"status"`
Summary string `json:"summary"`
Checks []UpdateReadinessCheck `json:"checks"`
}
// UpdateReadinessCheck is one concrete compatibility or continuity check for
// the requested server update.
type UpdateReadinessCheck struct {
ID string `json:"id"`
Status string `json:"status"`
Title string `json:"title"`
Summary string `json:"summary"`
Details []string `json:"details,omitempty"`
}
func EmptyUpdatePlan() UpdatePlan {
return UpdatePlan{}.NormalizeCollections()
}
func (p UpdatePlan) NormalizeCollections() UpdatePlan {
if p.Instructions == nil {
p.Instructions = []string{}
}
if p.Prerequisites == nil {
p.Prerequisites = []string{}
}
if p.Readiness != nil && p.Readiness.Checks == nil {
p.Readiness.Checks = []UpdateReadinessCheck{}
}
return p
}
// UpdateProgress represents progress updates during an update
type UpdateProgress struct {
Stage string `json:"stage"`
Progress int `json:"progress"` // 0-100
Message string `json:"message"`
IsComplete bool `json:"isComplete"`
Error string `json:"error,omitempty"`
}
// ProgressCallback is called during update execution
type ProgressCallback func(progress UpdateProgress)
// Updater defines the interface for deployment-specific update logic
type Updater interface {
// SupportsApply returns true if this deployment type supports automated updates
SupportsApply() bool
// PrepareUpdate returns a plan describing how the update will be performed
PrepareUpdate(ctx context.Context, request UpdateRequest) (*UpdatePlan, error)
// Execute performs the update
Execute(ctx context.Context, request UpdateRequest, progressCb ProgressCallback) error
// Rollback rolls back to a previous version using a backup
Rollback(ctx context.Context, eventID string) error
// GetDeploymentType returns the deployment type this updater handles
GetDeploymentType() string
}
// UpdaterRegistry manages updaters for different deployment types
type UpdaterRegistry struct {
updaters map[string]Updater
}
// NewUpdaterRegistry creates a new updater registry
func NewUpdaterRegistry() *UpdaterRegistry {
return &UpdaterRegistry{
updaters: make(map[string]Updater),
}
}
// Register registers an updater for a deployment type
func (r *UpdaterRegistry) Register(deploymentType string, updater Updater) {
r.updaters[deploymentType] = updater
}
// Get returns the updater for a deployment type
func (r *UpdaterRegistry) Get(deploymentType string) (Updater, error) {
updater, ok := r.updaters[deploymentType]
if !ok {
return nil, fmt.Errorf("no updater registered for deployment type: %s", deploymentType)
}
return updater, nil
}