package builder import ( "context" "os" "path/filepath" "strings" "testing" "time" "github.com/novox/mesh-controller/internal/catalogue" ) // A repository becoming artifacts the mesh can pin. // // git and docker are injected rather than run, because what is under test is the ORDER and the // refusals — that nothing is published until everything is built, that a build reads only its own // tree, that two builds of one commit produce one digest. Running docker here would test docker. type recorded struct { ran []string // dirs is the directory each entry in ran was run from, same index — so a test can ask not // only what ran but where. dirs []string images map[string]string archives map[string]string failPush bool // contents is what a clone of this repository lands, so the fake clone can restore the tree // Build deliberately removes first. contents map[string]string // secondary is what a clone of a repository OTHER than the one under test lands, keyed by // that repository's URL — an artifact's own build context, cloned apart from the module. secondary map[string]map[string]string // stamped is the modification time the clone gives every file. Set differently between two // builds of one commit, because otherwise both land in the same second and a packer that // carried timestamps would still produce one digest — which is a test that passes for a // reason that has nothing to do with what it claims. stamped time.Time // tree is what git names as the tree of the module's directory; empty answers as for a commit. tree string } func (r *recorded) run(_ context.Context, dir, name string, args ...string) (string, error) { line := name + " " + strings.Join(args, " ") r.ran = append(r.ran, line) r.dirs = append(r.dirs, dir) // A clone may carry `-c` configuration in front of the verb — the credential store — so the // verb is found rather than assumed first. isClone := false for _, a := range args { if a == "clone" { isClone = true break } } switch { case name == "git" && isClone: repository := args[len(args)-2] tree := args[len(args)-1] if err := os.MkdirAll(tree, 0o755); err != nil { return "", err } lands := r.contents if by, is := r.secondary[repository]; is { lands = by } for path, body := range lands { full := filepath.Join(tree, path) if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil { return "", err } if err := os.WriteFile(full, []byte(body), 0o644); err != nil { return "", err } if !r.stamped.IsZero() { if err := os.Chtimes(full, r.stamped, r.stamped); err != nil { return "", err } } } return "", nil case name == "git" && len(args) > 1 && args[0] == "rev-parse" && strings.HasPrefix(args[1], "HEAD:") && r.tree != "": return r.tree + "\n", nil case name == "git" && len(args) > 0 && args[0] == "rev-parse": return "c0ffeec0ffeec0ffeec0ffeec0ffeec0ffeec0ff\n", nil case name == "git" && len(args) > 1 && args[0] == "rev-list" && args[1] == "--count": return "42\n", nil } return "", nil } func (r *recorded) PublishImage(_ context.Context, localTag, repository string) (string, error) { if r.failPush { return "", os.ErrPermission } if r.images == nil { r.images = map[string]string{} } r.images[repository] = localTag return "registry.invalid/" + repository + "@sha256:" + strings.Repeat("a", 64), nil } func (r *recorded) PublishArchive(_ context.Context, repository string, body []byte, digest string) (string, error) { if r.failPush { return "", os.ErrPermission } if r.archives == nil { r.archives = map[string]string{} } r.archives[repository] = digest _ = body return "https://store.invalid/" + repository, nil } // aRepository is a workspace whose clone lands a manifest and some files. func aRepository(t *testing.T, manifest string, files map[string]string) (*recorded, string) { t.Helper() contents := map[string]string{ManifestName: manifest} for name, body := range files { contents[name] = body } return &recorded{contents: contents}, t.TempDir() } const withBoth = `{"module":"meshboard","version":"1", "build":{"artifacts":[ {"name":"server","kind":"image","from":"Dockerfile"}, {"name":"look","kind":"archive","from":"files"}]}, "resources":[ {"id":"svc","type":"container","name":"meshboard","artifact":"server"}, {"id":"theme","type":"archive","path":"/opt/meshboard","artifact":"look"}]}` func TestAnUpstreamImageWithNoTagIsRefused(t *testing.T) { // What gets mirrored would be whatever `latest` means today, and a module pinned to that is // not pinned. _, err := catalogue.ParseManifest([]byte(`{"module":"a","version":"1","build":{"artifacts":[ {"name":"x","kind":"upstream","from":"postgres"}]}}`)) if err == nil { t.Fatal("an untagged upstream reference was accepted") } if !strings.Contains(err.Error(), "no tag or digest") { t.Fatalf("unhelpful refusal: %v", err) } } func TestAnUpstreamReferenceIsNotAPathInTheRepository(t *testing.T) { // The rule that a build reads only its own repository must not refuse every reference with a // registry host in it. if _, err := catalogue.ParseManifest([]byte(`{"module":"a","version":"1","build":{"artifacts":[ {"name":"x","kind":"upstream","from":"registry.example/library/postgres:17"}]}}`)); err != nil { t.Fatalf("a perfectly ordinary upstream reference was refused: %v", err) } } // **A module is a repository and a path within it** (novox/hq ADR 0069). The catalogue holds its // modules one to a directory and the system this replaces has always built one that way, so a // builder that could only read a repository's root could build none of what exists. // A build reads only its own tree. A path climbing out of the clone would otherwise let a build // read — and an archive artifact publish — whatever the build machine happens to hold, which is // the one thing a machine that builds other people's repositories must not do. // **Packaging and source are allowed to live apart** — a module that ships only the recipe for // source that lives in a second repository (the reference route-proxy, packaged in the catalogue // but built from mesh-controller's own repository) names where that source actually is, rather // than vendoring a second copy the two could drift from. // The forge credential is offered through git's own credential store — a file, never argv — and // git decides when it applies. What is checked: the clone names the store, the secret never // appears in a command line, and the file holds exactly the URL at 0600. // storeNamedIn is the credential store a git command line offers. func storeNamedIn(t *testing.T, line string) string { t.Helper() _, after, ok := strings.Cut(line, "credential.helper=store --file=") if !ok { t.Fatalf("the clone does not name the credential store: %s", line) } return strings.Fields(after)[0] } // Without a credential, a clone is exactly the invocation it always was, and no credential file // appears — the builder a mesh of public repositories runs is unchanged. // An artifact's own context is cloned with the same offer: a private module whose context is a // second private repository on the same forge builds, and the secret still never reaches argv.