Files
mesh-controller/internal/chain/records.go
T
jschoubben cb00bbbfcf End an unassigned key with ended and remove no key of the chain (issue 1093)
ADR 0408 section 4 says no key of the chain is removed, a delete marker included. The Backend loses Remove and the Chain RemoveAssignment. An unassignment, and the end of a value only pulled in by another, write the value again with ended set, by compare-and-set; assigning it again writes a fresh standing value over it. A value with ended or a state is no assignment (Assignment.Stands) to the assignment lists, the joins, forgetting a module, seat holdings, the given values, the move and the assignment-changed event.
2026-10-18 03:17:47 +02:00

202 lines
9.2 KiB
Go

// 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"`
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 `<node>.<module>`. 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, in a later delivery, the pending assignment waiting for its build (issue 1094).
Ended time.Time `json:"ended,omitzero"`
State string `json:"state,omitempty"`
}
// 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 }