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