Three hosts: arch, alpine and android

ADR 0060, built. `make hosts` produces mesh-host-arch, mesh-host-alpine and
mesh-host-android, each pinned to its system at link time.

The claim that "almost all of it is shared" held up. All 36 existing apply
tests pass unchanged -- the only edit was naming which system they run against,
which was previously implicit. What moved into internal/system is two appliers'
worth of code and the probes that go with them.

Each system's differences are real and needed re-deriving rather than
translating:

apk reports absence by EMPTY OUTPUT and exits zero either way, where pacman
exits non-zero. Reading apk's exit code the way pacman's is read reports every
package as installed. That is the single most dangerous difference between the
two and it is invisible until it bites.

OpenRC has no LoadState, so "the service does not exist" is read from its prose
rather than a field. Same distinction, different evidence -- and this is exactly
what an interface spanning both would have had to drop, which is why 0060
rejected one.

OpenRC has no is-enabled either. Boot state comes from the runlevel listing:
"does it start at boot" becomes "does it appear in rc-update show default".

Android is a partial host and that is the point. It implements file, directory
and action -- the shapes needing only a filesystem and a way to run something --
and refuses the other three by name, before anything is applied. Its unreachable
appliers return ErrUnsupported rather than a zero value, so "unreachable" fails
loudly if it stops being true.

A host also confirms it is on the machine it was built for, once, at the start.
The alpine host on this Arch machine says "this machine is not Alpine" instead
of failing later inside a package manager that is not there. And a host built
without -X main.builtFor refuses everything, naming the hosts that exist.

Two test problems found by injecting faults. One injection did not compile, so
the check now reports that separately from a pass. The other passed with the
behaviour removed: the missing-service assertion matched "does not exist", which
the FALL-THROUGH error also contains because it echoes the raw output. It now
asserts the diagnosis, which only the correct branch produces.

Verified with the real binaries: android refuses a package naming what it does
support; alpine on Arch refuses the machine; arch applies and is idempotent; a
system-less build refuses everything.
This commit is contained in:
2026-08-28 01:08:11 +02:00
parent 5b7b280e3a
commit 02f1fcc865
9 changed files with 886 additions and 173 deletions
+48 -36
View File
@@ -10,6 +10,7 @@ import (
"github.com/novox/mesh-host/internal/declaration"
"github.com/novox/mesh-host/internal/store"
"github.com/novox/mesh-host/internal/system"
)
// Each test names the decision it defends (novox/hq ADR 0034).
@@ -38,7 +39,7 @@ func TestApplyingTwiceChangesNothingTheSecondTime(t *testing.T) {
{"id":"f","type":"file","path":"`+dir+`/etc/a.conf","content":"hello\n","mode":"0640"}
]}`)
first, state, err := Apply(context.Background(), d, store.State{}, noServices, nil)
first, state, err := Apply(context.Background(), archHost(t), d, store.State{}, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -46,7 +47,7 @@ func TestApplyingTwiceChangesNothingTheSecondTime(t *testing.T) {
t.Fatal("the first apply on an empty machine changed nothing")
}
second, _, err := Apply(context.Background(), d, state, noServices, nil)
second, _, err := Apply(context.Background(), archHost(t), d, state, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -64,7 +65,7 @@ func TestADriftedMachineIsReturned(t *testing.T) {
{"id":"f","type":"file","path":"`+path+`","content":"correct\n","mode":"0644"}
]}`)
_, state, err := Apply(context.Background(), d, store.State{}, noServices, nil)
_, state, err := Apply(context.Background(), archHost(t), d, store.State{}, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -72,7 +73,7 @@ func TestADriftedMachineIsReturned(t *testing.T) {
t.Fatal(err)
}
report, _, err := Apply(context.Background(), d, state, noServices, nil)
report, _, err := Apply(context.Background(), archHost(t), d, state, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -96,7 +97,7 @@ func TestADroppedResourceIsRemoved(t *testing.T) {
{"id":"keep","type":"file","path":"`+keep+`","content":"a\n"},
{"id":"drop","type":"file","path":"`+drop+`","content":"b\n"}
]}`)
_, state, err := Apply(context.Background(), both, store.State{}, noServices, nil)
_, state, err := Apply(context.Background(), archHost(t), both, store.State{}, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -104,7 +105,7 @@ func TestADroppedResourceIsRemoved(t *testing.T) {
one := parse(t, `{"declaration":1,"resources":[
{"id":"keep","type":"file","path":"`+keep+`","content":"a\n"}
]}`)
report, state, err := Apply(context.Background(), one, state, noServices, nil)
report, state, err := Apply(context.Background(), archHost(t), one, state, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -136,7 +137,7 @@ func TestNothingTheHostDidNotCreateIsTouched(t *testing.T) {
d := parse(t, `{"declaration":1,"resources":[
{"id":"ours","type":"file","path":"`+filepath.Join(dir, "ours.conf")+`","content":"a\n"}
]}`)
if _, _, err := Apply(context.Background(), d, store.State{}, noServices, nil); err != nil {
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, noServices, nil); err != nil {
t.Fatal(err)
}
@@ -155,7 +156,7 @@ func TestARenameToTheSamePathDoesNotDeleteTheNewFile(t *testing.T) {
before := parse(t, `{"declaration":1,"resources":[
{"id":"old","type":"file","path":"`+path+`","content":"old\n"}
]}`)
_, state, err := Apply(context.Background(), before, store.State{}, noServices, nil)
_, state, err := Apply(context.Background(), archHost(t), before, store.State{}, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -163,7 +164,7 @@ func TestARenameToTheSamePathDoesNotDeleteTheNewFile(t *testing.T) {
after := parse(t, `{"declaration":1,"resources":[
{"id":"new","type":"file","path":"`+path+`","content":"new\n"}
]}`)
if _, _, err := Apply(context.Background(), after, state, noServices, nil); err != nil {
if _, _, err := Apply(context.Background(), archHost(t), after, state, noServices, nil); err != nil {
t.Fatal(err)
}
@@ -191,7 +192,7 @@ func TestAFailedStepFailsTheApply(t *testing.T) {
{"id":"never","type":"file","path":"`+filepath.Join(dir, "never.conf")+`","content":"b\n"}
]}`)
_, _, err := Apply(context.Background(), d, store.State{}, noServices, nil)
_, _, err := Apply(context.Background(), archHost(t), d, store.State{}, noServices, nil)
if err == nil {
t.Fatal("an impossible resource did not fail the apply")
}
@@ -224,7 +225,7 @@ func TestNothingIsRecordedUntilItWorked(t *testing.T) {
{"id":"doomed","type":"directory","path":"`+blocker+`"}
]}`)
_, state, err := Apply(context.Background(), d, store.State{}, noServices, nil)
_, state, err := Apply(context.Background(), archHost(t), d, store.State{}, noServices, nil)
if err == nil {
t.Fatal("expected a failure")
}
@@ -243,7 +244,7 @@ func TestAModeIsMaintainedNotJustSet(t *testing.T) {
{"id":"f","type":"file","path":"`+path+`","content":"s\n","mode":"0600"}
]}`)
_, state, err := Apply(context.Background(), d, store.State{}, noServices, nil)
_, state, err := Apply(context.Background(), archHost(t), d, store.State{}, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -251,7 +252,7 @@ func TestAModeIsMaintainedNotJustSet(t *testing.T) {
t.Fatal(err)
}
report, _, err := Apply(context.Background(), d, state, noServices, nil)
report, _, err := Apply(context.Background(), archHost(t), d, state, noServices, nil)
if err != nil {
t.Fatal(err)
}
@@ -282,7 +283,7 @@ func TestAServiceIsReadBackNotAssumed(t *testing.T) {
d := parse(t, `{"declaration":1,"resources":[
{"id":"s","type":"service","unit":"doomed.service","state":"running"}
]}`)
_, _, err := Apply(context.Background(), d, store.State{}, run, nil)
_, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err == nil {
t.Fatal("a service that died immediately was reported as running")
}
@@ -298,7 +299,7 @@ func TestAnUnknownServiceStateIsRefusedNotGuessed(t *testing.T) {
d := parse(t, `{"declaration":1,"resources":[
{"id":"s","type":"service","unit":"odd.service","state":"running"}
]}`)
_, _, err := Apply(context.Background(), d, store.State{}, run, nil)
_, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err == nil || !strings.Contains(err.Error(), "neither running nor stopped") {
t.Errorf("an unrecognised service state was not refused: %v", err)
}
@@ -322,7 +323,7 @@ func TestADroppedServiceIsStoppedNotDeleted(t *testing.T) {
{"id":"other","type":"file","path":"`+filepath.Join(t.TempDir(), "a")+`","content":"a\n"}
]}`)
if _, _, err := Apply(context.Background(), d, state, run, nil); err != nil {
if _, _, err := Apply(context.Background(), archHost(t), d, state, run, nil); err != nil {
t.Fatal(err)
}
joined := strings.Join(commands, "; ")
@@ -348,7 +349,7 @@ func TestAUnitThatDoesNotExistIsNotStopped(t *testing.T) {
{"id":"s","type":"service","unit":"never-installed.service","state":"stopped"}
]}`)
_, state, err := Apply(context.Background(), d, store.State{}, absent, nil)
_, state, err := Apply(context.Background(), archHost(t), d, store.State{}, absent, nil)
if err == nil {
t.Fatal("a unit that does not exist was reported as satisfactorily stopped")
}
@@ -369,7 +370,7 @@ func TestAMaskedUnitIsRefused(t *testing.T) {
d := parse(t, `{"declaration":1,"resources":[
{"id":"s","type":"service","unit":"masked.service","state":"running"}
]}`)
if _, _, err := Apply(context.Background(), d, store.State{}, masked, nil); err == nil {
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, masked, nil); err == nil {
t.Fatal("a masked unit was accepted")
}
}
@@ -397,7 +398,7 @@ func TestForgettingAUnitThatIsGoneDoesNotStrandTheNode(t *testing.T) {
{"id":"f","type":"file","path":"`+filepath.Join(t.TempDir(), "a")+`","content":"a\n"}
]}`)
report, state, err := Apply(context.Background(), d, known, run, nil)
report, state, err := Apply(context.Background(), archHost(t), d, known, run, nil)
if err != nil {
t.Fatalf("a vanished unit stranded the apply: %v", err)
}
@@ -441,7 +442,7 @@ func TestABrokenPackageDatabaseIsNotReadAsNotInstalled(t *testing.T) {
{"id":"rt","type":"package","package":"docker"}
]}`)
_, _, err := Apply(context.Background(), d, store.State{}, run, nil)
_, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err == nil {
t.Fatal("a broken package database was read as 'not installed'")
}
@@ -462,7 +463,7 @@ func TestAnInstalledPackageIsNotReinstalled(t *testing.T) {
{"id":"rt","type":"package","package":"docker"}
]}`)
report, _, err := Apply(context.Background(), d, store.State{}, run, nil)
report, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err != nil {
t.Fatalf("apply failed: %v", err)
}
@@ -492,7 +493,7 @@ func TestAPackageIsNeverUninstalled(t *testing.T) {
{"id":"f","type":"file","path":"`+filepath.Join(t.TempDir(), "a")+`","content":"a\n"}
]}`)
report, state, err := Apply(context.Background(), d, known, run, nil)
report, state, err := Apply(context.Background(), archHost(t), d, known, run, nil)
if err != nil {
t.Fatalf("dropping a package stranded the apply: %v", err)
}
@@ -522,7 +523,7 @@ func TestAnActionThatIsAlreadyTrueDoesNotRun(t *testing.T) {
{"id":"db","type":"action","command":["create-db","mesh"],"verify":["has-db","mesh"]}
]}`)
report, _, err := Apply(context.Background(), d, store.State{}, run, nil)
report, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err != nil {
t.Fatalf("apply failed: %v", err)
}
@@ -548,7 +549,7 @@ func TestAnActionThatSucceedsAndDoesNothingFails(t *testing.T) {
{"id":"db","type":"action","command":["create-db","mesh"],"verify":["has-db","mesh"]}
]}`)
_, state, err := Apply(context.Background(), d, store.State{}, run, nil)
_, state, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err == nil {
t.Fatal("an action that reported success and did nothing was accepted")
}
@@ -575,7 +576,7 @@ func TestAnActionRunsInsideTheContainerItNames(t *testing.T) {
{"id":"db","type":"action","in":"store","command":["createdb","mesh"],"verify":["psql","-lqt"]}
]}`)
if _, _, err := Apply(context.Background(), d, store.State{}, run, nil); err != nil {
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil); err != nil {
t.Fatalf("apply failed: %v", err)
}
if !sawExec {
@@ -602,7 +603,7 @@ func TestAContainerThatExitsImmediatelyFailsTheApply(t *testing.T) {
{"id":"store","type":"container","name":"store","image":"`+pinned+`"}
]}`)
_, state, err := Apply(context.Background(), d, store.State{}, run, nil)
_, state, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err == nil {
t.Fatal("a container that exited immediately was reported as applied")
}
@@ -644,7 +645,7 @@ func TestAContainerWhoseDeclarationChangedIsReplaced(t *testing.T) {
return "", nil
}
report, _, err := Apply(context.Background(), d, store.State{}, run, nil)
report, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err != nil {
t.Fatalf("apply failed: %v", err)
}
@@ -674,7 +675,7 @@ func TestAContainerThatMatchesIsLeftAlone(t *testing.T) {
return "", nil
}
report, _, err := Apply(context.Background(), d, store.State{}, run, nil)
report, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err != nil {
t.Fatalf("apply failed: %v", err)
}
@@ -733,7 +734,7 @@ func TestAServiceIsEnabledAtBootWhenAsked(t *testing.T) {
{"id":"rt","type":"service","unit":"docker.service","state":"running","boot":"enabled"}
]}`)
report, _, err := Apply(context.Background(), d, store.State{},
report, _, err := Apply(context.Background(), archHost(t), d, store.State{},
systemctlStub(t, "loaded", "inactive", "disabled", &verbs), nil)
if err != nil {
t.Fatalf("apply failed: %v", err)
@@ -753,7 +754,7 @@ func TestBootIsEnabledBeforeTheUnitIsStarted(t *testing.T) {
d := parseTrusted(t, `{"declaration":1,"resources":[
{"id":"rt","type":"service","unit":"docker.service","state":"running","boot":"enabled"}
]}`)
if _, _, err := Apply(context.Background(), d, store.State{},
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{},
systemctlStub(t, "loaded", "inactive", "disabled", &verbs), nil); err != nil {
t.Fatal(err)
}
@@ -768,7 +769,7 @@ func TestAlreadyEnabledAndRunningIsUnchanged(t *testing.T) {
{"id":"rt","type":"service","unit":"docker.service","state":"running","boot":"enabled"}
]}`)
report, _, err := Apply(context.Background(), d, store.State{},
report, _, err := Apply(context.Background(), archHost(t), d, store.State{},
systemctlStub(t, "loaded", "active", "enabled", &verbs), nil)
if err != nil {
t.Fatalf("apply failed: %v", err)
@@ -788,7 +789,7 @@ func TestOmittingBootLeavesItAlone(t *testing.T) {
d := parseTrusted(t, `{"declaration":1,"resources":[
{"id":"rt","type":"service","unit":"docker.service","state":"running"}
]}`)
if _, _, err := Apply(context.Background(), d, store.State{},
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{},
systemctlStub(t, "loaded", "inactive", "enabled", &verbs), nil); err != nil {
t.Fatal(err)
}
@@ -808,7 +809,7 @@ func TestAStaticUnitCannotBeEnabled(t *testing.T) {
{"id":"rt","type":"service","unit":"dbus.socket","state":"running","boot":"enabled"}
]}`)
_, _, err := Apply(context.Background(), d, store.State{},
_, _, err := Apply(context.Background(), archHost(t), d, store.State{},
systemctlStub(t, "loaded", "active", "static", &verbs), nil)
if err == nil {
t.Fatal("a static unit was accepted as enable-able")
@@ -823,7 +824,7 @@ func TestAnUnknownBootStateIsRefusedNotGuessed(t *testing.T) {
d := parseTrusted(t, `{"declaration":1,"resources":[
{"id":"rt","type":"service","unit":"x.service","state":"running","boot":"enabled"}
]}`)
_, _, err := Apply(context.Background(), d, store.State{},
_, _, err := Apply(context.Background(), archHost(t), d, store.State{},
systemctlStub(t, "loaded", "active", "indirect", &verbs), nil)
if err == nil {
t.Fatal("an unrecognised boot state was guessed at instead of refused")
@@ -899,7 +900,7 @@ func TestAContainerUsesTheRuntimeTheMachineHas(t *testing.T) {
// It will fail at read-back — the stub never reports it running — and what matters is
// WHICH binary it used getting there.
_, _, _ = Apply(context.Background(), d, store.State{}, run, nil)
_, _, _ = Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
for _, c := range calledWith {
if c != "podman" {
@@ -921,7 +922,7 @@ func TestNoRuntimeIsSaidPlainly(t *testing.T) {
{"id":"store","type":"container","name":"store","image":"`+pinned+`"}
]}`)
_, _, err := Apply(context.Background(), d, store.State{}, run, nil)
_, _, err := Apply(context.Background(), archHost(t), d, store.State{}, run, nil)
if err == nil {
t.Fatal("a machine with no container runtime applied a container")
}
@@ -931,3 +932,14 @@ func TestNoRuntimeIsSaidPlainly(t *testing.T) {
}
}
}
// archHost is the system these tests run against. They were written for pacman and systemd, and
// naming that is better than the implicit default it used to be.
func archHost(t *testing.T) system.System {
t.Helper()
s, err := system.For("arch")
if err != nil {
t.Fatal(err)
}
return s
}