Name the provisioner client's account in the declaration, and share the apply's record
The local write of a filled file (ADR 0349 §4) accepts only the provisioner client's account, which the declaration names; the reader takes provisioner_account before any controller sends it (contract 10). The apply's record and its rule for what moved become helpers the write uses too. Issue 668. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+43
-29
@@ -999,34 +999,7 @@ func ApplyMindingWindows(
|
||||
kept, keptMode, keptOwner = from.Kept, from.KeptMode, from.KeptOwner
|
||||
}
|
||||
// Only now. The record follows the fact, never leads it.
|
||||
known.Record(store.Applied{
|
||||
Origin: origin,
|
||||
ID: resource.Identity(), Type: string(resource.Kind()),
|
||||
Target: outcome.Target, AppliedAt: time.Now().UTC(),
|
||||
Wrote: outcome.wrote,
|
||||
Into: outcome.into,
|
||||
Kept: kept,
|
||||
KeptMode: keptMode,
|
||||
KeptOwner: keptOwner,
|
||||
Reads: outcome.reads,
|
||||
BusySince: outcome.busySince,
|
||||
Stateless: outcome.stateless,
|
||||
Scope: outcome.scope,
|
||||
User: outcome.user,
|
||||
Found: outcome.found,
|
||||
DeclaredUnit: outcome.declared,
|
||||
Shell: outcome.shell,
|
||||
Linger: outcome.linger,
|
||||
Groups: outcome.groups,
|
||||
Unpacked: outcome.unpacked,
|
||||
AsFound: outcome.asFound,
|
||||
Declared: outcome.asFoundDeclared,
|
||||
FoundAt: outcome.asFoundAt,
|
||||
Holds: holds(resource),
|
||||
})
|
||||
if outcome.found != nil {
|
||||
known.DropFound(resource.Identity())
|
||||
}
|
||||
recordApplied(&known, origin, resource, outcome, kept, keptMode, keptOwner)
|
||||
// Its module has been taken, and what was held for it is now the mesh's. A file written
|
||||
// into, or a service whose lifecycle is the machine's, replaced nothing that was found, so
|
||||
// its outcome says what the apply did, not that a cutover happened; a hold from when it was
|
||||
@@ -1075,7 +1048,7 @@ func ApplyMindingWindows(
|
||||
if outcome.Action == heldStill || outcome.Action == waitsBusy {
|
||||
// Not changed: nothing about it moved, so nothing that restarts on it restarts.
|
||||
log(fmt.Sprintf(" %s %s (%s): %s", outcome.Action, outcome.ID, outcome.Target, outcome.Detail))
|
||||
} else if outcome.Action != "unchanged" {
|
||||
} else if moved(outcome) {
|
||||
changed[resource.Identity()] = true
|
||||
// With the detail, when there is one: "updated app" says a container was replaced;
|
||||
// which file made that happen is what somebody reading the log at the time needs
|
||||
@@ -1168,6 +1141,47 @@ func ApplyMindingWindows(
|
||||
return report, known, nil
|
||||
}
|
||||
|
||||
// recordApplied records what applying a resource did, once it is done: the record follows the fact, never leads it.
|
||||
// kept, keptMode and keptOwner are where the original of what it replaced was kept, and how it was found. What an
|
||||
// earlier apply found its unit as goes with it.
|
||||
func recordApplied(known *store.State, origin string, resource declaration.Resource, outcome Outcome,
|
||||
kept, keptMode, keptOwner string) {
|
||||
known.Record(store.Applied{
|
||||
Origin: origin,
|
||||
ID: resource.Identity(), Type: string(resource.Kind()),
|
||||
Target: outcome.Target, AppliedAt: time.Now().UTC(),
|
||||
Wrote: outcome.wrote,
|
||||
Into: outcome.into,
|
||||
Kept: kept,
|
||||
KeptMode: keptMode,
|
||||
KeptOwner: keptOwner,
|
||||
Reads: outcome.reads,
|
||||
BusySince: outcome.busySince,
|
||||
Stateless: outcome.stateless,
|
||||
Scope: outcome.scope,
|
||||
User: outcome.user,
|
||||
Found: outcome.found,
|
||||
DeclaredUnit: outcome.declared,
|
||||
Shell: outcome.shell,
|
||||
Linger: outcome.linger,
|
||||
Groups: outcome.groups,
|
||||
Unpacked: outcome.unpacked,
|
||||
AsFound: outcome.asFound,
|
||||
Declared: outcome.asFoundDeclared,
|
||||
FoundAt: outcome.asFoundAt,
|
||||
Holds: holds(resource),
|
||||
})
|
||||
if outcome.found != nil {
|
||||
known.DropFound(resource.Identity())
|
||||
}
|
||||
}
|
||||
|
||||
// moved reports whether applying a resource moved it, so what restarts or reloads on it does: anything but
|
||||
// unchanged, and not a container held still or waiting for its busy one to say idle.
|
||||
func moved(outcome Outcome) bool {
|
||||
return outcome.Action != "unchanged" && outcome.Action != heldStill && outcome.Action != waitsBusy
|
||||
}
|
||||
|
||||
// afterWhatTheyRead is the resources in declared order, except that **a service comes after every
|
||||
// resource it names under restart-on or reload-on** (novox/hq issue 260).
|
||||
//
|
||||
|
||||
@@ -1645,6 +1645,25 @@ type Declaration struct {
|
||||
// node-engine write only these. Absent is a controller that hands no file to the provisioner, and every
|
||||
// file is written from the declaration as before.
|
||||
Filled []string
|
||||
|
||||
// Provisioner is the account the provisioner's client runs as on this machine, by name (novox/hq ADR 0349 §4):
|
||||
// the only account whose write of a filled file the node-engine's local write socket accepts. Absent is a
|
||||
// controller that names none, and every such write is refused.
|
||||
Provisioner string
|
||||
}
|
||||
|
||||
// checkProvisioner refuses a provisioner client's account that is no account name, or is root: the account whose
|
||||
// writes the node-engine accepts must be one that could not write the file without it.
|
||||
func checkProvisioner(name string) []string {
|
||||
switch {
|
||||
case name == "":
|
||||
return nil
|
||||
case !accountName.MatchString(name):
|
||||
return []string{fmt.Sprintf("provisioner_account %q is not an account name", name)}
|
||||
case name == "root":
|
||||
return []string{"provisioner_account is root; the provisioner's client runs as an account of its own"}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fills reports whether the provisioner fills the file at path on this machine.
|
||||
@@ -2027,6 +2046,9 @@ type envelope struct {
|
||||
// Filled is optional too (novox/hq ADR 0349 §5), as is a file's `owner-at-runtime`. **An older host refuses
|
||||
// these keys**; a controller sends them only to a host whose health says link.OwnershipContract.
|
||||
Filled []string `json:"filled_files,omitempty"`
|
||||
// Provisioner is optional too (novox/hq ADR 0349 §4). **An older host refuses this key**; a controller sends it
|
||||
// only to a host whose health says link.ProvisionerContract.
|
||||
Provisioner string `json:"provisioner_account,omitempty"`
|
||||
}
|
||||
|
||||
func parse(raw []byte, allowActions bool) (*Declaration, error) {
|
||||
@@ -2045,8 +2067,10 @@ func parse(raw []byte, allowActions bool) (*Declaration, error) {
|
||||
|
||||
d := &Declaration{Version: env.Version, For: env.For, Adoption: env.Adoption, Sequence: env.Sequence,
|
||||
Epoch: env.Epoch, LeftOut: env.LeftOut, Generation: env.Generation, PutBack: env.PutBack,
|
||||
Identities: env.Identities, Groups: env.Groups, Libraries: env.Libraries, Filled: env.Filled}
|
||||
Identities: env.Identities, Groups: env.Groups, Libraries: env.Libraries, Filled: env.Filled,
|
||||
Provisioner: env.Provisioner}
|
||||
problems := checkIdentities(env.Identities, env.Groups)
|
||||
problems = append(problems, checkProvisioner(env.Provisioner)...)
|
||||
problems = append(problems, checkLibraries(env.Groups, env.Libraries)...)
|
||||
if env.Generation < 0 {
|
||||
// Below zero would read as "none claimed" and pass every refusal of an older generation.
|
||||
|
||||
@@ -302,6 +302,12 @@ const BusyContract = 8
|
||||
// fields; an older host refuses the whole declaration for them.
|
||||
const OwnershipContract = 9
|
||||
|
||||
// ProvisionerContract is the statement of a host that also reads a declaration's `provisioner_account` (novox/hq ADR
|
||||
// 0349 §4) and serves the local write socket: it accepts a write of a filled file from that account alone. Its
|
||||
// presence is what tells the controller this host may be sent the field; an older host refuses the whole
|
||||
// declaration for it.
|
||||
const ProvisionerContract = 10
|
||||
|
||||
// Health is one statement of every long-running resource's state on this machine (to-be 48 §4). Said in
|
||||
// every report, as an event on each change, and again every minute while anything is not healthy — so a
|
||||
// lost statement is not a lost fault.
|
||||
|
||||
Reference in New Issue
Block a user