upgrade

package
v0.2.7 Latest Latest
Warning

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

Go to latest
Published: Aug 26, 2026 License: MIT Imports: 22 Imported by: 0

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

View Source
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.

View Source
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

func AssetName(version, goos, goarch string) string

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

func NormalizeVersion(v string) string

NormalizeVersion trims and ensures a leading "v" so "0.1.7" and "v0.1.7" compare equal.

func ParseChecksums

func ParseChecksums(r io.Reader) (map[string]string, error)

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

func RestartService(_ context.Context, name string, _ RestartOptions, _ *slog.Logger) error

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

func SameVersion(a, b string) bool

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

func ValidVersion(v string) bool

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.

type State

type State int

State is the coarse phase of an in-progress upgrade. It maps 1:1 to the apiv1.UpgradeState enum (translated in pkg/scheduler/grpc.go) so this package needn't import the generated protobuf types.

const (
	StatePreflight State = iota
	StateUpToDate
	StateDraining
	StateDownloading
	StateVerifying
	StateStaging
	StateSwapping
	StateRestarting
	StateFailed
)

Jump to

Keyboard shortcuts

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