package apply import ( "context" "encoding/json" "fmt" "os" "path/filepath" "strings" "testing" "github.com/novox/mesh-host/internal/store" ) // Defends novox/hq issue 128 and ADR 0102: a text file the mesh shares with software it did not // install is written into a marked block, never over — every line outside the mesh's markers is // the machine's and is kept byte for byte, and undeclaring gives the file back. const namesID = "mesh-wireguard.fact-node-names" func blockDecl(t *testing.T, path, content string, extra ...string) string { t.Helper() more := "" for _, e := range extra { more += "," + e } return fmt.Sprintf(`{"declaration":1,"resources":[ {"id":%q,"type":"file","path":%q,"into":"block","content":%q%s} ]}`, namesID, path, content, more) } func applyBlockDecl(t *testing.T, raw string, known store.State) (Report, store.State) { t.Helper() report, state, err := Apply(context.Background(), archHost(t), parse(t, raw), known, store.OriginDeclared, nil, nil, nil) if err != nil { t.Fatal(err) } return report, state } func undeclare(t *testing.T, known store.State) (Report, store.State) { t.Helper() report, state, err := Apply(context.Background(), archHost(t), somethingElse(t), known, store.OriginDeclared, nil, nil, nil) if err != nil { t.Fatal(err) } return report, state } func readText(t *testing.T, path string) string { t.Helper() raw, err := os.ReadFile(path) if err != nil { t.Fatal(err) } return string(raw) } func marked(id, body string) string { return "# BEGIN mesh " + id + "\n" + body + "# END mesh " + id + "\n" } // A workstation's hosts file, the way issue 128 found it: the distribution's lines, a development // tool's own marked blocks, and the operator's hand-added names. const workstationHosts = "127.0.0.1\tlocalhost\n" + "127.0.1.1\tg14.localdomain g14\n" + "\n" + "# BEGIN devtool project-a\n" + "127.0.0.1 a.test api.a.test\n" + "# END devtool project-a\n" + "# BEGIN devtool project-b\n" + "127.0.0.1 b.test\n" + "# END devtool project-b\n" + "192.168.1.20 printer # the operator's\n" const meshNames = "10.42.0.1 ace\n10.42.0.2 novox\n" func TestABlockKeepsEveryLineOutsideItsMarkers(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") if err := os.WriteFile(path, []byte(workstationHosts), 0o640); err != nil { t.Fatal(err) } report, state := applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) want := workstationHosts + "\n" + marked(namesID, meshNames) if got := readText(t, path); got != want { t.Fatalf("the file after writing into it:\n%q\nwant\n%q", got, want) } if got := report.Outcomes[0].Action; got != "updated" { t.Errorf("adding the region was %q", got) } if info, _ := os.Stat(path); info.Mode().Perm() != 0o640 { t.Errorf("the machine's file mode was changed to %o", info.Mode().Perm()) } rec, _ := state.Find(namesID) if rec.Into == nil || rec.Into.Format != "block" || rec.Into.Region != nil || rec.Into.Created { t.Fatalf("recorded as %+v", rec.Into) } // Again, with nothing changed: nothing written. report, state = applyBlockDecl(t, blockDecl(t, path, meshNames), state) if got := report.Outcomes[0].Action; got != "unchanged" { t.Errorf("a second apply was %q", got) } // The development tool rewrites its block, and the operator adds a line after the mesh's // region; the mesh's names change. Only the region moves. edited := strings.Replace(readText(t, path), "127.0.0.1 b.test\n", "127.0.0.1 b.test c.test\n", 1) + "10.0.0.5 nas # added after\n" _ = os.WriteFile(path, []byte(edited), 0o640) changed := meshNames + "10.42.0.3 shanks\n" report, state = applyBlockDecl(t, blockDecl(t, path, changed), state) want = strings.Replace(edited, marked(namesID, meshNames), marked(namesID, changed), 1) if got := readText(t, path); got != want { t.Fatalf("rewriting the region moved something else:\n%q\nwant\n%q", got, want) } if got := report.Outcomes[0].Action; got != "updated" { t.Errorf("rewriting the region was %q", got) } // Undeclared: the region, its markers and the blank line the host put before it go; every // other line is where it was. report, _ = undeclare(t, state) want = strings.Replace(edited, "\n"+marked(namesID, meshNames), "", 1) if got := readText(t, path); got != want { t.Fatalf("undeclaring left:\n%q\nwant\n%q", got, want) } if got := report.Outcomes[0].Action; got != "restored" { t.Errorf("undeclaring was %q", got) } } func TestUndeclaringABlockAddedAtTheEndGivesTheFileBackExactly(t *testing.T) { for name, original := range map[string]string{ "ending in a line": "127.0.0.1 localhost\n", "ending in a blank line": "127.0.0.1 localhost\n\n", "empty": "", } { t.Run(name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte(original), 0o644) _, state := applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) undeclare(t, state) if got := readText(t, path); got != original { t.Errorf("undeclaring left %q, the machine had %q", got, original) } }) } } func TestABlockAddedAtTheEndIsSetApartFromTheMachinesLines(t *testing.T) { for name, c := range map[string]struct{ before, after string }{ "no line end": {"127.0.0.1 localhost", "127.0.0.1 localhost\n\n" + marked(namesID, meshNames)}, "a line end": {"127.0.0.1 localhost\n", "127.0.0.1 localhost\n\n" + marked(namesID, meshNames)}, "a blank line already": {"127.0.0.1 localhost\n\n", "127.0.0.1 localhost\n\n" + marked(namesID, meshNames)}, "empty": {"", marked(namesID, meshNames)}, "only a blank line": {"\n", "\n" + marked(namesID, meshNames)}, "content without an end": {"x\n\n", "x\n\n" + marked(namesID, meshNames)}, } { t.Run(name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte(c.before), 0o644) applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) if got := readText(t, path); got != c.after { t.Errorf("got %q, want %q", got, c.after) } }) } } func TestTheRegionEndsInExactlyOneLineEnd(t *testing.T) { for content, body := range map[string]string{ "10.42.0.1 ace": "10.42.0.1 ace\n", "10.42.0.1 ace\n": "10.42.0.1 ace\n", "10.42.0.1 ace\n\n\n": "10.42.0.1 ace\n", "": "", "\n\n": "", } { path := filepath.Join(t.TempDir(), "hosts") _, state := applyBlockDecl(t, blockDecl(t, path, content), store.State{}) if got := readText(t, path); got != marked(namesID, body) { t.Errorf("content %q was written as %q", content, got) } // And the same content again is not a change. report, _ := applyBlockDecl(t, blockDecl(t, path, content), state) if got := report.Outcomes[0].Action; got != "unchanged" { t.Errorf("content %q applied twice was %q", content, got) } } } func TestARegionAlreadyThereIsRewrittenInPlaceAndGivenBack(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") before := "127.0.0.1 localhost\n" after := "# BEGIN devtool x\n127.0.0.1 x.test\n# END devtool x\n192.168.1.20 printer\n" found := "10.42.0.9 old-name\n" original := before + marked(namesID, found) + after _ = os.WriteFile(path, []byte(original), 0o644) report, state := applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) if got := readText(t, path); got != before+marked(namesID, meshNames)+after { t.Fatalf("the region was not rewritten in place: %q", got) } if got := report.Outcomes[0].Action; got != "updated" { t.Errorf("rewriting a found region was %q", got) } rec, _ := state.Find(namesID) if rec.Into.Region == nil || *rec.Into.Region != found { t.Fatalf("what the region held before was recorded as %v", rec.Into.Region) } // Undeclared: what the region held goes back, where it was. report, _ = undeclare(t, state) if got := readText(t, path); got != original { t.Errorf("undeclaring left %q, the machine had %q", got, original) } if got := report.Outcomes[0].Action; got != "restored" { t.Errorf("undeclaring was %q", got) } } func TestARegionWithNoRecordHoldingExactlyTheDeclaredLinesIsTheMeshs(t *testing.T) { // A host that wrote the region and died before saving its state: what is between the markers // is exactly what the mesh declares, and remembered as the machine's it would never go. path := filepath.Join(t.TempDir(), "hosts") original := "127.0.0.1 localhost\n" _ = os.WriteFile(path, []byte(original+"\n"+marked(namesID, meshNames)), 0o644) _, state := applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) if rec, _ := state.Find(namesID); rec.Into.Region != nil { t.Fatalf("the mesh's own lines were recorded as the machine's: %q", *rec.Into.Region) } undeclare(t, state) if got := readText(t, path); !strings.HasPrefix(got, original) || strings.Contains(got, "BEGIN mesh") { t.Errorf("undeclaring left the mesh's region behind: %q", got) } } func TestADriftedRegionIsCorrected(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte(workstationHosts), 0o644) _, state := applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) written := readText(t, path) _ = os.WriteFile(path, []byte(strings.Replace(written, "10.42.0.2 novox\n", "10.42.0.2 novox\n6.6.6.6 evil\n", 1)), 0o644) report, _ := applyBlockDecl(t, blockDecl(t, path, meshNames), state) if got := report.Outcomes[0].Action; got != "corrected" { t.Errorf("a region edited on the machine was %q", got) } if got := readText(t, path); got != written { t.Errorf("the region was not put back: %q", got) } // The region taken out by hand is drift too, and it is put back. _ = os.WriteFile(path, []byte(workstationHosts), 0o644) report, _ = applyBlockDecl(t, blockDecl(t, path, meshNames), state) if got := report.Outcomes[0].Action; got != "corrected" { t.Errorf("a region removed on the machine was %q", got) } if got := readText(t, path); got != written { t.Errorf("the region was not put back: %q", got) } } func TestTwoRegionsInOneFileAreEachTheirOwn(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte(workstationHosts), 0o644) raw := fmt.Sprintf(`{"declaration":1,"resources":[ {"id":"a.names","type":"file","path":%q,"into":"block","content":"10.42.0.1 ace\n"}, {"id":"b.names","type":"file","path":%q,"into":"block","content":"10.43.0.1 lab\n"} ]}`, path, path) _, state := applyBlockDecl(t, raw, store.State{}) want := workstationHosts + "\n" + marked("a.names", "10.42.0.1 ace\n") + "\n" + marked("b.names", "10.43.0.1 lab\n") if got := readText(t, path); got != want { t.Fatalf("two regions:\n%q\nwant\n%q", got, want) } report, state := applyBlockDecl(t, raw, state) for _, o := range report.Outcomes { if o.Action != "unchanged" { t.Errorf("%s applied twice was %q", o.ID, o.Action) } } // One undeclared: only its region goes. only := fmt.Sprintf(`{"declaration":1,"resources":[ {"id":"b.names","type":"file","path":%q,"into":"block","content":"10.43.0.1 lab\n"} ]}`, path) applyBlockDecl(t, only, state) want = workstationHosts + "\n" + marked("b.names", "10.43.0.1 lab\n") if got := readText(t, path); got != want { t.Errorf("undeclaring one region:\n%q\nwant\n%q", got, want) } } func TestABlockInAFileThatWasNotThereIsCreatedAndRemovedWithIt(t *testing.T) { path := filepath.Join(t.TempDir(), "conf.d", "mesh.conf") report, state := applyBlockDecl(t, blockDecl(t, path, meshNames, `"mode":"0600"`), store.State{}) if got := readText(t, path); got != marked(namesID, meshNames) { t.Fatalf("a created file holds %q", got) } if info, _ := os.Stat(path); info.Mode().Perm() != 0o600 { t.Errorf("a created file is mode %o, declared 0600", info.Mode().Perm()) } if got := report.Outcomes[0].Action; got != "created" { t.Errorf("creating was %q", got) } if rec, _ := state.Find(namesID); !rec.Into.Created { t.Error("the mesh creating the file was not recorded") } report, _ = undeclare(t, state) if _, err := os.Stat(path); !os.IsNotExist(err) { t.Errorf("a file the mesh created, holding only its region, was left behind") } if got := report.Outcomes[0].Action; got != "removed" { t.Errorf("undeclaring was %q", got) } // Somebody else wrote into it meanwhile: it is no longer only the mesh's, and it stays. _, state = applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) _ = os.WriteFile(path, []byte(readText(t, path)+"their = line\n"), 0o600) undeclare(t, state) if got := readText(t, path); got != "their = line\n" { t.Errorf("undeclaring a created file somebody wrote into left %q", got) } } func TestMarkersThatDoNotPairAreRefusedAndLeftAlone(t *testing.T) { for name, text := range map[string]string{ "a begin with no end": "a\n# BEGIN mesh " + namesID + "\nb\n", "an end with no begin": "a\n# END mesh " + namesID + "\n", "an end before a begin": "# END mesh " + namesID + "\n# BEGIN mesh " + namesID + "\n", "a begin twice": marked(namesID, "x\n") + "# BEGIN mesh " + namesID + "\n", } { t.Run(name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte(text), 0o644) if _, _, err := Apply(context.Background(), archHost(t), parse(t, blockDecl(t, path, meshNames)), store.State{}, store.OriginDeclared, nil, nil, nil); err == nil { t.Fatal("markers that do not pair were written between") } if got := readText(t, path); got != text { t.Errorf("the file was changed: %q", got) } }) } } func TestAMarkerOfAnotherIDIsNotThisRegion(t *testing.T) { // The id is part of the marker: a region whose id merely starts with this one is not it. path := filepath.Join(t.TempDir(), "hosts") other := marked(namesID+"-extra", "10.9.9.9 other\n") _ = os.WriteFile(path, []byte(other), 0o644) _, state := applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) if got := readText(t, path); got != other+"\n"+marked(namesID, meshNames) { t.Fatalf("got %q", got) } undeclare(t, state) if got := readText(t, path); got != other { t.Errorf("undeclaring touched the other region: %q", got) } } func TestABlockAtTheStartStandsAboveEverything(t *testing.T) { // dhcpcd scopes every line after `interface X` to that interface, so the mesh's global options // go above all of it. dhcpcd := "hostname\nduid\n\ninterface enp6s0\nstatic ip_address=192.168.1.5/24\n" opts := "nohook resolv.conf\ndenyinterfaces mesh0\n" for name, c := range map[string]struct{ before, after string }{ "a file with content": {dhcpcd, marked(namesID, opts) + "\n" + dhcpcd}, "an empty file": {"", marked(namesID, opts)}, "a file opening blank": {"\n" + dhcpcd, marked(namesID, opts) + "\n" + dhcpcd}, "a last line with no end": {"interface enp6s0", marked(namesID, opts) + "\ninterface enp6s0"}, } { t.Run(name, func(t *testing.T) { path := filepath.Join(t.TempDir(), "dhcpcd.conf") _ = os.WriteFile(path, []byte(c.before), 0o644) report, state := applyBlockDecl(t, blockDecl(t, path, opts, `"at":"start"`), store.State{}) if got := readText(t, path); got != c.after { t.Fatalf("got %q, want %q", got, c.after) } if got := report.Outcomes[0].Action; got != "updated" { t.Errorf("adding the region was %q", got) } report, state = applyBlockDecl(t, blockDecl(t, path, opts, `"at":"start"`), state) if got := report.Outcomes[0].Action; got != "unchanged" { t.Errorf("a second apply was %q", got) } undeclare(t, state) if got := readText(t, path); got != c.before { t.Errorf("undeclaring left %q, the machine had %q", got, c.before) } }) } t.Run("a file that was not there", func(t *testing.T) { path := filepath.Join(t.TempDir(), "dhcpcd.conf") applyBlockDecl(t, blockDecl(t, path, opts, `"at":"start"`), store.State{}) if got := readText(t, path); got != marked(namesID, opts) { t.Errorf("got %q", got) } }) } func TestARegionAlreadyThereIsNotMovedWhereverAtSaysItGoes(t *testing.T) { for _, at := range []string{`"at":"start"`, `"at":"end"`} { path := filepath.Join(t.TempDir(), "dhcpcd.conf") original := "hostname\n" + marked(namesID, "old\n") + "interface enp6s0\n" _ = os.WriteFile(path, []byte(original), 0o644) applyBlockDecl(t, blockDecl(t, path, "nohook resolv.conf\n", at), store.State{}) want := "hostname\n" + marked(namesID, "nohook resolv.conf\n") + "interface enp6s0\n" if got := readText(t, path); got != want { t.Errorf("%s: a found region was moved: %q", at, got) } } } func TestAFileWrittenIntoABlockIsNeverHeldOnAnAdoptedNode(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte(workstationHosts), 0o644) resource := fmt.Sprintf(`{"id":%q,"type":"file","path":%q,"into":"block","content":%q}`, namesID, path, meshNames) d := adopted(t, `{"taken":[],"untaken":{"mesh-wireguard":["`+namesID+`"]}}`, resource) report, state := applyAdopted(t, d, store.State{}, &machine{}, t.TempDir()) if got := outcomeOf(report, namesID).Action; got == "held" { t.Fatal("a file written into a block was held, though it replaces nothing that was found") } if len(state.Held) != 0 { t.Errorf("something was held: %+v", state.Held) } if got := readText(t, path); got != workstationHosts+"\n"+marked(namesID, meshNames) { t.Errorf("the adopted node's file was not written into: %q", got) } // Held from when it was declared whole, the hold does not keep the region out. path2 := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path2, []byte(workstationHosts), 0o644) known := store.State{Held: []store.Held{{ID: namesID, Module: "mesh-wireguard", Kind: "file", Target: path2}}} resource2 := fmt.Sprintf(`{"id":%q,"type":"file","path":%q,"into":"block","content":%q}`, namesID, path2, meshNames) d2 := adopted(t, `{"taken":[],"untaken":{"mesh-wireguard":["`+namesID+`"]}}`, resource2) report, state = applyAdopted(t, d2, known, &machine{}, t.TempDir()) if got := outcomeOf(report, namesID).Action; got != "updated" { t.Errorf("the file was %q, not written into", got) } if len(state.Held) != 0 { t.Errorf("the old hold outlived the block declaration: %+v", state.Held) } // And the preview says the same: written into, not held. for _, s := range Plan(d2, known, store.OriginDeclared) { if s.ID == namesID && s.Verb == "hold" { t.Errorf("the preview holds a file written into a block: %+v", s) } } } func TestTheRecordOfABlockSurvivesTheStateFile(t *testing.T) { // What undeclaring needs is in the state a host saves, not only in memory. path := filepath.Join(t.TempDir(), "hosts") original := "a\n" + marked(namesID, "old\n") _ = os.WriteFile(path, []byte(original), 0o644) _, state := applyBlockDecl(t, blockDecl(t, path, meshNames, `"at":"start"`), store.State{}) raw, err := json.Marshal(state) if err != nil { t.Fatal(err) } var back store.State if err := json.Unmarshal(raw, &back); err != nil { t.Fatal(err) } undeclare(t, back) if got := readText(t, path); got != original { t.Errorf("undeclaring from a saved state left %q", got) } } // The mesh's old whole hosts file, as the controller composed it before issue 128. const oldWholeHosts = "# Generated by the mesh. Do not edit — this file is replaced whenever a machine\n" + "# joins or leaves, and an edit would survive until then and vanish.\n\n" + "127.0.0.1\tlocalhost\n" + "::1\t\tlocalhost ip6-localhost ip6-loopback\n" + "127.0.1.1\tg14\n" + "\n" + "10.42.0.1\tace.internal\tace\n" + "10.42.0.9\tg14.internal\tg14\t# this machine\n" func wholeDecl(path, content string) string { return fmt.Sprintf(`{"declaration":1,"resources":[ {"id":%q,"type":"file","path":%q,"content":%q} ]}`, namesID, path, content) } func applyKeepingIn(t *testing.T, raw string, known store.State, keepDir string) (Report, store.State) { t.Helper() report, state, err := ApplyKeeping(context.Background(), archHost(t), parse(t, raw), known, store.OriginDeclared, (&machine{}).run, nil, nil, KeepIn(keepDir)) if err != nil { t.Fatalf("apply failed: %v", err) } return report, state } func TestAFileTheMeshWroteWholeAndMadeItselfKeepsOnlyItsLoopbackLines(t *testing.T) { // Written whole into a file that was not there, then declared as a block under the same id: // the old names must not stay above the region, where a resolver would answer from them first. path := filepath.Join(t.TempDir(), "hosts") _, state := applyKeepingIn(t, wholeDecl(path, oldWholeHosts), store.State{}, t.TempDir()) report, state := applyKeepingIn(t, blockDecl(t, path, meshNames), state, t.TempDir()) floor := "127.0.0.1\tlocalhost\n::1\t\tlocalhost ip6-localhost ip6-loopback\n127.0.1.1\tg14\n" if got := readText(t, path); got != floor+"\n"+marked(namesID, meshNames) { t.Fatalf("the old whole file became:\n%q", got) } o := outcomeOf(report, namesID) if o.Action != "updated" || !strings.Contains(o.Detail, "loopback lines") { t.Errorf("the rebuild was reported as %q: %s", o.Action, o.Detail) } if rec, _ := state.Find(namesID); !rec.Into.Created { t.Error("a file the mesh made itself was not recorded as the mesh's") } report, _ = applyKeepingIn(t, blockDecl(t, path, meshNames), state, t.TempDir()) if got := outcomeOf(report, namesID).Action; got != "unchanged" { t.Errorf("applied again, the rebuilt file was %q", got) } undeclare(t, state) if got := readText(t, path); got != floor { t.Errorf("undeclared, the file holds %q", got) } } func TestAFileTheMeshWroteWholeOverAnOriginalGetsTheOriginalBack(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte(workstationHosts), 0o644) keep := t.TempDir() _, state := applyKeepingIn(t, wholeDecl(path, oldWholeHosts), store.State{}, keep) if rec, _ := state.Find(namesID); rec.Kept == "" { t.Fatal("where the original was kept was not recorded") } report, state := applyKeepingIn(t, blockDecl(t, path, meshNames), state, keep) if got := readText(t, path); got != workstationHosts+"\n"+marked(namesID, meshNames) { t.Fatalf("the old whole file became:\n%q", got) } if d := outcomeOf(report, namesID).Detail; !strings.Contains(d, "original kept at") { t.Errorf("the rebuild was reported as: %s", d) } undeclare(t, state) if got := readText(t, path); got != workstationHosts { t.Errorf("undeclared, the machine did not get its original back: %q", got) } } func TestAFileTheMeshWroteWholeAndSomebodyChangedIsWrittenIntoAsItStands(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _, state := applyKeepingIn(t, wholeDecl(path, oldWholeHosts), store.State{}, t.TempDir()) edited := oldWholeHosts + "192.168.1.20 printer\n" _ = os.WriteFile(path, []byte(edited), 0o644) report, _ := applyKeepingIn(t, blockDecl(t, path, meshNames), state, t.TempDir()) if got := readText(t, path); got != edited+"\n"+marked(namesID, meshNames) { t.Fatalf("an edited whole file became:\n%q", got) } if d := outcomeOf(report, namesID).Detail; !strings.Contains(d, "changed since; its old lines were kept") { t.Errorf("the outcome does not say so: %s", d) } } func TestALinkedFileStaysALink(t *testing.T) { dir := t.TempDir() real := filepath.Join(dir, "static", "hosts") _ = os.MkdirAll(filepath.Dir(real), 0o755) _ = os.WriteFile(real, []byte(workstationHosts), 0o644) link := filepath.Join(dir, "hosts") if err := os.Symlink(real, link); err != nil { t.Fatal(err) } _, state := applyBlockDecl(t, blockDecl(t, link, meshNames), store.State{}) if info, err := os.Lstat(link); err != nil || info.Mode()&os.ModeSymlink == 0 { t.Fatalf("the link was replaced by a file") } if got := readText(t, real); got != workstationHosts+"\n"+marked(namesID, meshNames) { t.Errorf("the file the link names holds %q", got) } undeclare(t, state) if info, err := os.Lstat(link); err != nil || info.Mode()&os.ModeSymlink == 0 { t.Fatalf("undeclaring replaced the link with a file") } if got := readText(t, real); got != workstationHosts { t.Errorf("undeclared, the file the link names holds %q", got) } } func TestALastLineWithNoEndIsGivenBackWithNone(t *testing.T) { path := filepath.Join(t.TempDir(), "hosts") _ = os.WriteFile(path, []byte("x"), 0o644) _, state := applyBlockDecl(t, blockDecl(t, path, meshNames), store.State{}) if got := readText(t, path); got != "x\n\n"+marked(namesID, meshNames) { t.Fatalf("got %q", got) } undeclare(t, state) if got := readText(t, path); got != "x" { t.Errorf("undeclaring left %q, the machine had %q", got, "x") } } func TestAFailedBlockWriteKeepsItsHold(t *testing.T) { // Held from when it was declared whole, then declared as a block into a file whose markers do // not pair: the write is refused, and the hold — with where its original is — stays. path := filepath.Join(t.TempDir(), "hosts") broken := "a\n# BEGIN mesh " + namesID + "\n" _ = os.WriteFile(path, []byte(broken), 0o644) known := store.State{Held: []store.Held{{ID: namesID, Module: "mesh-wireguard", Kind: "file", Target: path, Kept: "/var/lib/mesh/kept/hosts"}}} resource := fmt.Sprintf(`{"id":%q,"type":"file","path":%q,"into":"block","content":%q}`, namesID, path, meshNames) d := adopted(t, `{"taken":[],"untaken":{"mesh-wireguard":["`+namesID+`"]}}`, resource) _, state, err := ApplyKeeping(context.Background(), archHost(t), d, known, store.OriginDeclared, (&machine{}).run, nil, nil, KeepIn(t.TempDir())) if err == nil { t.Fatal("a write into unpaired markers was not refused") } h, held := state.HeldAt(namesID) if !held || h.Kept != "/var/lib/mesh/kept/hosts" { t.Errorf("a failed write released the hold: %+v", state.Held) } }