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:
+40
-5
@@ -13,6 +13,7 @@ package apply
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
@@ -37,8 +38,17 @@ type Outcome struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Target string `json:"target"`
|
||||
Action string `json:"action"` // created · updated · unchanged · removed
|
||||
// Action is created · updated · unchanged · corrected · removed.
|
||||
//
|
||||
// "corrected" is its own answer and not a kind of "updated": it means the machine had drifted
|
||||
// from what this host last wrote, so somebody changed it by hand. The mesh converging is
|
||||
// right either way; being unable to say which happened is not.
|
||||
Action string `json:"action"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
|
||||
// wrote is a digest of what this apply put there, kept so the next one can tell a machine
|
||||
// that drifted from one the mesh changed its mind about. Not reported: it is bookkeeping.
|
||||
wrote string
|
||||
}
|
||||
|
||||
// Report is what an apply did, in the order it did it.
|
||||
@@ -118,7 +128,8 @@ func Apply(
|
||||
changed := map[string]bool{}
|
||||
|
||||
for _, resource := range d.Resources {
|
||||
outcome, err := applyOne(ctx, sys, resource, run, changed)
|
||||
was, _ := known.Find(resource.Identity())
|
||||
outcome, err := applyOne(ctx, sys, resource, run, changed, was)
|
||||
if err != nil {
|
||||
return report, known, &Error{Resource: resource.Identity(), Err: err, Done: report}
|
||||
}
|
||||
@@ -128,6 +139,7 @@ func Apply(
|
||||
Origin: origin,
|
||||
ID: resource.Identity(), Type: string(resource.Kind()),
|
||||
Target: outcome.Target, AppliedAt: time.Now().UTC(),
|
||||
Wrote: outcome.wrote,
|
||||
})
|
||||
report.Outcomes = append(report.Outcomes, outcome)
|
||||
if outcome.Action != "unchanged" {
|
||||
@@ -139,12 +151,12 @@ func Apply(
|
||||
}
|
||||
|
||||
func applyOne(ctx context.Context, sys system.System, r declaration.Resource, run Runner,
|
||||
changed map[string]bool) (Outcome, error) {
|
||||
changed map[string]bool, previous store.Applied) (Outcome, error) {
|
||||
switch res := r.(type) {
|
||||
case *declaration.Directory:
|
||||
return applyDirectory(res)
|
||||
case *declaration.File:
|
||||
return applyFile(res)
|
||||
return applyFile(res, previous)
|
||||
case *declaration.Service:
|
||||
return applyService(ctx, sys, res, run, changed)
|
||||
case *declaration.Package:
|
||||
@@ -227,8 +239,9 @@ func applyDirectory(r *declaration.Directory) (Outcome, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func applyFile(r *declaration.File) (Outcome, error) {
|
||||
func applyFile(r *declaration.File, previous store.Applied) (Outcome, error) {
|
||||
out := begin(r)
|
||||
out.wrote = digestOf(r.Content)
|
||||
mode, err := modeOf(r.Mode, 0o644)
|
||||
if err != nil {
|
||||
return out, err
|
||||
@@ -248,6 +261,11 @@ func applyFile(r *declaration.File) (Outcome, error) {
|
||||
}
|
||||
|
||||
contentSame := existed && string(existing) == r.Content
|
||||
|
||||
// Whether the machine still holds what this host last put there. When it does not, and the
|
||||
// declaration has not changed either, somebody edited it — and saying so is the whole
|
||||
// difference between a change that vanishes mysteriously and one that is reported.
|
||||
drifted := existed && previous.Wrote != "" && digestOf(string(existing)) != previous.Wrote
|
||||
modeSame := existed && beforeMode == mode.Perm()
|
||||
|
||||
if !contentSame {
|
||||
@@ -282,6 +300,13 @@ func applyFile(r *declaration.File) (Outcome, error) {
|
||||
switch {
|
||||
case !existed:
|
||||
out.Action = "created"
|
||||
case drifted:
|
||||
// Somebody changed this on the machine. The mesh puts it back either way — that is what
|
||||
// holding a machine to what it was told means — but a change that vanishes with nothing
|
||||
// said is how a person ends up editing the same file every five minutes, believing the
|
||||
// machine is broken.
|
||||
out.Action = "corrected"
|
||||
out.Detail = "it had been changed on the machine since this host last wrote it"
|
||||
case !contentSame && !modeSame:
|
||||
out.Action = "updated"
|
||||
out.Detail = "content and mode"
|
||||
@@ -769,3 +794,13 @@ func containerRuntime(ctx context.Context, run Runner) (string, error) {
|
||||
return "", fmt.Errorf(
|
||||
"no container runtime answers on this machine (tried %s)", strings.Join(tried, ", "))
|
||||
}
|
||||
|
||||
// digestOf is how this host recognises what it wrote.
|
||||
//
|
||||
// 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 machine rather
|
||||
// than with the number of resources.
|
||||
func digestOf(content string) string {
|
||||
sum := sha256.Sum256([]byte(content))
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user