absent: true on a package has the host remove it through the machine's own package manager when it is installed and leave alone a machine that never had it; read back either way. Undeclaring a package still removes nothing. A found firewall whose command is gone is recorded as removed, said once, and asked nothing of.
215 lines
7.3 KiB
Go
215 lines
7.3 KiB
Go
package apply
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// A taken container keeps a found network by a per-machine setting (novox/hq ADR 0163, rule 4):
|
|
// joined once it runs, part of its spec, and refused when it cannot be joined.
|
|
func TestAContainerJoinsTheNetworksItKeeps(t *testing.T) {
|
|
var ran []string
|
|
connectFails := false
|
|
run := func(_ context.Context, name string, args ...string) (string, error) {
|
|
if name != "docker" {
|
|
return "", errors.New("not installed")
|
|
}
|
|
ran = append(ran, strings.Join(args, " "))
|
|
switch args[0] {
|
|
case "info":
|
|
return "29.0.0\n", nil
|
|
case "container":
|
|
if len(ran) > 2 {
|
|
return "true\t" + specOfLast, nil
|
|
}
|
|
return "false\t\n", errors.New("no such container")
|
|
case "run":
|
|
return "deadbeef\n", nil
|
|
case "network":
|
|
if connectFails {
|
|
return "", errors.New("network predecessor_default not found")
|
|
}
|
|
}
|
|
return "", nil
|
|
}
|
|
d := parseTrusted(t, `{"declaration":1,"resources":[
|
|
{"id":"app","type":"container","name":"app","image":"`+pinned+`",
|
|
"networks":["predecessor_default"]}
|
|
]}`)
|
|
specOfLast = containerSpec(d.Resources[0].(*declaration.Container), inputs{})
|
|
alone := *d.Resources[0].(*declaration.Container)
|
|
alone.Networks = nil
|
|
if specOfLast == containerSpec(&alone, inputs{}) {
|
|
t.Fatal("the kept network is not part of the container's spec: kept or let go, the container would be left alone")
|
|
}
|
|
report, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
joined := false
|
|
for i, line := range ran {
|
|
if line == "network connect predecessor_default app" {
|
|
joined = true
|
|
if ran[i-1] != "container inspect --format {{.State.Running}}\t{{index .Config.Labels \""+specLabel+"\"}} app" &&
|
|
!strings.HasPrefix(ran[i-1], "container inspect") {
|
|
t.Errorf("joined before the container was read back as running: %v", ran)
|
|
}
|
|
}
|
|
}
|
|
if !joined || report.Outcomes[0].Action != "created" {
|
|
t.Fatalf("the container did not join the kept network: %v\n%+v", ran, report.Outcomes)
|
|
}
|
|
|
|
connectFails, ran = true, nil
|
|
if _, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil); err == nil ||
|
|
!strings.Contains(err.Error(), "could not join the kept network predecessor_default") {
|
|
t.Fatalf("a network that cannot be joined was passed over: %v", err)
|
|
}
|
|
}
|
|
|
|
var specOfLast string
|
|
|
|
// A module the mesh left out of a declaration is not a module the mesh removed (novox/hq ADR 0163,
|
|
// rule 6): what the host wrote for it stays, recorded and said; what it holds for it stays held.
|
|
// A module simply absent is removed as it always was.
|
|
func TestALeftOutModuleIsNeitherRemovedNorForgotten(t *testing.T) {
|
|
var removed []string
|
|
gone := map[string]bool{}
|
|
run := func(_ context.Context, name string, args ...string) (string, error) {
|
|
if name != "docker" {
|
|
return "", nil
|
|
}
|
|
switch args[0] {
|
|
case "info":
|
|
return "29.0.0\n", nil
|
|
case "rm":
|
|
removed = append(removed, args[len(args)-1])
|
|
gone[args[len(args)-1]] = true
|
|
case "container":
|
|
if gone[args[len(args)-1]] {
|
|
return "", errors.New("no such container")
|
|
}
|
|
return "true\tspec", nil
|
|
}
|
|
return "", nil
|
|
}
|
|
known := store.State{
|
|
Resources: []store.Applied{
|
|
{ID: "web.server", Type: "container", Target: "web", Origin: store.OriginDeclared},
|
|
{ID: "old.server", Type: "container", Target: "old", Origin: store.OriginDeclared},
|
|
},
|
|
Held: []store.Held{{ID: "web.page", Module: "web", Kind: "file", Target: "/srv/web/index.html"}},
|
|
}
|
|
d := parse(t, `{"declaration":1,"left_out":["web"],"resources":[
|
|
{"id":"notes.conf","type":"file","path":"`+t.TempDir()+`/notes.conf","content":"x"}
|
|
]}`)
|
|
report, state, err := Apply(context.Background(), archHost(t), d, known, store.OriginDeclared, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(removed) != 1 || removed[0] != "old" {
|
|
t.Fatalf("removed %v; only the module that is absent goes", removed)
|
|
}
|
|
if _, kept := state.At("container", "web"); !kept {
|
|
t.Fatal("the left-out module's record was forgotten")
|
|
}
|
|
if _, held := state.HeldAt("web.page"); !held {
|
|
t.Fatal("the left-out module's hold was released")
|
|
}
|
|
said := false
|
|
for _, o := range report.Outcomes {
|
|
if o.ID == "web.server" && o.Action == "unchanged" && strings.Contains(o.Detail, "web was left out of this declaration by the mesh") {
|
|
said = true
|
|
}
|
|
if o.ID == "web.server" && o.Action != "unchanged" {
|
|
t.Errorf("the left-out module's container was %s", o.Action)
|
|
}
|
|
}
|
|
if !said {
|
|
t.Fatalf("keeping the left-out module's container was not said: %+v", report.Outcomes)
|
|
}
|
|
}
|
|
|
|
// A container's capabilities reach the runtime and are part of its spec (novox/hq ADR 0170).
|
|
func TestACapabilityReachesTheRuntimeAndTheSpec(t *testing.T) {
|
|
var ran []string
|
|
run := func(_ context.Context, name string, args ...string) (string, error) {
|
|
if name != "docker" {
|
|
return "", errors.New("not installed")
|
|
}
|
|
switch args[0] {
|
|
case "info":
|
|
return "29.0.0\n", nil
|
|
case "container":
|
|
return "false\t\n", errors.New("no such container")
|
|
case "run":
|
|
ran = args
|
|
return "deadbeef\n", nil
|
|
}
|
|
return "", nil
|
|
}
|
|
d := parseTrusted(t, `{"declaration":1,"resources":[
|
|
{"id":"fw","type":"container","name":"fw","image":"`+pinned+`","network":"host","capabilities":["NET_ADMIN"]}
|
|
]}`)
|
|
_, _, _ = Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
|
granted := false
|
|
for i, a := range ran {
|
|
if a == "--cap-add" && i+1 < len(ran) && ran[i+1] == "NET_ADMIN" {
|
|
granted = true
|
|
}
|
|
}
|
|
if !granted {
|
|
t.Fatalf("the capability was not granted: %v", ran)
|
|
}
|
|
with := d.Resources[0].(*declaration.Container)
|
|
without := *with
|
|
without.Capabilities = nil
|
|
if containerSpec(with, inputs{}) == containerSpec(&without, inputs{}) {
|
|
t.Fatal("a capability is not part of the container's spec")
|
|
}
|
|
}
|
|
|
|
// A package may be declared absent (novox/hq ADR 0175): removed when it is installed, read back,
|
|
// left alone when it is not.
|
|
func TestAPackageDeclaredAbsentIsRemovedWhenPresentAndLeftWhenNot(t *testing.T) {
|
|
installed := true
|
|
var ran []string
|
|
run := func(_ context.Context, name string, args ...string) (string, error) {
|
|
ran = append(ran, name+" "+strings.Join(args, " "))
|
|
if name != "pacman" {
|
|
return "", nil
|
|
}
|
|
switch args[0] {
|
|
case "-Q":
|
|
if args[1] == "pacman" || installed {
|
|
return args[1] + " 1.0\n", nil
|
|
}
|
|
return "", errors.New("package not found")
|
|
case "-R":
|
|
installed = false
|
|
}
|
|
return "", nil
|
|
}
|
|
d := parseTrusted(t, `{"declaration":1,"resources":[{"id":"front-end","type":"package","package":"ufw","absent":true}]}`)
|
|
report, _, err := Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if report.Outcomes[0].Action != "removed" || !strings.Contains(strings.Join(ran, "\n"), "pacman -R --noconfirm ufw") {
|
|
t.Fatalf("an installed package declared absent was not removed: %+v\n%v", report.Outcomes[0], ran)
|
|
}
|
|
ran = nil
|
|
report, _, err = Apply(context.Background(), archHost(t), d, store.State{}, store.OriginCarried, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if report.Outcomes[0].Action != "unchanged" || strings.Contains(strings.Join(ran, "\n"), "-R") {
|
|
t.Fatalf("a package already absent was touched: %+v\n%v", report.Outcomes[0], ran)
|
|
}
|
|
}
|