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.
150 lines
5.2 KiB
Go
150 lines
5.2 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"os/user"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"sync/atomic"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/apply"
|
|
"github.com/novox/mesh-host/internal/link"
|
|
"github.com/novox/mesh-host/internal/store"
|
|
)
|
|
|
|
// A directory used as found is never quiet (novox/hq issue 339).
|
|
//
|
|
// The engine leaves the owner and mode of a directory it did not make: changing them would be root handing a
|
|
// directory it never made to whichever account a declaration named. That is safe and, said nowhere, a module
|
|
// whose data directory it cannot write would simply fail. So each such directory is said three ways: a WARNING
|
|
// line in every apply's log; a resource of its module of kind `directory`, stated unhealthy on every look,
|
|
// which the controller raises as that module's condition naming the directory and its owner; and a way out at
|
|
// the machine's terminal, `mesh-host hand-over <path>`, after which the next apply gives it the declared owner
|
|
// and mode like any directory of the mesh's.
|
|
|
|
// foundState is where the node's state is, for the looks to read the directories used as found from; empty
|
|
// until the engine runs.
|
|
var foundState atomic.Value
|
|
|
|
// handedOverFrom has the apply read the directories handed over from beside the state at statePath.
|
|
func handedOverFrom(statePath string) {
|
|
apply.HandedOver = func() map[string]bool {
|
|
all, err := store.LoadHandedOver(statePath)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "mesh-host: %v; no directory counts as handed over until it reads\n", err)
|
|
return nil
|
|
}
|
|
out := map[string]bool{}
|
|
for _, h := range all {
|
|
out[apply.HandOverKey(h.ID, h.Path)] = true
|
|
}
|
|
return out
|
|
}
|
|
}
|
|
|
|
// foundDirectories is every directory the node's state records as used as found, sorted by id.
|
|
func foundDirectories(known store.State) []store.Applied {
|
|
var out []store.Applied
|
|
for _, r := range known.Resources {
|
|
if r.Type == "directory" && r.AsFound {
|
|
out = append(out, r)
|
|
}
|
|
}
|
|
sort.Slice(out, func(a, b int) bool { return out[a].ID < out[b].ID })
|
|
return out
|
|
}
|
|
|
|
// withFoundDirectories adds to a statement every directory used as found, as its module's resource of kind
|
|
// directory, unhealthy: the reason names its owner and mode as they are now, what was declared, and how to hand
|
|
// it over. The path is the target, which the controller keeps as evidence.
|
|
func withFoundDirectories(h *link.Health, found []store.Applied) *link.Health {
|
|
for _, r := range found {
|
|
at := strings.LastIndex(r.ID, ".")
|
|
if at <= 0 {
|
|
continue
|
|
}
|
|
owner, mode := "unknown", "unknown"
|
|
if info, err := os.Lstat(r.Target); err == nil {
|
|
mode = fmt.Sprintf("%o", info.Mode().Perm())
|
|
if uid, gid, ok := apply.OwnerOf(info); ok {
|
|
owner = fmt.Sprintf("%d:%d", uid, gid)
|
|
}
|
|
}
|
|
declared := r.Declared
|
|
if declared == "" {
|
|
declared = "another owner or mode"
|
|
}
|
|
h.Resources = append(h.Resources, link.ResourceHealth{Module: r.ID[:at], Resource: r.ID,
|
|
Kind: link.KindDirectory, Target: r.Target, State: link.StateUnhealthy,
|
|
Reason: fmt.Sprintf("%s owned by %s, mode %s, as found; %s was declared and not given it — "+
|
|
"`mesh-host hand-over` at the machine hands it to the mesh", link.ReasonUsedAsFound, owner, mode,
|
|
declared),
|
|
Since: foundSince(r).UTC()})
|
|
}
|
|
return h
|
|
}
|
|
|
|
// foundNow is the directories used as found, read from the node's state; none when it cannot be read, which
|
|
// the apply says on its own.
|
|
func foundNow() []store.Applied {
|
|
path, _ := foundState.Load().(string)
|
|
if path == "" {
|
|
return nil
|
|
}
|
|
known, err := store.Load(path)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return foundDirectories(known)
|
|
}
|
|
|
|
// handOverCommand hands one directory used as found to the mesh: run by a person, as root, at the machine.
|
|
// It changes nothing on disk itself; the next apply gives the directory its declared owner and mode, and says so.
|
|
func handOverCommand(opts options) error {
|
|
path := filepath.Clean(opts.file)
|
|
if !filepath.IsAbs(path) {
|
|
return fmt.Errorf("hand-over takes the directory's absolute path, and %q is not one", opts.file)
|
|
}
|
|
known, err := store.Load(opts.state)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
for _, r := range foundDirectories(known) {
|
|
if filepath.Clean(r.Target) != path {
|
|
continue
|
|
}
|
|
by := "a person at the machine"
|
|
if u, err := user.Current(); err == nil {
|
|
by = u.Username
|
|
}
|
|
if err := store.AddHandedOver(opts.state, store.HandOver{ID: r.ID, Path: path, At: time.Now().UTC(), By: by}); err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(opts.out, "%s (%s) is handed to the mesh: the next apply gives it %s, and it is the mesh's from "+
|
|
"then on — removed when empty and no longer declared\n", path, r.ID, orUnsaid(r.Declared))
|
|
return nil
|
|
}
|
|
return errors.New(path + " is not a directory this machine uses as found (`mesh-host owned` lists what the " +
|
|
"engine applied); nothing was handed over")
|
|
}
|
|
|
|
// foundSince is when a directory was first found as it is: kept across applies, so the controller can tell one
|
|
// a send found from one found before it. A record from before that was kept says when it was last applied.
|
|
func foundSince(r store.Applied) time.Time {
|
|
if !r.FoundAt.IsZero() {
|
|
return r.FoundAt
|
|
}
|
|
return r.AppliedAt
|
|
}
|
|
|
|
func orUnsaid(s string) string {
|
|
if s == "" {
|
|
return "the declared owner and mode"
|
|
}
|
|
return s
|
|
}
|