Help us shape the future of Go! Take the 2026 Go Developer Survey

update

package
v0.15.1 Latest Latest
Warning

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

Go to latest
Published: Oct 22, 2026 License: AGPL-3.0 Imports: 21 Imported by: 0

Documentation

Overview

Package update implements the staged core update lifecycle: a persisted operation whose phases (compatibility, release verification, backup, activation, health and links, final acceptance) are reported and completed independently, protected by a durable lease, driven by a resumable job, and reconciled after a process restart. Sending a deployment request never counts as a successful update: activation needs the serving process to report the expected identity, and acceptance needs every required check to pass.

Failure handling may activate a compatible prior artifact on the current data. It never restores a database or media snapshot over newer writes.

Index

Constants

View Source
const (
	ActivationQueued  = "queued"
	ActivationRunning = "running"
	ActivationActive  = "active"
	ActivationFailed  = "failed"
	ActivationUnknown = "unknown"
)

Activation statuses an adapter reports for a deployment reference.

View Source
const (
	HolderUpdate  = "update"
	HolderBackup  = "backup"
	HolderRestore = "restore"
)

Lease holders.

View Source
const (
	PhaseCompatibility = "compatibility"
	PhaseVerification  = "verification"
	PhaseBackup        = "backup"
	PhaseActivation    = "activation"
	PhaseHealth        = "health"
	PhaseAcceptance    = "acceptance"

	PhaseRollbackCompatibility = "rollback_compatibility"
	PhaseRollbackActivation    = "rollback_activation"
	PhaseRollbackHealth        = "rollback_health"
)

Phase names, in lifecycle order; rollback phases are appended on demand.

View Source
const (
	StatusPending        = "pending"
	StatusRunning        = "running"
	StatusSucceeded      = "succeeded"
	StatusFailed         = "failed"
	StatusInterrupted    = "interrupted"
	StatusRolledBack     = "rolled_back"
	StatusRollbackFailed = "rollback_failed"
)

Operation statuses.

View Source
const (
	PhasePending     = "pending"
	PhaseRunning     = "running"
	PhasePassed      = "passed"
	PhaseFailed      = "failed"
	PhaseSkipped     = "skipped"
	PhaseInterrupted = "interrupted"
)

Phase statuses.

View Source
const (
	MigrationUnchecked = "unchecked"
	MigrationUnchanged = "unchanged"
	MigrationApplied   = "applied"
	MigrationUnknown   = "unknown"
)

Migration states recorded on an operation.

View Source
const JobKind = "update.run"

JobKind runs one operation; it is resumable and never retried blindly.

View Source
const LeaseScope = "data"

LeaseScope is the one scope every data-directory operation competes for.

View Source
const ServiceName = "update"

ServiceName is the kernel service name the admin module publishes.

Variables

View Source
var (
	ErrInvalidTarget = errors.New("update: target is not a release version")
	ErrNotFound      = errors.New("update: operation not found")
)

Errors returned by Request.

View Source
var ErrLeaseLost = errors.New("update: lease lost")

ErrLeaseLost is returned by Renew when the caller no longer owns the lease.

View Source
var ErrNotConfigured = errors.New("update: release source not configured")

ErrNotConfigured means no trusted release source is configured.

View Source
var ErrUnpinnable = errors.New("update: adapter cannot pin the requested artifact")

ErrUnpinnable means the adapter cannot guarantee that activation targets the requested artifact (for example a Coolify resource that builds from a git branch). The lifecycle refuses such an update instead of presenting a branch deployment as a version-directed update.

Functions

func DecodeArtifact

func DecodeArtifact(raw json.RawMessage, a *Artifact) bool

DecodeArtifact reads a saved observation payload into a.

func IsRelease

func IsRelease(s string) bool

IsRelease reports whether s is a comparable release tag.

func Migrations

func Migrations() fs.FS

Migrations returns the update tables' migrations.

func NewOwnerToken

func NewOwnerToken() string

NewOwnerToken returns a random lease owner token.

func Newer

func Newer(candidate, current string) bool

Newer reports whether candidate is a newer release than current. Either side not being a release tag yields false.

Types

type Activation

type Activation struct {
	Status string `json:"status" enum:"queued,running,active,failed,unknown"`
	Detail string `json:"detail"`
}

Activation is the provider's view of a deployment reference. Active means the provider says the artifact runs; the lifecycle still requires the serving process to report the expected identity before the phase passes.

type Adapter

type Adapter interface {
	Name() string
	// Activate asks the provider to make artifact active and returns a
	// durable reference. Acceptance is not activation.
	Activate(ctx context.Context, artifact Artifact) (string, error)
	// Status reports the state of a reference returned by Activate.
	Status(ctx context.Context, ref string) (Activation, error)
}

Adapter requests activation of an artifact through a local supervisor or a configured deployment provider. Credentials and hosts come from server configuration only.

type Artifact

type Artifact struct {
	Version string `json:"version"`
	ID      string `json:"id" doc:"Immutable artifact identity, e.g. an image reference or archive name"`
	Digest  string `json:"digest" doc:"sha256:… from the release source; empty when the source publishes none"`
	Source  string `json:"source"`
	// Migrations and Themes are compatibility data the release source may
	// publish; nil means the source did not say.
	Migrations []string `json:"migrations,omitempty"`
	Themes     []string `json:"themes,omitempty"`
}

Artifact is an immutable release identity from a trusted source.

type Config

type Config struct {
	// RunningVersion is the serving process identity (the build string),
	// never a selected artifact or saved target.
	RunningVersion string
	Source         Source
	Adapter        Adapter
	// AllowDowngrade lets a lower target pass compatibility when the
	// operator accepts the recorded downgrade constraints.
	AllowDowngrade bool
	// RequireDigest fails verification when the source publishes no digest.
	RequireDigest bool
	// ActivationTimeout bounds waiting for a provider or operator to
	// activate the artifact (default 15 minutes). PollInterval is how often
	// the adapter status is read (default 10 seconds).
	ActivationTimeout time.Duration
	PollInterval      time.Duration
	// LeaseTTL and Heartbeat protect the operation across processes
	// (defaults 2 minutes and 30 seconds).
	LeaseTTL  time.Duration
	Heartbeat time.Duration
	// HealthLinks are absolute URLs that must answer 2xx after activation.
	HealthLinks []string

	// Evidence providers, supplied by the host (nil ones record
	// "unavailable" and fail the phase that needs them).
	Compatibility func(ctx context.Context, target string) (Report, error)
	Backup        func(ctx context.Context) (Report, error)
	Health        func(ctx context.Context) (Report, error)
	Link          func(ctx context.Context, url string) error
	// Schema lists the applied migrations ("module/name"); the difference
	// before and after activation decides whether the prior artifact is
	// still compatible with the current data.
	Schema func(ctx context.Context) ([]string, error)
}

Config wires the lifecycle to the host. Only RunningVersion and Source are required for requests; a nil Adapter means manual activation.

type CoolifyAdapter

type CoolifyAdapter struct {
	// contains filtered or unexported fields
}

CoolifyAdapter requests a deployment of one operator-configured resource and reads the deployment status. The endpoint, token and resource come from server configuration; request parameters cannot change them.

func NewCoolifyAdapter

func NewCoolifyAdapter(rawURL, token, resource string, client *http.Client) (*CoolifyAdapter, error)

NewCoolifyAdapter validates the operator configuration (HTTPS origin only, no credentials, query or path) and returns the adapter.

func (*CoolifyAdapter) Activate

func (c *CoolifyAdapter) Activate(ctx context.Context, a Artifact) (string, error)

Activate pins the resource's image tag to the artifact (Docker image resources only), then posts the deploy request and requires the provider to echo the configured resource with a deployment id.

func (*CoolifyAdapter) CanActivate

func (c *CoolifyAdapter) CanActivate(ctx context.Context, a Artifact) error

CanActivate reports whether the resource is a Docker image application, the only Coolify build pack whose target can be pinned to an artifact. Resources that build from a repository deploy whatever the branch holds, so they are refused rather than reported as a version-directed update.

func (*CoolifyAdapter) Name

func (c *CoolifyAdapter) Name() string

func (*CoolifyAdapter) Status

func (c *CoolifyAdapter) Status(ctx context.Context, ref string) (Activation, error)

Status reads GET /api/v1/deployments/{id}. Coolify statuses map to: finished → active; failed/cancelled → failed; queued → queued; in_progress → running; others → unknown.

type ErrConflict

type ErrConflict struct {
	OperationID string
	Holder      string
	Reason      string
}

ErrConflict reports an operation or lease that blocks a new request.

func (*ErrConflict) Error

func (e *ErrConflict) Error() string

type ErrLeaseHeld

type ErrLeaseHeld struct {
	Holder      string
	OperationID string
	ExpiresAt   string
}

ErrLeaseHeld reports that another operation owns the data directory.

func (*ErrLeaseHeld) Error

func (e *ErrLeaseHeld) Error() string

type Holder

type Holder struct {
	Holder, Owner, OperationID, ExpiresAt string
}

Holder describes the current lease.

type Lease

type Lease struct {
	// contains filtered or unexported fields
}

Stored expiries have second precision and are inclusive: a lease is live through its expiry second.

Lease is the durable ownership record. A process-local mutex or a time cooldown cannot protect a restarted process; the row with its owner token and expiry can. Acquire takes a free or expired lease, or one already held by the same owner; Renew extends it only for its owner.

func NewLease

func NewLease(d *sql.DB, ttl time.Duration) *Lease

NewLease returns a lease on d with ttl (default 2 minutes).

func (*Lease) Acquire

func (l *Lease) Acquire(ctx context.Context, holder, owner, operationID string) error

Acquire takes the lease for owner (holder names the kind of work).

func (*Lease) AcquireTx

func (l *Lease) AcquireTx(ctx context.Context, tx *sql.Tx, holder, owner, operationID string) error

AcquireTx is Acquire inside tx.

func (*Lease) Current

func (l *Lease) Current(ctx context.Context) (Holder, bool, error)

Current returns the live lease, if any.

func (*Lease) Release

func (l *Lease) Release(ctx context.Context, owner string) error

Release frees the lease if owner holds it (a lost lease is not an error).

func (*Lease) Renew

func (l *Lease) Renew(ctx context.Context, owner string) error

Renew extends the lease held by owner; ErrLeaseLost otherwise.

type ManualAdapter

type ManualAdapter struct{}

ManualAdapter is the fallback when no provider is configured: an operator replaces the binary or image themselves. Activate records the request; Status stays queued until the serving identity changes (which Reconcile observes) or the activation timeout expires.

func (ManualAdapter) Activate

func (ManualAdapter) Activate(_ context.Context, a Artifact) (string, error)

func (ManualAdapter) Name

func (ManualAdapter) Name() string

func (ManualAdapter) Status

type Operation

type Operation struct {
	ID               string    `json:"id"`
	Status           string    `json:"status" enum:"pending,running,succeeded,failed,interrupted,rolled_back,rollback_failed"`
	CurrentPhase     string    `json:"current_phase"`
	RequestedVersion string    `json:"requested_version" doc:"Target asked for"`
	RunningVersion   string    `json:"running_version" doc:"Serving identity when the operation was requested"`
	ActivatedVersion *string   `json:"activated_version" doc:"Identity confirmed by the serving process after activation; null until then"`
	AcceptedVersion  *string   `json:"accepted_version" doc:"Target durably accepted after every required check passed; null otherwise"`
	ExpectedDigest   string    `json:"expected_digest"`
	Artifact         *Artifact `json:"artifact" doc:"Verified release identity; null before verification"`
	Previous         *Artifact `json:"previous" doc:"Prior artifact eligible for runtime rollback on current data; null when unknown"`
	BackupStamp      string    `json:"backup_stamp"`
	DeploymentRef    string    `json:"deployment_ref" doc:"Provider reference; accepted is not active"`
	RollbackRef      string    `json:"rollback_ref"`
	MigrationState   string    `` /* 135-byte string literal not displayed */
	Source           string    `json:"source"`
	Adapter          string    `json:"adapter"`
	RequestedBy      string    `json:"requested_by"`
	Error            string    `json:"error"`
	Remediation      string    `json:"remediation"`
	Retryable        bool      `json:"retryable"`
	HeartbeatAt      *string   `json:"heartbeat_at"`
	CreatedAt        string    `json:"created_at"`
	StartedAt        *string   `json:"started_at"`
	FinishedAt       *string   `json:"finished_at"`
	UpdatedAt        string    `json:"updated_at"`
	Phases           []Phase   `json:"phases"`
}

Operation is a persisted update with its phases.

func (Operation) Terminal

func (o Operation) Terminal() bool

Terminal reports whether the operation can no longer change.

type Phase

type Phase struct {
	Name        string          `json:"name"`
	Status      string          `json:"status" enum:"pending,running,passed,failed,skipped,interrupted"`
	StartedAt   *string         `json:"started_at"`
	FinishedAt  *string         `json:"finished_at"`
	DurationMS  *int64          `json:"duration_ms" doc:"Null until the phase finished"`
	Progress    *float64        `json:"progress" doc:"0..1 when measurable, else null"`
	Attempts    int             `json:"attempts"`
	Result      json.RawMessage `json:"result" doc:"Phase evidence; never credentials or hosts"`
	Error       string          `json:"error"`
	Remediation string          `json:"remediation"`
	Retryable   bool            `json:"retryable"`
}

Phase is one reported lifecycle stage.

type ReleaseIndex

type ReleaseIndex struct {
	// contains filtered or unexported fields
}

ReleaseIndex reads an operator-trusted HTTPS JSON document:

{"latest":"0.15.0",
 "releases":[{"version":"0.15.0","artifacts":[{"id":"ghcr.io/zadstack/gorun:0.15.0","digest":"sha256:…"}]}]}

Artifact selects which artifact of a release to use (exact id, or a substring when the release lists several); a release with one artifact needs no selector.

func NewLoopbackReleaseIndex

func NewLoopbackReleaseIndex(rawURL, token, artifact string, client *http.Client) (*ReleaseIndex, error)

NewLoopbackReleaseIndex is NewReleaseIndex for a plain-HTTP index served on the local host (a local supervisor); any other scheme/host is refused.

func NewReleaseIndex

func NewReleaseIndex(rawURL, token, artifact string, client *http.Client) (*ReleaseIndex, error)

NewReleaseIndex validates the HTTPS URL (no credentials, no fragment).

func (*ReleaseIndex) Latest

func (r *ReleaseIndex) Latest(ctx context.Context) (Artifact, error)

func (*ReleaseIndex) Name

func (r *ReleaseIndex) Name() string

func (*ReleaseIndex) Resolve

func (r *ReleaseIndex) Resolve(ctx context.Context, ver string) (Artifact, error)

type Report

type Report struct {
	OK          bool
	Reason      string
	Remediation string
	Retryable   bool
	Data        map[string]any
}

Report is what an evidence provider returns for a phase.

type Request

type Request struct {
	Version        string `json:"version" doc:"Release version to activate, e.g. 0.15.0"`
	ExpectedDigest string `json:"expected_digest,omitempty" doc:"Optional sha256:… the verified artifact must match"`
	RequestedBy    string `json:"-"`
}

Request asks for an update to Version.

type Service

type Service struct {
	// contains filtered or unexported fields
}

Service runs operations.

func New

func New(d *sql.DB, q *jobs.Queue, log *slog.Logger, cfg Config) *Service

New returns a service. Call Register before the queue starts.

func (*Service) Accepted

func (s *Service) Accepted(ctx context.Context) (string, error)

Accepted returns the last durably accepted target version, if any.

func (*Service) Active

func (s *Service) Active(ctx context.Context) (*Operation, error)

Active returns the pending or running operation, if any.

func (*Service) AdapterName

func (s *Service) AdapterName() string

AdapterName returns the configured deployment adapter name.

func (*Service) Configured

func (s *Service) Configured() bool

Configured reports whether a trusted release source exists.

func (*Service) Get

func (s *Service) Get(ctx context.Context, id string) (Operation, error)

Get returns one operation with its phases.

func (*Service) Last

func (s *Service) Last(ctx context.Context) (*Operation, error)

Last returns the most recent operation, if any.

func (*Service) Lease

func (s *Service) Lease() *Lease

Lease exposes the durable lease so backup and restore share it.

func (*Service) List

func (s *Service) List(ctx context.Context, limit int) ([]Operation, error)

List returns the newest operations first, without phases.

func (*Service) Reconcile

func (s *Service) Reconcile(ctx context.Context, atBoot bool) error

Reconcile repairs operations left pending or running by a stopped process. atBoot says every previous runner is dead (one process per data directory); otherwise only operations whose heartbeat is older than the lease TTL are touched. An activation whose expected identity now serves passes on that evidence and the lifecycle continues in this process; an activation still inside its timeout is resumed; anything else is marked interrupted, never left looking successful or in progress.

func (*Service) Register

func (s *Service) Register()

Register binds the job handler.

func (*Service) Request

func (s *Service) Request(ctx context.Context, req Request) (Operation, error)

Request validates, records and queues an operation, returning at once. A pending or running operation, a held lease, or a prior deployment the provider still reports in progress is a conflict.

func (*Service) RunningVersion

func (s *Service) RunningVersion() string

RunningVersion is the serving identity.

func (*Service) Source

func (s *Service) Source() Source

Source returns the configured release source (nil when unconfigured).

func (*Service) SourceName

func (s *Service) SourceName() string

SourceName and AdapterName describe the configured integrations.

type Source

type Source interface {
	Name() string
	Resolve(ctx context.Context, version string) (Artifact, error)
	Latest(ctx context.Context) (Artifact, error)
}

Source resolves release identities and digests from an operator-trusted location. It is read-only.

type TargetVerifier

type TargetVerifier interface {
	CanActivate(ctx context.Context, artifact Artifact) error
}

TargetVerifier is implemented by adapters that can say, before any backup or deployment, whether they can activate exactly artifact.

Jump to

Keyboard shortcuts

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