builder: a clone may offer the forge's credential, through git's own store

A private repository could not be built: the builder clones anonymously,
and had no way to say who it is. It already holds exactly one credential
to exactly the right place — the package-registry binding and its sealed
secret, one gitea user whose password answers npm and git alike — so a
clone now offers that, and nothing new is minted or carried.

Offered, never pushed: the credential is written as a git
credential-store file (0600, in the workspace, never argv) and named
with -c credential.helper, so git itself decides when it applies — only
on an authentication challenge, and only for the URL it was written
for, scheme, host and port included. A public repository clones exactly
as before; a repository on any other host is never shown it. The same
store rides along on an artifact's own context clone, so a private
module with a private context builds too.
This commit is contained in:
2026-09-25 21:47:32 +02:00
parent 7ffe6ce21b
commit 6ac9013d6e
6 changed files with 221 additions and 28 deletions
+49
View File
@@ -24,6 +24,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url"
"os" "os"
"os/signal" "os/signal"
"strings" "strings"
@@ -202,6 +203,7 @@ func answer(ctx context.Context, channel *amqp.Channel, publisher builder.Publis
// not after a clone that then fails at npm ci. // not after a clone that then fails at npm ci.
built, err = builder.Build(ctx, builder.Command, publisher, built, err = builder.Build(ctx, builder.Command, publisher,
request.Repository, request.Path, request.Ref, workspace, request.Held, npmrc, request.Repository, request.Path, request.Ref, workspace, request.Held, npmrc,
forgeFrom(),
func(step, message string) { func(step, message string) {
fmt.Fprintf(os.Stderr, " [%s] %s\n", step, message) fmt.Fprintf(os.Stderr, " [%s] %s\n", step, message)
}) })
@@ -367,6 +369,53 @@ func packagesFrom() (builder.Npmrc, error) {
return builder.Npmrc{Scope: scope, Registry: registry, Token: secret}, nil return builder.Npmrc{Scope: scope, Registry: registry, Token: secret}, nil
} }
// forgeFrom is the git credential this builder may offer a clone, composed from the same binding
// and sealed secret its package-registry half already reads: the forge that answers npm is the
// forge that hosts the repositories, and its provisioner applies one password to one user for
// both. Anything missing means no credential, and every clone stays anonymous — which is all a
// mesh of public repositories ever needs.
//
// The URL names the binding's own address — the machine the mesh says the forge is on — so a
// private repository is registered and built by that address, and a clone of anything else is
// never shown this credential (git's credential store matches the whole origin).
func forgeFrom() builder.GitCredential {
path := strings.TrimSpace(os.Getenv("MESH_PACKAGE_BINDING"))
if path == "" {
return builder.GitCredential{}
}
raw, err := os.ReadFile(path)
if err != nil {
return builder.GitCredential{}
}
var told struct {
At string `json:"at"`
As string `json:"as"`
Serves map[string]any `json:"serves"`
}
if err := json.Unmarshal(raw, &told); err != nil || told.At == "" || told.As == "" {
return builder.GitCredential{}
}
secret := strings.TrimSpace(os.Getenv("MESH_NPM_TOKEN"))
if file := strings.TrimSpace(os.Getenv("MESH_NPM_TOKEN_FILE")); file != "" {
if raw, err := os.ReadFile(file); err == nil {
secret = strings.TrimSpace(string(raw))
}
}
if secret == "" {
return builder.GitCredential{}
}
scheme := "https"
if s, ok := told.Serves["scheme"]; ok {
scheme = fmt.Sprintf("%v", s)
}
host := told.At
if port, ok := told.Serves["port"]; ok {
host = fmt.Sprintf("%s:%v", told.At, port)
}
made := url.URL{Scheme: scheme, User: url.UserPassword(told.As, secret), Host: host}
return builder.GitCredential{URL: made.String()}
}
func short(commit string) string { func short(commit string) string {
if len(commit) > 8 { if len(commit) > 8 {
return commit[:8] return commit[:8]
+1
View File
@@ -89,6 +89,7 @@ func buildOnce(ctx context.Context, args []string) error {
return err return err
} }
built, buildErr := builder.Build(ctx, builder.Command, publisher, repository, *path, *ref, where, bases, npmrc, built, buildErr := builder.Build(ctx, builder.Command, publisher, repository, *path, *ref, where, bases, npmrc,
forgeFrom(),
func(step, message string) { fmt.Fprintf(os.Stderr, " [%s] %s\n", step, message) }) func(step, message string) { fmt.Fprintf(os.Stderr, " [%s] %s\n", step, message) })
if buildErr != nil { if buildErr != nil {
return buildErr return buildErr
+45 -7
View File
@@ -63,6 +63,19 @@ type Result struct {
Built []catalogue.Built Built []catalogue.Built
} }
// GitCredential is the forge credential a clone may present when the server asks for one.
//
// **Offered, never pushed.** It is written as a git credential-store file and named to git with
// `-c credential.helper=store`, so git itself decides when it applies: only on an authentication
// challenge, and only for the URL it was written for — scheme, host and port included. A public
// repository clones exactly as before, and a repository on any other host is never shown it.
type GitCredential struct {
// URL is the credential-store line — scheme://user:password@host[:port] — naming the one
// server this credential belongs to. Empty means the builder holds none and every clone is
// anonymous, as it always was.
URL string
}
// Build clones a repository at a ref, reads its manifest, produces what it declares, publishes // Build clones a repository at a ref, reads its manifest, produces what it declares, publishes
// each, and returns the manifest the mesh should hold. // each, and returns the manifest the mesh should hold.
// //
@@ -70,7 +83,8 @@ type Result struct {
// archive failed would otherwise leave half of itself in the store under a digest the mesh never // archive failed would otherwise leave half of itself in the store under a digest the mesh never
// records — reachable, unreferenced, and indistinguishable from something in use. // records — reachable, unreferenced, and indistinguishable from something in use.
func Build(ctx context.Context, run Runner, publish Publisher, func Build(ctx context.Context, run Runner, publish Publisher,
repository, path, ref, workspace string, held map[string]string, npmrc Npmrc, log Log) (Result, error) { repository, path, ref, workspace string, held map[string]string, npmrc Npmrc,
forge GitCredential, log Log) (Result, error) {
say := logging(log) say := logging(log)
say("clone", "%s%s at %s", repository, describePath(path), refOrHead(ref)) say("clone", "%s%s at %s", repository, describePath(path), refOrHead(ref))
@@ -80,6 +94,15 @@ func Build(ctx context.Context, run Runner, publish Publisher,
if err := os.MkdirAll(workspace, 0o755); err != nil { if err := os.MkdirAll(workspace, 0o755); err != nil {
return Result{}, err return Result{}, err
} }
// The credential is a file git reads, never an argument: a URL carrying a password in argv
// would be readable by anything that can list processes for as long as a clone runs.
credentials := ""
if forge.URL != "" {
credentials = filepath.Join(workspace, "git-credentials")
if err := os.WriteFile(credentials, []byte(forge.URL+"\n"), 0o600); err != nil {
return Result{}, err
}
}
tree := filepath.Join(workspace, "source") tree := filepath.Join(workspace, "source")
if err := os.RemoveAll(tree); err != nil { if err := os.RemoveAll(tree); err != nil {
return Result{}, err return Result{}, err
@@ -87,7 +110,7 @@ func Build(ctx context.Context, run Runner, publish Publisher,
// A fresh clone every time rather than a fetch into a tree that is already there. A build // A fresh clone every time rather than a fetch into a tree that is already there. A build
// that reuses a working tree can succeed because of something a previous build left behind, // that reuses a working tree can succeed because of something a previous build left behind,
// and that is a build nobody can reproduce. // and that is a build nobody can reproduce.
if _, err := run(ctx, workspace, "git", "clone", "--quiet", repository, tree); err != nil { if _, err := run(ctx, workspace, "git", cloneWith(credentials, "clone", "--quiet", repository, tree)...); err != nil {
say("clone", "FAILED: %v", err) say("clone", "FAILED: %v", err)
return Result{}, fmt.Errorf("cannot clone %s: %w", repository, err) return Result{}, fmt.Errorf("cannot clone %s: %w", repository, err)
} }
@@ -177,7 +200,7 @@ func Build(ctx context.Context, run Runner, publish Publisher,
sort.Slice(artifacts, func(i, j int) bool { return artifacts[i].Name < artifacts[j].Name }) sort.Slice(artifacts, func(i, j int) bool { return artifacts[i].Name < artifacts[j].Name })
for _, a := range artifacts { for _, a := range artifacts {
say("artifact", "%s (%s%s) — starting", a.Name, a.Kind, langSuffix(a)) say("artifact", "%s (%s%s) — starting", a.Name, a.Kind, langSuffix(a))
made, err := one(ctx, run, publish, manifest.Module, within, workspace, commit, a, args, held, npmrcPath, say) made, err := one(ctx, run, publish, manifest.Module, within, workspace, commit, credentials, a, args, held, npmrcPath, say)
if err != nil { if err != nil {
say("artifact", "%s FAILED: %v", a.Name, err) say("artifact", "%s FAILED: %v", a.Name, err)
return Result{}, err return Result{}, err
@@ -213,14 +236,14 @@ func logging(log Log) func(step, format string, args ...any) {
// contextFrom clones an image artifact's own build context, when it names one apart from this // contextFrom clones an image artifact's own build context, when it names one apart from this
// module's own repository — a fresh tree, the same way the module's own is, keyed by artifact // module's own repository — a fresh tree, the same way the module's own is, keyed by artifact
// name so two artifacts of one module naming different contexts do not collide. // name so two artifacts of one module naming different contexts do not collide.
func contextFrom(ctx context.Context, run Runner, workspace, artifact string, func contextFrom(ctx context.Context, run Runner, workspace, artifact, credentials string,
from catalogue.ArtifactContext, say func(step, format string, args ...any)) (string, error) { from catalogue.ArtifactContext, say func(step, format string, args ...any)) (string, error) {
say("context", "cloning %s at %s for %s", from.Repository, refOrHead(from.Ref), artifact) say("context", "cloning %s at %s for %s", from.Repository, refOrHead(from.Ref), artifact)
dir := filepath.Join(workspace, "context-"+artifact) dir := filepath.Join(workspace, "context-"+artifact)
if err := os.RemoveAll(dir); err != nil { if err := os.RemoveAll(dir); err != nil {
return "", err return "", err
} }
if _, err := run(ctx, workspace, "git", "clone", "--quiet", from.Repository, dir); err != nil { if _, err := run(ctx, workspace, "git", cloneWith(credentials, "clone", "--quiet", from.Repository, dir)...); err != nil {
return "", fmt.Errorf("cannot clone %s: %w", from.Repository, err) return "", fmt.Errorf("cannot clone %s: %w", from.Repository, err)
} }
if from.Ref != "" { if from.Ref != "" {
@@ -232,6 +255,21 @@ func contextFrom(ctx context.Context, run Runner, workspace, artifact string,
return dir, nil return dir, nil
} }
// cloneWith is a git invocation that may offer a stored credential.
//
// The first `-c credential.helper=` clears every helper the environment might carry, so exactly
// one place answers an authentication challenge: the file the builder wrote. Without a file, the
// invocation is exactly what it always was.
func cloneWith(credentials string, rest ...string) []string {
if credentials == "" {
return rest
}
return append([]string{
"-c", "credential.helper=",
"-c", "credential.helper=store --file=" + credentials,
}, rest...)
}
func describePath(path string) string { func describePath(path string) string {
if path == "" { if path == "" {
return "" return ""
@@ -355,7 +393,7 @@ func wantsPackages(manifest catalogue.Manifest, within string) bool {
} }
func one(ctx context.Context, run Runner, publish Publisher, func one(ctx context.Context, run Runner, publish Publisher,
module, tree, workspace, commit string, a catalogue.Artifact, args []string, module, tree, workspace, commit, credentials string, a catalogue.Artifact, args []string,
held map[string]string, npmrc string, say func(step, format string, args ...any)) (catalogue.Built, error) { held map[string]string, npmrc string, say func(step, format string, args ...any)) (catalogue.Built, error) {
switch a.Kind { switch a.Kind {
@@ -433,7 +471,7 @@ func one(ctx context.Context, run Runner, publish Publisher,
recipePath := a.From recipePath := a.From
buildDir := tree buildDir := tree
if a.Context != nil { if a.Context != nil {
cloned, err := contextFrom(ctx, run, workspace, a.Name, *a.Context, say) cloned, err := contextFrom(ctx, run, workspace, a.Name, credentials, *a.Context, say)
if err != nil { if err != nil {
return catalogue.Built{}, fmt.Errorf("%s: %s's context: %w", module, a.Name, err) return catalogue.Built{}, fmt.Errorf("%s: %s's context: %w", module, a.Name, err)
} }
+117 -12
View File
@@ -42,8 +42,17 @@ func (r *recorded) run(_ context.Context, dir, name string, args ...string) (str
line := name + " " + strings.Join(args, " ") line := name + " " + strings.Join(args, " ")
r.ran = append(r.ran, line) r.ran = append(r.ran, line)
r.dirs = append(r.dirs, dir) 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 { switch {
case name == "git" && len(args) > 0 && args[0] == "clone": case name == "git" && isClone:
repository := args[len(args)-2] repository := args[len(args)-2]
tree := args[len(args)-1] tree := args[len(args)-1]
if err := os.MkdirAll(tree, 0o755); err != nil { if err := os.MkdirAll(tree, 0o755); err != nil {
@@ -119,7 +128,7 @@ func TestABuildProducesAManifestThePinsAreIn(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{ r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark", "Dockerfile": "FROM scratch", "files/theme.conf": "dark",
}) })
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, nil) got, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -145,7 +154,7 @@ func TestTwoBuildsOfOneCommitProduceOneDigest(t *testing.T) {
}) })
// A year apart, so a packer carrying timestamps cannot accidentally agree. // A year apart, so a packer carrying timestamps cannot accidentally agree.
r.stamped = time.Date(2020+i, time.March, 3, 4, 5, 6, 0, time.UTC) r.stamped = time.Date(2020+i, time.March, 3, 4, 5, 6, 0, time.UTC)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, nil) got, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -165,7 +174,7 @@ func TestNothingIsPublishedUntilEverythingIsBuilt(t *testing.T) {
// unreferenced, and indistinguishable from something in use. // unreferenced, and indistinguishable from something in use.
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch"}) r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch"})
// `files` is missing, so packing the archive fails — after the image would have been pushed. // `files` is missing, so packing the archive fails — after the image would have been pushed.
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, nil) _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil { if err == nil {
t.Fatal("a build with a missing input succeeded") t.Fatal("a build with a missing input succeeded")
} }
@@ -177,7 +186,7 @@ func TestNothingIsPublishedUntilEverythingIsBuilt(t *testing.T) {
func TestARepositoryWithNoManifestSaysSo(t *testing.T) { func TestARepositoryWithNoManifestSaysSo(t *testing.T) {
workspace := t.TempDir() workspace := t.TempDir()
r := &recorded{contents: map[string]string{"README.md": "nothing to see"}} r := &recorded{contents: map[string]string{"README.md": "nothing to see"}}
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, nil) _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil { if err == nil {
t.Fatal("a repository with nothing saying what it is was built") t.Fatal("a repository with nothing saying what it is was built")
} }
@@ -190,7 +199,7 @@ func TestAModuleThatBuildsNothingStillProducesAManifest(t *testing.T) {
// Most of what a person installs is configuration. // Most of what a person installs is configuration.
r, workspace := aRepository(t, `{"module":"shell","version":"1","resources":[ r, workspace := aRepository(t, `{"module":"shell","version":"1","resources":[
{"id":"rc","type":"file","path":"/etc/zsh/zshrc","content":"setopt"}]}`, nil) {"id":"rc","type":"file","path":"/etc/zsh/zshrc","content":"setopt"}]}`, nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/shell.git", "", "", workspace, nil, Npmrc{}, nil) got, err := Build(context.Background(), r.run, r, "https://forge.invalid/shell.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -220,7 +229,7 @@ func TestTheTreeIsFreshEveryTime(t *testing.T) {
if err := os.WriteFile(leftover, []byte("stale"), 0o644); err != nil { if err := os.WriteFile(leftover, []byte("stale"), 0o644); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, nil); err != nil { if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if _, err := os.Stat(leftover); err == nil { if _, err := os.Stat(leftover); err == nil {
@@ -233,7 +242,7 @@ func TestABuildThatCannotPushFails(t *testing.T) {
"Dockerfile": "FROM scratch", "files/a": "b", "Dockerfile": "FROM scratch", "files/a": "b",
}) })
r.failPush = true r.failPush = true
if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, nil); err == nil { if _, err := Build(context.Background(), r.run, r, "https://forge.invalid/x.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err == nil {
t.Fatal("a build that could publish nothing reported success") t.Fatal("a build that could publish nothing reported success")
} }
} }
@@ -247,7 +256,7 @@ func TestAnUpstreamImageIsMirroredRatherThanBuilt(t *testing.T) {
"resources":[{"id":"db","type":"container","name":"mesh-postgres","artifact":"store"}]}` "resources":[{"id":"db","type":"container","name":"mesh-postgres","artifact":"store"}]}`
r, workspace := aRepository(t, mirrors, nil) r, workspace := aRepository(t, mirrors, nil)
got, err := Build(context.Background(), r.run, r, "https://forge.invalid/postgres.git", "", "", workspace, nil, Npmrc{}, nil) got, err := Build(context.Background(), r.run, r, "https://forge.invalid/postgres.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -313,7 +322,7 @@ func TestAModuleIsBuiltFromItsPathWithinTheRepository(t *testing.T) {
"modules/other/" + ManifestName: `{"module":"other","version":"1"}`, "modules/other/" + ManifestName: `{"module":"other","version":"1"}`,
}} }}
got, err := Build(context.Background(), r.run, r, got, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "modules/shell", "", t.TempDir(), nil, Npmrc{}, nil) "https://forge.invalid/catalogue.git", "modules/shell", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -332,7 +341,7 @@ func TestAPathThatLeavesTheRepositoryIsRefused(t *testing.T) {
for _, escaping := range []string{"../../etc", "/etc"} { for _, escaping := range []string{"../../etc", "/etc"} {
r := &recorded{contents: map[string]string{ManifestName: withBoth}} r := &recorded{contents: map[string]string{ManifestName: withBoth}}
_, err := Build(context.Background(), r.run, r, _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/x.git", escaping, "", t.TempDir(), nil, Npmrc{}, nil) "https://forge.invalid/x.git", escaping, "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err == nil { if err == nil {
t.Fatalf("%q was accepted as a module's path", escaping) t.Fatalf("%q was accepted as a module's path", escaping)
} }
@@ -369,7 +378,7 @@ func TestAnArtifactWithItsOwnContextIsBuiltFromThere(t *testing.T) {
}, },
} }
_, err := Build(context.Background(), r.run, r, _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "", "", t.TempDir(), nil, Npmrc{}, nil) "https://forge.invalid/catalogue.git", "", "", t.TempDir(), nil, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -411,3 +420,99 @@ func TestAnArtifactWithItsOwnContextIsBuiltFromThere(t *testing.T) {
t.Errorf("the build was not given a context: %s", build) t.Errorf("the build was not given a context: %s", build)
} }
} }
// 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.
func TestABuildOffersTheForgesCredentialThroughGitsOwnStore(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "",
workspace, nil, Npmrc{},
GitCredential{URL: "http://mesh_novox_builder:sw0rdfi5h@forge.invalid:20000"}, nil)
if err != nil {
t.Fatal(err)
}
stored := filepath.Join(workspace, "git-credentials")
clone := r.ran[0]
if !strings.Contains(clone, "credential.helper=store --file="+stored) {
t.Fatalf("the clone does not name the credential store: %s", clone)
}
for _, line := range r.ran {
if strings.Contains(line, "sw0rdfi5h") {
t.Fatalf("the secret is in a command line, readable by anything that can list processes: %s", line)
}
}
raw, err := os.ReadFile(stored)
if err != nil {
t.Fatal(err)
}
if strings.TrimSpace(string(raw)) != "http://mesh_novox_builder:sw0rdfi5h@forge.invalid:20000" {
t.Fatalf("the store does not hold the credential as given: %q", raw)
}
info, err := os.Stat(stored)
if err != nil {
t.Fatal(err)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("the credential file is readable beyond its owner: %v", info.Mode())
}
}
// 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.
func TestABuildWithNoCredentialClonesExactlyAsBefore(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{
"Dockerfile": "FROM scratch", "files/theme.conf": "dark",
})
_, err := Build(context.Background(), r.run, r, "https://forge.invalid/meshboard.git", "", "",
workspace, nil, Npmrc{}, GitCredential{}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(r.ran[0], "git clone --quiet ") {
t.Fatalf("a credential-less clone grew flags: %s", r.ran[0])
}
if _, err := os.Stat(filepath.Join(workspace, "git-credentials")); !os.IsNotExist(err) {
t.Fatal("a credential file was written with no credential to put in it")
}
}
// 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.
func TestAContextCloneCarriesTheSameCredentialStore(t *testing.T) {
const withContext = `{"module":"route-proxy","version":"1",
"build":{"artifacts":[
{"name":"server","kind":"image","from":"Dockerfile",
"context":{"repository":"https://forge.invalid/source.git","ref":"main"}}]}}`
r := &recorded{
contents: map[string]string{
ManifestName: withContext,
"Dockerfile": "FROM scratch\nCOPY go.mod ./\n",
},
secondary: map[string]map[string]string{
"https://forge.invalid/source.git": {"go.mod": "module route-proxy\n"},
},
}
workspace := t.TempDir()
_, err := Build(context.Background(), r.run, r,
"https://forge.invalid/catalogue.git", "", "", workspace, nil, Npmrc{},
GitCredential{URL: "https://builder:s3cret@forge.invalid"}, nil)
if err != nil {
t.Fatal(err)
}
stored := filepath.Join(workspace, "git-credentials")
var contextClone string
for _, line := range r.ran {
if strings.Contains(line, "clone") && strings.Contains(line, "source.git") {
contextClone = line
}
}
if contextClone == "" {
t.Fatalf("the context was never cloned: %v", r.ran)
}
if !strings.Contains(contextClone, "credential.helper=store --file="+stored) {
t.Fatalf("the context clone does not name the credential store: %s", contextClone)
}
}
+4 -4
View File
@@ -54,7 +54,7 @@ func TestABundleIsCompiledAndPackedWithNoDockerfile(t *testing.T) {
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)} held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
got, err := Build(context.Background(), compiling{r}.run, r, got, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, nil) "https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatalf("a module with a language and no Dockerfile did not build: %v", err) t.Fatalf("a module with a language and no Dockerfile did not build: %v", err)
} }
@@ -91,7 +91,7 @@ func TestABundleWhoseToolchainIsNotHeldIsRefusedFirst(t *testing.T) {
r, workspace := aRepository(t, aBundle, map[string]string{"index.ts": "console.log(1)"}) r, workspace := aRepository(t, aBundle, map[string]string{"index.ts": "console.log(1)"})
_, err := Build(context.Background(), compiling{r}.run, r, _, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, nil, Npmrc{}, nil) "https://forge.invalid/greeter.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil { if err == nil {
t.Fatal("a bundle was built with no toolchain to compile it in") t.Fatal("a bundle was built with no toolchain to compile it in")
} }
@@ -112,7 +112,7 @@ func TestABundleInAnUnknownLanguageIsRefused(t *testing.T) {
_, err := Build(context.Background(), compiling{r}.run, r, _, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, "https://forge.invalid/greeter.git", "", "", workspace,
map[string]string{"mesh-tools/build": "registry.invalid/x@sha256:" + strings.Repeat("c", 64)}, Npmrc{}, nil) map[string]string{"mesh-tools/build": "registry.invalid/x@sha256:" + strings.Repeat("c", 64)}, Npmrc{}, GitCredential{}, nil)
if err == nil { if err == nil {
t.Fatal("a language nothing can compile was accepted") t.Fatal("a language nothing can compile was accepted")
} }
@@ -140,7 +140,7 @@ func TestTwoBundlesInOneModuleArePackedSeparately(t *testing.T) {
held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)} held := map[string]string{"mesh-tools/build": "registry.invalid/mesh-tools/build@sha256:" + strings.Repeat("b", 64)}
got, err := Build(context.Background(), compiling{r}.run, r, got, err := Build(context.Background(), compiling{r}.run, r,
"https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, nil) "https://forge.invalid/greeter.git", "", "", workspace, held, Npmrc{}, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatalf("a module with two bundles did not build: %v", err) t.Fatalf("a module with two bundles did not build: %v", err)
} }
+5 -5
View File
@@ -71,7 +71,7 @@ func TestAnImageBuildGetsTheCredentialInTheContextAndHostNetwork(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY .npmrc ./", "files/x": "y"}) r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY .npmrc ./", "files/x": "y"})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"} n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
if _, err := Build(context.Background(), r.run, r, if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, n, nil); err != nil { "https://forge.invalid/meshboard.git", "", "", workspace, nil, n, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err) t.Fatalf("the build failed: %v", err)
} }
@@ -101,7 +101,7 @@ func TestAnImageBuildGetsTheCredentialInTheContextAndHostNetwork(t *testing.T) {
func TestAnImageBuildWithoutACredentialGetsNoHostNetwork(t *testing.T) { func TestAnImageBuildWithoutACredentialGetsNoHostNetwork(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch", "files/x": "y"}) r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch", "files/x": "y"})
if _, err := Build(context.Background(), r.run, r, if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, nil); err != nil { "https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err) t.Fatalf("the build failed: %v", err)
} }
for _, line := range r.ran { for _, line := range r.ran {
@@ -127,7 +127,7 @@ func TestAPackageIsBuiltOnAPublicBaseAndPublishedByVersion(t *testing.T) {
}) })
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"} n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
got, err := Build(context.Background(), r.run, r, got, err := Build(context.Background(), r.run, r,
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, n, nil) "https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, n, GitCredential{}, nil)
if err != nil { if err != nil {
t.Fatalf("the package did not build: %v", err) t.Fatalf("the package did not build: %v", err)
} }
@@ -159,7 +159,7 @@ func TestAPackageWithNoRegistryIsRefused(t *testing.T) {
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`, "package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
}) })
_, err := Build(context.Background(), r.run, r, _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, Npmrc{}, nil) "https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, Npmrc{}, GitCredential{}, nil)
if err == nil { if err == nil {
t.Fatal("a package built with no registry to publish to, silently") t.Fatal("a package built with no registry to publish to, silently")
} }
@@ -206,7 +206,7 @@ func TestAnImageThatDoesNotAskForTheCredentialDoesNotGetIt(t *testing.T) {
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"}) r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch\nCOPY . .", "files/x": "y"})
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"} n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
if _, err := Build(context.Background(), r.run, r, if _, err := Build(context.Background(), r.run, r,
"https://forge.invalid/meshboard.git", "", "", workspace, nil, n, nil); err != nil { "https://forge.invalid/meshboard.git", "", "", workspace, nil, n, GitCredential{}, nil); err != nil {
t.Fatalf("the build failed: %v", err) t.Fatalf("the build failed: %v", err)
} }
for _, line := range r.ran { for _, line := range r.ran {