A host running as a service says what its apply did
The serving path passed nil where the apply writes its detail. Nil is silence, so everything the apply says — a file held, a container replaced, the found firewall retired — was visible when a person ran the one-shot command and discarded in the way the host actually runs, which is always. Measured: after a machine was converged and its found firewall was not retired, what the host decided was unrecoverable, because it had said it to nobody. That is why issue 143 has candidates instead of a cause. say already reaches stdout and the unit sends that to the journal, so this needed no new mechanism — only for the argument to be passed. Both paths now reach the apply through one named helper, so a reader asking where the apply's output goes finds one answer. The test asserts the log is never nil and cannot catch the fault it was written for, which is wiring; that is proved by a deployed host whose journal carries the detail.
This commit is contained in:
+26
-1
@@ -1246,6 +1246,21 @@ func applyDeclared(ctx context.Context, opts options, raw []byte, sched *apply.S
|
||||
return applyAndKeep(ctx, opts, raw, nil, sched, say)
|
||||
}
|
||||
|
||||
// announceOr is what the apply writes its detail with, given what the caller has to say things with.
|
||||
//
|
||||
// **Never nil.** This argument was nil on the serving path, and nil is silence: everything the apply
|
||||
// says — a file held, a container replaced, the found firewall retired — was visible when a person ran
|
||||
// the one-shot command and discarded in the way the host actually runs (novox/hq 04-ISSUES/143).
|
||||
//
|
||||
// Named rather than written inline at the call site so both paths reach the apply the same way, and so
|
||||
// a reader asking "where does the apply's output go" finds one answer.
|
||||
func announceOr(say link.Announce) func(string) {
|
||||
if say == nil {
|
||||
return func(string) {}
|
||||
}
|
||||
return say
|
||||
}
|
||||
|
||||
// applying serialises applies within this process.
|
||||
//
|
||||
// **Two things apply here: the link and the reconcile loop**, and each reads the node's state,
|
||||
@@ -1308,8 +1323,18 @@ func applyAndKeep(ctx context.Context, opts options, raw []byte, signed *store.D
|
||||
|
||||
// Declared, not carried. A declaration from the mesh removes only what the mesh previously
|
||||
// declared — never what this machine raised for itself from its bundle (04-ISSUES/010).
|
||||
// **What the apply says goes to the console, which is the journal when this runs as a service.**
|
||||
//
|
||||
// It was nil, and nil is silence. The one-shot path has always passed a real one, so every detail
|
||||
// the apply produces — a file held, a container replaced, the found firewall retired — was visible
|
||||
// when a person ran it by hand and discarded in the way the host actually runs. Measured: after a
|
||||
// machine was converged and its found firewall was not retired, what the host decided was
|
||||
// unrecoverable, because it had said it to nobody (novox/hq 04-ISSUES/143).
|
||||
//
|
||||
// `say` already reaches stdout, and the launcher's unit sends that to the journal, so this needs
|
||||
// no new mechanism — only for the argument to be passed.
|
||||
outcome, updated, applyErr := apply.ApplyKeeping(ctx, built, declared, known, store.OriginDeclared,
|
||||
apply.ExecRunner, nil, sealOpener(opts.state), apply.KeepIn(filepath.Dir(opts.state)))
|
||||
apply.ExecRunner, announceOr(say), sealOpener(opts.state), apply.KeepIn(filepath.Dir(opts.state)))
|
||||
|
||||
// The mode the mesh said, recorded whichever way the apply went: the declaration is kept
|
||||
// either way, and the node is held to it from the next reconcile (novox/hq ADR 0100).
|
||||
|
||||
@@ -568,3 +568,28 @@ func TestAConvergedMachineSaysItsOutwardLinksUnasked(t *testing.T) {
|
||||
t.Fatal("a refused report is offered as news about the machine")
|
||||
}
|
||||
}
|
||||
|
||||
// **A host running as a service says what its apply did.**
|
||||
//
|
||||
// The serving path passed nil where the apply writes its detail, and nil is silence. The one-shot path
|
||||
// has always passed a real function, so everything the apply says was visible when a person ran it by
|
||||
// hand and discarded in the way the host actually runs. Measured before this was written: a machine was
|
||||
// converged, its found firewall was not retired, and what the host decided was unrecoverable because it
|
||||
// had been said to nobody (novox/hq 04-ISSUES/143).
|
||||
//
|
||||
// This asserts only that the apply's log is never nil and that a line reaches what the caller gave.
|
||||
// **It cannot catch the fault it was written for** — a call site passing nil directly — because that is
|
||||
// wiring, and wiring is only proved by running the thing. That proof is a deployed host whose journal
|
||||
// carries the apply's detail, which is how this fix was verified.
|
||||
func TestTheApplysLogIsNeverNil(t *testing.T) {
|
||||
if announceOr(nil) == nil {
|
||||
t.Fatal("a host with nowhere to say things got a nil log, which the apply will call")
|
||||
}
|
||||
announceOr(nil)("this goes nowhere and must not panic")
|
||||
|
||||
var said []string
|
||||
announceOr(func(line string) { said = append(said, line) })(" disabled ufw")
|
||||
if len(said) != 1 || !strings.Contains(said[0], "disabled ufw") {
|
||||
t.Fatalf("the apply's detail did not reach the caller's announce: %v", said)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user