upgradesched

package
v0.8.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 26, 2026 License: Apache-2.0 Imports: 16 Imported by: 0

Documentation

Overview

Package upgradesched runs scheduled and automatic control plane upgrades. It decides when to start an upgrade and whether it is safe to; the upgrade itself is the existing host-run self-upgrade engine (internal/selfupgrade), started through Host. Every decision is level-triggered from persisted state, so a restart at any point resumes instead of repeating.

Index

Constants

View Source
const (
	ModeOff   = "off"
	ModePatch = "patch"
	ModeAny   = "any"
)

Policy modes.

View Source
const (
	MinWindowDuration = 10 * time.Minute
	MaxWindowDuration = 24 * time.Hour
	MaxMinReleaseAge  = 90
)

Window bounds, so a typo cannot make a window that never closes or never fits an upgrade.

View Source
const (
	VerdictEligible    = "eligible"
	VerdictNotNewer    = "not_newer"
	VerdictOff         = "off"
	VerdictPaused      = "paused"
	VerdictNotPatch    = "not_patch"
	VerdictMajor       = "major_jump"
	VerdictBreaking    = "breaking_changes"
	VerdictNoNotes     = "notes_unavailable"
	VerdictTooYoung    = "too_young"
	VerdictNoWindow    = "no_window"
	VerdictPriorFailed = "prior_failed"
)

Verdict codes.

View Source
const DefaultInterval = 30 * time.Second

DefaultInterval is how often Run evaluates schedules.

Variables

View Source
var (
	ErrReleaseNotFound  = errors.New("upgradesched: release not found on the configured channel")
	ErrBackupNotAvail   = errors.New("upgradesched: control plane backups are not configured")
	ErrAlreadyScheduled = errors.New("upgradesched: an upgrade is already scheduled")
	ErrRunning          = errors.New("upgradesched: the upgrade is already running")
	ErrNothingScheduled = errors.New("upgradesched: no upgrade is scheduled")
	ErrNoWindow         = errors.New("upgradesched: no maintenance window is configured")
	ErrNeedsAck         = errors.New("upgradesched: breaking changes need acknowledgement")
	ErrNeedsMajorAck    = errors.New("upgradesched: a major version jump needs acknowledgement")
	ErrInvalid          = errors.New("upgradesched: invalid request")
	// ErrEdgeTarget marks a target that is an edge container image: it has
	// no release binary, so no upgrade can be scheduled for it.
	ErrEdgeTarget = errors.New("upgradesched: edge builds are container images and cannot be applied by an upgrade")
)

Sentinel errors the API maps to status codes.

Functions

func BlockerSummary

func BlockerSummary(checks []upgrade.Check) string

BlockerSummary renders the blocking checks as one line.

func CrossesMajor

func CrossesMajor(current, target string) bool

CrossesMajor reports whether target has a different major version than current.

func NewScheduleID

func NewScheduleID() string

NewScheduleID returns a random schedule id.

func SamePatchLine

func SamePatchLine(current, target string) bool

SamePatchLine reports whether target differs from current only in the patch number.

Types

type AckError

type AckError struct{ IDs []string }

AckError lists the breaking changes a request left unacknowledged.

func (*AckError) Error

func (e *AckError) Error() string

func (*AckError) Is

func (e *AckError) Is(target error) bool

Is lets errors.Is match ErrNeedsAck.

type AttemptInfo

type AttemptInfo struct {
	ID      string
	Outcome string
	Error   string
}

AttemptInfo is the outcome of a self-upgrade run.

type Breaking

type Breaking struct {
	ID          string
	Version     string
	Summary     string
	RequiresAck bool
}

Breaking is one breaking change between the running version and a target.

type Candidate

type Candidate struct {
	Tag         string
	PublishedAt time.Time
}

Candidate is a release being considered.

type Config

type Config struct {
	// Grace is how long after a manual run_at the scheduler keeps retrying.
	Grace time.Duration
	// RetryInterval is the minimum gap between fire-time re-checks.
	RetryInterval time.Duration
	// StartTimeout is how long a fired upgrade may show no attempt before it
	// is reported as not started.
	StartTimeout time.Duration
	// MaxAhead bounds how far in the future a manual schedule may be.
	MaxAhead time.Duration
	// MinLead is the shortest time between now and a scheduled run.
	MinLead time.Duration
}

Config holds the scheduler's tunables, all overridable from the environment.

func ConfigFromEnv

func ConfigFromEnv(lookup func(string) (string, bool)) Config

ConfigFromEnv applies APP_UPGRADE_SCHEDULE_* overrides to the defaults.

func DefaultConfig

func DefaultConfig() Config

DefaultConfig returns the defaults used when no environment override is set.

type Host

type Host interface {
	// Latest is the newest release on the configured channel. It returns the
	// candidate together with ErrEdgeTarget when that is an edge image.
	Latest(ctx context.Context) (Candidate, bool, error)
	// Plan describes target, or returns ErrReleaseNotFound.
	Plan(ctx context.Context, target string) (Plan, error)
	// Preflight evaluates the checks for target, bypassing release caches.
	Preflight(ctx context.Context, target string) (PreflightResult, error)
	// BackupNow takes a control plane backup and returns its name.
	BackupNow(ctx context.Context) (string, error)
	// Apply starts the host upgrade.
	Apply(ctx context.Context, target string, ack []string, initiator string) error
	// Running reports whether a self-upgrade is in progress on the host.
	Running(ctx context.Context) bool
	// Attempt finds the newest recorded attempt started by initiator.
	Attempt(ctx context.Context, initiator string) (AttemptInfo, bool, error)
}

Host is everything the scheduler needs from the running control plane. The API router implements it, so the scheduler uses the same release lookup, preflight and launcher a person clicking "Upgrade now" does.

type Notifier

type Notifier interface {
	Notify(ctx context.Context, channelIDs []string, text string) error
}

Notifier sends a plain-text notice to alert channels. An empty id list means every enabled channel, so an unconfigured policy still alerts.

type Plan

type Plan struct {
	Current        string
	Target         string
	Breaking       []Breaking
	NotesAvailable bool
	CanApply       bool
	CannotApplyWhy string
	PublishedAt    time.Time
}

Plan is what a target release would change.

func (Plan) MissingAcks

func (p Plan) MissingAcks(acked []string) []string

MissingAcks returns the ids of breaking changes that need an acknowledgement and were not given one.

func (Plan) NeedAckCount

func (p Plan) NeedAckCount() int

NeedAckCount counts breaking changes that need an acknowledgement.

type Policy

type Policy struct {
	Mode              string
	Window            Window
	MinReleaseAgeDays int
	Paused            bool
	NotifyChannelIDs  []string
}

Policy is the automatic upgrade policy.

func PolicyFromStore

func PolicyFromStore(row store.UpgradePolicy, channelIDs []string) Policy

PolicyFromStore converts the persisted row.

func (Policy) Active

func (p Policy) Active() bool

Active reports whether the policy may start upgrades on its own.

func (Policy) Validate

func (p Policy) Validate() error

Validate checks a policy before it is saved. A window is required once automatic upgrades are on, since "any time" is exactly what it must avoid.

type PreflightResult

type PreflightResult struct {
	Checks  []upgrade.Check
	Blocked bool
	// Edge is set when the target is an edge container image, which is never
	// installable by an upgrade.
	Edge bool
}

PreflightResult is a fresh evaluation of the upgrade preflight.

type Request

type Request struct {
	// Target is the release tag, empty for the newest on the channel.
	Target string
	// At is the earliest run time. Ignored when Window is set.
	At time.Time
	// Window runs in the next maintenance window instead of at At.
	Window bool
	// Timezone is the zone the operator thinks in, recorded for display.
	Timezone string
	Ack      []string
	MajorAck bool
	// Replace cancels a pending or blocked schedule first.
	Replace bool
	Actor   string
}

Request asks for one upgrade at a time.

type Scheduler

type Scheduler struct {
	Store    Store
	Host     Host
	Windows  func(ctx context.Context) ([]alerting.MaintenanceWindow, error)
	Notifier Notifier
	Config   Config
	Logger   *slog.Logger
	Now      func() time.Time
	// Current returns the running version.
	Current func() string
	// Audit records a scheduler-initiated change in the audit log.
	Audit func(ctx context.Context, detail string)
	// NewID returns a schedule id.
	NewID func() string
	// contains filtered or unexported fields
}

Scheduler fires scheduled upgrades and, when a policy allows, creates them.

func (*Scheduler) Cancel

func (s *Scheduler) Cancel(ctx context.Context, actor string) (store.UpgradeSchedule, error)

Cancel withdraws the active schedule unless it is already running.

func (*Scheduler) NextWindow

func (s *Scheduler) NextWindow(ctx context.Context, now time.Time) (Span, bool, error)

NextWindow picks the earliest opening among the upgrade policy's window and the enabled alert maintenance windows that cover everything.

func (*Scheduler) Run

func (s *Scheduler) Run(ctx context.Context, interval time.Duration) error

Run evaluates the schedule on every interval until ctx is done.

func (*Scheduler) Schedule

func (s *Scheduler) Schedule(ctx context.Context, req Request) (store.UpgradeSchedule, error)

Schedule validates req against the target release and stores it.

func (*Scheduler) Tick

func (s *Scheduler) Tick(ctx context.Context) error

Tick advances the active schedule, or considers creating one under the automatic policy. It is safe to call at any time and any number of times.

type Span

type Span struct {
	Start time.Time
	End   time.Time
}

Span is one concrete opening of a window.

type Store

type Store interface {
	GetUpgradePolicy(ctx context.Context) (store.UpgradePolicy, error)
	SetUpgradeDecision(ctx context.Context, tag, decision, at string) error
	InsertUpgradeSchedule(ctx context.Context, s store.UpgradeSchedule) error
	ActiveUpgradeSchedule(ctx context.Context) (*store.UpgradeSchedule, error)
	ListUpgradeSchedules(ctx context.Context, limit int) ([]store.UpgradeSchedule, error)
	UpdateUpgradeSchedule(ctx context.Context, s store.UpgradeSchedule, from ...string) (bool, error)
}

Store is the persistence the scheduler uses. *store.DB satisfies it.

type Verdict

type Verdict struct {
	OK     bool
	Code   string
	Reason string
	// WaitUntil is set when the only thing missing is time.
	WaitUntil time.Time
}

Verdict is the result of weighing a candidate against a policy.

func Eligible

func Eligible(p Policy, current string, cand Candidate, breakingNeedAck int, notesAvailable bool, now time.Time) Verdict

Eligible decides whether the policy may apply cand on its own. Anything that needs a human (breaking changes, a major jump, unreadable notes) is never eligible: automatic upgrades only take releases nobody needs to read.

type Window

type Window struct {
	Cron     string
	Duration time.Duration
	Timezone string
}

Window is a recurring maintenance window: it opens at each cron match in Timezone and stays open for Duration. It reuses the alerting window model so cron and daylight saving semantics are the ones operators already know.

func (Window) Next

func (w Window) Next(now time.Time) (Span, bool, error)

Next returns the window opening to use at now: the current one when the window is open, otherwise the next one.

func (Window) Validate

func (w Window) Validate() error

Validate checks the cron expression, duration and timezone.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL