Documentation
¶
Overview ¶
Package selfupgrade replaces the control plane binary with a verified newer release and rolls binary and data back if it does not become healthy. It runs from a host command, never inside the process being replaced.
Index ¶
- Constants
- Variables
- func AckError(missing []Breaking) error
- func DowngradeMessage(in GuardInput) string
- func ImportJournal(ctx context.Context, j *Journal, db AttemptStore) (int, error)
- func ParseChecksums(r io.Reader) (map[string]string, error)
- func ToStore(a Attempt) store.SelfUpgradeAttempt
- func ValidTag(tag string) bool
- func VerifyChecksum(path, checksumsPath, asset string) error
- type Attempt
- type AttemptStore
- type BackupInfo
- type Breaking
- type Deps
- type Fetched
- type Fetcher
- type GuardInput
- type Journal
- type ProbeInfo
- type ReleaseNotes
- type Request
- type Signer
- type StepRecord
Constants ¶
const ( StepGuard = "guard" StepDownload = "download" StepChecksum = "verify_checksum" StepSignature = "verify_signature" StepProbe = "probe_binary" StepBackup = "backup" StepMigrationCheck = "migration_check" StepStop = "stop" StepSwap = "swap" StepStart = "start" StepHealth = "health" StepRollback = "rollback" )
Step names, in the order Apply runs them.
const ( StatusOK = "ok" StatusFailed = "failed" StatusSkipped = "skipped" )
Step statuses.
const ( OutcomeRunning = "running" OutcomeSucceeded = "succeeded" OutcomeRolledBack = "rolled_back" OutcomeRefused = "refused" OutcomeFailed = "failed" )
Attempt outcomes. Running is only ever seen in a journal.
const ( SourceMetadata = "metadata" SourceHeading = "heading" )
Breaking change sources.
const ( VerifyAuto = "auto" VerifyRequire = "require" VerifyOff = "off" )
Signature modes, the same vocabulary as the installer's APP_INSTALL_VERIFY.
const DefaultHealthTimeout = 90 * time.Second
DefaultHealthTimeout bounds how long a new release may take to become healthy before the previous binary and data are put back.
const ExitDowngradeRefused = 78
ExitDowngradeRefused is the process exit status when a binary refuses to start on a database written by a newer release. systemd can be told not to restart on it (RestartPreventExitStatus).
const JournalDir = "self-upgrade"
JournalDir is the directory under the data dir that holds attempt files.
const MetaMarker = "levelrail-upgrade:"
MetaMarker opens the machine-readable upgrade metadata comment a release body may carry. The JSON inside lists breaking changes explicitly.
Variables ¶
var ( ErrChecksumMismatch = errors.New("downloaded binary does not match its published checksum") ErrChecksumMissing = errors.New("release publishes no checksum for this binary") ErrSignatureInvalid = errors.New("release signature verification failed") ErrSignatureRequired = errors.New("signature verification is required but unavailable") ErrBreakingNotAcked = errors.New("breaking changes need acknowledgement") ErrDowngrade = errors.New("target release is older than the database schema") ErrVersionMismatch = errors.New("downloaded binary reports a different version than requested") ErrMigrationCheck = errors.New("migration dry-run failed") ErrNotHealthy = errors.New("new release did not become healthy") // ErrDryRunUnsupported is returned by a migration check when the target // binary has no dry-run subcommand, which is not a failure. ErrDryRunUnsupported = errors.New("target binary has no migration dry-run") )
Refusals that change nothing on the host.
Functions ¶
func DowngradeMessage ¶
func DowngradeMessage(in GuardInput) string
DowngradeMessage explains the refusal and gives the exact recovery commands. Migrations only go forward, so the database cannot be opened by an older binary, and the message says which release to run instead.
func ImportJournal ¶
ImportJournal copies every journaled attempt into the database and removes the files of finished ones. Running attempts stay on disk so a recovery can still read their paths. It returns how many rows were written.
func ParseChecksums ¶
ParseChecksums reads sha256sum output into name to hex digest.
func ToStore ¶
func ToStore(a Attempt) store.SelfUpgradeAttempt
ToStore converts an attempt to its database row.
func ValidTag ¶
ValidTag accepts release tags such as v1.2.3 or v1.2.3-beta.4 and nothing that could escape a URL path or a file name.
func VerifyChecksum ¶
VerifyChecksum checks the file at path against asset's entry in the checksums file.
Types ¶
type Attempt ¶
type Attempt struct {
ID string `json:"id"`
FromVersion string `json:"from_version"`
ToVersion string `json:"to_version"`
FromSchema int `json:"from_schema"`
ToSchema int `json:"to_schema"`
Initiator string `json:"initiator"`
Outcome string `json:"outcome"`
FailedStep string `json:"failed_step,omitempty"`
Error string `json:"error,omitempty"`
BackupName string `json:"backup_name,omitempty"`
Acked []string `json:"acked,omitempty"`
Steps []StepRecord `json:"steps"`
StartedAt time.Time `json:"started_at"`
FinishedAt time.Time `json:"finished_at,omitempty"`
PrevBinary string `json:"prev_binary,omitempty"`
SnapshotPath string `json:"snapshot_path,omitempty"`
SwappedBinary bool `json:"swapped_binary,omitempty"`
}
Attempt is one upgrade run. The unexported-to-the-database fields at the end (paths) only live in the journal so a restart in the middle can recover.
func Apply ¶
Apply runs the upgrade: guard, download, verify, back up, migration dry run, swap, restart, health check, and rollback on failure. Nothing on the host changes before the stop step. err is nil only on success.
func Recover ¶
Recover finishes an attempt whose host command died mid-way: closes one that never stopped the service, rolls back one that did, confirms a healthy one.
type AttemptStore ¶
type AttemptStore interface {
UpsertSelfUpgradeAttempt(ctx context.Context, a store.SelfUpgradeAttempt) error
}
AttemptStore is the persistence ImportJournal needs; *store.DB satisfies it.
type BackupInfo ¶
BackupInfo names the pre-upgrade restore point.
type Breaking ¶
type Breaking struct {
ID string `json:"id"`
Version string `json:"version"`
Summary string `json:"summary"`
RequiresAck bool `json:"requires_ack"`
Source string `json:"source"`
}
Breaking is one breaking change between two versions. RequiresAck means the upgrade refuses to start until the operator acknowledges it by id.
func BreakingBetween ¶
func BreakingBetween(releases []ReleaseNotes, current, target string) []Breaking
BreakingBetween collects the breaking changes of every release newer than current and no newer than target, oldest first. Versions that do not order are skipped: an unknown tag cannot be said to sit between the two.
func MissingAcks ¶
MissingAcks returns the breaking changes that require acknowledgement and were not acknowledged.
func ParseBreaking ¶
ParseBreaking extracts the breaking changes one release body declares. The explicit metadata comment wins; without it, bullets under a "Breaking changes" heading count and always require acknowledgement.
type Deps ¶
type Deps struct {
BinaryPath string
WorkDir string
Asset string
Journal *Journal
Log *slog.Logger
Now func() time.Time
Fetch func(ctx context.Context, tag, asset, dir string) (Fetched, error)
VerifySig func(ctx context.Context, checksumsPath, bundlePath string) (string, error)
Probe func(ctx context.Context, binary string) (ProbeInfo, error)
DBSchema func(ctx context.Context) (int, error)
Backup func(ctx context.Context) (BackupInfo, error)
MigrateTest func(ctx context.Context, binary, snapshotPath string) (int, error)
Stop func(ctx context.Context) error
Start func(ctx context.Context) error
WaitHealthy func(ctx context.Context, timeout time.Duration) error
// RestoreDB puts the snapshot in place of the live database and returns
// where the replaced database was kept.
RestoreDB func(ctx context.Context, snapshotPath string) (string, error)
WriteMarker func(ctx context.Context, a Attempt) error
ClearMarker func()
Audit func(ctx context.Context, detail string) error
}
Deps are the host operations Apply drives. Tests fake them; the host command wires real ones (HTTPS, cosign, systemd, sqlite, filesystem).
type Fetched ¶
Fetched is a downloaded release: the binary, its checksums file and the signature bundle (empty when the release publishes none).
type Fetcher ¶
type Fetcher struct {
// BaseURL is the release download prefix with the tag as its last path
// segment appended by Fetch, for example https://host/org/repo/releases/download.
BaseURL string
Client *http.Client
// AllowHTTP permits a plain-http BaseURL, for tests against a local server.
AllowHTTP bool
MaxBytes int64
}
Fetcher downloads release assets over HTTPS.
type GuardInput ¶
type GuardInput struct {
BinaryVersion string
BinarySchema int
DBSchema int
// DBVersion is the release that last ran on the database, "" if unknown.
DBVersion string
// Program is the executable's name, used in the recovery commands.
Program string
// InstallerCommand is the verified installer invocation prefix, for
// example "curl -fsSL <url> | sudo".
InstallerCommand string
}
GuardInput describes a binary that found a database newer than itself.
type Journal ¶
type Journal struct {
Dir string
}
Journal persists attempts as one JSON file each. It is the source of truth while an attempt runs, because the database may be swapped underneath it.
func NewJournal ¶
NewJournal returns the journal under dataDir.
func (*Journal) List ¶
List returns every readable attempt, oldest first. Unreadable files are skipped: a half-written journal must not block an upgrade.
type ReleaseNotes ¶
ReleaseNotes is one release's tag and notes body.
type Request ¶
type Request struct {
ID string
CurrentVersion string
TargetVersion string
Initiator string
Breaking []Breaking
Acked []string
HealthTimeout time.Duration
}
Request is one upgrade the operator asked for.
type Signer ¶
type Signer struct {
Mode string
CosignPath string
IdentityRegexp string
Issuer string
// Run executes the verifier; nil uses os/exec. Tests inject a fake.
Run func(ctx context.Context, name string, args ...string) ([]byte, error)
}
Signer verifies a cosign keyless bundle over a checksums file.