Check an image's Dockerfile place instead of refusing the image kind (ADR 0487 clause 13)
mesh/check failed: unit: i3/cmd/i3-tools.TestTheMainFileAndEveryModulesContributionLoadTogether: manifest_test.go:247: expected the launcher, clipboar…
mesh/delivery-group group feat/r4d rejected: a member's own check failed
mesh/merge-gate waits for its group's composed check
mesh/repo-check head 707bfba4ea36 merged into trunk 4463991e0609 beside mesh-catalog#434@68183ee4 — fail: its merge-check.sh failed: --- FAIL: TestTheBusTh…
mesh/delivery superseded: a newer head of the same pull request

This commit is contained in:
2026-10-22 11:45:06 +02:00
parent 499ff5c218
commit 707bfba4ea
2 changed files with 92 additions and 69 deletions
+92
View File
@@ -0,0 +1,92 @@
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/<module> (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)
}
}
}
@@ -1,69 +0,0 @@
package ownmanifest
import (
"os"
"path/filepath"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
)
// builderThatStillBuildsAnImage is the one builder allowed an image built from a Dockerfile until R4g removes it
// (novox/hq ADR 0480 clause 11, ADR 0453 §10, ADR 0452 §1).
const builderThatStillBuildsAnImage = "build-agent"
// imageArtifactModules lists the modules among manifests (module name to its manifest's text) that declare an
// artifact of kind image, apart from the one builder allowed it.
func imageArtifactModules(t *testing.T, manifests map[string][]byte) []string {
t.Helper()
var out []string
for module, raw := range manifests {
if module == builderThatStillBuildsAnImage {
continue
}
m, err := catalogue.ParseManifest(raw)
if err != nil {
t.Fatalf("%s does not parse: %v", module, err)
}
if m.Build == nil {
continue
}
for _, a := range m.Build.Artifacts {
if a.Kind == "image" {
out = append(out, module+" ("+a.Name+")")
}
}
}
return out
}
// Every module of this repository but build-agent runs an upstream image with its program mounted in, so none
// declares an image built from a Dockerfile (novox/hq ADR 0480 clause 11). The catalogue's own modules are held to
// the same by a test in mesh-catalog.
func TestNoModuleManifestButBuildAgentsDeclaresAnImage(t *testing.T) {
paths, err := filepath.Glob(filepath.Join("..", "..", "modules", "*", "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 := imageArtifactModules(t, manifests); len(got) != 0 {
t.Errorf("these modules declare an artifact of kind image, which only %s may until R4g (ADR 0480 clause 11, "+
"ADR 0452 §1): %v. Ship the program as a Go bundle or an archive on an upstream image",
builderThatStillBuildsAnImage, got)
}
}
// The test fails on a manifest with an image artifact, so it holds something.
func TestAnImageArtifactIsFoundInAnyModuleButBuildAgents(t *testing.T) {
image := []byte(`{"module":"x","version":"1","build":{"artifacts":[{"name":"server","kind":"image","from":"Dockerfile"}]}}`)
if got := imageArtifactModules(t, map[string][]byte{"x": image, builderThatStillBuildsAnImage: image}); len(got) != 1 || got[0] != "x (server)" {
t.Fatalf("found %v, want only x (server)", got)
}
}