Somebody editing a managed file is now visible instead of mysterious

Asked how the mesh would know if somebody edited their hosts file. It would
not. The file was rewritten within five minutes and the outcome said
"updated" -- which is exactly what the mesh changing its own mind looks like.
So the change vanished, nothing anywhere said why, and the obvious thing to do
is edit it again.

The host now records a digest of what it wrote, which is enough to tell the two
apart on the next pass:

  the file matches the declaration          unchanged
  it matches what was last written          updated -- the mesh changed its mind
  it matches neither                        corrected -- somebody changed it here

The machine is put back either way, because holding it to what it was told is
the point. What changes is that it says so.

A digest rather than the content: the store is read on every reconcile and sits
beside the state on disk, and keeping every managed file twice would make it
grow with the size of the machine rather than with the number of resources.
This commit is contained in:
2026-08-29 22:55:26 +02:00
parent ef400b8c66
commit 827ce481f2
3 changed files with 142 additions and 5 deletions
+92
View File
@@ -1059,3 +1059,95 @@ func recordingServices(commands *[]string) Runner {
return "", nil
}
}
func TestAFileChangedOnTheMachineIsCorrectedAndSaidSo(t *testing.T) {
// The question this answers: how would anybody know somebody edited a managed file? Before
// this they would not. It was rewritten within five minutes and reported as "updated",
// which is what the mesh changing its mind looks like — so the person's change vanished and
// nothing anywhere said why. They edit it again, and again.
dir := t.TempDir()
path := filepath.Join(dir, "thing.conf")
d := parse(t, fmt.Sprintf(`{"declaration":1,"resources":[
{"id":"conf","type":"file","path":%q,"content":"from the mesh\n","mode":"0644"}
]}`, path))
_, state, err := Apply(context.Background(), archHost(t), d, store.State{},
store.OriginCarried, noServices, nil)
if err != nil {
t.Fatal(err)
}
// Somebody edits it.
if err := os.WriteFile(path, []byte("edited by hand\n"), 0o644); err != nil {
t.Fatal(err)
}
report, state, err := Apply(context.Background(), archHost(t), d, state,
store.OriginCarried, noServices, nil)
if err != nil {
t.Fatal(err)
}
if got := report.Outcomes[0].Action; got != "corrected" {
t.Errorf("a hand edit was reported as %q; the mesh cannot tell it from changing its own "+
"mind, and neither can anybody reading this", got)
}
// And it is put back, because holding the machine to what it was told is the point.
back, err := os.ReadFile(path)
if err != nil {
t.Fatal(err)
}
if string(back) != "from the mesh\n" {
t.Errorf("the file was left as %q", back)
}
}
func TestTheMeshChangingItsMindIsNotDrift(t *testing.T) {
// The other half. A new declaration is an ordinary update and must not read as somebody
// having meddled, or every real change would look like an incident.
dir := t.TempDir()
path := filepath.Join(dir, "thing.conf")
first := parse(t, fmt.Sprintf(`{"declaration":1,"resources":[
{"id":"conf","type":"file","path":%q,"content":"one\n","mode":"0644"}
]}`, path))
second := parse(t, fmt.Sprintf(`{"declaration":1,"resources":[
{"id":"conf","type":"file","path":%q,"content":"two\n","mode":"0644"}
]}`, path))
_, state, err := Apply(context.Background(), archHost(t), first, store.State{},
store.OriginCarried, noServices, nil)
if err != nil {
t.Fatal(err)
}
report, _, err := Apply(context.Background(), archHost(t), second, state,
store.OriginCarried, noServices, nil)
if err != nil {
t.Fatal(err)
}
if got := report.Outcomes[0].Action; got != "updated" {
t.Errorf("the mesh changing what it wants was reported as %q", got)
}
}
func TestAnUntouchedFileIsStillUnchanged(t *testing.T) {
// And nothing about this makes a steady machine look busy.
dir := t.TempDir()
path := filepath.Join(dir, "thing.conf")
d := parse(t, fmt.Sprintf(`{"declaration":1,"resources":[
{"id":"conf","type":"file","path":%q,"content":"steady\n","mode":"0644"}
]}`, path))
_, state, err := Apply(context.Background(), archHost(t), d, store.State{},
store.OriginCarried, noServices, nil)
if err != nil {
t.Fatal(err)
}
report, _, err := Apply(context.Background(), archHost(t), d, state,
store.OriginCarried, noServices, nil)
if err != nil {
t.Fatal(err)
}
if got := report.Outcomes[0].Action; got != "unchanged" {
t.Errorf("an untouched file was reported as %q", got)
}
}