mesh/merge-gate pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes with the change as it did without (4 of 4 compose)
mesh/repo-check pass: its merge-check.sh passed
mesh/delivery covered: a later merge that contains it was delivered: novox/mesh-host@7c046a8a1d84 (merged as d28ca959 into main, walk plan-17915049347305…
The review of #64 asked for containerd's tree, dockerd's --data-root from its unit and drop-ins, and podman's graphroot to be refused like docker's own (hq issue 339); and the controller now tells a directory a send found from one found before it, which needs the time it was first found kept across applies instead of the time of the last apply. A hand-over now names the resource it was for, so it lapses if another declaration points at the path.
88 lines
2.5 KiB
Go
88 lines
2.5 KiB
Go
package store
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
)
|
|
|
|
// HandedOverName is the file, beside the node's state, of the directories a person handed to the mesh at the
|
|
// machine (novox/hq issue 339). Its own file rather than a field of the state: the engine saves its state at
|
|
// the end of every apply, from what it loaded at the start, and a hand-over written into the state during an
|
|
// apply would be lost to that save.
|
|
const HandedOverName = "handed-over.json"
|
|
|
|
// HandedOverPath is that file beside the state at statePath.
|
|
func HandedOverPath(statePath string) string {
|
|
return filepath.Join(filepath.Dir(statePath), HandedOverName)
|
|
}
|
|
|
|
// HandOver is one directory a person handed to the mesh.
|
|
type HandOver struct {
|
|
// ID is the resource that was used as found there: the hand-over is for it alone, and lapses if another
|
|
// resource later points at the path.
|
|
ID string `json:"id"`
|
|
Path string `json:"path"`
|
|
At time.Time `json:"at"`
|
|
// By is who ran the command, as the machine named them.
|
|
By string `json:"by,omitempty"`
|
|
}
|
|
|
|
// LoadHandedOver reads every hand-over. None is an empty list; a file that cannot be read is an error.
|
|
func LoadHandedOver(statePath string) ([]HandOver, error) {
|
|
raw, err := os.ReadFile(HandedOverPath(statePath))
|
|
if errors.Is(err, fs.ErrNotExist) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, fmt.Errorf("reading the directories handed to the mesh: %w", err)
|
|
}
|
|
var out []HandOver
|
|
if err := json.Unmarshal(raw, &out); err != nil {
|
|
return nil, fmt.Errorf("the directories handed to the mesh (%s) are unreadable: %w", HandedOverPath(statePath), err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// AddHandedOver records a hand-over, once per path, atomically.
|
|
func AddHandedOver(statePath string, h HandOver) error {
|
|
all, err := LoadHandedOver(statePath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, had := range all {
|
|
if had.Path == h.Path && had.ID == h.ID {
|
|
return nil
|
|
}
|
|
}
|
|
all = append(all, h)
|
|
sort.Slice(all, func(a, b int) bool { return all[a].Path < all[b].Path })
|
|
raw, err := json.MarshalIndent(all, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
path := HandedOverPath(statePath)
|
|
tmp, err := os.CreateTemp(filepath.Dir(path), ".handed-over-*.json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer os.Remove(tmp.Name())
|
|
if _, err := tmp.Write(append(raw, '\n')); err != nil {
|
|
tmp.Close()
|
|
return err
|
|
}
|
|
if err := tmp.Sync(); err != nil {
|
|
tmp.Close()
|
|
return err
|
|
}
|
|
if err := tmp.Close(); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(tmp.Name(), path)
|
|
}
|