152 lines
6.8 KiB
Go
152 lines
6.8 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// A module may name the directory its templates are kept in (`"templates": "<dir>"`, default files/; issue 731): a
|
|
// module whose files/ is already an archive unpacked onto the machine keeps its templates in a directory of their
|
|
// own, packed into an archive no resource names, so its unpacked files/ is exactly what it was.
|
|
|
|
// The two forms of one module whose files/ is unpacked onto the machine: its text inline, and in templates/.
|
|
const (
|
|
unpackingInline = `{"module":"tidy","version":"1",
|
|
"build":{"artifacts":[{"name":"scripts","kind":"archive","from":"files"}]},
|
|
"resources":[
|
|
{"id":"scripts","type":"archive","path":"/usr/local/lib/tidy","artifact":"scripts"},
|
|
{"id":"conf","type":"directory","path":"${dir:conf}"},
|
|
{"id":"main","type":"file","path":"/etc/tidy.conf","mode":"0644","content":"` + mainText + `"},
|
|
{"id":"extra","type":"file","path":"/etc/extra.conf","content":"` + extraText + `"}]}`
|
|
unpackingTemplated = `{"module":"tidy","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/tidy","artifact":"scripts"},
|
|
{"id":"conf","type":"directory","path":"${dir:conf}"},
|
|
{"id":"main","type":"file","path":"/etc/tidy.conf","mode":"0644","template":"templates/tidy.conf"},
|
|
{"id":"extra","type":"file","path":"/etc/extra.conf","template":"templates/deeper/extra.conf"}]}`
|
|
)
|
|
|
|
func builtUnpacking(templates map[string]string) []Built {
|
|
built := []Built{{Name: "scripts", Kind: ArtifactArchive,
|
|
Reference: "https://store.invalid/scripts.tar.gz",
|
|
Digest: "sha256:" + strings.Repeat("a", 64)}}
|
|
if templates != nil {
|
|
built = append(built, Built{Name: "templates", Kind: ArtifactArchive,
|
|
Reference: ArtifactStoreScheme + "tidy/templates/blobs/sha256:" + strings.Repeat("b", 64),
|
|
Digest: "sha256:" + strings.Repeat("b", 64), Texts: templates})
|
|
}
|
|
return built
|
|
}
|
|
|
|
func TestAModuleWithItsOwnTemplatesDirectoryComposesByteIdenticallyToItsInlineTwin(t *testing.T) {
|
|
inline := composed(t, held(t, unpackingInline, builtUnpacking(nil)))
|
|
templated := composed(t, held(t, unpackingTemplated, builtUnpacking(templateTexts(t))))
|
|
if inline != templated {
|
|
t.Fatalf("the two forms compose differently:\ninline: %s\ntemplated: %s", inline, templated)
|
|
}
|
|
// The unpacked archive is the files/ one, by its digest; the templates archive reaches no machine.
|
|
if !strings.Contains(templated, strings.Repeat("a", 64)) || strings.Contains(templated, strings.Repeat("b", 64)) {
|
|
t.Fatalf("the composition does not unpack files/ alone: %s", templated)
|
|
}
|
|
}
|
|
|
|
func TestTheManifestTheMeshHoldsNamesNoTemplatesDirectory(t *testing.T) {
|
|
m := held(t, unpackingTemplated, builtUnpacking(templateTexts(t)))
|
|
raw, err := json.Marshal(m)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(raw), `"templates"`) {
|
|
t.Fatalf("the held manifest still names its templates directory: %s", raw)
|
|
}
|
|
}
|
|
|
|
func TestAModuleWithItsOwnTemplatesDirectoryIsReadFromItsTree(t *testing.T) {
|
|
dir := t.TempDir()
|
|
for name, text := range templateTexts(t) {
|
|
full := filepath.Join(dir, "templates", filepath.FromSlash(name))
|
|
if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(full, []byte(text), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
// files/ holds what the machine unpacks, a symlink among it judged by nobody here: it is not the templates.
|
|
if err := os.MkdirAll(filepath.Join(dir, "files", "bin"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "files", "bin", "tidy"), []byte("#!/bin/sh\n"), 0o755); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
m, err := ParseManifest([]byte(unpackingTemplated))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
filled, err := WithTemplatesFrom(m, dir)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
resolved, err := filled.Resolve(builtUnpacking(map[string]string{}))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got, want := composed(t, resolved), composed(t, held(t, unpackingInline, builtUnpacking(nil))); got != want {
|
|
t.Fatalf("read from the tree, the module composes differently:\ninline: %s\ntemplated: %s", want, got)
|
|
}
|
|
|
|
// A symlink under the templates directory is refused by its path, as under files/.
|
|
if err := os.Symlink("/etc/hostname", filepath.Join(dir, "templates", "elsewhere")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := WithTemplatesFrom(m, dir); err == nil || !strings.Contains(err.Error(), "templates/elsewhere is a symlink") {
|
|
t.Fatalf("a symlink under templates/ was not refused by its path: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestATemplateOutsideTheNamedTemplatesDirectoryIsRefused(t *testing.T) {
|
|
raw := strings.Replace(unpackingTemplated, `"template":"templates/tidy.conf"`, `"template":"files/tidy.conf"`, 1)
|
|
if _, err := ParseManifest([]byte(raw)); err == nil || !strings.Contains(err.Error(), "files/tidy.conf") ||
|
|
!strings.Contains(err.Error(), "templates/") {
|
|
t.Fatalf("a template outside the named templates directory was not refused by name: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestATemplatesDirectoryNoArchiveIsBuiltFromIsRefused(t *testing.T) {
|
|
raw := strings.Replace(unpackingTemplated, `,
|
|
{"name":"templates","kind":"archive","from":"templates"}`, "", 1)
|
|
if _, err := ParseManifest([]byte(raw)); err == nil || !strings.Contains(err.Error(), "no archive from templates/") {
|
|
t.Fatalf("a module whose build packs no archive from its templates directory was not refused: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestATemplatesDirectoryOnAModuleNamingNoTemplateIsRefused(t *testing.T) {
|
|
raw := strings.Replace(unpackingInline, `"version":"1",`, `"version":"1","templates":"templates",`, 1)
|
|
if _, err := ParseManifest([]byte(raw)); err == nil || !strings.Contains(err.Error(), "names no template") {
|
|
t.Fatalf("a templates directory read by nothing was not refused: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestATemplatesDirectoryThatIsNotAPlainRelativeDirectoryIsRefused(t *testing.T) {
|
|
for _, dir := range []string{"/etc", "../other", "..", "templates/../files", "./templates", ".", "a\\b", "templates/"} {
|
|
raw := strings.Replace(unpackingTemplated, `"templates":"templates"`, `"templates":`+jsonString(dir), 1)
|
|
if _, err := ParseManifest([]byte(raw)); err == nil || !strings.Contains(err.Error(), "templates directory") {
|
|
t.Errorf("the templates directory %q was not refused: %v", dir, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestATemplatesDirectoryNamedFilesIsTheDefault(t *testing.T) {
|
|
raw := strings.Replace(templatedTwin, `"version":"1",`, `"version":"1","templates":"files",`, 1)
|
|
if got, want := composed(t, held(t, raw, builtFiles(templateTexts(t)))),
|
|
composed(t, held(t, inlineTwin, builtFiles(nil))); got != want {
|
|
t.Fatalf("naming files/ as the templates directory composes differently:\n%s\n%s", got, want)
|
|
}
|
|
}
|