Documentation
¶
Overview ¶
Package deploylifecycle owns the deployment record state machine: the set of valid statuses and phases, and the transitions between them.
It exists as its own package because both servers/deployment and servers/build need it, and servers/build importing servers/deployment would be backwards.
Index ¶
- Constants
- Variables
- func CheckPhase(status Status, phase Phase) error
- func SourceFromGitInfo(info core_v1alpha.GitInfo) core_v1alpha.Source
- func Transition(from, to Status) error
- type BeginParams
- type Holder
- type LockHeldError
- type Locks
- func (l *Locks) Acquire(ctx context.Context, appName, deploymentID string) (*Holder, error)
- func (l *Locks) Blocking(ctx context.Context, appName string) (*Holder, error)
- func (l *Locks) CommitActivation(ctx context.Context, appName string, appID, versionID entity.Id, ...) error
- func (l *Locks) CommitActivationAtRevision(ctx context.Context, appName string, appID, versionID entity.Id, ...) error
- func (l *Locks) Get(ctx context.Context, appName string) (*Holder, error)
- func (l *Locks) Owns(ctx context.Context, appName, deploymentID string) (bool, error)
- func (l *Locks) Release(ctx context.Context, appName, deploymentID string) error
- type Operation
- type Phase
- type Query
- type Record
- func (r *Record) AppID() entity.Id
- func (r *Record) AppVersion() string
- func (r *Record) Canonical() bool
- func (r *Record) CompletedAt() time.Time
- func (r *Record) Operation() Operation
- func (r *Record) ParentDeploymentID() string
- func (r *Record) Phase() Phase
- func (r *Record) StartedAt() time.Time
- func (r *Record) Status() Status
- type Status
- type StatusLookup
- type Store
- func (s *Store) AppByName(ctx context.Context, name string) (*core_v1alpha.App, int64, error)
- func (s *Store) AppVersionByID(ctx context.Context, id string) (*core_v1alpha.AppVersion, error)
- func (s *Store) Create(ctx context.Context, dep *core_v1alpha.Deployment) (*Record, error)
- func (s *Store) Delete(ctx context.Context, deploymentID string) error
- func (s *Store) EnsureApp(ctx context.Context, name string) (*core_v1alpha.App, int64, error)
- func (s *Store) Get(ctx context.Context, deploymentID string) (*Record, error)
- func (s *Store) List(ctx context.Context, q Query) ([]*Record, error)
- func (s *Store) MarkPreviousActiveAs(ctx context.Context, appName, exceptID string, target Status) error
- func (s *Store) PublishLockOwner(ctx context.Context, dep *core_v1alpha.Deployment, ...) (*Record, error)
- func (s *Store) Put(ctx context.Context, rec *Record) error
- func (s *Store) ReserveLockOwner(ctx context.Context, deploymentID entity.Id) (*lockOwnerReservation, error)
- func (s *Store) SetActivePointers(ctx context.Context, appID entity.Id, revision int64, ...) error
- func (s *Store) Status(ctx context.Context, deploymentID string) (Status, error)
- type Tracker
- func (t *Tracker) Activate(ctx context.Context, deploymentID string) error
- func (t *Tracker) ActivateAtRevision(ctx context.Context, deploymentID string, appRevision int64) error
- func (t *Tracker) ActivateRollback(ctx context.Context, deploymentID string) error
- func (t *Tracker) Begin(ctx context.Context, params BeginParams) (*Record, error)
- func (t *Tracker) Cancel(ctx context.Context, deploymentID, reason string) error
- func (t *Tracker) Fail(ctx context.Context, deploymentID, failureSummary string) error
- func (t *Tracker) FailIfUnsettled(ctx context.Context, deploymentID, failureSummary string) error
- func (t *Tracker) Locks() *Locks
- func (t *Tracker) Reconcile(ctx context.Context, deploymentID string) error
- func (t *Tracker) SetAppVersion(ctx context.Context, deploymentID, appVersionID string) error
- func (t *Tracker) SetPhase(ctx context.Context, deploymentID string, phase Phase) error
- func (t *Tracker) Store() *Store
Constants ¶
const DefaultLockTTL = 30 * time.Minute
DefaultLockTTL is how long a lock survives without its holder finishing. It backstops a deploy whose driver died without releasing. A holder that reaches a terminal status is stealable sooner than this.
Variables ¶
var ErrLockHeld = errors.New("deployment lock held")
ErrLockHeld reports that a live deployment already holds the lock. It is an expected outcome, not a failure — match against it with errors.Is.
Functions ¶
func CheckPhase ¶
CheckPhase reports whether a phase may be recorded against a deployment in the given status. Phases describe work still in flight, so they are meaningless once a deployment has left in_progress.
func SourceFromGitInfo ¶ added in v0.15.0
func SourceFromGitInfo(info core_v1alpha.GitInfo) core_v1alpha.Source
SourceFromGitInfo converts the verbose, legacy build metadata into the small provenance record owned by AppVersion. Repository credentials and request decorations are deliberately discarded before the value becomes durable.
func Transition ¶
Transition reports whether a deployment may move from one status to another. It is the single authority on that question: every write path goes through it rather than re-deriving the rules.
A rejected transition is a conflict, not a validation failure — the request is well-formed, it just lost a race or arrived against a record that has already finished.
Types ¶
type BeginParams ¶
type BeginParams struct {
AppName string
AppID entity.Id
ClusterID string
Operation Operation
// AppVersion is normally empty: a forward deploy does not know its version
// until the build produces one. Rollback knows it up front.
AppVersion string
GitInfo core_v1alpha.GitInfo
DeployedBy core_v1alpha.DeployedBy
Subject string
AuthMethod string
OrganizationID string
// ParentDeploymentID records the deployment this attempt was based on.
ParentDeploymentID string
}
BeginParams describes a deployment about to start.
type Holder ¶
type Holder struct {
AppName string
DeploymentID string
AcquiredAt time.Time
ExpiresAt time.Time
Revision int64
}
Holder describes the deployment currently holding a lock.
func HolderFrom ¶
HolderFrom extracts the blocking holder from an error returned by Acquire or Begin, reporting false for any other error.
type LockHeldError ¶
type LockHeldError struct {
Holder *Holder
}
LockHeldError carries the blocking holder along with the error, so a caller several layers up can render "who is in the way" without re-reading the lock and risking a different answer.
func (*LockHeldError) Error ¶
func (e *LockHeldError) Error() string
func (*LockHeldError) Is ¶
func (e *LockHeldError) Is(target error) bool
type Locks ¶
type Locks struct {
// contains filtered or unexported fields
}
Locks manages the expiring deployment lock stored on an app.
The app is the single compare-and-swap point for acquisition. A revision-guarded patch ensures that two callers racing to deploy the same app cannot both become its lock holder.
The lock is scoped to the app, not app+cluster: a coordinator's entity store is a loopback into its own etcd, so it only ever holds this cluster's deployments, and the client-supplied cluster_id is unreliable anyway (a manual deploy sends the cluster name, a CI/OIDC deploy sends the raw address). Keying on it would let those two deploys of the same app run concurrently. See MIR-1465.
func NewLocks ¶
func NewLocks(log *slog.Logger, eac *entityserver_v1alpha.EntityAccessClient, status StatusLookup) *Locks
NewLocks builds a lock manager. status may be nil, in which case a held lock is only stealable once expired.
func (*Locks) Acquire ¶
Acquire takes the deploy lock for deploymentID, returning the holder it established.
If another live deployment holds it, Acquire returns that holder along with ErrLockHeld. Re-acquiring a lock this same deployment already holds succeeds and refreshes the expiry, so a retried call is not an error.
func (*Locks) Blocking ¶
Blocking returns the holder that would block a new deployment for this app, or nil if nothing would.
This is the question callers actually have — a pre-flight check before uploading a build context, or the lock info rendered in a "deployment blocked" message. An empty lock, an expired lock, and a lock whose deployment already finished all read as free.
func (*Locks) CommitActivation ¶ added in v0.15.0
func (l *Locks) CommitActivation(ctx context.Context, appName string, appID, versionID entity.Id, deploymentID string) error
CommitActivation swings the serving pointers while the same app revision still proves that deploymentID owns the lock. The lock deliberately stays in place until the attempt's terminal outcome is durable. That preserves a recovery witness if the process dies between these two entity writes.
func (*Locks) CommitActivationAtRevision ¶ added in v0.15.0
func (l *Locks) CommitActivationAtRevision(ctx context.Context, appName string, appID, versionID entity.Id, deploymentID string, expectedRevision int64) error
CommitActivationAtRevision preserves optimistic concurrency for a version derived from a particular app snapshot. Unlike a regular deployment, a configuration mutation must be rebuilt if that snapshot has moved.
func (*Locks) Owns ¶ added in v0.15.0
Owns reports whether deploymentID is the current, unexpired holder.
type Operation ¶ added in v0.15.0
type Operation string
Operation records why an attempt was started. Unlike a status, it never changes as work progresses.
type Phase ¶
type Phase string
Phase is the fine-grained progress of a deployment that is still in_progress.
func ParsePhase ¶
ParsePhase converts a wire/stored string into a Phase, rejecting unknown values with a validation failure.
type Query ¶
type Query struct {
AppName string
Status Status
// LegacyStatus selects the downgrade representation. It is used only for
// compatibility bookkeeping such as settling the previously active row.
LegacyStatus Status
// Limit caps the result after sorting newest-first. Zero means no cap.
Limit int
}
Query selects deployments. An empty field means "any".
There is deliberately no cluster filter: a coordinator's store only holds its own cluster's deployments, and the client-supplied cluster_id is unreliable, so filtering on it would hide legitimate deploys (see MIR-1465).
type Record ¶
type Record struct {
Deployment *core_v1alpha.Deployment
Entity *entityserver_v1alpha.Entity
Revision int64
}
Record is a deployment entity together with what a caller needs to write it back: the revision it was read at, and the raw entity for short-id rendering.
func (*Record) AppVersion ¶
AppVersion returns the canonical version reference, or the normalized legacy value for an unmigrated record.
func (*Record) Canonical ¶ added in v0.15.0
Canonical reports whether any attempt-shaped field is present. Outcome cannot be the discriminator because it is deliberately absent while an attempt is in progress.
func (*Record) CompletedAt ¶ added in v0.15.0
func (*Record) ParentDeploymentID ¶ added in v0.15.0
type Status ¶
type Status string
Status is the lifecycle state of a deployment record.
func ParseStatus ¶
ParseStatus converts a wire/stored string into a Status, rejecting unknown values with a validation failure.
func (Status) Canonical ¶ added in v0.15.0
Canonical removes serving-state history from a legacy status.
type StatusLookup ¶
StatusLookup reports the stored status of a deployment record. Acquire uses it so a lock whose holder has already finished can be taken immediately instead of waiting out the TTL.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store reads and writes deployment records.
Every query goes through an index when the filters allow one. The previous implementation listed every deployment ever created and filtered in memory on each history query, lock check, and activation.
func NewStore ¶
func NewStore(log *slog.Logger, eac *entityserver_v1alpha.EntityAccessClient) *Store
func (*Store) AppVersionByID ¶ added in v0.15.0
func (s *Store) AppVersionByID(ctx context.Context, id string) (*core_v1alpha.AppVersion, error)
func (*Store) Create ¶
func (s *Store) Create(ctx context.Context, dep *core_v1alpha.Deployment) (*Record, error)
Create writes a new deployment record. Ensure makes a fantastically unlikely ID collision explicit instead of updating an existing attempt.
func (*Store) MarkPreviousActiveAs ¶
func (s *Store) MarkPreviousActiveAs(ctx context.Context, appName, exceptID string, target Status) error
MarkPreviousActiveAs moves the deployments that were active for this app into a settled status, leaving the incoming deployment alone.
func (*Store) PublishLockOwner ¶ added in v0.15.0
func (s *Store) PublishLockOwner(ctx context.Context, dep *core_v1alpha.Deployment, reservation *lockOwnerReservation) (*Record, error)
PublishLockOwner atomically replaces a lock-owner reservation with the full deployment record after both the compatibility and canonical locks are held.
func (*Store) Put ¶
Put writes a record back under its revision, so a concurrent writer causes a conflict rather than a silent overwrite.
func (*Store) ReserveLockOwner ¶ added in v0.15.0
func (s *Store) ReserveLockOwner(ctx context.Context, deploymentID entity.Id) (*lockOwnerReservation, error)
ReserveLockOwner creates a private transition entity for a deployment ID before lock acquisition. During a rolling upgrade, an older runtime treats a legacy lock whose deployment record is missing as abandoned and steals it. The transition entity closes that publication window without making an unsuccessful lock attempt appear in deployment history.
func (*Store) SetActivePointers ¶ added in v0.15.0
func (s *Store) SetActivePointers(ctx context.Context, appID entity.Id, revision int64, versionID, deploymentID entity.Id) error
SetActivePointers changes the two app pointers as one entity update. The revision guard prevents an unrelated stale app write from silently undoing an activation; callers retry after re-reading.
type Tracker ¶
type Tracker struct {
// contains filtered or unexported fields
}
Tracker is the deployment lifecycle as a set of operations, and the surface the build paths call. It exists so the record is a byproduct of the work actually happening rather than something a client narrates.
Every mutation goes through it, which is what makes the state machine and the lock unavoidable rather than merely available.
func NewTracker ¶
func NewTracker(log *slog.Logger, eac *entityserver_v1alpha.EntityAccessClient) *Tracker
NewTracker wires a tracker over the entity store. The lock manager is given the store's status lookup, so an abandoned app lock can be reconciled against the record it names.
func (*Tracker) Activate ¶
Activate marks the deployment live and settles the one it replaced as succeeded. The lock is released: the deploy is over.
func (*Tracker) ActivateAtRevision ¶ added in v0.15.0
func (t *Tracker) ActivateAtRevision(ctx context.Context, deploymentID string, appRevision int64) error
ActivateAtRevision activates only if the app is still at the revision from which a configuration mutation was derived. A conflict lets the caller discard its speculative version and rebuild it from the winning state.
func (*Tracker) ActivateRollback ¶
ActivateRollback is Activate for a rollback, which settles the deployment it replaced as rolled_back rather than succeeded.
func (*Tracker) Begin ¶
Begin acquires the deploy lock and publishes a deployment record.
If another live deployment holds the lock, Begin returns a *LockHeldError (matching errors.Is(err, ErrLockHeld)) describing the blocker, and leaves no deployment in history. A private lock-owner reservation ensures that an older runtime cannot mistake the compatibility lock's not-yet-published owner for an abandoned deployment during a rolling upgrade.
func (*Tracker) Fail ¶
Fail records a failed deployment and releases the lock.
A deployment that was cancelled stays cancelled: the operator's action is the more meaningful account of what happened, and the build failing afterwards is a consequence of it.
func (*Tracker) FailIfUnsettled ¶
FailIfUnsettled records a failure only if the deployment has not already finished, reporting success either way.
It exists for deferred error handlers, which fire on every exit path including the ones that follow a successful activation. Such a handler should not have to reason about the state machine: "record a failure unless the deploy already finished" is exactly what a defer wants, and a deployment that is already active, succeeded or cancelled has a better account of itself than a late error does.
func (*Tracker) Locks ¶
Locks exposes the lock manager for read-only inspection, such as the pre-flight check a client makes before uploading a build context.
func (*Tracker) Reconcile ¶ added in v0.15.0
Reconcile converges the crash window between the app pointer CAS and the attempt's terminal write, and marks abandoned attempts interrupted once their lock lease and grace period are both gone.
func (*Tracker) SetAppVersion ¶
SetAppVersion records the version the build produced. It replaces the "pending-build" placeholder the client used to write at creation time.