selfupdate

package
v0.124.0 Latest Latest
Warning

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

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

Documentation

Index

Constants

View Source
const ApplierMaxRestarts = 3

ApplierMaxRestarts bounds Docker's restarts of a failing applier while the predecessor still serves and can recover the handover. The predecessor lifts the bound right before it drains, since the applier is then the only process left to finish or roll back.

View Source
const CrashEnvVar = "SELF_UPDATE_CRASH_AT"

CrashEnvVar names the journal state at which the process must die, to test crash recovery. Never set it outside tests.

View Source
const DefaultHealthTimeout = 90 * time.Second

DefaultHealthTimeout is used when the deploy config sets no timeout.

View Source
const RecordVersion = 1

RecordVersion is bumped when the on-disk record layout changes.

View Source
const RecoveryRetries = 2

RecoveryRetries bounds in-process setup, cleanup and rollback retries. The applier's restart policy retries beyond that, so it only absorbs brief Docker or filesystem hiccups.

Variables

View Source
var (
	// ErrHandover signals that a deploy handed the stack over to another
	// process. It is neither a success nor a failure for the calling stage.
	ErrHandover = errors.New("self-update handover in progress")

	// ErrDisabled is returned when a stack contains this doco-cd instance but
	// the self-update feature is off.
	ErrDisabled = errors.New("self-update is disabled")

	// ErrUnsupported is returned when the self stack cannot be updated by any
	// strategy.
	ErrUnsupported = errors.New("self-update is not supported for this stack")

	// ErrInvalidTransition is returned when a journal state change is not allowed.
	ErrInvalidTransition = errors.New("invalid self-update state transition")

	// ErrStaleRecord is returned when another process advanced or removed a
	// journal record before a Save or Update could persist its changes.
	ErrStaleRecord = errors.New("stale self-update record")

	// ErrNoRecord is returned when no journal record exists for an ID.
	ErrNoRecord = errors.New("no self-update record")
)
View Source
var ErrUnhealthy = errors.New("container did not become healthy")

ErrUnhealthy is returned when a container failed its health gate.

View Source
var RetryPause = time.Second

RetryPause is the delay between Retry attempts. Tests may shorten it.

Functions

func DrainRequests

func DrainRequests() <-chan struct{}

DrainRequests is the coordinator's side of RequestDrain.

func HealthTimeout

func HealthTimeout(seconds int) time.Duration

HealthTimeout converts a deploy timeout in seconds, falling back to DefaultHealthTimeout when it is unset.

func MaybeCrash

func MaybeCrash(dataMountPath, point string, log *slog.Logger)

MaybeCrash exits the process when the configured crash point is reached for the first time. The marker on the data volume makes it fire once, so the restarted actor can make progress.

func RequestDrain

func RequestDrain()

RequestDrain asks the coordinator to stop accepting work and hand over.

func Retry

func Retry(ctx context.Context, fn func() error, onError func(attempt int, err error)) error

Retry runs fn up to RecoveryRetries+1 times, pausing between attempts. onError, if set, is called after every failed attempt with its 1-based number. Cancelling ctx stops further attempts and joins ctx.Err() to the last error; pass context.WithoutCancel for recovery that must keep going.

func WaitHealthy

func WaitHealthy(ctx context.Context, apiClient client.APIClient, containerID string, timeout time.Duration, log *slog.Logger) error

WaitHealthy blocks until containerID reports healthy twice in a row, or the timeout expires. Containers without a healthcheck are probed by running the binary's own healthcheck subcommand inside them.

func WithBootstrapLock added in v0.122.1

func WithBootstrapLock(ctx context.Context, dataMountPath string, run func() error) error

WithBootstrapLock runs bootstrap deployments while excluding managed poll runs.

func WithPollLock added in v0.122.1

func WithPollLock(ctx context.Context, dataMountPath string, run func() error) error

WithPollLock lets poll runs overlap each other while waiting for bootstrap to finish.

Types

type Actor

type Actor string

Actor is the process allowed to advance a state.

const (
	ActorPredecessor Actor = "predecessor"
	ActorApplier     Actor = "applier"
	ActorSuccessor   Actor = "successor"
	ActorRestored    Actor = "restored"
)

type CommitStatusInfo added in v0.122.1

type CommitStatusInfo struct {
	// SourceURL is the deployment repository, used to resolve credentials.
	SourceURL string               `json:"source_url"`
	RepoURL   string               `json:"repo_url,omitempty"`
	FullName  string               `json:"full_name,omitempty"`
	CommitSHA string               `json:"commit_sha"`
	Context   string               `json:"context"`
	StartedAt time.Time            `json:"started_at,omitzero"`
	Target    *commitstatus.Target `json:"target,omitempty"`
}

CommitStatusInfo identifies the commit status of a handed over deployment, so the process that resolves the handover can post its final state.

type Constraints

type Constraints struct {
	// NetworkDrift is true when a project network must be recreated, which
	// would stop every attached container including this one.
	NetworkDrift bool
}

Constraints are runtime facts the selector cannot read off the service.

type ContainerRef

type ContainerRef struct {
	ID     string `json:"id"`
	Name   string `json:"name"`
	Number int    `json:"number,omitempty"`
}

ContainerRef identifies a container involved in a handover.

type DeployInfo

type DeployInfo struct {
	TimeoutSeconds int      `json:"timeout_seconds"`
	RecreateMode   string   `json:"recreate_mode"`
	Services       []string `json:"services,omitempty"`
	NetworkDrift   bool     `json:"network_drift,omitempty"`
	RemoveOrphans  bool     `json:"remove_orphans,omitempty"`
}

DeployInfo carries the deploy parameters the applier must reproduce.

func (DeployInfo) HealthTimeout

func (d DeployInfo) HealthTimeout() time.Duration

HealthTimeout is how long the successor may take to become healthy.

type DriftSnapshot

type DriftSnapshot struct {
	Networks   map[string]network.Inspect           `json:"networks"`
	Containers map[string]container.InspectResponse `json:"containers"`
}

DriftSnapshot is the state that must be recoverable if Compose replaces a network shared by the predecessor and other project services.

type Identity

type Identity struct {
	// OK is false when this process does not run in a compose-managed container.
	OK            bool
	ContainerID   string
	ContainerName string
	Project       string
	Service       string
	Number        int
	Image         string
	Labels        map[string]string
}

Identity describes this doco-cd container's place in a compose project.

func Detect

func Detect(ctx context.Context, apiClient client.APIClient, containerID string) (Identity, error)

Detect inspects the own container and derives its compose identity.

func IdentityFromLabels

func IdentityFromLabels(containerID, containerName string, labels map[string]string) Identity

IdentityFromLabels builds an Identity from a container's labels.

type Poison

type Poison struct {
	Context     string    `json:"context"`
	Stack       string    `json:"stack"`
	CommitSHA   string    `json:"commit_sha"`
	ProjectHash string    `json:"project_hash"`
	Reason      string    `json:"reason"`
	FailedAt    time.Time `json:"failed_at"`
	Attempts    int       `json:"attempts"`
}

Poison records a self-update that failed for a specific commit, so the retry logic cannot loop on it. A new commit clears the block by not matching.

type Record

type Record struct {
	Version      int               `json:"version"`
	ID           string            `json:"id"`
	State        State             `json:"state"`
	Strategy     Strategy          `json:"strategy"`
	Stack        string            `json:"stack"`
	Context      string            `json:"context"`
	Service      string            `json:"service"`
	Predecessor  ContainerRef      `json:"predecessor"`
	Successor    ContainerRef      `json:"successor,omitzero"`
	Applier      ContainerRef      `json:"applier,omitzero"`
	Restored     ContainerRef      `json:"restored,omitzero"`
	Source       SourceInfo        `json:"source"`
	Deploy       DeployInfo        `json:"deploy"`
	Drift        *DriftSnapshot    `json:"drift,omitempty"`
	DriftStarted bool              `json:"drift_started,omitempty"`
	Labels       map[string]string `json:"labels"`
	Error        string            `json:"error,omitempty"`
	TimedOut     bool              `json:"timed_out,omitempty"`
	CreatedAt    time.Time         `json:"created_at"`
	UpdatedAt    time.Time         `json:"updated_at"`
	History      []Transition      `json:"history"`
}

Record is the on-disk handover journal for one self-update.

func (Record) SameProgress

func (r Record) SameProgress(other Record) bool

SameProgress reports whether r has the same state and full transition history as other, i.e. no process advanced one past the other.

type SourceInfo

type SourceInfo struct {
	RepoName     string `json:"repo_name"`
	SourceURL    string `json:"source_url"`
	FullName     string `json:"full_name"`
	SourceType   string `json:"source_type"`
	Reference    string `json:"reference"`
	ConfigTarget string `json:"config_target"`
	CommitSHA    string `json:"commit_sha"`
	ProjectHash  string `json:"project_hash"`
	JobID        string `json:"job_id"`
	Trigger      string `json:"trigger"`
	// CommitStatus is the status the predecessor left pending, nil when it
	// posted none. It never holds a token: the finishing process resolves one.
	CommitStatus *CommitStatusInfo `json:"commit_status,omitempty"`
}

SourceInfo carries everything the successor needs to report the deploy that it did not itself run.

type State

type State string

State is a step in the self-update handover.

const (
	StateStaged       State = "staged"        // predecessor: creating the successor or applier
	StateStarted      State = "started"       // predecessor (scale-out): successor created and started, health pending
	StateHandover     State = "handover"      // predecessor (scale-out): successor healthy
	StateDrained      State = "drained"       // predecessor (scale-out): no in-flight work, safe to stop me
	StateApplying     State = "applying"      // predecessor then applier: applier running
	StateApplyReady   State = "apply_ready"   // applier: preflight complete; predecessor may drain
	StateApplyDrained State = "apply_drained" // predecessor: admission drained; applier may change containers
	StateApplied      State = "applied"       // applier: successor healthy, predecessor gone
	StateRolledBack   State = "rolled_back"   // applier: successor unhealthy, predecessor restored
	StateFailed       State = "failed"        // applier: error, predecessor restored or still alive
	StateFinalising   State = "finalising"    // successor: predecessor removed, reporting pending
	StateAborted      State = "aborted"       // predecessor: gave up on the successor
)

func (State) ApplierFinished

func (s State) ApplierFinished() bool

ApplierFinished reports states the applier must not apply or recover again.

func (State) InApplierPhase

func (s State) InApplierPhase() bool

InApplierPhase reports states in which an applier still owns the handover and can recover it.

func (State) RolledBackOrFailed

func (s State) RolledBackOrFailed() bool

RolledBackOrFailed reports the applier's unsuccessful outcomes.

func (State) Unsuccessful

func (s State) Unsuccessful() bool

Unsuccessful reports outcomes the predecessor reports and cleans up.

type Store

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

Store persists handover records on the data volume.

func NewStore

func NewStore(dataMountPath string) *Store

NewStore returns a store rooted at <dataMountPath>/self-update.

func (*Store) Active

func (s *Store) Active() (*Record, error)

Active returns the single in-flight record, or nil when there is none. Malformed records are quarantined rather than failing the caller.

func (*Store) AddPoison

func (s *Store) AddPoison(p Poison) error

AddPoison blocks further attempts for the same commit and project hash. Re-adding the same key increments the attempt counter.

func (*Store) ClearPoison

func (s *Store) ClearPoison(contextName, stack string) error

ClearPoison removes the block for a stack.

func (*Store) Create

func (s *Store) Create(record *Record) error

Create writes the initial record. The caller sets everything but the bookkeeping fields.

func (*Store) IsPoisoned

func (s *Store) IsPoisoned(contextName, stack, commitSHA, projectHash string) (Poison, bool, error)

IsPoisoned reports whether this exact commit and project hash already failed.

func (*Store) Load

func (s *Store) Load(id string) (Record, error)

Load reads one record by ID.

func (*Store) Quarantine

func (s *Store) Quarantine(id string) error

Quarantine renames an unreadable record so it stops blocking startup.

func (*Store) ReadSnapshot

func (s *Store) ReadSnapshot(id string) (container.InspectResponse, bool, error)

ReadSnapshot returns the stored predecessor snapshot, if one exists.

func (*Store) Remove

func (s *Store) Remove(id string) error

Remove deletes a record and its snapshot.

func (*Store) RemoveIfUnchanged

func (s *Store) RemoveIfUnchanged(expected Record) error

RemoveIfUnchanged prevents a failed staging attempt from deleting a record that another process has advanced or assigned to another container.

func (*Store) Root

func (s *Store) Root() string

Root returns the directory the store writes to.

func (*Store) Save

func (s *Store) Save(record Record) error

Save persists metadata only while the record's state and transition history remain unchanged. It cannot recreate a removed record or overwrite a transition or clear a newly recorded recovery error written by another process.

func (*Store) Update

func (s *Store) Update(record Record, to State, actor Actor) (Record, error)

Update advances a record if its state and transition history still match the persisted version. UpdatedAt is not part of the comparison: callers may Save(record) then Update(record, ...) using the same value.

func (*Store) WriteSnapshot

func (s *Store) WriteSnapshot(id string, inspect container.InspectResponse) error

WriteSnapshot stores the predecessor's inspect output for rollback.

type Strategy

type Strategy string

Strategy is how doco-cd replaces its own container.

const (
	// StrategyAuto picks scale_out unless a constraint forces applier.
	StrategyAuto Strategy = "auto"
	// StrategyScaleOut starts a second container, health-checks it, then hands over.
	StrategyScaleOut Strategy = "scale_out"
	// StrategyApplier delegates the replacement to a throwaway clone.
	StrategyApplier Strategy = "applier"
)

func Select

func Select(svc types.ServiceConfig, sourceType, contextName string, requested Strategy, c Constraints) (Strategy, []string, error)

Select decides the strategy and returns the reasons that ruled out scale-out.

type Transition

type Transition struct {
	State State     `json:"state"`
	Actor Actor     `json:"actor"`
	At    time.Time `json:"at"`
}

Transition is one entry of a record's audit trail.

Jump to

Keyboard shortcuts

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