Files
mesh-host/internal/store/handed_over.go
T
jochen ae1293e73e
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…
Read every runtime's data root, keep when a directory was found, and key a hand-over to its resource
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.
2026-10-09 00:55:21 +02:00

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)
}