137 lines
5.2 KiB
Go
137 lines
5.2 KiB
Go
package builder
|
|
|
|
import (
|
|
"context"
|
|
"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 ""
|
|
}
|
|
|
|
func TestABuildReadsEachTemplateBackFromItsArchive(t *testing.T) {
|
|
r, workspace := aRepository(t, templatedModule, templatedFiles)
|
|
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
|
|
Npmrc{}, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if text := contentOf(t, got.Manifest, "conf"); text != templatedFiles["files/meshboard.conf"] {
|
|
t.Fatalf("the template's text is %q, not the file's", text)
|
|
}
|
|
if text := contentOf(t, got.Manifest, "deeper"); text != templatedFiles["files/a/deeper.conf"] {
|
|
t.Fatalf("the template's text is %q, not the file's", text)
|
|
}
|
|
if r.archives["meshboard/files"] == "" {
|
|
t.Fatalf("the templates archive was not published: %v", r.archives)
|
|
}
|
|
if problem := catalogue.HeldTemplateProblem(got.Manifest); problem != "" {
|
|
t.Fatal(problem)
|
|
}
|
|
}
|
|
|
|
func TestABuildWhoseTemplateIsMissingFailsNamingIt(t *testing.T) {
|
|
files := map[string]string{}
|
|
for k, v := range templatedFiles {
|
|
files[k] = v
|
|
}
|
|
delete(files, "files/a/deeper.conf")
|
|
r, workspace := aRepository(t, templatedModule, files)
|
|
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil,
|
|
Npmrc{}, GitCredential{}, nil)
|
|
if err == nil || !strings.Contains(err.Error(), "files/a/deeper.conf") {
|
|
t.Fatalf("a build whose template is missing did not fail naming it: %v", err)
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
func TestAWalkTakingAChecksBuildReadsTheTemplatesBackToo(t *testing.T) {
|
|
withVersion(t, "builder-1")
|
|
contents := map[string]string{"mod/" + ManifestName: templatedModule}
|
|
for k, v := range templatedFiles {
|
|
contents["mod/"+k] = v
|
|
}
|
|
scratch := newScratch()
|
|
r, run, workspace := aModuleWith(t, "tree-one", contents)
|
|
checked := aCheckBuiltBy(t, scratch, r, run, workspace)
|
|
|
|
r, run, workspace = aModuleWith(t, "tree-one", contents)
|
|
got, err := BuildReusing(t.Context(), run, r, Reuse{Scratch: scratch, Pulls: []PullRef{aPull}, Named: named(checked)},
|
|
"https://forge.invalid/meshboard.git", "mod", "main", workspace, nil, Npmrc{}, GitCredential{}, nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Reused == nil || len(r.archives) > 0 {
|
|
t.Fatalf("the walk built again: reused %+v, why %q, published %v", got.Reused, got.Why, r.archives)
|
|
}
|
|
if text := contentOf(t, got.Manifest, "conf"); text != templatedFiles["files/meshboard.conf"] {
|
|
t.Fatalf("the template's text is %q, not the file's", text)
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|