package resolution import ( "strings" "testing" mcat "git.novox.be/novox/mesh-sdk/go/catalogue" ) func recycler() mcat.Manifest { return mcat.Manifest{Module: "sonarr", Resources: []map[string]any{ {"id": "recycle-bin", "type": "directory", "path": "/srv/sonarr/recycle", "mode": "0755", "owner": "${setting:puid}:${setting:pgid}"}, {"id": "identity", "type": "file", "path": "/srv/sonarr/identity.env", "mode": "0644", "content": "PUID=${setting:puid}\nPGID=${setting:pgid}\n"}, }, } } func copied(r map[string]any) map[string]any { out := map[string]any{} for k, v := range r { out[k] = v } return out } // The node's identity reaches the directory's owner, the node's layer over the mesh's, a number given // as a string or as a JSON number alike. func TestASettingOwnsADirectory(t *testing.T) { m := recycler() layers := []mcat.Layer{ {From: "the mesh", Values: map[string]any{"puid": "1000", "pgid": "1000"}}, {From: "this node", Values: map[string]any{"puid": float64(1001), "pgid": "2000"}}, } r := copied(m.Resources[0]) if err := settingInto(r, layers, m.Module); err != nil { t.Fatal(err) } if r["owner"] != "1001:2000" { t.Fatalf("owned by %q, not 1001:2000", r["owner"]) } user := copied(m.Resources[0]) user["owner"] = "${setting:puid}" if err := settingInto(user, layers, m.Module); err != nil || user["owner"] != "1001" { t.Fatalf("user alone: %v, %v", user["owner"], err) } } // Composed for a machine, the way a push writes it: the directory reaches the node-engine owned by the // number the node's settings name, never by the placeholder. func TestAComposedMachineGetsTheOwnerTheSettingNames(t *testing.T) { r := anAdoptedAnchor() r.Modules = append(r.Modules, recycler()) with := anchorRendering(false) with.Settings["sonarr"] = []mcat.Layer{{From: "anchor", Values: map[string]any{"puid": "1001", "pgid": "2000"}}} composed, err := Compose(r, with) if err != nil { t.Fatal(err) } if why, left := composed.LeftOut["sonarr"]; left { t.Fatalf("left out: %s", why) } got := byID(composed.Resources)["sonarr.recycle-bin"] if got["owner"] != "1001:2000" { t.Fatalf("composed owned by %v, not 1001:2000", got["owner"]) } } // Only a whole number owns a directory: an account's name, text, a sign, a fraction or a value past the // kernel's ids is refused by name, and the directory is left as it was. So is a key nothing sets. func TestAnOwnerSettingThatIsNoNumberIsRefused(t *testing.T) { m := recycler() for _, bad := range []any{"abc", "root", "", "-1", "+5", "1000:1000", "10 00", "1000\nx", float64(1.5), float64(-3), "4294967295", "99999999999", true} { r := copied(m.Resources[0]) err := settingInto(r, []mcat.Layer{{From: "ace", Values: map[string]any{"puid": bad, "pgid": "1000"}}}, m.Module) if err == nil || !strings.Contains(err.Error(), `"puid"`) || !strings.Contains(err.Error(), "whole number") { t.Errorf("%v: %v", bad, err) } if r["owner"] != m.Resources[0]["owner"] { t.Errorf("%v: the owner was changed on refusal: %v", bad, r["owner"]) } } r := copied(m.Resources[0]) if err := settingInto(r, nil, m.Module); err == nil || !strings.Contains(err.Error(), "${setting:puid}") { t.Errorf("nothing set: %v", err) } if err := JudgeSettings(m, []mcat.Layer{{From: "ace", Values: map[string]any{"puid": "media", "pgid": "1000"}}}, false); err == nil || !strings.Contains(err.Error(), "whole number") { t.Errorf("a stored name is not refused when judged: %v", err) } } // An owner is written as the number parsed: "0001" and "00" reach the node-engine as 1 and 0, never as text. // Uid 0 is allowed, because a key an owner asks for is the terminal's alone. func TestAnOwnerSettingIsWrittenAsTheNumber(t *testing.T) { m := recycler() r := copied(m.Resources[0]) if err := settingInto(r, []mcat.Layer{{From: "ace", Values: map[string]any{"puid": "0001", "pgid": "00"}}}, m.Module); err != nil { t.Fatal(err) } if r["owner"] != "1:0" { t.Fatalf("owned by %q, not 1:0", r["owner"]) } }