package declaration import ( "strings" "testing" ) // Defends novox/hq ADR 0349 §5 (issue 667): a file the provisioner places says who owns it at runtime // — owned, seed or managed — and the declaration names the files the provisioner fills. The words are // read and judged here before any controller sends them. func TestAFileSaysWhoOwnsItAtRuntime(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"o","type":"file","path":"/etc/o","content":"a","owner-at-runtime":"owned"}, {"id":"s","type":"file","path":"/etc/s","content":"a","owner-at-runtime":"seed"}, {"id":"s2","type":"file","path":"/etc/s2","content":"a","owner-at-runtime":"seed","create-once":true}, {"id":"mj","type":"file","path":"/etc/mj","into":"json","content":"{\"k\":1}","owner-at-runtime":"managed"}, {"id":"mb","type":"file","path":"/etc/mb","into":"block","content":"a\n","owner-at-runtime":"managed"} ]}`)) if err != nil { t.Fatal(err) } want := map[string]string{"o": RuntimeOwned, "s": RuntimeSeed, "s2": RuntimeSeed, "mj": RuntimeManaged, "mb": RuntimeManaged} for _, r := range d.Resources { f := r.(*File) if f.OwnerAtRuntime != want[f.ID] || f.Ownership() != want[f.ID] { t.Errorf("%s: read as %q, ownership %q; want %q", f.ID, f.OwnerAtRuntime, f.Ownership(), want[f.ID]) } } } // A file that does not say is owned by its old form, so the word means exactly what that form does. func TestAFileThatDoesNotSayIsOwnedByItsOldForm(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"resources":[ {"id":"o","type":"file","path":"/etc/o","content":"a"}, {"id":"s","type":"file","path":"/etc/s","content":"a","create-once":true}, {"id":"mj","type":"file","path":"/etc/mj","into":"json","content":"{}"}, {"id":"mb","type":"file","path":"/etc/mb","into":"block","content":"a\n"} ]}`)) if err != nil { t.Fatal(err) } want := map[string]string{"o": RuntimeOwned, "s": RuntimeSeed, "mj": RuntimeManaged, "mb": RuntimeManaged} for _, r := range d.Resources { f := r.(*File) if f.OwnerAtRuntime != "" || f.Ownership() != want[f.ID] { t.Errorf("%s: said %q, ownership %q; want unsaid and %q", f.ID, f.OwnerAtRuntime, f.Ownership(), want[f.ID]) } if f.Seeded() != (want[f.ID] == RuntimeSeed) { t.Errorf("%s: seeded %v", f.ID, f.Seeded()) } } } func TestAnOwnershipThatContradictsTheFileIsRefused(t *testing.T) { for name, c := range map[string]struct{ resource, refusal string }{ "another word": {`{"id":"f","type":"file","path":"/etc/x","content":"a","owner-at-runtime":"mine"}`, `owner-at-runtime "mine"`}, "owned and create-once": {`{"id":"f","type":"file","path":"/etc/x","content":"a","owner-at-runtime":"owned","create-once":true}`, "create-once"}, "owned and into": {`{"id":"f","type":"file","path":"/etc/x","into":"json","content":"{}","owner-at-runtime":"owned"}`, "into"}, "seed and into": {`{"id":"f","type":"file","path":"/etc/x","into":"block","content":"a","owner-at-runtime":"seed"}`, "into"}, "managed and whole": {`{"id":"f","type":"file","path":"/etc/x","content":"a","owner-at-runtime":"managed"}`, "into"}, "managed and create-once": {`{"id":"f","type":"file","path":"/etc/x","into":"json","content":"{}","owner-at-runtime":"managed","create-once":true}`, "create-once"}, // The rule that was there before stays: a file written into is never sealed or created once. "managed and sealed": {`{"id":"f","type":"file","path":"/etc/x","into":"json","content":"{}","sealed":"x","owner-at-runtime":"managed"}`, "sealed"}, } { _, err := Parse([]byte(`{"declaration":1,"resources":[` + c.resource + `]}`)) if err == nil || !strings.Contains(err.Error(), c.refusal) { t.Errorf("%s: want a refusal naming %q, got %v", name, c.refusal, err) } } } func TestTheDeclarationNamesTheFilesTheProvisionerFills(t *testing.T) { d, err := Parse([]byte(`{"declaration":1,"filled_files":["/etc/a","/etc/b"],"resources":[ {"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}, {"id":"b","type":"file","path":"/etc/b","into":"block","content":"b\n","owner-at-runtime":"managed"}, {"id":"c","type":"file","path":"/etc/c","content":"c"} ]}`)) if err != nil { t.Fatal(err) } if len(d.Filled) != 2 || d.Filled[0] != "/etc/a" || d.Filled[1] != "/etc/b" { t.Fatalf("filled files read as %v", d.Filled) } if !d.Fills("/etc/a") || !d.Fills("/etc/b") || d.Fills("/etc/c") { t.Errorf("Fills: a %v, b %v, c %v", d.Fills("/etc/a"), d.Fills("/etc/b"), d.Fills("/etc/c")) } // A declaration that names none fills none, as every declaration before this did. d, err = Parse([]byte(`{"declaration":1,"resources":[{"id":"c","type":"file","path":"/etc/c","content":"c"}]}`)) if err != nil { t.Fatal(err) } if d.Filled != nil || d.Fills("/etc/c") { t.Errorf("a declaration naming no filled files fills %v", d.Filled) } } func TestAFilledFileMustBeADeclaredFileThatSaysItsOwner(t *testing.T) { for name, c := range map[string]struct{ filled, resources, refusal string }{ "not declared": {`["/etc/none"]`, `{"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}`, `"/etc/none"`}, "a directory, not a file": {`["/etc/d"]`, `{"id":"d","type":"directory","path":"/etc/d"}`, `"/etc/d"`}, "says no owner": {`["/etc/a"]`, `{"id":"a","type":"file","path":"/etc/a","content":"a"}`, "owner-at-runtime"}, "named twice": {`["/etc/a","/etc/a"]`, `{"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}`, "twice"}, "no path": {`[""]`, `{"id":"a","type":"file","path":"/etc/a","content":"a","owner-at-runtime":"owned"}`, "no path"}, } { _, err := Parse([]byte(`{"declaration":1,"filled_files":` + c.filled + `,"resources":[` + c.resources + `]}`)) if err == nil || !strings.Contains(err.Error(), c.refusal) { t.Errorf("%s: want a refusal naming %q, got %v", name, c.refusal, err) } } }