feat: move distribution builds to platform Docker builder

This commit is contained in:
npc0-hue
2026-07-30 19:25:50 +08:00
parent 1e004dc9ec
commit e614a17fe3
45 changed files with 4492 additions and 294 deletions
@@ -0,0 +1,327 @@
package service
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/gzip"
"context"
"errors"
"io"
"os"
"path/filepath"
"strings"
"testing"
"time"
"browser.local/platform/domain"
)
func TestPinnedBuilderImageRequiresExplicitTagOrDigest(t *testing.T) {
validDigest := strings.Repeat("a", 64)
for _, test := range []struct {
image string
want bool
}{
{image: "browser-platform-distribution-builder:1.0.0", want: true},
{image: "registry.example.test:5000/builders/distribution:v1", want: true},
{image: "registry.example.test/builders/distribution@sha256:" + validDigest, want: true},
{image: "browser-platform-distribution-builder", want: false},
{image: "browser-platform-distribution-builder:latest", want: false},
{image: "registry.example.test/builders/distribution@sha256:", want: false},
{image: "registry.example.test/builders/distribution@sha256:not-a-digest", want: false},
} {
t.Run(test.image, func(t *testing.T) {
if got := pinnedBuilderImage(test.image); got != test.want {
t.Fatalf("pinnedBuilderImage(%q) = %t, want %t", test.image, got, test.want)
}
})
}
}
func TestDockerDistributionBuilderReadinessNamesPlatformBuilderFailures(t *testing.T) {
sourceDir := createBuilderSource(t)
workspaceDir := t.TempDir()
for _, test := range []struct {
name string
config DockerDistributionBuilderConfig
reason string
}{
{
name: "floating image",
config: DockerDistributionBuilderConfig{Image: "golang:latest", SourceDir: sourceDir, WorkspaceDir: workspaceDir},
reason: "platform builder image must be pinned",
},
{
name: "missing source",
config: DockerDistributionBuilderConfig{Image: "builder:1.0.0", WorkspaceDir: workspaceDir},
reason: "platform builder run source directory is not configured",
},
{
name: "image unavailable",
config: DockerDistributionBuilderConfig{
Image: "builder:1.0.0",
SourceDir: sourceDir,
WorkspaceDir: workspaceDir,
CommandRunner: func(_ context.Context, _ string, args ...string) ([]byte, error) {
if len(args) > 0 && args[0] == "version" {
return []byte("27.0.0"), nil
}
return nil, errors.New("image missing")
},
},
reason: "platform builder image is unavailable",
},
{
name: "container runtime unavailable",
config: DockerDistributionBuilderConfig{
Image: "builder:1.0.0",
SourceDir: sourceDir,
WorkspaceDir: workspaceDir,
CommandRunner: func(context.Context, string, ...string) ([]byte, error) {
return nil, errors.New("docker unavailable")
},
},
reason: "platform builder container runtime is unavailable",
},
} {
t.Run(test.name, func(t *testing.T) {
ready, reason := NewDockerDistributionBuilder(test.config).Readiness()
if ready || !strings.Contains(reason, test.reason) || !strings.Contains(reason, "platform builder") {
t.Fatalf("expected platform builder readiness failure %q, ready=%t reason=%q", test.reason, ready, reason)
}
})
}
}
func TestDockerDistributionBuilderKeepsSecretInIsolatedInput(t *testing.T) {
sourceDir := createBuilderSource(t)
workspaceDir := t.TempDir()
secret := "component-auth-key-that-must-not-leave-input"
var dockerArgs []string
builder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{
DockerBinary: "docker-test",
Image: "browser-platform-distribution-builder:1.0.0",
SourceDir: sourceDir,
WorkspaceDir: workspaceDir,
CommandRunner: func(_ context.Context, name string, args ...string) ([]byte, error) {
if name != "docker-test" {
t.Fatalf("unexpected container runtime %q", name)
}
if len(args) > 0 && (args[0] == "version" || args[0] == "image") {
return []byte("27.0.0"), nil
}
dockerArgs = append([]string(nil), args...)
inputDir := builderMountHostPath(t, args, "/workspace/input:ro")
outputDir := builderMountHostPath(t, args, "/workspace/output")
authPath := filepath.Join(inputDir, "auth-key")
payload, err := os.ReadFile(authPath)
if err != nil {
t.Fatalf("read per-job auth input: %v", err)
}
if string(payload) != secret {
t.Fatalf("unexpected per-job auth input %q", payload)
}
info, err := os.Stat(authPath)
if err != nil {
t.Fatalf("stat per-job auth input: %v", err)
}
if info.Mode().Perm() != 0o600 {
t.Fatalf("auth input mode = %o, want 600", info.Mode().Perm())
}
script, err := os.ReadFile(filepath.Join(inputDir, "build.sh"))
if err != nil {
t.Fatalf("read build script: %v", err)
}
if bytes.Contains(script, []byte(secret)) {
t.Fatal("build script must not embed the component auth key")
}
if err := os.WriteFile(filepath.Join(outputDir, "run.exe"), []byte("compiled-run"), 0o700); err != nil {
t.Fatalf("write fake build output: %v", err)
}
return nil, nil
},
})
input := domain.DistributionBuildInput{
JobID: "job/build:one",
ComponentKind: domain.DistributionComponentRun,
ServerInstanceID: "server-one",
PluginID: "game.scum",
RunEndpointID: "server-run-server-one",
TargetOS: "windows",
TargetArch: "amd64",
TargetRelease: "release-one",
PlatformURL: "https://platform.example.test",
OutputFilename: "run.exe",
KeyGeneration: 1,
AuthKey: secret,
}
payload, err := builder.Build(input)
if err != nil {
t.Fatalf("build Run distribution: %v", err)
}
if string(payload) != "compiled-run" {
t.Fatalf("unexpected built payload %q", payload)
}
joinedArgs := strings.Join(dockerArgs, "\x00")
if strings.Contains(joinedArgs, secret) {
t.Fatal("component auth key leaked into Docker arguments or environment")
}
for _, required := range []string{"--read-only", "--pull\x00never", sourceDir + ":/workspace/source:ro", ":/workspace/input:ro", ":/workspace/output"} {
if !strings.Contains(joinedArgs, required) {
t.Fatalf("Docker arguments do not contain required isolation %q: %q", required, joinedArgs)
}
}
jobDir := filepath.Join(workspaceDir, "game.scum", "job-build-one")
if _, err := os.Stat(jobDir); !errors.Is(err, os.ErrNotExist) {
t.Fatalf("per-job workspace was not removed: %v", err)
}
}
func TestDockerDistributionBuilderRedactsFailureAndTimeout(t *testing.T) {
sourceDir := createBuilderSource(t)
workspaceDir := t.TempDir()
secret := "sensitive-component-key"
input := domain.DistributionBuildInput{
JobID: "job-redaction",
ComponentKind: domain.DistributionComponentRun,
PluginID: "game.scum",
TargetOS: "linux",
TargetArch: "amd64",
OutputFilename: "run",
AuthKey: secret,
}
builder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{
Image: "builder:1.0.0",
SourceDir: sourceDir,
WorkspaceDir: workspaceDir,
CommandRunner: func(_ context.Context, _ string, args ...string) ([]byte, error) {
if len(args) > 0 && (args[0] == "version" || args[0] == "image") {
return []byte("27.0.0"), nil
}
jobDir := filepath.Join(workspaceDir, "game.scum", "job-redaction")
return []byte(secret + "\n" + sourceDir + "/go.mod: build failed\n" + jobDir + "/input/auth-key"), errors.New("exit 1")
},
})
_, err := builder.Build(input)
if err == nil {
t.Fatal("expected failed builder command")
}
if strings.Contains(err.Error(), secret) || strings.Contains(err.Error(), sourceDir) || strings.Contains(err.Error(), workspaceDir) || strings.Contains(err.Error(), "auth-key") {
t.Fatalf("builder failure leaked secret or host path: %v", err)
}
timeoutBuilder := NewDockerDistributionBuilder(DockerDistributionBuilderConfig{
Image: "builder:1.0.0",
SourceDir: sourceDir,
WorkspaceDir: workspaceDir,
Timeout: 5 * time.Millisecond,
CommandRunner: func(ctx context.Context, _ string, args ...string) ([]byte, error) {
if len(args) > 0 && (args[0] == "version" || args[0] == "image") {
return []byte("27.0.0"), nil
}
<-ctx.Done()
return nil, ctx.Err()
},
})
_, err = timeoutBuilder.Build(input)
if err == nil || !strings.Contains(err.Error(), "platform builder timed out") {
t.Fatalf("expected bounded platform builder timeout, got %v", err)
}
}
func TestPackageClientManagerDistributionProducesProtectedArchives(t *testing.T) {
for _, packageFormat := range []string{"zip", "tar.gz"} {
t.Run(packageFormat, func(t *testing.T) {
payload, err := packageClientManagerDistribution(packageFormat, "client-manager.exe", []byte("binary"), []byte("credential: protected"))
if err != nil {
t.Fatalf("package client manager: %v", err)
}
files := readDistributionArchive(t, packageFormat, payload)
if string(files["client-manager.exe"].payload) != "binary" || files["client-manager.exe"].mode.Perm() != 0o755 {
t.Fatalf("unexpected executable archive entry: %+v", files["client-manager.exe"])
}
if string(files["config.yaml"].payload) != "credential: protected" || files["config.yaml"].mode.Perm() != 0o600 {
t.Fatalf("unexpected config archive entry: %+v", files["config.yaml"])
}
})
}
}
type distributionArchiveFile struct {
mode os.FileMode
payload []byte
}
func createBuilderSource(t *testing.T) string {
t.Helper()
sourceDir := t.TempDir()
if err := os.WriteFile(filepath.Join(sourceDir, "go.mod"), []byte("module browser.local/run\n"), 0o600); err != nil {
t.Fatalf("write run source go.mod: %v", err)
}
return sourceDir
}
func builderMountHostPath(t *testing.T, args []string, containerSuffix string) string {
t.Helper()
for index := 0; index+1 < len(args); index++ {
if args[index] != "-v" || !strings.HasSuffix(args[index+1], ":"+containerSuffix) {
continue
}
return strings.TrimSuffix(args[index+1], ":"+containerSuffix)
}
t.Fatalf("Docker arguments do not mount %s: %+v", containerSuffix, args)
return ""
}
func readDistributionArchive(t *testing.T, packageFormat string, payload []byte) map[string]distributionArchiveFile {
t.Helper()
files := map[string]distributionArchiveFile{}
switch packageFormat {
case "zip":
reader, err := zip.NewReader(bytes.NewReader(payload), int64(len(payload)))
if err != nil {
t.Fatalf("open zip: %v", err)
}
for _, entry := range reader.File {
body, err := entry.Open()
if err != nil {
t.Fatalf("open zip entry %s: %v", entry.Name, err)
}
content, err := io.ReadAll(body)
if closeErr := body.Close(); err == nil {
err = closeErr
}
if err != nil {
t.Fatalf("read zip entry %s: %v", entry.Name, err)
}
files[entry.Name] = distributionArchiveFile{mode: entry.Mode(), payload: content}
}
case "tar.gz":
gzipReader, err := gzip.NewReader(bytes.NewReader(payload))
if err != nil {
t.Fatalf("open gzip: %v", err)
}
tarReader := tar.NewReader(gzipReader)
for {
header, err := tarReader.Next()
if errors.Is(err, io.EOF) {
break
}
if err != nil {
t.Fatalf("read tar header: %v", err)
}
content, err := io.ReadAll(tarReader)
if err != nil {
t.Fatalf("read tar entry %s: %v", header.Name, err)
}
files[header.Name] = distributionArchiveFile{mode: os.FileMode(header.Mode), payload: content}
}
if err := gzipReader.Close(); err != nil {
t.Fatalf("close gzip: %v", err)
}
default:
t.Fatalf("unsupported archive format %q", packageFormat)
}
return files
}