// Command paths holds the catalogue's modules to talking over the bus (novox/hq ADR 0260): no module // names a path under /run or /var/lib that another module's manifest places. Such a path is that // module's state on its machine, and a second module reading or writing it is two modules talking // through a file, which the bus exists to replace. // // A module owns every `path` of its resources under /run/ or /var/lib/. Every other module's manifest // and every file of its own except its documentation and its tests is searched for each, as a whole // path: followed by nothing, a slash, or a character no path takes. // // go run . // // Exits 1 on a finding. package main import ( "encoding/json" "fmt" "io/fs" "os" "path/filepath" "regexp" "sort" "strings" ) // stateRoots are where a machine keeps a module's state. var stateRoots = []string{"/run/", "/var/lib/"} type manifest struct { Module string `json:"module"` Resources []map[string]any `json:"resources"` } // Finding is one module naming another's path. type Finding struct { Module, File, Path, Owner string } func (f Finding) String() string { return fmt.Sprintf("%s names %s in %s, and the path is %s's; modules talk over the bus (novox/hq ADR 0260)", f.Module, f.Path, f.File, f.Owner) } // Check is every finding in the catalogue at root. func Check(root string) ([]Finding, error) { dirs, err := filepath.Glob(filepath.Join(root, "modules", "*", "module.json")) if err != nil { return nil, err } owner := map[string]string{} modules := map[string]string{} for _, f := range dirs { raw, err := os.ReadFile(f) if err != nil { return nil, err } var m manifest if err := json.Unmarshal(raw, &m); err != nil { return nil, fmt.Errorf("%s: %w", f, err) } modules[m.Module] = filepath.Dir(f) for _, r := range m.Resources { p, _ := r["path"].(string) for _, root := range stateRoots { if strings.HasPrefix(p, root) && len(p) > len(root) { owner[strings.TrimRight(p, "/")] = m.Module } } } } paths := make([]string, 0, len(owner)) for p := range owner { paths = append(paths, p) } sort.Strings(paths) patterns := map[string]*regexp.Regexp{} for _, p := range paths { patterns[p] = regexp.MustCompile(regexp.QuoteMeta(p) + `(?:$|[^A-Za-z0-9._-])`) } var out []Finding names := make([]string, 0, len(modules)) for n := range modules { names = append(names, n) } sort.Strings(names) for _, module := range names { dir := modules[module] err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { if err != nil || d.IsDir() { return err } name := d.Name() if strings.HasSuffix(name, ".md") || strings.HasSuffix(name, "_test.go") || name == "go.sum" || name == "package-lock.json" { return nil } raw, err := os.ReadFile(path) if err != nil { return err } text := string(raw) for _, p := range paths { if owner[p] != module && patterns[p].MatchString(text) { rel, _ := filepath.Rel(root, path) out = append(out, Finding{Module: module, File: rel, Path: p, Owner: owner[p]}) } } return nil }) if err != nil { return nil, err } } return out, nil } func main() { root := "." if len(os.Args) > 1 { root = os.Args[1] } found, err := Check(root) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(2) } for _, f := range found { fmt.Println(f) } if len(found) > 0 { os.Exit(1) } fmt.Println("paths: no module names another's state on its machine") }