Let a directory say how it reacts to files it does not know, and move out what a move-out directory does not know (issue 1024, ADR 0422)
mesh/merge-gate head d237dee4c9e3 merged into trunk cd13f7726ef0 — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head d237dee4c9e3 merged into trunk cd13f7726ef0 — fail: its merge-check.sh failed: --- FAIL: TestAMoveOutDirectoryKeepsOnlyWhatItKnows (0.…
mesh/delivery superseded: a newer head of the same pull request
mesh/merge-gate head d237dee4c9e3 merged into trunk cd13f7726ef0 — pass: builds mesh-host → ace, g14, novox, shanks; no bus step; every machine composes wi…
mesh/repo-check head d237dee4c9e3 merged into trunk cd13f7726ef0 — fail: its merge-check.sh failed: --- FAIL: TestAMoveOutDirectoryKeepsOnlyWhatItKnows (0.…
mesh/delivery superseded: a newer head of the same pull request
A directory or archive resource carries `unknown` (move-out, module, program) and `knows`. For move-out under a home, every look moves each path the declaration does not know into ~/.local/state/mesh-moved/<module>/<time>/, the list written before each move, without following a link, a young path waiting, a socket or pipe left and reported, nothing made when nothing moved, and another file system refused. Reviewed in two rounds.
This commit is contained in:
@@ -124,6 +124,60 @@ type Directory struct {
|
||||
// Owner is the user this belongs to, by name. Absent means root, which is what everything
|
||||
// managed was until users existed.
|
||||
Owner string `json:"owner,omitempty"`
|
||||
// Unknown says how the directory treats a path inside it that nothing declared (issue 1024, novox/hq ADR 0422):
|
||||
// move-out, module or program. Empty means not said, and nothing moves.
|
||||
Unknown string `json:"unknown,omitempty"`
|
||||
// Knows names paths relative to Path that are not declared but belong here; a trailing "/" names a folder and
|
||||
// everything in it.
|
||||
Knows []string `json:"knows,omitempty"`
|
||||
}
|
||||
|
||||
// How a directory or archive treats what it did not write (issue 1024, ADR 0422).
|
||||
const (
|
||||
UnknownMoveOut = "move-out"
|
||||
UnknownModule = "module"
|
||||
UnknownProgram = "program"
|
||||
)
|
||||
|
||||
// checkUnknown judges the unknown and knows fields of a directory or an archive.
|
||||
func checkUnknown(where, kind, unknown string, knows []string) []string {
|
||||
var problems []string
|
||||
switch unknown {
|
||||
case "", UnknownMoveOut, UnknownModule, UnknownProgram:
|
||||
default:
|
||||
problems = append(problems, fmt.Sprintf(
|
||||
"%s: a %s's unknown is %q; it is %q, %q or %q (issue 1024)", where, kind, unknown,
|
||||
UnknownMoveOut, UnknownModule, UnknownProgram))
|
||||
}
|
||||
if len(knows) > 0 && unknown == "" {
|
||||
problems = append(problems, where+": knows is given without unknown, and says nothing without it")
|
||||
}
|
||||
seen := map[string]bool{}
|
||||
for _, k := range knows {
|
||||
switch {
|
||||
case k == "":
|
||||
problems = append(problems, where+": knows holds an empty entry")
|
||||
continue
|
||||
case strings.HasPrefix(k, "/"):
|
||||
problems = append(problems, fmt.Sprintf("%s: knows entry %q is absolute; it is relative to the path", where, k))
|
||||
continue
|
||||
}
|
||||
bad := false
|
||||
for _, c := range strings.Split(strings.TrimSuffix(k, "/"), "/") {
|
||||
if c == "" || c == "." || c == ".." {
|
||||
bad = true
|
||||
}
|
||||
}
|
||||
if bad {
|
||||
problems = append(problems, fmt.Sprintf("%s: knows entry %q holds an empty, . or .. component", where, k))
|
||||
continue
|
||||
}
|
||||
if seen[k] {
|
||||
problems = append(problems, fmt.Sprintf("%s: knows entry %q is repeated", where, k))
|
||||
}
|
||||
seen[k] = true
|
||||
}
|
||||
return problems
|
||||
}
|
||||
|
||||
func (d *Directory) Identity() string { return d.ID }
|
||||
@@ -141,6 +195,7 @@ func (d *Directory) validate(where string, _ bool) []string {
|
||||
if len(mode) == 4 && mode[0] == '2' {
|
||||
mode = "0" + mode[1:]
|
||||
}
|
||||
problems = append(problems, checkUnknown(where, "directory", d.Unknown, d.Knows)...)
|
||||
return append(problems, checkMode(where, mode)...)
|
||||
}
|
||||
|
||||
@@ -696,6 +751,9 @@ type Archive struct {
|
||||
Path string `json:"path"`
|
||||
// Owner is the user the unpacked files belong to. Absent means root.
|
||||
Owner string `json:"owner,omitempty"`
|
||||
// Unknown and Knows are as a directory's (issue 1024, ADR 0422).
|
||||
Unknown string `json:"unknown,omitempty"`
|
||||
Knows []string `json:"knows,omitempty"`
|
||||
}
|
||||
|
||||
func (a *Archive) Identity() string { return a.ID }
|
||||
@@ -716,6 +774,7 @@ func (a *Archive) validate(where string, _ bool) []string {
|
||||
problems = append(problems, where+
|
||||
": an archive is pinned by digest, as sha256:<64 hex characters>")
|
||||
}
|
||||
problems = append(problems, checkUnknown(where, "archive", a.Unknown, a.Knows)...)
|
||||
return problems
|
||||
}
|
||||
|
||||
|
||||
@@ -545,3 +545,51 @@ func TestAContainerMayLogToTheJournalAndNowhereElse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 1024, ADR 0422: a directory or archive says how it treats files it did not write.
|
||||
|
||||
func TestADirectoryAndAnArchiveSayWhatTheyDoWithUnknownFiles(t *testing.T) {
|
||||
d, err := Parse([]byte(`{"declaration":1,"resources":[
|
||||
{"id":"d","type":"directory","path":"/home/a/x","unknown":"move-out","knows":["keep","sub/"]},
|
||||
{"id":"r","type":"archive","source":"https://x/y","digest":"sha256:` + strings.Repeat("a", 64) + `","path":"/home/a/y","unknown":"module"}
|
||||
]}`))
|
||||
if err != nil {
|
||||
t.Fatalf("refused: %v", err)
|
||||
}
|
||||
dir := d.Resources[0].(*Directory)
|
||||
if dir.Unknown != UnknownMoveOut || len(dir.Knows) != 2 {
|
||||
t.Errorf("directory: %+v", dir)
|
||||
}
|
||||
if ar := d.Resources[1].(*Archive); ar.Unknown != UnknownModule {
|
||||
t.Errorf("archive: %+v", ar)
|
||||
}
|
||||
for _, v := range []string{UnknownMoveOut, UnknownModule, UnknownProgram} {
|
||||
if _, err := Parse([]byte(`{"declaration":1,"resources":[{"id":"d","type":"directory","path":"/x","unknown":"` + v + `"}]}`)); err != nil {
|
||||
t.Errorf("%s refused: %v", v, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadUnknownAndKnowsFormsAreRefusedNamingTheResource(t *testing.T) {
|
||||
digest := `"digest":"sha256:` + strings.Repeat("a", 64) + `"`
|
||||
cases := map[string]string{
|
||||
"bad value": `{"id":"d","type":"directory","path":"/x","unknown":"delete"}`,
|
||||
"empty entry": `{"id":"d","type":"directory","path":"/x","unknown":"move-out","knows":[""]}`,
|
||||
"absolute": `{"id":"d","type":"directory","path":"/x","unknown":"move-out","knows":["/etc"]}`,
|
||||
"empty comp": `{"id":"d","type":"directory","path":"/x","unknown":"move-out","knows":["a//b"]}`,
|
||||
"dot": `{"id":"d","type":"directory","path":"/x","unknown":"move-out","knows":["./a"]}`,
|
||||
"dotdot": `{"id":"d","type":"directory","path":"/x","unknown":"move-out","knows":["a/../b"]}`,
|
||||
"repeat": `{"id":"d","type":"directory","path":"/x","unknown":"move-out","knows":["a","a"]}`,
|
||||
"no unknown": `{"id":"d","type":"directory","path":"/x","knows":["a"]}`,
|
||||
"archive bad": `{"id":"d","type":"archive","source":"s",` + digest + `,"path":"/x","unknown":"nope"}`,
|
||||
"archive knws": `{"id":"d","type":"archive","source":"s",` + digest + `,"path":"/x","knows":["a"]}`,
|
||||
}
|
||||
for name, res := range cases {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
refusal := refusalFor(t, `{"declaration":1,"resources":[`+res+`]}`)
|
||||
if !strings.Contains(strings.Join(refusal.Problems, "\n"), `resource "d"`) {
|
||||
t.Errorf("resource not named: %v", refusal.Problems)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user