Documentation
¶
Overview ¶
Package updater is the on-box update agent: it reads the signed channel manifest, picks a target under policy, and stages it for the supervisor.
Index ¶
- Constants
- Variables
- func Bucket(id []byte, version string) int
- func DiskBlocker(free func(dir string) (uint64, error), dir string, artifactSize int64) string
- func FreeDisk(dir string) (uint64, error)
- func InWindow(now time.Time, loc *time.Location, start, end *int) bool
- func InstallID(dataDir string) ([]byte, error)
- func ParseMode(v string) (string, bool)
- func PinMatches(pin, v string) bool
- func RampPct(steps []release.RampStep, now time.Time) int
- func SDKBlockers(rec *release.Record, ranges []NamedRange) []string
- func SameMajor(a, b string) bool
- type Backuper
- type Decision
- type Env
- type Fetcher
- type ImportProbe
- type Input
- type NamedRange
- type PluginSource
- type Querier
- type RunOpts
- type Settings
- type Status
- type Store
- func (s *Store) Load(ctx context.Context) (Settings, Status, error)
- func (s *Store) SavePolicy(ctx context.Context, set Settings, cols ...string) error
- func (s *Store) SaveSettings(ctx context.Context, set Settings) error
- func (s *Store) SaveStatus(ctx context.Context, st Status) error
- func (s *Store) ServerMajor(ctx context.Context) (int, error)
- type Updater
Constants ¶
const ( ActionNone = "none" ActionApply = "apply" ActionRollback = "rollback" ActionAvailable = "available" // a major release exists; never applied automatically ActionBlocked = "blocked" ActionWait = "wait" )
Actions a Decision can ask for.
const ( KindUpToDate = "up_to_date" KindScheduled = "scheduled" KindWaitingWindow = "waiting_window" KindWaitingDefer = "waiting_defer" KindBlocked = "blocked" KindAvailableMajor = "available_major" KindApplying = "applying" KindRolledBackYank = "rolled_back_yanked" KindRolledBack = "rolled_back" // the health gate rolled the newest release back KindDev = "dev" KindBackupFailed = "backup_failed" // the pre-update backup failed; the update was not installed )
Decision kinds (the Status.Decision vocabulary that Decide produces).
const ( MaxManifestSize = 1 << 20 MaxRecordSize = 64 << 10 MaxSigSize = 4 << 10 )
Size caps for what the updater downloads.
const ( KindStaged = "staged" KindOff = "off" KindNotify = "notify" // KindRollbackPending: a check found the running release withdrawn and // changed nothing; the next scheduled run rolls the site back. KindRollbackPending = "rollback_pending" KindBusy = "busy" // KindFailed is a managed apply that stopped with an error (a download or // signature failure, a withdrawn target): the owner's page shows it. KindFailed = "update_failed" )
Further Status.Decision values, next to those Decide produces.
const ( ResultOK = "ok" ResultError = "error" ResultSkipped = "skipped" )
Status.LastResult values.
const ( ColMode = "mode" ColChannel = "channel" ColWindow = "window" ColDefer = "defer_days" ColPin = "pin" )
Policy columns SavePolicy can write.
const ( ModeAuto = "auto" ModeNotify = "notify" ModeOff = "off" // DefaultChannel is the channel a box follows when nothing says otherwise. DefaultChannel = "stable" // MaxDeferDays is the longest a non-security release may be held back. MaxDeferDays = 14 )
Update modes.
const ( EnvMode = "HATAMOTO_UPDATES" EnvChannel = "HATAMOTO_UPDATE_CHANNEL" )
Environment variables that override the settings row.
const DefaultBaseURL = "https://hatamoto.dev/releases"
DefaultBaseURL is where release channels and artifacts are published.
const DiskFactor = 3
DiskFactor is how many times the artifact size must be free under the data directory before an update starts (download plus staged copy plus slack).
const EnvBackup = "HATAMOTO_UPDATE_BACKUP"
EnvBackup set to "0" turns the pre-update backup off.
const InstallIDFile = "install_id"
InstallIDFile is the name of the install id file under the data directory.
const MaxRecordFetches = 5
MaxRecordFetches bounds how many records one decision may read.
Variables ¶
var ErrInstallID = errors.New("install_id is unreadable")
ErrInstallID means the install id file exists but is not 32 bytes. It is never replaced silently: a new id would move the box to another bucket.
var ErrStalled = errors.New("download stalled")
ErrStalled means a download delivered no bytes for too long.
var ErrTooLarge = errors.New("response too large")
ErrTooLarge means a response exceeded its size cap.
Functions ¶
func Bucket ¶
Bucket places an install in 0..99 for one version: the first two bytes of sha256(id || version) as a big-endian number, modulo 100. An install is in a rollout of pct percent when Bucket < pct.
func DiskBlocker ¶
DiskBlocker returns a reason when dir has less than DiskFactor times artifactSize free, or "" when it has enough. free reads the free bytes (FreeDisk in production); a failure to read blocks.
func InWindow ¶
InWindow reports whether now falls inside the maintenance window, hours 0-23 in loc (UTC when nil). Either bound nil, or start == end, means any time; start > end wraps midnight. The window is [start, end).
func InstallID ¶
InstallID returns the box's random id, creating the file (0600) on first use. The id picks the rollout bucket and never leaves the machine.
func ParseMode ¶
ParseMode reads a HATAMOTO_UPDATES value: off|false|0|no|disabled, on|auto|true|1, or notify, in any case.
func PinMatches ¶
PinMatches reports whether v lies inside the pin ("vX.Y" allows vX.Y.*). An empty pin allows everything; an invalid pin or version allows nothing.
func RampPct ¶
RampPct is the percentage of the last ramp step that has started by now (the step with the latest start time not after now). A step whose start time does not parse is ignored; no started step means 0. Step order does not matter.
func SDKBlockers ¶
func SDKBlockers(rec *release.Record, ranges []NamedRange) []string
SDKBlockers returns one sentence per plugin or theme the target release would break: its range is met neither by rec.SDK nor by any version in rec.SDKCompatibleWith. An invalid range or version blocks (fail closed).
Types ¶
type Backuper ¶
Backuper takes the backup that must exist before a switch. An error blocks the update; the note is added to the status text.
type Decision ¶
type Decision struct {
Action string
Kind string
Target string
Rec *release.Record // set for apply and wait
Reason string
AvailableMajor string
FirstSeen map[string]time.Time // input map plus newly seen candidates, pruned to the manifest
// Fallback is the yank-back to use when this apply turns out not to be
// installable (the preflight Decide cannot see). Set only for an apply that
// replaces a withdrawn running release, and only when that yank-back is allowed.
Fallback *Decision
}
Decision is what to do about the channel.
func Decide ¶
Decide picks a target. Order: a yank of the running version (move to its replacement or roll back), then candidates of the same major that are not yanked, bad or halted and whose bucket is inside the ramp, newest first; records are read lazily for min_from stepping, pin and kind; then defer and window. Security releases ignore defer, window and the pin (inside the major). Majors only ever yield ActionAvailable.
type Env ¶
type Env struct {
Data string // absolute data dir
Version string // the running version
Platform string // "" = this build's platform
Supervisor supervisor.Config
Store *Store // nil: no stored policy or status (the offline CLI)
Plugins PluginSource
Imports ImportProbe
Backups Backuper // nil blocks the update unless the backup is skipped
Site func(ctx context.Context) *time.Location
DatabaseURL string
BaseURL string // "" = DefaultBaseURL
Log *slog.Logger
Now func() time.Time
}
Env is what the updater needs from the process around it. The updater imports neither the app nor the server; they hand in these pieces.
type Fetcher ¶
type Fetcher struct {
// contains filtered or unexported fields
}
Fetcher makes the updater's only network requests: plain anonymous GETs. A request carries no headers of its own (Go adds User-Agent and Accept-Encoding), no query, no cookies and no identifier of the install.
func NewFetcher ¶
NewFetcher validates baseURL ("" = DefaultBaseURL). It must be https; plain http to a loopback host is accepted only in test builds.
func (*Fetcher) Download ¶
func (f *Fetcher) Download(ctx context.Context, version, platform, dst string, max int64) (err error)
Download streams the binary for version and platform to dst, refusing more than max bytes. It stops after a stall (no bytes for two minutes) or 30 minutes in all. The clock is detached from ctx's deadline, since the scheduler's job cap is far shorter than a slow download. Cancelling ctx (the server shutting down) does stop it. A failed download leaves no file behind.
type ImportProbe ¶
ImportProbe reports whether a content import is running right now.
type Input ¶
type Input struct {
Running string
RunningRecord *release.Record // for rollback_floor; may be nil
Manifest *release.ChannelManifest
Settings Settings // effective: defaults and environment applied
Now time.Time
Loc *time.Location // site timezone for the window; nil = UTC
Bucket func(version string) int
Bad func(version string) bool // versions that failed health gating; may be nil
BadReason func(version string) string // why a bad version failed; may be nil
FirstSeen map[string]time.Time
// Previous is what a yank-back returns to: the supervisor's previous
// release, or when PreviousIsBootstrap the bootstrap build's version
// ("" or not semver when unknown). "" with PreviousIsBootstrap false: none.
Previous string
PreviousIsBootstrap bool
// Record reads and verifies one release record. Decide memoises it and
// calls it at most MaxRecordFetches times.
Record func(version string) (*release.Record, error)
}
Input is everything Decide looks at. It does no I/O of its own.
type NamedRange ¶
NamedRange is the SDK range one enabled plugin or the active theme declares.
type PluginSource ¶
type PluginSource interface {
EnabledSDKRanges(ctx context.Context) ([]NamedRange, error)
}
PluginSource lists the SDK ranges of the enabled plugins and the active theme. The adapter over the package store lives with the server wiring.
type Querier ¶
type Querier interface {
QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
Exec(ctx context.Context, sql string, args ...any) (pgconn.CommandTag, error)
}
Querier is the part of a pgx pool the Store needs.
type RunOpts ¶
type RunOpts struct {
DryRun bool // decide only; nothing is stored or staged
Apply string // a named version: the explicit operator route
Force bool // skip the plugin preflight
// CheckOnly is a dry run whose outcome is stored like a scheduled check:
// fetch, decide and preflight, never download, stage or switch.
CheckOnly bool
// Managed makes a named Apply behave like a scheduled install where it
// matters to the owner: it waits for a running check instead of giving up,
// takes the pre-update database backup and stores its outcome whatever it
// is. The CLI leaves it off.
Managed bool
// SkipBackup skips the pre-update backup for this run only.
SkipBackup bool
}
RunOpts shapes one Run.
type Settings ¶
type Settings struct {
Mode string
Channel string
WindowStart *int
WindowEnd *int
Pin string // "vX.Y" or ""
DeferDays int // 0..MaxDeferDays
}
Settings is the update policy. Mode and Channel are empty when unset; Effective fills the defaults. A nil window hour means "any time".
func (Settings) Effective ¶
Effective returns s with the defaults filled and the environment applied. Environment values win over the row. An unrecognised HATAMOTO_UPDATES fails closed to off (with a warning), since a mistyped "false" must not leave the box calling out and installing; an invalid channel is logged and ignored.
type Status ¶
type Status struct {
LastCheckAt *time.Time
LastResult string
Decision string
Reason string
Target string
AvailableMajor string
FirstSeen map[string]time.Time
}
Status is the outcome of the last run. Decision is one of up_to_date, scheduled, waiting_window, waiting_defer, blocked, available_major, staged, applying, rolled_back_yanked, off, notify, busy, dev; LastResult is ok, error or skipped; Reason is human text.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes the single hatamoto_update_settings row.
func (*Store) SavePolicy ¶
SavePolicy validates set and writes only the named policy columns, so what was not named keeps its stored value: a column left NULL stays the built-in default, and a pin set meanwhile (the command line) is not overwritten.
func (*Store) SaveSettings ¶
SaveSettings validates and stores the policy columns, leaving the status alone.
func (*Store) SaveStatus ¶
SaveStatus stores the status columns, leaving the policy alone.
type Updater ¶
type Updater struct {
// contains filtered or unexported fields
}
Updater runs update checks, one at a time.
func (*Updater) Run ¶
Run does one check and, under policy, the update. It makes exactly one request before a target exists: the channel manifest.
func (*Updater) RunningVersion ¶
RunningVersion is the version the supervisor runs now, which after a self-update or a rollback is the current release and not the version this process was stamped with. It falls back to Version when the state cannot be read.
func (*Updater) Schedule ¶
Schedule registers the periodic check: every six hours give or take half an hour, the first one a few minutes after start. The run detaches from the scheduler's 10 minute cap, which is shorter than a slow download, but not from its cancellation: the scheduler's context is the server's lifecycle context, so a shutdown stops the download and the pg_dump.