Files
mesh-catalog/modules/pacman/cmd/pacman-tools/shapes.go
T

475 lines
19 KiB
Go

package main
// The keeper's bus shapes (novox/hq ADR 0507, ADR 0508): the value of every key of the module's states and the
// body of every event its process emits, in one place, so the process that writes them and the verbs that read
// them agree by type. Each is a struct of named fields only: no map and no free-form value, so nothing but what
// is named here goes on the bus (ADR 0507 §1: package names, versions, unit and container names, file paths and
// exit statuses, never a process's command line or environment). A time in a value is the time of the thing it
// describes (a transaction, a step, an opening), never the time it was read: a key is written only when what it
// describes changed (ADR 0289 §1), and no field changes on a read alone (ADR 0289 §4). When a key last changed
// is the bus's to say (its revision), not a field's.
//
// A key is `<node>` or `<node>.<id>`: a node name and an id are each one key token ([A-Za-z0-9_-]), so the
// dot only ever separates them.
import (
"fmt"
"time"
)
// The module's states (manifest `state` and the keeper's `bus` `state`).
const (
// StateInstalled holds each node's installed packages: key `<node>` an InstalledValue, and key
// `<node>.not-current` a NotCurrent while the node's packages are over the bus's cap (ADR 0507 §2).
StateInstalled = "installed"
// StateUpdates holds what a full upgrade would change: key `<node>` an UpdatesValue (ADR 0507 §4).
StateUpdates = "updates"
// StateRuns holds every upgrade and rollback: key `<node>.<run>` a Run, kept (ADR 0508 §1).
StateRuns = "runs"
// StateRecords holds the package record taken before a run: key `<node>.<run>` a Record, deleted after
// the config value `record-days` (ADR 0508 §2).
StateRecords = "records"
// StateRounds holds the weekly rounds: key `<round>` a Round, created once by the first node of the order,
// and key `<round>.<node>` that node's RoundNode (ADR 0508 §5, §6).
StateRounds = "rounds"
// StateAsks holds the acts asked of a node: key `<node>.<id>` an Ask, taken by that node's keeper alone
// (ADR 0508 §8).
StateAsks = "asks"
)
// States are the module's states, in the manifest's order.
var States = []string{StateInstalled, StateUpdates, StateRuns, StateRecords, StateRounds, StateAsks}
// The events the keeper emits (the keeper's `bus` `emits`, and the manifest's `emits`).
const (
EventPackageChanged = "package.changed" // PackageChanged (ADR 0507 §3)
EventRecorded = "upgrade.recorded" // UpgradeRecorded (ADR 0508 §1, §2)
EventKernelHeld = "upgrade.kernel-held" // KernelHeld (ADR 0508 §3)
EventStarted = "upgrade.started" // UpgradeStarted
EventFinished = "upgrade.finished" // UpgradeFinished
EventFailed = "upgrade.failed" // UpgradeFailed
EventChecked = "upgrade.checked" // UpgradeChecked (ADR 0508 §4)
EventRollbackStarted = "rollback.started" // RollbackStarted (ADR 0508 §7)
EventRollbackFinished = "rollback.finished" // RollbackFinished
)
// Events are the events the keeper emits, each with the type of its body.
var Events = []struct {
Name string
Body any
}{
{EventPackageChanged, PackageChanged{}},
{EventRecorded, UpgradeRecorded{}},
{EventKernelHeld, KernelHeld{}},
{EventStarted, UpgradeStarted{}},
{EventFinished, UpgradeFinished{}},
{EventFailed, UpgradeFailed{}},
{EventChecked, UpgradeChecked{}},
{EventRollbackStarted, RollbackStarted{}},
{EventRollbackFinished, RollbackFinished{}},
}
// Key names.
// NotCurrentKey is the key in `installed` that marks a node's packages as not current.
func NotCurrentKey(node string) string { return node + ".not-current" }
// NodeKey is a key `<node>.<id>`: a run in `runs` or `records`, an ask in `asks`.
func NodeKey(node, id string) string { return node + "." + id }
// RoundNodeKey is a node's key in a round: `<round>.<node>` in `rounds`.
func RoundNodeKey(round, node string) string { return round + "." + node }
// RunID names a run by the moment it began, in UTC: `20261024T040012Z`. A run read from the package manager's
// log is named by the log's time of its start the same way.
func RunID(began time.Time) string { return began.UTC().Format("20060102T150405Z") }
// RoundID names the round of the ISO week that holds the given moment, in its own time zone: `2026-W43`. The
// round opens on Saturday 04:00 local time, so the local date decides the week.
func RoundID(at time.Time) string {
year, week := at.ISOWeek()
return fmt.Sprintf("%04d-W%02d", year, week)
}
// installed
// InstalledValue is `installed.<node>`: every installed package, sorted by name, and the time of the transaction
// that left the database so (ADR 0507 §2). Written compactly: one short entry per package.
type InstalledValue struct {
At time.Time `json:"at"`
Packages []InstalledPackage `json:"packages"`
}
// InstalledPackage is one installed package: its version, whether it was installed explicitly (else as a
// dependency), and its repository, or RepoForeign when no repository holds it (ADR 0507 §7).
type InstalledPackage struct {
Name string `json:"name"`
Version string `json:"version"`
Explicit bool `json:"explicit"`
Repo string `json:"repo"`
}
// RepoForeign marks a package from outside the repositories.
const RepoForeign = "foreign"
// NotCurrent is `installed.<node>.not-current`: the node's packages came to more than the bus's cap on a value,
// so `installed.<node>` keeps the last value written. Written once, when it first happens, and deleted at the
// next successful write of `installed.<node>`.
type NotCurrent struct {
Bytes int `json:"bytes"`
Since time.Time `json:"since"`
}
// updates
// UpdatesValue is `updates.<node>`: every package a full upgrade would change, sorted by name, and whether the
// node reached its mirrors (ADR 0507 §4). A failed read writes Reachable false and keeps the last list, never an
// empty list in its place.
type UpdatesValue struct {
Reachable bool `json:"reachable"`
Packages []VersionChange `json:"packages"`
}
// VersionChange is one package going from one version to another.
type VersionChange struct {
Name string `json:"name"`
From string `json:"from"`
To string `json:"to"`
}
// PackageVersion is one package at one version.
type PackageVersion struct {
Name string `json:"name"`
Version string `json:"version"`
}
// runs
// The kinds of run.
const (
RunUpgrade = "upgrade"
RunRollback = "rollback"
)
// What started a run.
const (
FromRound = "round" // its turn in the week's round
FromAsk = "ask" // an ask: pacman_upgrade, or pacman_rollback with apply
FromLog = "log" // read from the package manager's log at the keeper's first start (ADR 0508 §1)
)
// The steps of a run, in the order they are written. A run that does not start ends at StepRefused; a
// transaction ends at StepFinished or StepFailed, and the after-upgrade check follows either.
const (
StepRecorded = "recorded" // the record is written and read back (upgrade only)
StepCheckedBefore = "checked-before" // the DKMS gate ran; Held names what it held
StepRefused = "refused" // the run did not start; Why says why
StepStarted = "started" // the transaction's unit started
StepFinished = "finished" // the transaction exited 0
StepFailed = "failed" // the transaction failed
StepCheckedAfter = "checked-after" // the after-upgrade check
)
// Run is `runs.<node>.<run>`: one upgrade or rollback of a node, written at each step and at no other time
// (ADR 0508 §1). Round names the round it ran in; Ask the ask that started it (the keeper deletes the ask once
// the run names it); Of, for a rollback, the run whose record it returns to.
type Run struct {
Node string `json:"node"`
Run string `json:"run"`
Kind string `json:"kind"`
From string `json:"from"`
Round string `json:"round,omitempty"`
Ask string `json:"ask,omitempty"`
Of string `json:"of,omitempty"`
Steps []Step `json:"steps"`
}
// Step is one step of a run with its time and outcome; only the fields of its kind are set.
type Step struct {
Step string `json:"step"`
At time.Time `json:"at"`
// StepRefused: why the run did not start.
Why string `json:"why,omitempty"`
// StepCheckedBefore: the kernels held from this run.
Held []HeldKernel `json:"held,omitempty"`
// StepStarted: the transaction's unit, the packages left out of it (held kernels and their headers), and
// for a rollback its plan.
Unit string `json:"unit,omitempty"`
Ignored []string `json:"ignored,omitempty"`
Plan []RollbackItem `json:"plan,omitempty"`
// StepFinished and StepFailed: the exit status, the last error line, and what the transaction changed,
// read from its lines in the package manager's log.
Exit *int `json:"exit,omitempty"`
Error string `json:"error,omitempty"`
Changes *Changes `json:"changes,omitempty"`
// StepCheckedAfter: the after-upgrade check.
Check *Check `json:"check,omitempty"`
}
// HeldKernel is a kernel package left out of a run because an installed DKMS module's own bound excludes its
// new version (ADR 0508 §3): `Linux-Maximum` in the module's META, or `BUILD_EXCLUSIVE_KERNEL` in its dkms.conf.
type HeldKernel struct {
Package string `json:"package"`
Version string `json:"version"`
Module string `json:"module"`
Bound string `json:"bound"`
}
// Changes is what a transaction changed, each list sorted by name.
type Changes struct {
Added []PackageVersion `json:"added"`
Removed []PackageVersion `json:"removed"`
Changed []VersionChange `json:"changed"`
}
// Check is the after-upgrade check (ADR 0508 §4). Passed when the transaction succeeded, no unit newly failed
// and every DKMS module is built for every installed kernel; the rest is said and does not stop the round.
type Check struct {
Passed bool `json:"passed"`
// The .pacnew and .pacsave files the transaction left, by path.
Pacnew []string `json:"pacnew"`
Pacsave []string `json:"pacsave"`
// The distribution's news since the node's previous upgrade.
News []NewsTitle `json:"news"`
// Why the news could not be read, when it could not: no news is then not said to be none.
NewsError string `json:"news_error,omitempty"`
// The units and containers whose processes still map a file the upgrade replaced.
Mapped []Mapped `json:"mapped"`
// Why the mapped files could not be read, when they could not.
MappedError string `json:"mapped_error,omitempty"`
// The units failed now that were not failed in the record.
NewlyFailed []string `json:"newly_failed"`
// Whether a restart is needed: the running kernel is not the installed version of its package.
RestartNeeded bool `json:"restart_needed"`
RunningKernel string `json:"running_kernel"`
InstalledKernel string `json:"installed_kernel"`
// The DKMS modules not built for an installed kernel.
Unbuilt []DKMSBuild `json:"unbuilt"`
// Why the DKMS builds could not be read, when they could not; the check then does not pass.
DKMSError string `json:"dkms_error,omitempty"`
}
// NewsTitle is one item of the distribution's news, without its text.
type NewsTitle struct {
Title string `json:"title"`
Link string `json:"link"`
Published string `json:"published"`
}
// Mapped is a process still mapping a replaced file, named by its unit or its container and its executable's
// path; never its command line or environment.
type Mapped struct {
Unit string `json:"unit,omitempty"`
Container string `json:"container,omitempty"`
Executable string `json:"executable"`
}
// DKMSBuild is one DKMS module at one version for one kernel.
type DKMSBuild struct {
Module string `json:"module"`
Version string `json:"version"`
Kernel string `json:"kernel"`
}
// RollbackItem is one package a rollback brings back (ADR 0508 §7): from its version now to the record's, and
// where that version comes from.
type RollbackItem struct {
Name string `json:"name"`
From string `json:"from"`
To string `json:"to"`
Source string `json:"source"`
Location string `json:"location"`
}
// Where a rollback takes a package's version from, in the order it is looked for.
const (
SourceCache = "cache" // the node's package cache: a file path
SourceArchive = "archive" // the Arch Linux Archive's copy of that exact version: a URL
SourceRepository = "repository" // the archive's repository as it stood on the record's date: a URL
)
// records
// Record is `records.<node>.<run>`: the node's packages taken before a run and read back (ADR 0508 §2): a copy of
// `installed.<node>`, the running kernel (/proc/sys/kernel/osrelease), the units failed at that moment, and the
// installed DKMS modules.
type Record struct {
Node string `json:"node"`
Run string `json:"run"`
At time.Time `json:"at"`
Installed InstalledValue `json:"installed"`
Kernel string `json:"kernel"`
FailedUnits []string `json:"failed_units"`
DKMS []DKMSModule `json:"dkms"`
}
// DKMSModule is one installed DKMS module and the kernels it is built for.
type DKMSModule struct {
Module string `json:"module"`
Version string `json:"version"`
Kernels []string `json:"kernels"`
}
// rounds
// Round is `rounds.<round>`: the week's round, created once (a create, so one round per week) by the first node
// of the order when it opens it, with the order as it stood then (ADR 0508 §5).
type Round struct {
Round string `json:"round"`
Opened time.Time `json:"opened"`
OpenedBy string `json:"opened_by"`
Order []string `json:"order"`
}
// RoundNode is `rounds.<round>.<node>`: the node's place in the round. Run is the run it started in its turn,
// written by the node itself; Pass, the operator's pass of a node that stopped the round, written by the keeper
// of the node waiting on it (ADR 0508 §6). Both write through a compare-and-set.
type RoundNode struct {
Round string `json:"round"`
Node string `json:"node"`
Run string `json:"run,omitempty"`
Pass *Pass `json:"pass,omitempty"`
}
// Pass is a pass of a node asked of the operator through the operator's channel (ADR 0259): the reason, the
// channel's ask, and its answer once given (Answer empty while it waits).
type Pass struct {
Reason string `json:"reason"`
Ask string `json:"ask"`
Asked time.Time `json:"asked"`
Answer string `json:"answer,omitempty"`
Answered *time.Time `json:"answered,omitempty"`
}
// The operator's answers to a pass.
const (
PassApproved = "approved"
PassDeclined = "declined"
)
// asks
// The acts an ask asks.
const (
ActUpgrade = "upgrade" // pacman_upgrade
ActRollback = "rollback" // pacman_rollback with apply
ActPass = "pass" // pacman_round with pass
)
// Ask is `asks.<node>.<id>`: an act asked of a node, taken by that node's keeper alone, which answers in `runs`
// (an upgrade, a rollback) or in `rounds` (a pass) and then deletes the ask (ADR 0508 §8). A rollback names the
// run whose record it returns to and whether the packages the upgrade added are removed; a pass names the
// round, the node passed and the reason, and is asked of the node waiting on it.
type Ask struct {
ID string `json:"id"`
Node string `json:"node"`
Act string `json:"act"`
At time.Time `json:"at"`
Of string `json:"of,omitempty"`
RemoveAdded bool `json:"remove_added,omitempty"`
Round string `json:"round,omitempty"`
Pass string `json:"pass,omitempty"`
Reason string `json:"reason,omitempty"`
}
// events
// PackageChanged is `package.changed`: a write of `installed.<node>` that changed it (ADR 0507 §3).
type PackageChanged struct {
Node string `json:"node"`
At time.Time `json:"at"`
Added []PackageVersion `json:"added"`
Removed []PackageVersion `json:"removed"`
Changed []VersionChange `json:"changed"`
}
// UpgradeRecorded is `upgrade.recorded`: the run's record is written and read back.
type UpgradeRecorded struct {
Node string `json:"node"`
Run string `json:"run"`
Packages int `json:"packages"`
Kernel string `json:"kernel"`
}
// KernelHeld is `upgrade.kernel-held`: kernels left out of a run, each with the module and bound that hold it.
type KernelHeld struct {
Node string `json:"node"`
Run string `json:"run"`
Held []HeldKernel `json:"held"`
}
// UpgradeStarted is `upgrade.started`: a run's transaction started.
type UpgradeStarted struct {
Node string `json:"node"`
Run string `json:"run"`
From string `json:"from"`
Round string `json:"round,omitempty"`
Unit string `json:"unit"`
Ignored []string `json:"ignored"`
}
// UpgradeFinished is `upgrade.finished`: a run's transaction exited 0, with what it changed.
type UpgradeFinished struct {
Node string `json:"node"`
Run string `json:"run"`
Changes Changes `json:"changes"`
}
// UpgradeFailed is `upgrade.failed`: a run that did not start (Step StepRefused, with why in Error) or whose
// transaction failed (Step StepFailed, with its exit status and last error line).
type UpgradeFailed struct {
Node string `json:"node"`
Run string `json:"run"`
Step string `json:"step"`
Exit *int `json:"exit,omitempty"`
Error string `json:"error"`
}
// UpgradeChecked is `upgrade.checked`: the after-upgrade check of a run, an upgrade's or a rollback's; the
// check whole is in the run.
type UpgradeChecked struct {
Node string `json:"node"`
Run string `json:"run"`
Kind string `json:"kind"`
Passed bool `json:"passed"`
}
// RollbackStarted is `rollback.started`: a rollback's transaction started, returning to the record of run Of.
type RollbackStarted struct {
Node string `json:"node"`
Run string `json:"run"`
Of string `json:"of"`
Unit string `json:"unit"`
}
// RollbackFinished is `rollback.finished`: a rollback's transaction ended, with its exit status and, when it
// failed, its last error line.
type RollbackFinished struct {
Node string `json:"node"`
Run string `json:"run"`
Of string `json:"of"`
Exit int `json:"exit"`
Error string `json:"error,omitempty"`
}
// The keeper's config file
// The keeper's environment, named by the manifest's process resource.
const (
EnvBroker = "PACMAN_KEEPER_BROKER" // the keeper's bus account file
EnvConfig = "PACMAN_KEEPER_CONFIG" // the keeper's config file, a KeeperConfig
)
// KeeperConfig is the keeper's config file: the module's config values the mesh merges into it (ADR 0348 §5).
// The process reads it at its start; the mesh restarts the process when it changes (the manifest's
// `restart-on`).
type KeeperConfig struct {
// UpgradeOrder is the nodes in the order they upgrade; the first opens each week's round. Empty: no round.
UpgradeOrder []string `json:"upgrade-order"`
// RecordDays is how long a record is kept, in days.
RecordDays int `json:"record-days"`
// RoundStartMinutes is how long a node whose turn came may wait before starting, in minutes, before the
// round is said to stand still.
RoundStartMinutes int `json:"round-start-minutes"`
}