The builder resolves the SDK from the mesh registry, and can publish packages
A new 'package' artifact kind builds a module's own code on a public base image and publishes it to the mesh's package registry by version (hq ADR 0076) — the SDK above all, which the toolchain is built from and so cannot be built in the toolchain. The credential a build needs to resolve or publish packages is rendered as an .npmrc (basic auth, hq ADR 0048) and given to an image build as a buildkit secret, never a layer, so a token is not baked into the toolchain image. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
This commit is contained in:
@@ -0,0 +1,196 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNpmrcRendersRegistryAndTokenForTheScope(t *testing.T) {
|
||||
n := Npmrc{
|
||||
Scope: "@novox",
|
||||
Registry: "https://forge.invalid/api/packages/novox/npm/",
|
||||
Token: "a-token",
|
||||
}
|
||||
got, err := n.File()
|
||||
if err != nil {
|
||||
t.Fatalf("a complete credential did not render: %v", err)
|
||||
}
|
||||
if !strings.Contains(got, "@novox:registry=https://forge.invalid/api/packages/novox/npm/") {
|
||||
t.Fatalf("the scope's registry line is missing:\n%s", got)
|
||||
}
|
||||
// The auth line is keyed by the URL without its scheme, or npm never sends the token.
|
||||
if !strings.Contains(got, "//forge.invalid/api/packages/novox/npm/:_authToken=a-token") {
|
||||
t.Fatalf("the auth line does not match the registry key:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNpmrcAddsATrailingSlashSoTheAuthKeyMatches(t *testing.T) {
|
||||
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm", Token: "t"}
|
||||
got, err := n.File()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.Contains(got, "registry=https://forge.invalid/api/packages/novox/npm/\n") {
|
||||
t.Fatalf("a missing trailing slash was not normalised:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNpmrcRefusesTheHalfConfigured(t *testing.T) {
|
||||
cases := map[string]Npmrc{
|
||||
"scope without @": {Scope: "novox", Registry: "https://x.invalid/", Token: "t"},
|
||||
"registry not http": {Scope: "@novox", Registry: "ftp://x.invalid/", Token: "t"},
|
||||
"no token": {Scope: "@novox", Registry: "https://x.invalid/", Token: ""},
|
||||
}
|
||||
for name, n := range cases {
|
||||
if _, err := n.File(); err == nil {
|
||||
t.Fatalf("%s rendered an .npmrc rather than refusing", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNpmrcDisabledUntilThereIsARegistry(t *testing.T) {
|
||||
if (Npmrc{}).Enabled() {
|
||||
t.Fatal("an empty credential reported itself usable")
|
||||
}
|
||||
if (Npmrc{Scope: "@novox"}).Enabled() {
|
||||
t.Fatal("a scope with no registry reported itself usable")
|
||||
}
|
||||
if !(Npmrc{Scope: "@novox", Registry: "https://x.invalid/"}).Enabled() {
|
||||
t.Fatal("a scope and a registry did not count as usable")
|
||||
}
|
||||
}
|
||||
|
||||
// The credential reaches an image build as a buildkit secret and never as a file inside the build
|
||||
// context, because a token copied into a layer is a token published (novox/hq ADR 0076).
|
||||
func TestAnImageBuildGetsTheCredentialAsASecretNotALayer(t *testing.T) {
|
||||
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch", "files/x": "y"})
|
||||
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
|
||||
if _, err := Build(context.Background(), r.run, r,
|
||||
"https://forge.invalid/meshboard.git", "", "", workspace, nil, n, nil); err != nil {
|
||||
t.Fatalf("the build failed: %v", err)
|
||||
}
|
||||
|
||||
var build string
|
||||
for _, line := range r.ran {
|
||||
if strings.HasPrefix(line, "docker build") {
|
||||
build = line
|
||||
}
|
||||
}
|
||||
if build == "" {
|
||||
t.Fatal("no docker build ran")
|
||||
}
|
||||
if !strings.Contains(build, "--secret id=npmrc,src=") {
|
||||
t.Fatalf("the build was not given the credential as a secret: %s", build)
|
||||
}
|
||||
|
||||
// The .npmrc lives under the workspace, beside the clone, never inside the source tree that is
|
||||
// the docker context.
|
||||
tree := filepath.Join(workspace, "source")
|
||||
src := strings.SplitN(strings.SplitN(build, "--secret id=npmrc,src=", 2)[1], " ", 2)[0]
|
||||
if strings.HasPrefix(src, tree+string(os.PathSeparator)) {
|
||||
t.Fatalf("the credential file %s is inside the build context %s", src, tree)
|
||||
}
|
||||
if _, err := os.Stat(src); err != nil {
|
||||
t.Fatalf("the credential file the build was pointed at does not exist: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnImageBuildWithoutACredentialGetsNoSecret(t *testing.T) {
|
||||
r, workspace := aRepository(t, withBoth, map[string]string{"Dockerfile": "FROM scratch", "files/x": "y"})
|
||||
if _, err := Build(context.Background(), r.run, r,
|
||||
"https://forge.invalid/meshboard.git", "", "", workspace, nil, Npmrc{}, nil); err != nil {
|
||||
t.Fatalf("the build failed: %v", err)
|
||||
}
|
||||
for _, line := range r.ran {
|
||||
if strings.HasPrefix(line, "docker build") && strings.Contains(line, "--secret") {
|
||||
t.Fatalf("a build with no credential was still given a secret: %s", line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const aPackage = `{"module":"mesh-sdk","version":"1",
|
||||
"build":{"artifacts":[{"name":"lib","kind":"package","language":"typescript"}]},
|
||||
"resources":[]}`
|
||||
|
||||
// A package is compiled on a public base and published to the mesh's package registry by version,
|
||||
// with nothing pushed to the artifact store and the credential mounted, not baked (novox/hq ADR 0076).
|
||||
func TestAPackageIsBuiltOnAPublicBaseAndPublishedByVersion(t *testing.T) {
|
||||
r, workspace := aRepository(t, aPackage, map[string]string{
|
||||
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
|
||||
})
|
||||
n := Npmrc{Scope: "@novox", Registry: "https://forge.invalid/api/packages/novox/npm/", Token: "t"}
|
||||
got, err := Build(context.Background(), r.run, r,
|
||||
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, n, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("the package did not build: %v", err)
|
||||
}
|
||||
if len(got.Built) != 1 || got.Built[0].Reference != "@novox/mesh-sdk@0.1.0" {
|
||||
t.Fatalf("a package is published by name and version, got %+v", got.Built)
|
||||
}
|
||||
if len(r.images) != 0 || len(r.archives) != 0 {
|
||||
t.Fatal("a package was pushed to the artifact store, which is not where packages live")
|
||||
}
|
||||
var ran string
|
||||
for _, line := range r.ran {
|
||||
if strings.HasPrefix(line, "docker run") {
|
||||
ran = line
|
||||
}
|
||||
}
|
||||
if ran == "" {
|
||||
t.Fatal("nothing ran to build the package")
|
||||
}
|
||||
if !strings.Contains(ran, "node:22-bookworm-slim") {
|
||||
t.Fatalf("a package was not built on a public base: %s", ran)
|
||||
}
|
||||
if !strings.Contains(ran, ":/root/.npmrc:ro") {
|
||||
t.Fatalf("the credential was not mounted read-only for the publish: %s", ran)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPackageWithNoRegistryIsRefused(t *testing.T) {
|
||||
r, workspace := aRepository(t, aPackage, map[string]string{
|
||||
"package.json": `{"name":"@novox/mesh-sdk","version":"0.1.0"}`,
|
||||
})
|
||||
_, err := Build(context.Background(), r.run, r,
|
||||
"https://forge.invalid/mesh-sdk.git", "", "", workspace, nil, Npmrc{}, nil)
|
||||
if err == nil {
|
||||
t.Fatal("a package built with no registry to publish to, silently")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNpmrcRendersBasicAuthWhenGivenAUserAndPassword(t *testing.T) {
|
||||
n := Npmrc{
|
||||
Scope: "@novox",
|
||||
Registry: "http://forge.invalid:3000/api/packages/novox/npm/",
|
||||
Username: "mesh_anchor_builder",
|
||||
Password: "s3cret",
|
||||
}
|
||||
got, err := n.File()
|
||||
if err != nil {
|
||||
t.Fatalf("basic-auth credential did not render: %v", err)
|
||||
}
|
||||
key := "//forge.invalid:3000/api/packages/novox/npm/"
|
||||
if !strings.Contains(got, key+":username=mesh_anchor_builder\n") {
|
||||
t.Fatalf("username line missing:\n%s", got)
|
||||
}
|
||||
// npm reads the password base64-encoded.
|
||||
if !strings.Contains(got, key+":_password=czNjcmV0\n") {
|
||||
t.Fatalf("base64 password line missing or wrong:\n%s", got)
|
||||
}
|
||||
if !strings.Contains(got, key+":always-auth=true\n") {
|
||||
t.Fatalf("always-auth missing, so reads would go unauthenticated:\n%s", got)
|
||||
}
|
||||
if strings.Contains(got, "_authToken") {
|
||||
t.Fatalf("a token line was rendered for a basic-auth credential:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNpmrcRefusesWhenGivenNeitherTokenNorPassword(t *testing.T) {
|
||||
n := Npmrc{Scope: "@novox", Registry: "http://x.invalid/npm/", Username: "u"}
|
||||
if _, err := n.File(); err == nil {
|
||||
t.Fatal("a username with no password rendered an .npmrc")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user