The controller plans config, templates and moved providers' provisions, hands bus accounts to the vault and judges the new manifest words, on the feature's SDK and node-engine validator (feature 57, config and provisioning).
189 lines
7.7 KiB
Go
189 lines
7.7 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A module marked moved hands its templated files to the provisioner (novox/hq ADR 0349 §4, §6, §17, design 57 P6,
|
|
// issue 672): on a machine whose node-engine takes a filled file on the client's word and that runs a client, each
|
|
// is declared placed and without its text, named among the filled files, and planned for the client; anywhere
|
|
// else, and for every module not marked, the composition is byte for byte what it was.
|
|
|
|
const (
|
|
handedNotifier = `{"module":"notify","version":"1",
|
|
"config":{"width":{"type":"number","description":"How wide a notification is, in pixels.","default":250}},
|
|
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
|
|
"resources":[
|
|
{"id":"configuration-dir","type":"directory","path":"${machine:account-home}/.config/notify","owner":"${machine:account}","mode":"0755"},
|
|
{"id":"configuration","type":"file","path":"${machine:account-home}/.config/notify/rc","owner":"${machine:account}","mode":"0644","template":"files/rc"},
|
|
{"id":"legacy","type":"file","path":"/etc/notify.conf","content":"plain = yes\n"},
|
|
{"id":"notifier","type":"service","unit":"notify.service","scope":"user","user":"${machine:account}","reload-on":["configuration"]}]}`
|
|
notifierText = "width = ${config:width}\nhome = ${machine:account-home}\n"
|
|
)
|
|
|
|
func notifierMoved() string {
|
|
return strings.Replace(handedNotifier, `"version":"1",`, `"version":"1","moved":true,`, 1)
|
|
}
|
|
|
|
func builtNotifier() []Built {
|
|
return []Built{{Name: "files", Kind: ArtifactArchive,
|
|
Reference: ArtifactStoreScheme + "notify/files/blobs/sha256:" + strings.Repeat("d", 64),
|
|
Digest: "sha256:" + strings.Repeat("d", 64), Texts: map[string]string{"rc": notifierText}}}
|
|
}
|
|
|
|
func notifierOn(t *testing.T, m Manifest) Resolution {
|
|
t.Helper()
|
|
r, err := Resolve(shelf(m), []string{m.Module}, workstation(), World{})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
r.Account = "op"
|
|
return r
|
|
}
|
|
|
|
func composeWith(t *testing.T, r Resolution, with Rendering) (Composed, string) {
|
|
t.Helper()
|
|
c, err := r.Compose(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
raw, err := json.Marshal(c.Resources)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return c, string(raw)
|
|
}
|
|
|
|
func handingTo(store string) Rendering {
|
|
return Rendering{Hands: true, Provisioner: "mesh-provisioner-client", ArtifactStore: store}
|
|
}
|
|
|
|
func TestAMovedModulesBuildKeepsWhatTheProvisionerNeeds(t *testing.T) {
|
|
m := held(t, notifierMoved(), builtNotifier())
|
|
want := &Handed{Source: ArtifactStoreScheme + "notify/files/blobs/sha256:" + strings.Repeat("d", 64),
|
|
Digest: "sha256:" + strings.Repeat("d", 64), Templates: map[string]string{"configuration": "rc"}}
|
|
if !reflect.DeepEqual(m.Handed, want) {
|
|
t.Fatalf("the build kept %+v, want %+v", m.Handed, want)
|
|
}
|
|
if notMoved := held(t, handedNotifier, builtNotifier()); notMoved.Handed != nil {
|
|
t.Fatalf("a module not marked moved kept %+v", notMoved.Handed)
|
|
}
|
|
}
|
|
|
|
// Nothing changes for a module that has not moved, nor for a moved one where it cannot be handed.
|
|
func TestAMovedModuleComposesAsBeforeWhereItIsNotHanded(t *testing.T) {
|
|
_, before := composeWith(t, notifierOn(t, held(t, handedNotifier, builtNotifier())), handingTo("store:5000"))
|
|
for name, with := range map[string]Rendering{
|
|
"an engine that does not take a filled file alone": {Provisioner: "mesh-provisioner-client", ArtifactStore: "store:5000"},
|
|
"no provisioner client on the machine": {Hands: true, ArtifactStore: "store:5000"},
|
|
} {
|
|
c, got := composeWith(t, notifierOn(t, held(t, notifierMoved(), builtNotifier())), with)
|
|
if got != before {
|
|
t.Errorf("%s: the moved module composes differently from its unmarked twin:\nbefore: %s\nnow: %s",
|
|
name, before, got)
|
|
}
|
|
if len(c.Filled) > 0 || c.Plans != nil {
|
|
t.Errorf("%s: something was handed: %v %v", name, c.Filled, c.Plans)
|
|
}
|
|
}
|
|
if !strings.Contains(before, `width = 250`) {
|
|
t.Fatalf("the unmarked module is not filled the old way: %s", before)
|
|
}
|
|
}
|
|
|
|
func TestAMovedModulesTemplatedFileIsHandedToTheProvisioner(t *testing.T) {
|
|
c, got := composeWith(t, notifierOn(t, held(t, notifierMoved(), builtNotifier())), handingTo("store:5000"))
|
|
var file, legacy, dir map[string]any
|
|
for _, r := range c.Resources {
|
|
switch r["id"] {
|
|
case "notify.configuration":
|
|
file = r
|
|
case "notify.legacy":
|
|
legacy = r
|
|
case "notify.configuration-dir":
|
|
dir = r
|
|
}
|
|
}
|
|
if file == nil || legacy == nil || dir == nil {
|
|
t.Fatalf("not composed: %s", got)
|
|
}
|
|
wantFile := map[string]any{"id": "notify.configuration", "type": "file", "path": "/home/op/.config/notify/rc",
|
|
"owner": "op", "mode": "0644", "owner-at-runtime": "owned"}
|
|
if !reflect.DeepEqual(file, wantFile) {
|
|
t.Fatalf("the handed file is declared %v, want %v (placed, no text, owned at runtime)", file, wantFile)
|
|
}
|
|
if legacy["content"] != "plain = yes\n" || legacy["owner-at-runtime"] != nil {
|
|
t.Fatalf("a file with its text inline is not composed the old way: %v", legacy)
|
|
}
|
|
if dir["path"] != "/home/op/.config/notify" {
|
|
t.Fatalf("the module's other resources are not placed as before: %v", dir)
|
|
}
|
|
if strings.Contains(got, "width = ") {
|
|
t.Fatalf("the controller filled the handed file: %s", got)
|
|
}
|
|
if !reflect.DeepEqual(c.Filled, []string{"/home/op/.config/notify/rc"}) {
|
|
t.Fatalf("filled files %v", c.Filled)
|
|
}
|
|
want := map[string]Plan{"notify": {
|
|
Templates: PlanTemplates{Source: "http://store:5000/v2/notify/files/blobs/sha256:" + strings.Repeat("d", 64),
|
|
Digest: "sha256:" + strings.Repeat("d", 64)},
|
|
Files: []PlanFile{{ID: "configuration", Path: "/home/op/.config/notify/rc", Template: "rc",
|
|
OwnerAtRuntime: "owned", Takes: "reload-on"}},
|
|
Placement: map[string]string{"machine:account-home": "/home/op"},
|
|
}}
|
|
if !reflect.DeepEqual(c.Plans, want) {
|
|
t.Fatalf("the plans are %+v, want %+v", c.Plans, want)
|
|
}
|
|
}
|
|
|
|
func TestAMovedModulesMarkIsJudged(t *testing.T) {
|
|
for name, raw := range map[string]string{
|
|
"marked moved and naming no template": `{"module":"bare","version":"1","moved":true,
|
|
"resources":[{"id":"conf","type":"file","path":"/etc/bare","content":"x"}]}`,
|
|
"handed written in a repository": `{"module":"bare","version":"1","moved":true,
|
|
"handed":{"source":"s","digest":"d","templates":{}},
|
|
"build":{"artifacts":[{"name":"files","kind":"archive","from":"files"}]},
|
|
"resources":[{"id":"conf","type":"file","path":"/etc/bare","template":"files/bare"}]}`,
|
|
"handed and not moved": `{"module":"bare","version":"1",
|
|
"handed":{"source":"s","digest":"d","templates":{}},
|
|
"resources":[{"id":"conf","type":"file","path":"/etc/bare","content":"x"}]}`,
|
|
} {
|
|
if _, err := ParseManifest([]byte(raw)); err == nil {
|
|
t.Errorf("%s: accepted", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// What `module check` and the merge gate judge: a moved module read from its tree, its text inline. It is still
|
|
// marked moved, and composes as before, since nothing of its build's archive is there to hand.
|
|
func TestAMovedModuleReadFromItsTreeIsJudgedAsItComposesUnhanded(t *testing.T) {
|
|
dir := t.TempDir()
|
|
if err := os.MkdirAll(filepath.Join(dir, "files"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "files", "rc"), []byte(notifierText), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, err := ParseManifest([]byte(notifierMoved()))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
filled, err := WithTemplatesFrom(m, dir)
|
|
if err != nil {
|
|
t.Fatalf("a moved module read from its tree is refused: %v", err)
|
|
}
|
|
if !filled.Moved || filled.Handed != nil {
|
|
t.Fatalf("read from its tree: moved %v, handed %+v", filled.Moved, filled.Handed)
|
|
}
|
|
_, got := composeWith(t, notifierOn(t, filled), handingTo("store:5000"))
|
|
_, want := composeWith(t, notifierOn(t, held(t, handedNotifier, builtNotifier())), handingTo("store:5000"))
|
|
if got != want {
|
|
t.Fatalf("read from its tree it composes differently:\nwant: %s\ngot: %s", want, got)
|
|
}
|
|
}
|