package ownmanifest import ( "encoding/json" "os" "path/filepath" "strings" "testing" ) // imagesOutOfPlace lists the image artifacts among manifests (module name to its manifest's text) whose Dockerfile // is not a file inside that module's own directory, root/ (novox/hq ADR 0487 clause 13, which replaced the // refusal of the image kind of ADR 0480 clause 11). The image builder builds a module from its directory alone. // It reads the manifest as plain JSON, since the catalogue parser refuses some of these itself. It mirrors mesh-catalog's checks/imageplace: an empty from, an absolute one, one leaving the directory with .., // or a name that is no file there, is refused. func imagesOutOfPlace(t *testing.T, root string, manifests map[string][]byte) []string { t.Helper() var out []string for module, raw := range manifests { var m struct { Build struct { Artifacts []struct{ Name, Kind, From string } `json:"artifacts"` } `json:"build"` } if err := json.Unmarshal(raw, &m); err != nil { t.Fatalf("%s does not parse: %v", module, err) } for _, a := range m.Build.Artifacts { if a.Kind != "image" { continue } clean := filepath.Clean(a.From) bad := a.From == "" || filepath.IsAbs(clean) || clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) if !bad { info, err := os.Stat(filepath.Join(root, module, clean)) bad = err != nil || info.IsDir() } if bad { out = append(out, module+" ("+a.Name+" from "+a.From+")") } } } return out } // Every image a module of this repository declares is built from a Dockerfile in the module's own directory // (novox/hq ADR 0487 clause 13). The catalogue's own modules are held to the same by checks/imageplace in mesh-catalog. func TestEveryImageArtifactBuildsFromADockerfileInItsOwnModuleDirectory(t *testing.T) { root := filepath.Join("..", "..", "modules") paths, err := filepath.Glob(filepath.Join(root, "*", "module.json")) if err != nil || len(paths) == 0 { t.Fatalf("no module manifests found in this repository's modules: %v", err) } manifests := map[string][]byte{} for _, p := range paths { raw, err := os.ReadFile(p) if err != nil { t.Fatal(err) } manifests[filepath.Base(filepath.Dir(p))] = raw } if got := imagesOutOfPlace(t, root, manifests); len(got) != 0 { t.Errorf("these image artifacts do not build from a Dockerfile in their own module's directory "+ "(ADR 0487 clause 13): %v", got) } } // The check fails on each bad case and passes on a good one, so it holds something. func TestAnImageWhoseDockerfileIsOutOfPlaceIsFoundAndOneInPlaceIsNot(t *testing.T) { root := t.TempDir() if err := os.MkdirAll(filepath.Join(root, "x", "sub"), 0o755); err != nil { t.Fatal(err) } for _, f := range []string{"x/Dockerfile", "x/sub/Dockerfile", "outside"} { if err := os.WriteFile(filepath.Join(root, f), nil, 0o644); err != nil { t.Fatal(err) } } manifest := func(from string) map[string][]byte { return map[string][]byte{"x": []byte(`{"module":"x","version":"1","build":{"artifacts":[{"name":"server","kind":"image","from":"` + from + `"}]}}`)} } for _, from := range []string{"Dockerfile", "sub/Dockerfile", "./Dockerfile"} { if got := imagesOutOfPlace(t, root, manifest(from)); len(got) != 0 { t.Errorf("from %q: found %v, want none", from, got) } } for _, from := range []string{"", "/etc/Dockerfile", "../outside", "..", "sub/../../outside", "Missing", "sub"} { if got := imagesOutOfPlace(t, root, manifest(from)); len(got) != 1 { t.Errorf("from %q: found %v, want it refused", from, got) } } }