Documentation
¶
Overview ¶
Package upgrade replaces the running ephemerd binary with a specific published release and restarts the service into it.
The delivery model is "command, not bytes": a caller (the CLI or the control-plane Upgrade RPC) says "go to version vX.Y.Z" and the daemon downloads + checksum-verifies the release asset over its OWN outbound HTTPS — the same channel `install` and cloudflared already use. This is provider-agnostic and has no exec-channel size or timeout limit, unlike pushing an ~1 GB zip through a hypervisor guest-agent exec.
Safety is staged: nothing touches the live binary until the new one is downloaded, checksum-verified, and (when natively runnable) probed with `--version`. The old binary is kept alongside as `<name>.old` for rollback. Any failure before the final swap leaves the node running the old binary; only swap+restart is the point of no easy return, and it happens last.
Index ¶
- Constants
- func AssetName(version, goos, goarch string) string
- func ManualRestartHint() string
- func NormalizeVersion(v string) string
- func ParseChecksums(r io.Reader) (map[string]string, error)
- func RestartService(_ context.Context, name string, _ RestartOptions, _ *slog.Logger) error
- func Run(ctx context.Context, opts RunOptions, emit Emit) (retErr error)
- func SameVersion(a, b string) bool
- func ValidVersion(v string) bool
- type Drainer
- type Emit
- type Progress
- type RestartOptions
- type RunOptions
- type State
Constants ¶
const ( DefaultRestartStopTimeout = 8 * time.Minute DefaultRestartStartTimeout = 2 * time.Minute )
Default bounds for RestartService. The stop budget is deliberately larger than the daemon's own SCM stop backstop (6m in cmd/ephemerd/svc_windows.go) so the helper never gives up on a stop that is still legitimately draining.
const RestartHelperCommand = "__restart-service"
RestartHelperCommand is argv[1] of the hidden CLI command that performs a service restart on behalf of a daemon that is about to be stopped.
It lives here rather than in cmd/ephemerd because both sides need it: this package spawns `<ephemerd> __restart-service` (see triggerRestart on Windows) and cmd/ephemerd registers the command that answers to it. The double underscore marks it as internal plumbing; it is hidden from help.
Variables ¶
This section is empty.
Functions ¶
func AssetName ¶
AssetName derives the release asset filename for a target/OS/arch, e.g. ephemerd_v0.1.7_windows_amd64.zip. Confirmed against the v0.1.6 release.
func ManualRestartHint ¶ added in v0.1.9
func ManualRestartHint() string
ManualRestartHint is the command an operator runs to restart the service by hand on this platform. Used in the failure messages an upgrade emits when the automatic restart does not take, so the remediation is never a guess.
func NormalizeVersion ¶
NormalizeVersion trims and ensures a leading "v" so "0.1.7" and "v0.1.7" compare equal.
func ParseChecksums ¶
ParseChecksums parses a `sha256sum`-style checksums.txt into a filename→hex-digest map. Malformed lines are skipped; an empty result is an error.
func RestartService ¶ added in v0.1.9
RestartService is Windows-only. systemd and launchd both accept a restart request from a process inside the unit — `systemctl restart --no-block` hands the job to PID 1, `launchctl kickstart -k` to launchd — so the Unix paths need no detached helper and never call this.
func Run ¶
func Run(ctx context.Context, opts RunOptions, emit Emit) (retErr error)
Run executes the upgrade end to end, emitting Progress at each phase.
Sequence (reusing #132's cordon): preflight → cordon + wait for jobs to drain to idle → download → verify checksum → stage + probe → swap → restart. On success the final emitted Progress is StateRestarting and Run returns nil BEFORE the service actually restarts (the restart is scheduled detached, after restartDelay). The caller then polls Status until the reported version matches the target.
Any error before the swap emits StateFailed, re-uncordons the scheduler, and leaves the node running the old binary.
The cordon is never allowed to outlive a failed upgrade. Every exit path — error return, panic, and the post-return case where the service manager simply never restarts us — un-cordons the scheduler, because a node that is drained and NOT upgraded is worse than one that never attempted the upgrade: it looks healthy while quietly accepting no work.
Those paths all assume the upgrade eventually STOPS. The remaining way to hold a cordon forever is to hang, so the downloads carry a stall timeout and the post-drain phase carries an install budget that interrupts them and halts the local steps at the next phase boundary. Both turn a hang into an error, which the un-cordon above then handles like any other.
A hard kill of the daemon needs no handling: `draining` lives only in the scheduler's memory, so a process that dies cordoned comes back up serving.
func SameVersion ¶
SameVersion reports whether two version strings denote the same release. A blank or "dev" build never equals a real target, so an unstamped daemon is always eligible to upgrade.
func ValidVersion ¶
ValidVersion reports whether v is a release tag (vX.Y.Z[-suffix]).
Types ¶
type Drainer ¶
type Drainer interface {
Cordon() int // stop claiming new jobs; returns the current active count
Uncordon() int // resume claiming (used to back out an aborted upgrade)
ActiveJobs() int // number of jobs currently running
}
Drainer is the slice of the scheduler the upgrade needs: stop claiming new jobs, report how many are still running so we can wait for idle, and resume claiming if we abort before the restart. The scheduler's existing Cordon/Uncordon (added in #132) satisfy this; ActiveJobs is a thin accessor over the running-job map.
type Emit ¶
type Emit func(Progress)
Emit receives progress updates. Implementations must not block for long; the RPC handler forwards each to a gRPC stream Send.
type Progress ¶
type Progress struct {
State State
Message string
CurrentVersion string
TargetVersion string
ActiveJobs int // populated during StateDraining
BytesDownloaded int64 // populated during StateDownloading
BytesTotal int64 // total asset size if the server reported it, else 0
}
Progress is one observable step of an upgrade.
type RestartOptions ¶ added in v0.1.9
type RestartOptions struct {
StopTimeout time.Duration
StartTimeout time.Duration
Poll time.Duration
}
RestartOptions bounds a service-manager restart. Zero fields take the Default* values above.
type RunOptions ¶
type RunOptions struct {
TargetVersion string
CurrentVersion string
BaseURLOverride string // replaces the release base dir URL; for mirrors/tests
NoDrain bool
Force bool
DrainTimeout time.Duration
DrainPoll time.Duration
Drainer Drainer
Log *slog.Logger
// Shutdown, when non-nil, is closed (or is a ctx.Done()) as soon as the
// daemon begins going down. It is how the restart supervisor learns that
// the restart it asked for actually took effect: without it, a slow but
// healthy stop would be misread as a failed restart and the node would be
// un-cordoned on its way out the door. Optional; nil means the only
// evidence of success is process death.
Shutdown <-chan struct{}
// StallTimeout abandons the download when it makes no progress for this
// long. Zero means defaultStallTimeout; negative disables the check.
StallTimeout time.Duration
// InstallTimeout bounds everything between the end of the drain and the
// binary swap. Zero means defaultInstallTimeout; negative disables it.
InstallTimeout time.Duration
// Test/override seams.
InstallPath string // default: resolved os.Executable()
StageDir string // default: <installdir>/.ephemerd-upgrade
HTTPClient *http.Client // default: http.DefaultClient (no timeout; ctx-governed)
GOOS string // default: runtime.GOOS
GOARCH string // default: runtime.GOARCH
Probe func(path string) (string, error) // default: probeVersion (runs `<path> --version`)
Restart func() error // default: triggerRestart (per-OS service restart)
RestartDelay time.Duration // default: restartDelay; delay before the detached restart fires
RestartWatchdog time.Duration // default: restartWatchdog; how long a restart has to take effect
}
RunOptions configures a single upgrade. The exported override fields (InstallPath, Restart, Probe, GOOS, GOARCH, StageDir, HTTPClient) default to real behavior when zero and exist mainly so tests can inject seams.