// Package chain keeps the controller's chain on the bus alone (novox/hq ADR 0408, design 47): module, commit, build, // published artifact or package, rollout and assignment are one chain, a bucket each of the bus's key-value store, // each the only source of its kind, written by the controller that holds the lease by compare-and-set. // // **No key is removed.** A Backend has no delete and no purge (ADR 0408 §4): an assignment of a module taken off its // node is kept, saying so. **A value is never cut** (§6): one over the largest the bus holds is refused, and the key // stays as it was. **Each key keeps its earlier values** (§5), read back with History. package chain import ( "encoding/json" "time" ) // Ref names a build and the commit it was made from: one end of a change. type Ref struct { Build string `json:"build,omitempty"` Commit string `json:"commit,omitempty"` } // Package is a release a build published to the package registry. type Package struct { Name string `json:"name"` Version string `json:"version"` } // Published is what a build published: an artifact by its digest, or a package release by name and version. type Published struct { Artifact string `json:"artifact,omitempty"` Package *Package `json:"package,omitempty"` } // Artifact is one thing a build made. type Artifact struct { Name string `json:"name"` Kind string `json:"kind"` Reference string `json:"reference"` } // ReadRepository is a repository a build read source from besides the module's own. type ReadRepository struct { Repository string `json:"repository"` Ref string `json:"ref,omitempty"` Paths []string `json:"paths,omitempty"` Own bool `json:"own,omitempty"` } // Source is the build source a build read in one repository. type Source struct { Repository string `json:"repository,omitempty"` Ref string `json:"ref,omitempty"` Paths []string `json:"paths"` } // The states of a build record. A record in state asked is a request that no outcome has answered (issue 1091); one // without a state is an outcome, and keeps the Request it was asked with. const BuildAsked = "asked" // The states of a request's Reask. const ( ReaskPending = "pending" ReaskClaimed = "claimed" ) // Request is a build as it was asked for (novox/hq issue 325, ADR 0408 §1): kept in the build's own record under its id, // first alone as state asked and then beside the outcome that answered it. type Request struct { // Repository and Seat are the source as the mesh spells it; Path is the module's directory in it, Ref the branch or // commit asked, and Commit the commit that made the ask, where one did. Repository string `json:"repository,omitempty"` Seat string `json:"seat,omitempty"` Path string `json:"path,omitempty"` Ref string `json:"ref,omitempty"` Commit string `json:"commit,omitempty"` // For says who asked: merge, plan, build (a person), assign. For string `json:"for,omitempty"` // NotAsked is why the ask could not be handed over; OutcomeUnknown why an asker that handed it over stopped waiting. NotAsked string `json:"not-asked,omitempty"` OutcomeUnknown string `json:"outcome-unknown,omitempty"` At time.Time `json:"at,omitzero"` AtTerminal bool `json:"at-terminal,omitempty"` // Reask is, on a request the move found never handed over, "pending" until the controller asks it again, "claimed" // while it does, and the build id it was asked as once it has: published once, never twice (issue 981 note 7 Q5). Reask string `json:"reask,omitempty"` } // Build is one attempt, whichever way it went, kept under its id. It names its module, its repository and commit, its // reuse key (novox/hq ADR 0350) and what it published (ADR 0408 §1). type Build struct { ID string `json:"id"` State string `json:"state,omitempty"` Repository string `json:"repository"` Ref string `json:"ref,omitempty"` Module string `json:"module,omitempty"` Commit string `json:"commit,omitempty"` On string `json:"on,omitempty"` Path string `json:"path,omitempty"` Manifest json.RawMessage `json:"manifest,omitempty"` // ManifestDigest names the manifest in the manifests bucket (ManifestKey); a record that has it keeps no Manifest. ManifestDigest string `json:"manifest-digest,omitempty"` Against []string `json:"against,omitempty"` Mirrored []string `json:"mirrored,omitempty"` Read []ReadRepository `json:"read,omitempty"` Sources []Source `json:"sources,omitempty"` SourceFingerprint string `json:"source-fingerprint,omitempty"` Failed string `json:"failed,omitempty"` Made []Artifact `json:"made,omitempty"` Asked time.Time `json:"asked,omitzero"` At time.Time `json:"at,omitzero"` ReuseKey string `json:"reuse-key,omitempty"` Published *Published `json:"published,omitempty"` Request *Request `json:"request,omitempty"` } // Commit is one repository's commit a rollout carries. type Commit struct { Repository string `json:"repository"` Branch string `json:"branch,omitempty"` Commit string `json:"commit"` Merged time.Time `json:"merged,omitzero"` } // Change is one assignment a rollout changed: the build and commit it ran before and the ones it runs after. type Change struct { Node string `json:"node"` Module string `json:"module"` Before Ref `json:"before"` After Ref `json:"after"` } // Step is one tier sent to one machine. type Step struct { Tier int `json:"tier"` Node string `json:"node"` Sent *time.Time `json:"sent,omitempty"` Reported *time.Time `json:"reported,omitempty"` } // Links are the fields a rollout gains on the chain: the builds it carried, the machine steps it has taken and the // assignments it changed, each with the build before and after. The controller's plan embeds them, so the plan's own // fields and these are one value (ADR 0408 §1). type Links struct { Builds []string `json:"builds,omitempty"` Steps []Step `json:"steps,omitempty"` Changed []Change `json:"changed,omitempty"` } // HeldMerge is a merge a rollout holds: the columns the controller's table of batched merges had, kept in the record of the // batch or walk that answers it (novox/hq ADR 0276, issue 1092). A merge no plan answers (Waiting) stays in the record // that held it last. Placed is when it entered this record, so one found in two records after a crash between the two // writes is read from the one it entered last. type HeldMerge struct { Repository string `json:"repository"` Branch string `json:"branch,omitempty"` Commit string `json:"commit"` Merged time.Time `json:"merged,omitzero"` Heard time.Time `json:"heard,omitzero"` Event json.RawMessage `json:"event,omitempty"` Alone bool `json:"alone,omitempty"` Waiting bool `json:"waiting,omitempty"` Placed time.Time `json:"placed,omitzero"` } // Rollout is the controller's record of one batch's delivery, kept under the batch's id: the commit it fixed per // repository, its tiers, the builds it carried and the assignments it changed (ADR 0408 §1). The controller's plan // carries more (its modules, its times, its backups) beside these, under the same key. type Rollout struct { ID string `json:"id"` State string `json:"state,omitempty"` Commits []Commit `json:"commits,omitempty"` Tiers [][]string `json:"tiers,omitempty"` Links } // Assignment is one module on one node, kept under `.`. The first five fields are the ones its readers // have always read, with the names they have always had (novox/hq ADR 0408 §3); the rest name the chain: the build, the // artifact (by digest) or the package it runs, its commit, the rollout that put it there and since when. type Assignment struct { Node string `json:"node"` Module string `json:"module"` Assigned time.Time `json:"assigned,omitzero"` RequiredBy string `json:"required-by,omitempty"` Provisioning json.RawMessage `json:"provisioning,omitempty"` Build string `json:"build,omitempty"` Artifact string `json:"artifact,omitempty"` Package *Package `json:"package,omitempty"` Commit string `json:"commit,omitempty"` Rollout string `json:"rollout,omitempty"` Since time.Time `json:"since,omitzero"` // Ended is when the assignment ended (an unassignment): absent while it stands. **No key of the chain is removed** // (ADR 0408 §4), so an unassigned module keeps its key, saying so; every reader treats a value with Ended, or with a // State, as no assignment. State is StatePending while the key holds an assignment waiting for its build, or one that // was never made (issue 1094). Ended time.Time `json:"ended,omitzero"` State string `json:"state,omitempty"` // Pending is the assignment's wait for its build (novox/hq ADR 0261), kept in the value of the key it waits for and, when // it is made, carried into the value that stands: the record of what became of the wait. Pending *Pending `json:"pending,omitempty"` // PulledIn marks a standing value of a module that is on the machine only because another requires it, or because a // send carried it: no time assigned, but a key all the same, so what the machine was sent of it is kept and read // (issue 1109). Never a state: every reader treats a non-empty state as no assignment, and would take it off the machine. PulledIn bool `json:"pulled-in,omitempty"` // SentUnknown is when a send by hand made what the machine runs unknown (a declaration sent with no builds known): the // commit is cleared with it. Absent normally; a send that names its builds clears it. SentUnknown time.Time `json:"sent-unknown,omitzero"` } // StatePending is the State of a value that holds no assignment but a pending one. const StatePending = "pending" // The states of a pending assignment (novox/hq ADR 0261). const ( PendingWaiting = "waiting" PendingApplying = "applying" PendingApplied = "applied" PendingRefused = "refused" PendingExpired = "expired" PendingWithdrawn = "withdrawn" ) // Pending is an assignment kept until its module is registered: the build it waits for, where that build is from, and the // times of its claim and its end. Its id is p-, unique for a key, which holds one at a time. type Pending struct { ID string `json:"id"` State string `json:"state"` Build string `json:"build,omitempty"` Repository string `json:"repository,omitempty"` Path string `json:"path,omitempty"` Since time.Time `json:"since"` Note string `json:"note,omitempty"` Settled *time.Time `json:"settled,omitempty"` Raised *time.Time `json:"raised,omitempty"` Acknowledged *time.Time `json:"acknowledged,omitempty"` Cleared *time.Time `json:"cleared,omitempty"` // Earlier are the waits of this key that ended unmade and were replaced by this one before their condition was answered // (taken back or cleared): each stays, with its own marks, so a replaced wait never leaves the operator without the // condition it raised or was to raise. Dropped once answered. Earlier []Pending `json:"earlier,omitempty"` } // Open says the assignment is still to be made. func (p Pending) Open() bool { return p.State == PendingWaiting || p.State == PendingApplying } // CanSettle says a pending assignment in state from may end in state to: a wait expires or is withdrawn, a claim becomes // applied or refused, and nothing else. func CanSettle(from, to string) bool { switch from { case PendingWaiting: return to == PendingExpired || to == PendingWithdrawn case PendingApplying: return to == PendingApplied || to == PendingRefused } return false } // CanMove says a pending assignment in state from may go to state to without ending: claimed, or released again. func CanMove(from, to string) bool { return (from == PendingWaiting && to == PendingApplying) || (from == PendingApplying && to == PendingWaiting) } // Stands says the value is an assignment that holds: it has not ended and is not waiting in a state of its own. func (a Assignment) Stands() bool { return a.Ended.IsZero() && a.State == "" } // Key is an assignment's key. func (a Assignment) Key() string { return a.Node + "." + a.Module }