diff --git a/cmd/mesh-controller/merge_gate.go b/cmd/mesh-controller/merge_gate.go index fb781bda..a1c473b8 100644 --- a/cmd/mesh-controller/merge_gate.go +++ b/cmd/mesh-controller/merge_gate.go @@ -742,6 +742,21 @@ func shelfWithChange(base map[string]catalogue.Manifest, sources map[string]inve "of one module name (issue 239)", orRoot(dir), m.Module, s.Repository)) return nil } + // A module lives whole in one repository (novox/hq ADR 0420 §1, issue 1019): no artifact builds from another. + if m.Build != nil { + own := link.SourceMoved{Owner: ownerOf(repository), Repo: repoOf(repository)} + other := false + for _, a := range m.Build.Artifacts { + if a.Context != nil && a.Context.Repository != "" && !sameRepository(a.Context.Repository, own) { + failures = append(failures, fmt.Sprintf("%s builds artifact %s from %s: a module lives whole in "+ + "one repository (novox/hq ADR 0420 §1)", m.Module, a.Name, a.Context.Repository)) + other = true + } + } + if other { + return nil + } + } resolved, err := standInBuild(written, texts) if err == nil { // As registration reads a built manifest: strictly, again (takeIn). diff --git a/cmd/mesh-controller/merge_gate_test.go b/cmd/mesh-controller/merge_gate_test.go index 9852431d..38c3ae17 100644 --- a/cmd/mesh-controller/merge_gate_test.go +++ b/cmd/mesh-controller/merge_gate_test.go @@ -559,3 +559,32 @@ func TestADeliveryGroupMovingAModuleMachinesRunPassesTheGate(t *testing.T) { t.Errorf("the removal of a module machines run passed alone:\n%s", v.Report()) } } + +// **A module lives whole in one repository** (novox/hq ADR 0420 §1, issue 1019): an artifact whose build +// context names another repository is refused by the gate, and one naming its own passes it. +func TestTheGateRefusesAnArtifactContextNamingAnotherRepository(t *testing.T) { + withContext := func(repo string) string { + return `{"module":"proxy","version":"1","build":{"artifacts":[{"name":"server","kind":"image","from":"Dockerfile",` + + `"context":{"seat":"git","repository":"` + repo + `","ref":"main"}}]}}` + } + judge := func(repo string) []string { + t.Helper() + tree := aTree(t, map[string]string{"proxy": withContext(repo)}) + _, failures, err := shelfWithChange(map[string]catalogue.Manifest{}, map[string]inventory.Source{}, + snapshot.Facts{}, "novox/mesh-controller", tree, map[string]string{}, nil) + if err != nil { + t.Fatal(err) + } + return failures + } + const want = "proxy builds artifact server from novox/mesh-catalog: a module lives whole in one repository (novox/hq ADR 0420 §1)" + failures := judge("novox/mesh-catalog") + if len(failures) != 1 || !strings.Contains(failures[0], want) { + t.Errorf("a context naming another repository: %v, want it to say %q", failures, want) + } + for _, f := range judge("novox/mesh-controller") { + if strings.Contains(f, "ADR 0420 §1") { + t.Errorf("a context naming the module's own repository was refused: %s", f) + } + } +}