An archive had no removal, so an unassigned one failed as an orphan and aborted every apply after: a module with tools could not be unassigned, and a race between two pushes froze a machine against every change. The record now keeps what an archive unpacked: its files, the directories the host made inside its path, whether the host made the path itself, and the parents it made to reach it. Removal takes exactly that away, directories only once empty, never one that was there before; a directory that is the host's alone is renamed aside first so a reader sees the whole bundle or none of it. Whatever cannot be removed is said and forgotten, never fatal. A directory found before the archive is no longer swapped away with what was in it: the archive is moved in file by file, and one that would write over a file the mesh did not put there is refused before anything moves. A record from before this change learns its files from the archive's bytes on the next apply; one already orphaned is left in place, said and forgotten. A former target is still left in place: the version before is what a rollback starts (ADR 0141).
69 lines
2.5 KiB
Go
69 lines
2.5 KiB
Go
package apply
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// The host's own former archive stops nothing (novox/hq issue 194). A new host's first apply finds
|
|
// the version before it as a former target of the archive that delivered it; an archive has no
|
|
// removal then (issue 162), and the refusal stopped every machine applying anything. A former
|
|
// target of an archive is left in place, said, and forgotten: the version before is what a rollback
|
|
// starts (ADR 0141).
|
|
func TestTheHostsOwnFormerArchiveIsLeftInPlaceNotFatal(t *testing.T) {
|
|
run := func(_ context.Context, name string, args ...string) (string, error) {
|
|
if name == "docker" && args[0] == "info" {
|
|
return "29.0.0\n", nil
|
|
}
|
|
return "", nil
|
|
}
|
|
dir := t.TempDir()
|
|
former := store.FormerID("mesh-host.next", dir+"/versions/old")
|
|
known := store.State{Resources: []store.Applied{
|
|
{ID: "mesh-host.next", Type: "archive", Target: dir + "/versions/new", Origin: store.OriginDeclared},
|
|
{ID: former, Type: "archive", Target: dir + "/versions/old", Origin: store.OriginDeclared},
|
|
}}
|
|
body, digest := anArchive(t, map[string]string{"nox-mesh-host": "#!/bin/sh\n"})
|
|
d := parse(t, `{"declaration":1,"resources":[
|
|
{"id":"mesh-host.next","type":"archive","path":"`+dir+`/versions/new","source":"`+serving(t, body)+`","digest":"`+digest+`"},
|
|
{"id":"notes.conf","type":"file","path":"`+dir+`/notes.conf","content":"x"}
|
|
]}`)
|
|
report, state, err := Apply(context.Background(), archHost(t), d, known, store.OriginDeclared, run, nil, nil)
|
|
if err != nil {
|
|
t.Fatalf("the apply failed: %v", err)
|
|
}
|
|
if _, still := state.HeldAt(former); still {
|
|
t.Fatal("held?")
|
|
}
|
|
for _, r := range state.Resources {
|
|
if r.ID == former {
|
|
t.Fatal("the former archive is still on record")
|
|
}
|
|
}
|
|
said := false
|
|
for _, o := range report.Outcomes {
|
|
if o.ID == former && o.Action == "forgotten" && strings.Contains(o.Detail, "left in place") {
|
|
said = true
|
|
}
|
|
}
|
|
if !said {
|
|
t.Fatalf("leaving the former archive was not said: %+v", report.Outcomes)
|
|
}
|
|
applied := false
|
|
for _, o := range report.Outcomes {
|
|
if o.ID == "notes.conf" && o.Action == "created" {
|
|
applied = true
|
|
}
|
|
}
|
|
if !applied {
|
|
t.Fatalf("the rest of the declaration was not applied: %+v", report.Outcomes)
|
|
}
|
|
|
|
// An archive the declaration dropped is taken away since issue 162; one recorded before the
|
|
// host kept what it unpacked is left in place and forgotten, never fatal — that case is
|
|
// archive_removal_test.go's.
|
|
}
|