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:
+26
-135
@@ -25,11 +25,12 @@ import (
|
||||
|
||||
"github.com/novox/mesh-host/internal/declaration"
|
||||
"github.com/novox/mesh-host/internal/store"
|
||||
"github.com/novox/mesh-host/internal/system"
|
||||
)
|
||||
|
||||
// Runner executes a command. The real one is used everywhere outside unit tests; behaviour
|
||||
// against a real system is tested alongside rather than mocked (novox/hq ADR 0034).
|
||||
type Runner func(ctx context.Context, name string, args ...string) (string, error)
|
||||
type Runner = system.Runner
|
||||
|
||||
// Outcome is what happened to one resource.
|
||||
type Outcome struct {
|
||||
@@ -81,6 +82,7 @@ func (e *Error) Unwrap() error { return e.Err }
|
||||
// apply then fails — a recovery concern, where the other is a correctness one.
|
||||
func Apply(
|
||||
ctx context.Context,
|
||||
sys system.System,
|
||||
d *declaration.Declaration,
|
||||
known store.State,
|
||||
run Runner,
|
||||
@@ -97,7 +99,7 @@ func Apply(
|
||||
}
|
||||
|
||||
for _, orphan := range known.Orphans(declared) {
|
||||
action, detail, err := remove(ctx, orphan, run)
|
||||
action, detail, err := remove(ctx, sys, orphan, run)
|
||||
if err != nil {
|
||||
return report, known, &Error{Resource: orphan.ID, Err: err, Done: report}
|
||||
}
|
||||
@@ -110,7 +112,7 @@ func Apply(
|
||||
}
|
||||
|
||||
for _, resource := range d.Resources {
|
||||
outcome, err := applyOne(ctx, resource, run)
|
||||
outcome, err := applyOne(ctx, sys, resource, run)
|
||||
if err != nil {
|
||||
return report, known, &Error{Resource: resource.Identity(), Err: err, Done: report}
|
||||
}
|
||||
@@ -128,16 +130,16 @@ func Apply(
|
||||
return report, known, nil
|
||||
}
|
||||
|
||||
func applyOne(ctx context.Context, r declaration.Resource, run Runner) (Outcome, error) {
|
||||
func applyOne(ctx context.Context, sys system.System, r declaration.Resource, run Runner) (Outcome, error) {
|
||||
switch res := r.(type) {
|
||||
case *declaration.Directory:
|
||||
return applyDirectory(res)
|
||||
case *declaration.File:
|
||||
return applyFile(res)
|
||||
case *declaration.Service:
|
||||
return applyService(ctx, res, run)
|
||||
return applyService(ctx, sys, res, run)
|
||||
case *declaration.Package:
|
||||
return applyPackage(ctx, res, run)
|
||||
return applyPackage(ctx, sys, res, run)
|
||||
case *declaration.Container:
|
||||
return applyContainer(ctx, res, run)
|
||||
case *declaration.Action:
|
||||
@@ -314,7 +316,7 @@ func writeAtomically(path string, content []byte, mode os.FileMode) error {
|
||||
return os.Rename(tmp.Name(), path)
|
||||
}
|
||||
|
||||
func applyService(ctx context.Context, r *declaration.Service, run Runner) (Outcome, error) {
|
||||
func applyService(ctx context.Context, sys system.System, r *declaration.Service, run Runner) (Outcome, error) {
|
||||
out := begin(r)
|
||||
var changes []string
|
||||
|
||||
@@ -322,19 +324,15 @@ func applyService(ctx context.Context, r *declaration.Service, run Runner) (Outc
|
||||
// half way in the more useful direction: enabled-and-stopped comes back at the next boot,
|
||||
// where running-and-disabled does not.
|
||||
if r.Boot != "" {
|
||||
bootBefore, err := serviceBoot(ctx, r.Unit, run)
|
||||
bootBefore, err := sys.ServiceBoot(ctx, run, r.Unit)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
if bootBefore != r.Boot {
|
||||
verb := "enable"
|
||||
if r.Boot == "disabled" {
|
||||
verb = "disable"
|
||||
if err := sys.SetServiceBoot(ctx, run, r.Unit, r.Boot); err != nil {
|
||||
return out, fmt.Errorf("setting %s to %s at boot: %w", r.Unit, r.Boot, err)
|
||||
}
|
||||
if _, err := run(ctx, "systemctl", verb, r.Unit); err != nil {
|
||||
return out, fmt.Errorf("%s %s: %w", verb, r.Unit, err)
|
||||
}
|
||||
bootAfter, err := serviceBoot(ctx, r.Unit, run)
|
||||
bootAfter, err := sys.ServiceBoot(ctx, run, r.Unit)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -346,23 +344,18 @@ func applyService(ctx context.Context, r *declaration.Service, run Runner) (Outc
|
||||
}
|
||||
}
|
||||
|
||||
before, err := serviceState(ctx, r.Unit, run)
|
||||
before, err := sys.ServiceState(ctx, run, r.Unit)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
if before != r.State {
|
||||
verb := "start"
|
||||
if r.State == "stopped" {
|
||||
verb = "stop"
|
||||
}
|
||||
if _, err := run(ctx, "systemctl", verb, r.Unit); err != nil {
|
||||
return out, fmt.Errorf("%s %s: %w", verb, r.Unit, err)
|
||||
if err := sys.SetServiceState(ctx, run, r.Unit, r.State); err != nil {
|
||||
return out, fmt.Errorf("setting %s to %s: %w", r.Unit, r.State, err)
|
||||
}
|
||||
|
||||
// Read back. `systemctl start` returning zero says the transaction was accepted, not
|
||||
// that the unit is running — a unit that starts and immediately dies satisfies the
|
||||
// command.
|
||||
after, err := serviceState(ctx, r.Unit, run)
|
||||
// Read back. A service manager accepting a command says the transaction was accepted,
|
||||
// not that the unit is running — one that starts and immediately dies satisfies it.
|
||||
after, err := sys.ServiceState(ctx, run, r.Unit)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -382,90 +375,6 @@ func applyService(ctx context.Context, r *declaration.Service, run Runner) (Outc
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// serviceBoot reads whether a unit starts at boot.
|
||||
//
|
||||
// The same trap as serviceState, in a new place. `systemctl is-enabled` exits non-zero for
|
||||
// nearly everything that is not "enabled", so the exit code says nothing useful — and it has
|
||||
// more than two answers. `static` in particular is neither enabled nor disabled: the unit has
|
||||
// no install section and CANNOT be enabled, so reporting it as "disabled" would let the host
|
||||
// try, fail, and blame the wrong thing.
|
||||
func serviceBoot(ctx context.Context, unit string, run Runner) (string, error) {
|
||||
out, _ := run(ctx, "systemctl", "is-enabled", unit)
|
||||
switch state := strings.TrimSpace(out); state {
|
||||
case "enabled", "enabled-runtime", "alias":
|
||||
return "enabled", nil
|
||||
case "disabled":
|
||||
return "disabled", nil
|
||||
case "":
|
||||
return "", fmt.Errorf("the service manager said nothing about whether %s starts at boot", unit)
|
||||
case "static":
|
||||
return "", fmt.Errorf(
|
||||
"%s is static — it has no install section, so it cannot be enabled or disabled. "+
|
||||
"Something else pulls it in, and that is what a declaration should name", unit)
|
||||
case "masked", "masked-runtime":
|
||||
return "", fmt.Errorf("%s is masked, so its boot state cannot be declared", unit)
|
||||
default:
|
||||
return "", fmt.Errorf(
|
||||
"the service manager reports %s as %q at boot, which is neither enabled nor disabled",
|
||||
unit, state)
|
||||
}
|
||||
}
|
||||
|
||||
// serviceState reads what the service manager says about a unit.
|
||||
//
|
||||
// Two traps here, and both were hit before this read what it now reads.
|
||||
//
|
||||
// The exit code is not the answer: `is-active` exits non-zero for every state except active —
|
||||
// the same shape as the capability detector reading a degraded init as no init at all.
|
||||
//
|
||||
// And "inactive" does not mean stopped. `systemctl is-active` says "inactive" for a unit that
|
||||
// DOES NOT EXIST exactly as it does for one that is installed and stopped. Declaring a unit
|
||||
// stopped therefore reported success for a unit the host cannot manage at all — absence read
|
||||
// as satisfaction, which is 04-ISSUES/007 wearing a different hat. LoadState is what separates
|
||||
// them, so LoadState is what is read.
|
||||
func serviceState(ctx context.Context, unit string, run Runner) (string, error) {
|
||||
out, _ := run(ctx, "systemctl", "show", unit,
|
||||
"--property=LoadState", "--property=ActiveState")
|
||||
|
||||
var load, active string
|
||||
for _, line := range strings.Split(out, "\n") {
|
||||
key, value, found := strings.Cut(strings.TrimSpace(line), "=")
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "LoadState":
|
||||
load = value
|
||||
case "ActiveState":
|
||||
active = value
|
||||
}
|
||||
}
|
||||
|
||||
switch load {
|
||||
case "":
|
||||
return "", fmt.Errorf("the service manager said nothing about %s", unit)
|
||||
case "not-found":
|
||||
return "", fmt.Errorf(
|
||||
"%s does not exist on this machine. A declaration naming a unit that is not "+
|
||||
"installed cannot be satisfied, and reporting it stopped would be reporting "+
|
||||
"absence as success", unit)
|
||||
case "masked":
|
||||
return "", fmt.Errorf("%s is masked, so its state cannot be declared", unit)
|
||||
case "error", "bad-setting":
|
||||
return "", fmt.Errorf("%s is installed but its unit file cannot be loaded (%s)", unit, load)
|
||||
}
|
||||
|
||||
switch active {
|
||||
case "active", "activating", "reloading":
|
||||
return "running", nil
|
||||
case "inactive", "failed", "deactivating":
|
||||
return "stopped", nil
|
||||
default:
|
||||
return "", fmt.Errorf(
|
||||
"the service manager reports %s as %q, which is neither running nor stopped", unit, active)
|
||||
}
|
||||
}
|
||||
|
||||
// remove undoes one resource the host applied and the declaration no longer names, and reports
|
||||
// what it actually did.
|
||||
//
|
||||
@@ -475,7 +384,7 @@ func serviceState(ctx context.Context, unit string, run Runner) (string, error)
|
||||
// It returns the action rather than assuming "removed", because for half the vocabulary the
|
||||
// honest word is "forgotten". A host that reported a package removed when it left the package
|
||||
// installed would be describing an effect it declined to have.
|
||||
func remove(ctx context.Context, a store.Applied, run Runner) (string, string, error) {
|
||||
func remove(ctx context.Context, sys system.System, a store.Applied, run Runner) (string, string, error) {
|
||||
switch declaration.Type(a.Type) {
|
||||
case declaration.TypeFile, declaration.TypeDirectory:
|
||||
if err := os.RemoveAll(a.Target); err != nil {
|
||||
@@ -495,13 +404,13 @@ func remove(ctx context.Context, a store.Applied, run Runner) (string, string, e
|
||||
// host holding a record of an uninstalled unit would then be unable to apply anything,
|
||||
// ever, with no way out but editing its state by hand. Removal is idempotent for the
|
||||
// same reason `os.RemoveAll` is.
|
||||
if _, err := serviceState(ctx, a.Target, run); err != nil {
|
||||
if _, err := sys.ServiceState(ctx, run, a.Target); err != nil {
|
||||
if strings.Contains(err.Error(), "does not exist on this machine") {
|
||||
return "forgotten", "the unit no longer exists", nil
|
||||
}
|
||||
return "", "", err
|
||||
}
|
||||
if _, err := run(ctx, "systemctl", "stop", a.Target); err != nil {
|
||||
if err := sys.SetServiceState(ctx, run, a.Target, "stopped"); err != nil {
|
||||
return "", "", fmt.Errorf("stopping %s: %w", a.Target, err)
|
||||
}
|
||||
return "removed", "stopped; the unit file is not the host's to delete", nil
|
||||
@@ -562,10 +471,10 @@ func ExecRunner(ctx context.Context, name string, args ...string) (string, error
|
||||
// asserts, because version is the package manager's business and the mesh does not have a
|
||||
// second opinion about it (novox/hq ADR 0041 — the host depends on nothing, and that includes
|
||||
// not becoming a second package manager).
|
||||
func applyPackage(ctx context.Context, r *declaration.Package, run Runner) (Outcome, error) {
|
||||
func applyPackage(ctx context.Context, sys system.System, r *declaration.Package, run Runner) (Outcome, error) {
|
||||
out := begin(r)
|
||||
|
||||
installed, err := packageInstalled(ctx, r.Package, run)
|
||||
installed, err := sys.PackageInstalled(ctx, run, r.Package)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -575,12 +484,12 @@ func applyPackage(ctx context.Context, r *declaration.Package, run Runner) (Outc
|
||||
return out, nil
|
||||
}
|
||||
|
||||
if _, err := run(ctx, "pacman", "-S", "--noconfirm", "--needed", r.Package); err != nil {
|
||||
if err := sys.InstallPackage(ctx, run, r.Package); err != nil {
|
||||
return out, fmt.Errorf("installing %s: %w", r.Package, err)
|
||||
}
|
||||
|
||||
// Read back. A package manager exiting zero says the transaction was accepted.
|
||||
installed, err = packageInstalled(ctx, r.Package, run)
|
||||
installed, err = sys.PackageInstalled(ctx, run, r.Package)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
@@ -593,24 +502,6 @@ func applyPackage(ctx context.Context, r *declaration.Package, run Runner) (Outc
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// packageInstalled asks the package database, having first established that it answers.
|
||||
//
|
||||
// The two-step is the same trap `serviceState` documents. `pacman -Q name` exits non-zero for
|
||||
// a package that is not installed AND for a package database that cannot be read, so believing
|
||||
// the first answer would report a broken package manager as "nothing is installed" — absence
|
||||
// read as fact. Proving the tool answers about something that certainly exists separates them.
|
||||
func packageInstalled(ctx context.Context, name string, run Runner) (bool, error) {
|
||||
if _, err := run(ctx, "pacman", "-Q", "pacman"); err != nil {
|
||||
return false, fmt.Errorf(
|
||||
"the package database does not answer on this machine, so nothing can be said "+
|
||||
"about %q: %w", name, err)
|
||||
}
|
||||
if _, err := run(ctx, "pacman", "-Q", name); err != nil {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Labels the host puts on every container it creates.
|
||||
//
|
||||
// specLabel carries a digest of the declaration that made the container. It is what lets a
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user