Files
mesh-host/internal/apply/missing.go
T
jschoubben fc3975d535 WIP: judge an access by who writes it, and act on its path only on a warrant (issue 567)
Paused: ADR 0325 replaces runs-as and the group grant. Kept for reuse: the
bounded descriptor walks, the warrant-backed access act (share, create),
the access wait and the missing-path waiting state.
2026-10-12 17:00:41 +02:00

123 lines
4.5 KiB
Go

package apply
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/novox/mesh-host/internal/declaration"
)
// A missing accessed path waits for the operator (novox/hq ADR 0324, issue 567).
//
// ADR 0051 refused an access whose path is not there, and the refusal failed the whole apply: one module's
// folder not made yet held every other module on the machine. The runtime must still not make it — a bind
// whose source is missing is made by it as root, with whatever mode it picks (04-ISSUES/026) — so the engine
// mounts nothing for the access: every container that mounts the path or below it is not created, and is said
// waiting with the access, while the rest of the declaration applies. The access is stated waiting on every look
// with a wait of kind access, and the operator's warrant for the create act makes the path, each level as its
// nearest existing ancestor is. Only when that ancestor is a directory: one that is a file or a link is refused
// as before, and one owned by a group that is never granted is stated unhealthy, with no wait.
// MissingPath is an accessed path that is not there, and its nearest existing ancestor.
type MissingPath struct {
Ancestor string
// Levels are the names to make below Ancestor, outermost first.
Levels []string
// Info is the ancestor as lstat reads it: a link is not a directory.
Info os.FileInfo
}
// NearestAncestor reads, for a path that is not there, its nearest existing ancestor by lstat. An error for a
// path that is there, one not stated exactly, or an ancestor that cannot be read.
func NearestAncestor(path string) (MissingPath, error) {
if err := shareablePath(path); err != nil {
return MissingPath{}, err
}
if _, err := os.Lstat(path); err == nil || !errors.Is(err, os.ErrNotExist) {
return MissingPath{}, fmt.Errorf("%s is there, or cannot be read: %v", path, err)
}
var levels []string
for p := path; p != "/"; p = filepath.Dir(p) {
levels = append([]string{filepath.Base(p)}, levels...)
parent := filepath.Dir(p)
info, err := os.Lstat(parent)
if errors.Is(err, os.ErrNotExist) {
continue
}
if err != nil {
return MissingPath{}, err
}
return MissingPath{Ancestor: parent, Levels: levels, Info: info}, nil
}
return MissingPath{}, errors.New("no part of the path is there")
}
// missingAccess is whether r's path is missing below an ancestor that is a directory: the access waits.
func missingAccess(path string) (MissingPath, bool) {
m, err := NearestAncestor(filepath.Clean(path))
if err != nil || !m.Info.IsDir() {
return MissingPath{}, false
}
return m, true
}
// missingAccesses is every declared access whose path is missing below a directory, by path, with its id: what
// no container mounts until it is made. A path the guard refuses is not read (issue 339).
func missingAccesses(resources []declaration.Resource, refused map[string]error) map[string]string {
out := map[string]string{}
for _, r := range resources {
a, ok := r.(*declaration.Access)
if !ok || refused[filepath.Clean(a.Path)] != nil {
continue
}
if _, missing := missingAccess(a.Path); missing {
out[filepath.Clean(a.Path)] = a.ID
}
}
return out
}
// mountsMissing is the access a container waits for: one whose path it mounts, or mounts below, is missing.
func mountsMissing(c *declaration.Container, missing map[string]string) string {
for _, v := range c.Volumes {
src := mountSource(v)
if !strings.HasPrefix(src, "/") {
continue
}
for path, id := range missing {
if atOrBelow(filepath.Clean(src), path) {
return id
}
}
}
return ""
}
// WaitingOnAccess is every container of the declaration that waits for a missing accessed path, read now: not
// created by the apply, so not judged alive or dead and not fired on a schedule — the access says why.
func WaitingOnAccess(d *declaration.Declaration) map[string]bool {
out := map[string]bool{}
if d == nil {
return out
}
missing := missingAccesses(d.Resources, refusedPlaces(d.Resources))
if len(missing) == 0 {
return out
}
for _, r := range d.Resources {
if c, ok := r.(*declaration.Container); ok && mountsMissing(c, missing) != "" {
out[c.ID] = true
}
}
return out
}
// MissingWords is the outcome of an access whose path is missing below a directory.
func MissingWords(path string) string {
return fmt.Sprintf("%s is not there yet, below a folder that is: nothing mounts it and the containers that do are "+
"not created until it is made — on the operator's warrant, by the create act (novox/hq ADR 0324)", path)
}