package builder import ( "os" "path/filepath" "strings" "testing" "github.com/novox/mesh-controller/internal/catalogue" ) // A module's files kept as template files in its repository (novox/hq ADR 0349 §2, design 57 package T1, issue // 654): the build packs files/ into the module's templates archive and reads each template's text back from the // packed bytes into the file's content, so the manifest the mesh holds is the one it would hold for the text inline. const templatedModule = `{"module":"meshboard","version":"1", "build":{"artifacts":[ {"name":"server","kind":"image","from":"Dockerfile"}, {"name":"files","kind":"archive","from":"files"}]}, "resources":[ {"id":"svc","type":"container","name":"meshboard","artifact":"server"}, {"id":"conf","type":"file","path":"/etc/meshboard/meshboard.conf","template":"files/meshboard.conf"}, {"id":"deeper","type":"file","path":"/etc/meshboard/deeper.conf","template":"files/a/deeper.conf"}]}` var templatedFiles = map[string]string{ "Dockerfile": "FROM scratch", "files/meshboard.conf": "listen = 8080\n\"quoted\" é\n", "files/a/deeper.conf": "deeper\n", "files/unused.conf": "packed, named by nothing", } func contentOf(t *testing.T, m catalogue.Manifest, id string) string { t.Helper() for _, r := range m.Resources { if r["id"] == id { if _, has := r[catalogue.TemplateField]; has { t.Fatalf("%s still names its template in the built manifest: %v", id, r) } text, ok := r["content"].(string) if !ok { t.Fatalf("%s carries no content: %v", id, r) } return text } } t.Fatalf("no resource %s in %v", id, m.Resources) return "" } // A walk that takes its check's build copies the archive by digest and builds nothing: the templates are read back // from the tree packed again, which is the archive only when it packs to the digest taken. // The tree packed again must be the archive taken; text read from anything else is not the archive's. func TestTemplateTextsAreRefusedWhenTheTreeDoesNotPackToTheDigestTaken(t *testing.T) { m, err := catalogue.ParseManifest([]byte(templatedModule)) if err != nil { t.Fatal(err) } within := writeTree(t, t.TempDir(), templatedFiles) built := []catalogue.Built{{Name: "files", Kind: catalogue.ArtifactArchive, Digest: "sha256:" + strings.Repeat("0", 64)}} if _, err := withTemplateTexts(m, within, built); err == nil || !strings.Contains(err.Error(), "digest") { t.Fatalf("text was read from a tree that does not pack to the archive taken: %v", err) } } // writeTree writes files under dir and answers dir. func writeTree(t *testing.T, dir string, files map[string]string) string { t.Helper() for name, body := range files { full := filepath.Join(dir, filepath.FromSlash(name)) if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { t.Fatal(err) } if err := os.WriteFile(full, []byte(body), 0o644); err != nil { t.Fatal(err) } } return dir } // A module whose files/ is an archive unpacked onto the machine keeps its templates in a directory it names (issue // 731): the build reads each template back from the archive packed from that directory, and files/ is packed as it // was, its templates nowhere in it. const unpackingModule = `{"module":"meshboard","version":"1", "templates":"templates", "build":{"artifacts":[ {"name":"scripts","kind":"archive","from":"files"}, {"name":"templates","kind":"archive","from":"templates"}]}, "resources":[ {"id":"scripts","type":"archive","path":"/usr/local/lib/meshboard","artifact":"scripts"}, {"id":"conf","type":"file","path":"/etc/meshboard/meshboard.conf","template":"templates/meshboard.conf"}]}`