From 740bad5329bf381c40824d14e07feda0eb5f1c80 Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Wed, 15 Apr 2026 10:24:14 +1000 Subject: [PATCH 1/4] initial sugapack commit --- .gitignore | 3 + Dockerfile | 14 +++ Makefile | 95 ++++++++++++++++++ buildkitd.toml | 3 + config.go | 23 +++++ config_test.go | 69 ++++++++++++++ convert.go | 102 ++++++++++++++++++++ frontend.go | 235 +++++++++++++++++++++++++++++++++++++++++++++ frontend_test.go | 135 ++++++++++++++++++++++++++ go.mod | 88 +++++++++++++++++ go.sum | 243 +++++++++++++++++++++++++++++++++++++++++++++++ main.go | 75 +++++++++++++++ planner.go | 68 +++++++++++++ test.json | 4 + 14 files changed, 1157 insertions(+) create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 buildkitd.toml create mode 100644 config.go create mode 100644 config_test.go create mode 100644 convert.go create mode 100644 frontend.go create mode 100644 frontend_test.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 planner.go create mode 100644 test.json diff --git a/.gitignore b/.gitignore index 66fd13c..392e5ae 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# Built binary +sugapack diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..63f53a5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +FROM golang:1.26-alpine AS builder + +WORKDIR /build +COPY go.mod go.sum ./ +RUN go mod download +COPY . . +RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /sugapack . + +FROM debian:bookworm-slim +RUN apt-get update && apt-get install -y --no-install-recommends \ + ca-certificates curl git && \ + rm -rf /var/lib/apt/lists/* +COPY --from=builder /sugapack /bin/sugapack +ENTRYPOINT ["/bin/sugapack"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2352e8c --- /dev/null +++ b/Makefile @@ -0,0 +1,95 @@ +.PHONY: build image test clean infra infra-stop dry-run dry-run-private + +# Internal registry hostname (as seen by buildkitd inside the Docker network) +IMAGE := sugapack-registry:5000/sugapack:local +BUILDKITD := sugapack-buildkitd +REGISTRY_C := sugapack-registry +NETWORK := sugapack-net +SOCK := /tmp/sugapack-buildkit/buildkitd.sock +ADDR := unix://$(SOCK) +TEST_CONFIG ?= test.json + +# ── Local dev ──────────────────────────────────────────────── + +build: + go build -o sugapack . + +test: + go test ./... -v + +# ── Docker / BuildKit infra ────────────────────────────────── + +# Start local registry + buildkitd on a shared network +infra: + @docker network inspect $(NETWORK) >/dev/null 2>&1 || \ + docker network create $(NETWORK) + @if ! docker inspect $(REGISTRY_C) >/dev/null 2>&1; then \ + echo "Starting registry..."; \ + docker run -d --name $(REGISTRY_C) --network $(NETWORK) registry:2; \ + elif [ "$$(docker inspect -f '{{.State.Running}}' $(REGISTRY_C))" != "true" ]; then \ + docker start $(REGISTRY_C); \ + else \ + echo "registry already running"; \ + fi + @mkdir -p $(dir $(SOCK)) + @if ! docker inspect $(BUILDKITD) >/dev/null 2>&1; then \ + echo "Starting buildkitd..."; \ + docker run -d --name $(BUILDKITD) --privileged \ + --network $(NETWORK) \ + -v $(dir $(SOCK)):/run/buildkit \ + -v $(CURDIR)/buildkitd.toml:/etc/buildkit/buildkitd.toml:ro \ + moby/buildkit:latest \ + --addr unix:///run/buildkit/buildkitd.sock \ + --group $(shell id -g); \ + elif [ "$$(docker inspect -f '{{.State.Running}}' $(BUILDKITD))" != "true" ]; then \ + docker start $(BUILDKITD); \ + else \ + echo "buildkitd already running"; \ + fi + @echo "Waiting for buildkitd..." + @for i in 1 2 3 4 5; do \ + buildctl --addr $(ADDR) debug workers >/dev/null 2>&1 && break; \ + sleep 1; \ + done + +infra-stop: + docker rm -f $(BUILDKITD) $(REGISTRY_C) 2>/dev/null || true + docker network rm $(NETWORK) 2>/dev/null || true + rm -rf $(dir $(SOCK)) + +# Build and push the single image (frontend + railpack CLI) to local registry +image: infra + buildctl --addr $(ADDR) build \ + --frontend dockerfile.v0 \ + --local context=. \ + --local dockerfile=. \ + --output type=image,name=$(IMAGE),push=true,registry.insecure=true + +# ── Dry-run ────────────────────────────────────────────────── +# +# make dry-run # public repo, uses test.json +# make dry-run TEST_CONFIG=myapp.json # public repo, custom config +# GIT_AUTH_TOKEN=ghp_xxx make dry-run-private # private repo +# GIT_AUTH_TOKEN=ghp_xxx make dry-run-private TEST_CONFIG=private.json + +dry-run: image + buildctl --addr $(ADDR) build \ + --frontend gateway.v0 \ + --opt source=$(IMAGE) \ + --local dockerfile=. \ + --opt filename=$(TEST_CONFIG) \ + --output type=docker,name=sugapack-test-output | docker load + +dry-run-private: image + buildctl --addr $(ADDR) build \ + --frontend gateway.v0 \ + --opt source=$(IMAGE) \ + --local dockerfile=. \ + --opt filename=$(TEST_CONFIG) \ + --secret id=GIT_AUTH_TOKEN,env=GIT_AUTH_TOKEN \ + --output type=docker,name=sugapack-test-output | docker load + +# ── Cleanup ────────────────────────────────────────────────── + +clean: infra-stop + rm -f sugapack diff --git a/buildkitd.toml b/buildkitd.toml new file mode 100644 index 0000000..782e970 --- /dev/null +++ b/buildkitd.toml @@ -0,0 +1,3 @@ +[registry."sugapack-registry:5000"] + http = true + insecure = true diff --git a/config.go b/config.go new file mode 100644 index 0000000..3c642a1 --- /dev/null +++ b/config.go @@ -0,0 +1,23 @@ +package main + +// Config is the JSON document passed as the "Dockerfile" input to the frontend. +// It tells the frontend where to fetch source and how to configure railpack. +type Config struct { + // Git repository URL (HTTPS) + Repo string `json:"repo"` + // Git ref (commit SHA, branch, or tag) + Ref string `json:"ref"` + // Subdirectory within the repo to use as build context + Context string `json:"context,omitempty"` + // BuildKit secret ID containing the git auth token for private repos + AuthSecret string `json:"authSecret,omitempty"` + // Railpack-specific configuration + Railpack RailpackConfig `json:"railpack,omitempty"` +} + +// RailpackConfig holds railpack plan generation overrides. +type RailpackConfig struct { + BuildCmd string `json:"buildCmd,omitempty"` + StartCmd string `json:"startCmd,omitempty"` + Envs map[string]string `json:"envs,omitempty"` +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..5088f9c --- /dev/null +++ b/config_test.go @@ -0,0 +1,69 @@ +package main + +import ( + "encoding/json" + "testing" +) + +func TestConfigParsing(t *testing.T) { + input := `{ + "repo": "https://github.com/user/repo.git", + "ref": "abc123def", + "context": "apps/web", + "authSecret": "GIT_AUTH_TOKEN", + "railpack": { + "buildCmd": "npm run build", + "startCmd": "npm start", + "envs": {"NODE_ENV": "production"} + } + }` + + var config Config + if err := json.Unmarshal([]byte(input), &config); err != nil { + t.Fatalf("failed to parse config: %v", err) + } + + if config.Repo != "https://github.com/user/repo.git" { + t.Errorf("repo = %q, want %q", config.Repo, "https://github.com/user/repo.git") + } + if config.Ref != "abc123def" { + t.Errorf("ref = %q, want %q", config.Ref, "abc123def") + } + if config.Context != "apps/web" { + t.Errorf("context = %q, want %q", config.Context, "apps/web") + } + if config.AuthSecret != "GIT_AUTH_TOKEN" { + t.Errorf("authSecret = %q, want %q", config.AuthSecret, "GIT_AUTH_TOKEN") + } + if config.Railpack.BuildCmd != "npm run build" { + t.Errorf("railpack.buildCmd = %q, want %q", config.Railpack.BuildCmd, "npm run build") + } + if config.Railpack.StartCmd != "npm start" { + t.Errorf("railpack.startCmd = %q, want %q", config.Railpack.StartCmd, "npm start") + } + if config.Railpack.Envs["NODE_ENV"] != "production" { + t.Errorf("railpack.envs[NODE_ENV] = %q, want %q", config.Railpack.Envs["NODE_ENV"], "production") + } +} + +func TestConfigMinimal(t *testing.T) { + input := `{"repo": "https://github.com/user/repo.git"}` + + var config Config + if err := json.Unmarshal([]byte(input), &config); err != nil { + t.Fatalf("failed to parse config: %v", err) + } + + if config.Repo != "https://github.com/user/repo.git" { + t.Errorf("repo = %q, want %q", config.Repo, "https://github.com/user/repo.git") + } + if config.Ref != "" { + t.Errorf("ref = %q, want empty", config.Ref) + } + if config.Context != "" { + t.Errorf("context = %q, want empty", config.Context) + } + if config.AuthSecret != "" { + t.Errorf("authSecret = %q, want empty", config.AuthSecret) + } +} diff --git a/convert.go b/convert.go new file mode 100644 index 0000000..55a4f1d --- /dev/null +++ b/convert.go @@ -0,0 +1,102 @@ +package main + +import ( + "fmt" + "maps" + "slices" + "strings" + + "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/util/system" + specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/railwayapp/railpack/buildkit/build_llb" + "github.com/railwayapp/railpack/core/plan" +) + +const workingDir = "/app" + +type convertOptions struct { + Platform specs.Platform + SecretsHash string + CacheKey string + GitHubToken string +} + +// Image is the OCI image config attached to the build result. +// Mirrors github.com/railwayapp/railpack/buildkit.Image. +type Image struct { + specs.Image + + Config specs.ImageConfig `json:"config,omitempty"` + Variant string `json:"variant,omitempty"` +} + +// convertPlanToLLB converts a railpack build plan into LLB using the provided +// sourceState (typically from llb.Git) instead of llb.Local("context"). +// This is the key difference from railpack's built-in ConvertPlanToLLB. +func convertPlanToLLB(bp *plan.BuildPlan, sourceState llb.State, opts convertOptions) (*llb.State, *Image, error) { + platform := opts.Platform + + cacheStore := build_llb.NewBuildKitCacheStore(opts.CacheKey) + graph, err := build_llb.NewBuildGraph(bp, &sourceState, cacheStore, opts.SecretsHash, &platform, opts.GitHubToken) + if err != nil { + return nil, nil, fmt.Errorf("creating build graph: %w", err) + } + + graphOutput, err := graph.GenerateLLB() + if err != nil { + return nil, nil, fmt.Errorf("generating LLB: %w", err) + } + + state := graphOutput.State.Dir(workingDir) + + startCommand := bp.Deploy.StartCmd + if startCommand == "" { + startCommand = "/bin/bash" + } + + image := Image{ + Image: specs.Image{ + Platform: specs.Platform{ + OS: platform.OS, + Architecture: platform.Architecture, + }, + RootFS: specs.RootFS{ + Type: "layers", + }, + }, + Variant: platform.Variant, + Config: specs.ImageConfig{ + Env: buildImageEnv(graphOutput, bp), + WorkingDir: workingDir, + Entrypoint: []string{"/bin/bash", "-c"}, + Cmd: []string{startCommand}, + }, + } + + return &state, &image, nil +} + +// buildImageEnv constructs the final environment variable list for the image, +// merging graph output env, deploy variables, and PATH. +func buildImageEnv(graphOutput *build_llb.BuildGraphOutput, bp *plan.BuildPlan) []string { + paths := []string{} + paths = append(paths, bp.Deploy.Paths...) + paths = append(paths, graphOutput.GraphEnv.PathList...) + paths = append(paths, system.DefaultPathEnvUnix) + slices.Sort(paths) + pathString := strings.Join(paths, ":") + + envMap := make(map[string]string, len(graphOutput.GraphEnv.EnvVars)+len(bp.Deploy.Variables)+1) + maps.Copy(envMap, graphOutput.GraphEnv.EnvVars) + maps.Copy(envMap, bp.Deploy.Variables) + envMap["PATH"] = pathString + + envVars := make([]string, 0, len(envMap)) + for _, k := range slices.Sorted(maps.Keys(envMap)) { + v := envMap[k] + envVars = append(envVars, fmt.Sprintf("%s=%s", k, v)) + } + + return envVars +} diff --git a/frontend.go b/frontend.go new file mode 100644 index 0000000..0bdd085 --- /dev/null +++ b/frontend.go @@ -0,0 +1,235 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/moby/buildkit/client/llb" + "github.com/moby/buildkit/exporter/containerimage/exptypes" + "github.com/moby/buildkit/frontend/gateway/client" + specs "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/railwayapp/railpack/core/plan" +) + +const defaultConfigFile = "sugapack.json" + +// Build is the BuildKit frontend entry point, called by the gateway gRPC server. +func Build(ctx context.Context, c client.Client) (*client.Result, error) { + opts := c.BuildOpts().Opts + + // Read the JSON config from the "dockerfile" mount + configBytes, err := readConfig(ctx, c, opts) + if err != nil { + return nil, fmt.Errorf("reading config: %w", err) + } + + var config Config + if err := json.Unmarshal(configBytes, &config); err != nil { + return nil, fmt.Errorf("parsing config: %w", err) + } + + if config.Repo == "" { + return nil, fmt.Errorf("repo is required in config") + } + if config.Ref == "" { + config.Ref = "main" + } + + platform := parsePlatform(opts) + + // Phase 1: Fetch source via git + sourceState := fetchGitSource(config) + + // Phase 2: Run railpack plan on the fetched source + buildPlan, err := generatePlan(ctx, c, sourceState, config, platform) + if err != nil { + return nil, fmt.Errorf("generating plan: %w", err) + } + + // Phase 3: Convert plan to LLB using git source (not local context) + finalState, image, err := convertPlanToLLB(buildPlan, sourceState, convertOptions{ + Platform: platform, + SecretsHash: opts["build-arg:secrets-hash"], + CacheKey: opts["build-arg:cache-key"], + GitHubToken: opts["build-arg:github-token"], + }) + if err != nil { + return nil, fmt.Errorf("converting plan to LLB: %w", err) + } + + // Solve the final LLB state + def, err := finalState.Marshal(ctx, llb.Platform(platform)) + if err != nil { + return nil, fmt.Errorf("marshaling LLB: %w", err) + } + + res, err := c.Solve(ctx, client.SolveRequest{ + Definition: def.ToPB(), + }) + if err != nil { + return nil, fmt.Errorf("solving final state: %w", err) + } + + // Attach OCI image config + imageBytes, err := json.Marshal(image) + if err != nil { + return nil, fmt.Errorf("marshaling image config: %w", err) + } + res.AddMeta(exptypes.ExporterImageConfigKey, imageBytes) + + return res, nil +} + +// readConfig reads the JSON config file from the "dockerfile" build context mount. +func readConfig(ctx context.Context, c client.Client, opts map[string]string) ([]byte, error) { + filename := opts["filename"] + if filename == "" { + filename = defaultConfigFile + } + + src := llb.Local("dockerfile", + llb.IncludePatterns([]string{filename}), + llb.SessionID(c.BuildOpts().SessionID), + llb.WithCustomName("[sugapack] reading config"), + ) + + def, err := src.Marshal(ctx) + if err != nil { + return nil, err + } + + res, err := c.Solve(ctx, client.SolveRequest{ + Definition: def.ToPB(), + }) + if err != nil { + return nil, err + } + + ref, err := res.SingleRef() + if err != nil { + return nil, err + } + + return ref.ReadFile(ctx, client.ReadRequest{ + Filename: filename, + }) +} + +// fetchGitSource creates the LLB state for fetching source from git. +// If a context subdirectory is specified, it scopes the source to that directory. +func fetchGitSource(config Config) llb.State { + gitOpts := []llb.GitOption{ + llb.WithCustomNamef("[sugapack] fetching %s@%s", config.Repo, config.Ref), + } + if config.AuthSecret != "" { + gitOpts = append(gitOpts, llb.AuthTokenSecret(config.AuthSecret)) + } + + gitState := llb.Git(config.Repo, config.Ref, gitOpts...) + + if config.Context == "" { + return gitState + } + + // Extract the context subdirectory into a clean root + return llb.Scratch().File( + llb.Copy(gitState, config.Context+"/.", "/", &llb.CopyInfo{ + CreateDestPath: true, + CopyDirContentsOnly: true, + AllowWildcard: true, + }), + llb.WithCustomNamef("[sugapack] extracting context %s", config.Context), + ) +} + +// generatePlan runs `sugapack plan` (embedded railpack) on the git-fetched source. +// The binary calls core.GenerateBuildPlan() as a Go library — no separate railpack CLI needed. +func generatePlan(ctx context.Context, c client.Client, sourceState llb.State, config Config, platform specs.Platform) (*plan.BuildPlan, error) { + planArgs := buildPlanArgs(config) + + runOpts := []llb.RunOption{ + llb.Args(planArgs), + llb.WithCustomName("[sugapack] generating railpack plan"), + llb.AddMount("/src", sourceState, llb.Readonly), + } + + // Use the same image as the frontend (it contains both the frontend binary and railpack CLI). + // The "source" opt is the image reference buildkitd resolved for this frontend. + plannerImage := c.BuildOpts().Opts["source"] + if img, ok := c.BuildOpts().Opts["build-arg:PLANNER_IMAGE"]; ok && img != "" { + plannerImage = img + } + + planState := llb.Image(plannerImage, llb.Platform(platform)). + Run(runOpts...). + AddMount("/out", llb.Scratch()) + + def, err := planState.Marshal(ctx, llb.Platform(platform)) + if err != nil { + return nil, fmt.Errorf("marshaling plan state: %w", err) + } + + res, err := c.Solve(ctx, client.SolveRequest{ + Definition: def.ToPB(), + }) + if err != nil { + return nil, fmt.Errorf("solving plan: %w", err) + } + + ref, err := res.SingleRef() + if err != nil { + return nil, err + } + + planBytes, err := ref.ReadFile(ctx, client.ReadRequest{ + Filename: "plan.json", + }) + if err != nil { + return nil, fmt.Errorf("reading plan.json: %w", err) + } + + buildPlan := plan.NewBuildPlan() + if err := json.Unmarshal(planBytes, buildPlan); err != nil { + return nil, fmt.Errorf("parsing plan JSON: %w", err) + } + + return buildPlan, nil +} + +// buildPlanArgs constructs the args for the embedded plan subcommand. +func buildPlanArgs(config Config) []string { + args := []string{"sugapack", "plan", "/src", "--out", "/out/plan.json"} + if config.Railpack.BuildCmd != "" { + args = append(args, "--build-cmd", config.Railpack.BuildCmd) + } + if config.Railpack.StartCmd != "" { + args = append(args, "--start-cmd", config.Railpack.StartCmd) + } + for k, v := range config.Railpack.Envs { + args = append(args, "-e", k+"="+v) + } + return args +} + +// parsePlatform extracts the target platform from build options, defaulting to linux/amd64. +func parsePlatform(opts map[string]string) specs.Platform { + p := specs.Platform{ + OS: "linux", + Architecture: "amd64", + } + + if platformStr, ok := opts["platform"]; ok { + parts := strings.SplitN(platformStr, "/", 3) + if len(parts) >= 2 { + p.OS = parts[0] + p.Architecture = parts[1] + if len(parts) == 3 { + p.Variant = parts[2] + } + } + } + + return p +} diff --git a/frontend_test.go b/frontend_test.go new file mode 100644 index 0000000..55674f7 --- /dev/null +++ b/frontend_test.go @@ -0,0 +1,135 @@ +package main + +import ( + "testing" + + "github.com/moby/buildkit/client/llb" + specs "github.com/opencontainers/image-spec/specs-go/v1" +) + +func TestFetchGitSource(t *testing.T) { + config := Config{ + Repo: "https://github.com/user/repo.git", + Ref: "abc123", + } + + state := fetchGitSource(config) + + // Verify state can be marshaled (basic sanity check) + _, err := state.Marshal(t.Context(), llb.Platform(specs.Platform{OS: "linux", Architecture: "amd64"})) + if err != nil { + t.Fatalf("failed to marshal git source state: %v", err) + } +} + +func TestFetchGitSourceWithContext(t *testing.T) { + config := Config{ + Repo: "https://github.com/user/repo.git", + Ref: "abc123", + Context: "apps/web", + } + + state := fetchGitSource(config) + + _, err := state.Marshal(t.Context(), llb.Platform(specs.Platform{OS: "linux", Architecture: "amd64"})) + if err != nil { + t.Fatalf("failed to marshal git source state with context: %v", err) + } +} + +func TestFetchGitSourceWithAuth(t *testing.T) { + config := Config{ + Repo: "https://github.com/user/private-repo.git", + Ref: "main", + AuthSecret: "GIT_AUTH_TOKEN", + } + + state := fetchGitSource(config) + + _, err := state.Marshal(t.Context(), llb.Platform(specs.Platform{OS: "linux", Architecture: "amd64"})) + if err != nil { + t.Fatalf("failed to marshal git source state with auth: %v", err) + } +} + +func TestBuildPlanArgs(t *testing.T) { + tests := []struct { + name string + config Config + want []string + }{ + { + name: "basic", + config: Config{}, + want: []string{"sugapack", "plan", "/src", "--out", "/out/plan.json"}, + }, + { + name: "with build cmd", + config: Config{ + Railpack: RailpackConfig{BuildCmd: "npm run build"}, + }, + want: []string{"sugapack", "plan", "/src", "--out", "/out/plan.json", "--build-cmd", "npm run build"}, + }, + { + name: "with start cmd", + config: Config{ + Railpack: RailpackConfig{StartCmd: "npm start"}, + }, + want: []string{"sugapack", "plan", "/src", "--out", "/out/plan.json", "--start-cmd", "npm start"}, + }, + { + name: "with both", + config: Config{ + Railpack: RailpackConfig{BuildCmd: "make", StartCmd: "./server"}, + }, + want: []string{"sugapack", "plan", "/src", "--out", "/out/plan.json", "--build-cmd", "make", "--start-cmd", "./server"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := buildPlanArgs(tt.config) + if len(got) != len(tt.want) { + t.Fatalf("buildPlanArgs() = %v, want %v", got, tt.want) + } + for i := range got { + if got[i] != tt.want[i] { + t.Errorf("buildPlanArgs()[%d] = %q, want %q", i, got[i], tt.want[i]) + } + } + }) + } +} + +func TestParsePlatform(t *testing.T) { + tests := []struct { + name string + opts map[string]string + want specs.Platform + }{ + { + name: "default", + opts: map[string]string{}, + want: specs.Platform{OS: "linux", Architecture: "amd64"}, + }, + { + name: "arm64", + opts: map[string]string{"platform": "linux/arm64"}, + want: specs.Platform{OS: "linux", Architecture: "arm64"}, + }, + { + name: "arm64 v8", + opts: map[string]string{"platform": "linux/arm64/v8"}, + want: specs.Platform{OS: "linux", Architecture: "arm64", Variant: "v8"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parsePlatform(tt.opts) + if got.OS != tt.want.OS || got.Architecture != tt.want.Architecture || got.Variant != tt.want.Variant { + t.Errorf("parsePlatform() = %+v, want %+v", got, tt.want) + } + }) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5b62be9 --- /dev/null +++ b/go.mod @@ -0,0 +1,88 @@ +module github.com/sugapack/sugapack + +go 1.26.1 + +require ( + github.com/moby/buildkit v0.28.1 + github.com/opencontainers/image-spec v1.1.1 + github.com/railwayapp/railpack v0.23.0 + github.com/urfave/cli/v3 v3.8.0 +) + +require ( + github.com/BurntSushi/toml v1.6.0 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/alexflint/go-filemutex v1.3.0 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect + github.com/bahlo/generic-list-go v0.2.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect + github.com/buger/jsonparser v1.1.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/charmbracelet/colorprofile v0.4.3 // indirect + github.com/charmbracelet/lipgloss v1.1.0 // indirect + github.com/charmbracelet/log v1.0.0 // indirect + github.com/charmbracelet/x/ansi v0.11.6 // indirect + github.com/charmbracelet/x/cellbuf v0.0.15 // indirect + github.com/charmbracelet/x/term v0.2.2 // indirect + github.com/clipperhouse/displaywidth v0.11.0 // indirect + github.com/clipperhouse/uax29/v2 v2.7.0 // indirect + github.com/containerd/containerd/v2 v2.2.2 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v1.0.0-rc.3 // indirect + github.com/containerd/ttrpc v1.2.8 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/go-logfmt/logfmt v0.6.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/in-toto/attestation v1.2.0 // indirect + github.com/in-toto/in-toto-golang v0.10.0 // indirect + github.com/invopop/jsonschema v0.13.0 // indirect + github.com/klauspost/compress v1.18.5 // indirect + github.com/lucasb-eyer/go-colorful v1.3.0 // indirect + github.com/mailru/easyjson v0.9.2 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.21 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/patternmatcher v0.6.1 // indirect + github.com/moby/sys/signal v0.7.1 // indirect + github.com/muesli/termenv v0.16.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.10.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/sirupsen/logrus v1.9.4 // indirect + github.com/stretchr/objx v0.5.3 // indirect + github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd // indirect + github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f // indirect + github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/otel v1.42.0 // indirect + go.opentelemetry.io/otel/metric v1.42.0 // indirect + go.opentelemetry.io/otel/sdk v1.42.0 // indirect + go.opentelemetry.io/otel/trace v1.42.0 // indirect + golang.org/x/crypto v0.49.0 // indirect + golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 // indirect + golang.org/x/net v0.52.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.35.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + mvdan.cc/sh/v3 v3.13.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..62d002a --- /dev/null +++ b/go.sum @@ -0,0 +1,243 @@ +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk= +github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/alexflint/go-filemutex v1.3.0 h1:LgE+nTUWnQCyRKbpoceKZsPQbs84LivvgwUymZXdOcM= +github.com/alexflint/go-filemutex v1.3.0/go.mod h1:U0+VA/i30mGBlLCrFPGtTe9y6wGQfNAWPBTekHQ+c8A= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= +github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= +github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk= +github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/charmbracelet/colorprofile v0.4.3 h1:QPa1IWkYI+AOB+fE+mg/5/4HRMZcaXex9t5KX76i20Q= +github.com/charmbracelet/colorprofile v0.4.3/go.mod h1:/zT4BhpD5aGFpqQQqw7a+VtHCzu+zrQtt1zhMt9mR4Q= +github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= +github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= +github.com/charmbracelet/log v1.0.0 h1:HVVVMmfOorfj3BA9i8X8UL69Hoz9lI0PYwXfJvOdRc4= +github.com/charmbracelet/log v1.0.0/go.mod h1:uYgY3SmLpwJWxmlrPwXvzVYujxis1vAKRV/0VQB7yWA= +github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8= +github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ= +github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI= +github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q= +github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk= +github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI= +github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8= +github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0= +github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk= +github.com/clipperhouse/uax29/v2 v2.7.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/containerd/containerd/v2 v2.2.2 h1:mjVQdtfryzT7lOqs5EYUFZm8ioPVjOpkSoG1GJPxEMY= +github.com/containerd/containerd/v2 v2.2.2/go.mod h1:5Jhevmv6/2J+Iu/A2xXAdUIdI5Ah/hfyO7okJ4AFIdY= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/platforms v1.0.0-rc.3 h1:YdvwaHtrN6wHcGJ2mYRYP3Nso8OcysuqFe9Hxm1X/tI= +github.com/containerd/platforms v1.0.0-rc.3/go.mod h1:gw0R+alP3nFQPh1L4K9bv13fRWeeyokLGLu2fKuqI10= +github.com/containerd/ttrpc v1.2.8 h1:xbVu6D4qF2jihdh9rDVOKqUMiFBQk6YctTdo1zk087Y= +github.com/containerd/ttrpc v1.2.8/go.mod h1:wyZW2K79t4Hfcxl+GUvkZqRBzJlqFFvgEeeWXa42tyE= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/gkampitakis/ciinfo v0.3.1 h1:lzjbemlGI4Q+XimPg64ss89x8Mf3xihJqy/0Mgagapo= +github.com/gkampitakis/ciinfo v0.3.1/go.mod h1:1NIwaOcFChN4fa/B0hEBdAb6npDlFL8Bwx4dfRLRqAo= +github.com/gkampitakis/go-diff v1.3.2 h1:Qyn0J9XJSDTgnsgHRdz9Zp24RaJeKMUHg2+PDZZdC4M= +github.com/gkampitakis/go-diff v1.3.2/go.mod h1:LLgOrpqleQe26cte8s36HTWcTmMEur6OPYerdAAS9tk= +github.com/gkampitakis/go-snaps v0.5.9 h1:jO2Fe2q2fhLNcMQjYh/EAGUzrZiIdwD/CkM8+QSs5cE= +github.com/gkampitakis/go-snaps v0.5.9/go.mod h1:PcKmy8q5Se7p48ywpogN5Td13reipz1Iivah4wrTIvY= +github.com/go-logfmt/logfmt v0.6.1 h1:4hvbpePJKnIzH1B+8OR/JPbTx37NktoI9LE2QZBBkvE= +github.com/go-logfmt/logfmt v0.6.1/go.mod h1:EV2pOAQoZaT1ZXZbqDl5hrymndi4SY9ED9/z6CO0XAk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/goccy/go-yaml v1.15.13 h1:Xd87Yddmr2rC1SLLTm2MNDcTjeO/GYo0JGiww6gSTDg= +github.com/goccy/go-yaml v1.15.13/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/in-toto/attestation v1.2.0 h1:aPRUZ3azbqD7yEBD5fP3TD8Dszf+YHo284SOcpahjQk= +github.com/in-toto/attestation v1.2.0/go.mod h1:r79G45gOmzPismgObLSL+rZTFxUgZLOQJI6LofTZgXk= +github.com/in-toto/in-toto-golang v0.10.0 h1:+s2eZQSK3WmWfYV85qXVSBfqgawi/5L02MaqA4o/tpM= +github.com/in-toto/in-toto-golang v0.10.0/go.mod h1:wjT4RiyFlLWCmLUJjwB8oZcjaq7HA390aMJcD3xXgmg= +github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= +github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE= +github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= +github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mailru/easyjson v0.9.2 h1:dX8U45hQsZpxd80nLvDGihsQ/OxlvTkVUXH2r/8cb2M= +github.com/mailru/easyjson v0.9.2/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= +github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.21 h1:jJKAZiQH+2mIinzCJIaIG9Be1+0NR+5sz/lYEEjdM8w= +github.com/mattn/go-runewidth v0.0.21/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= +github.com/moby/buildkit v0.28.1 h1:Tq6H6gOMU2JyEQ5rA0pa7Ey3VGNR3qpw90liSIpMQoo= +github.com/moby/buildkit v0.28.1/go.mod h1:xO6wb9VBXszkIBxaGTLXc1rQORVQFIJRt3GSX7KzCFc= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U= +github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= +github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= +github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= +github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= +github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= +github.com/railwayapp/railpack v0.23.0 h1:JB0GUQkvbqug+Gi1Cqd0fam98HSLhdDG9h39v/wUFow= +github.com/railwayapp/railpack v0.23.0/go.mod h1:HdCjBGpP6IoBaJCdX1OBtr2YBpT/EUQwWhnSv3BJNLw= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/secure-systems-lab/go-securesystemslib v0.10.0 h1:l+H5ErcW0PAehBNrBxoGv1jjNpGYdZ9RcheFkB2WI14= +github.com/secure-systems-lab/go-securesystemslib v0.10.0/go.mod h1:MRKONWmRoFzPNQ9USRF9i1mc7MvAVvF1LlW8X5VWDvk= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w= +github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd h1:Rf9uhF1+VJ7ZHqxrG8pJ6YacmHvVCmByDmGbAWCc/gA= +github.com/tailscale/hujson v0.0.0-20260302212456-ecc657c15afd/go.mod h1:EbW0wDK/qEUYI0A5bqq0C2kF8JTQwWONmGDBbzsxxHo= +github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= +github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= +github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f h1:Z4NEQ86qFl1mHuCu9gwcE+EYCwDKfXAYXZbdIXyxmEA= +github.com/tonistiigi/fsutil v0.0.0-20251211185533-a2aa163d723f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= +github.com/urfave/cli/v3 v3.8.0 h1:XqKPrm0q4P0q5JpoclYoCAv0/MIvH/jZ2umzuf8pNTI= +github.com/urfave/cli/v3 v3.8.0/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso= +github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= +github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= +github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.67.0 h1:c9r/G1CSw4dPI1jaNNG9RnQP+q4SvZnHciDQJVIvchU= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.67.0/go.mod h1:gO9smoZe9KnZcJCqcB0lMmQ4Z5VEifYmjMTpnwtTSuQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/otel v1.42.0 h1:lSQGzTgVR3+sgJDAU/7/ZMjN9Z+vUip7leaqBKy4sho= +go.opentelemetry.io/otel v1.42.0/go.mod h1:lJNsdRMxCUIWuMlVJWzecSMuNjE7dOYyWlqOXWkdqCc= +go.opentelemetry.io/otel/metric v1.42.0 h1:2jXG+3oZLNXEPfNmnpxKDeZsFI5o4J+nz6xUlaFdF/4= +go.opentelemetry.io/otel/metric v1.42.0/go.mod h1:RlUN/7vTU7Ao/diDkEpQpnz3/92J9ko05BIwxYa2SSI= +go.opentelemetry.io/otel/sdk v1.42.0 h1:LyC8+jqk6UJwdrI/8VydAq/hvkFKNHZVIWuslJXYsDo= +go.opentelemetry.io/otel/sdk v1.42.0/go.mod h1:rGHCAxd9DAph0joO4W6OPwxjNTYWghRWmkHuGbayMts= +go.opentelemetry.io/otel/sdk/metric v1.42.0 h1:D/1QR46Clz6ajyZ3G8SgNlTJKBdGp84q9RKCAZ3YGuA= +go.opentelemetry.io/otel/sdk/metric v1.42.0/go.mod h1:Ua6AAlDKdZ7tdvaQKfSmnFTdHx37+J4ba8MwVCYM5hc= +go.opentelemetry.io/otel/trace v1.42.0 h1:OUCgIPt+mzOnaUTpOQcBiM/PLQ/Op7oq6g4LenLmOYY= +go.opentelemetry.io/otel/trace v1.42.0/go.mod h1:f3K9S+IFqnumBkKhRJMeaZeNk9epyhnCmQh/EysQCdc= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= +golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90 h1:jiDhWWeC7jfWqR9c/uplMOqJ0sbNlNWv0UkzE0vX1MA= +golang.org/x/exp v0.0.0-20260312153236-7ab1446f8b90/go.mod h1:xE1HEv6b+1SCZ5/uscMRjUBKtIxworgEcEi+/n9NQDQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= +golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= +golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7 h1:ndE4FoJqsIceKP2oYSnUZqhTdYufCYYkqwtFzfrhI7w= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +mvdan.cc/sh/v3 v3.13.0 h1:dSfq/MVsY4w0Vsi6Lbs0IcQquMVqLdKLESAOZjuHdLg= +mvdan.cc/sh/v3 v3.13.0/go.mod h1:KV1GByGPc/Ho0X1E6Uz9euhsIQEj4hwyKnodLlFLoDM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..e2bd389 --- /dev/null +++ b/main.go @@ -0,0 +1,75 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/moby/buildkit/frontend/gateway/grpcclient" + cli "github.com/urfave/cli/v3" +) + +func main() { + app := &cli.Command{ + Name: "sugapack", + Usage: "BuildKit frontend wrapping railpack with remote git source support", + Commands: []*cli.Command{ + { + Name: "frontend", + Usage: "Start the BuildKit gRPC frontend server", + Action: func(ctx context.Context, cmd *cli.Command) error { + return grpcclient.RunFromEnvironment(ctx, Build) + }, + }, + { + Name: "plan", + Usage: "Generate a railpack build plan for a directory", + ArgsUsage: "DIRECTORY", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "out", + Aliases: []string{"o"}, + Value: "/out/plan.json", + Usage: "output file for the plan JSON", + }, + &cli.StringFlag{ + Name: "build-cmd", + Usage: "build command override", + }, + &cli.StringFlag{ + Name: "start-cmd", + Usage: "start command override", + }, + &cli.StringSliceFlag{ + Name: "env", + Aliases: []string{"e"}, + Usage: "environment variables (KEY=VAL)", + }, + }, + Action: func(ctx context.Context, cmd *cli.Command) error { + sourceDir := cmd.Args().First() + if sourceDir == "" { + return fmt.Errorf("source directory is required") + } + return runPlanner(PlannerOptions{ + SourceDir: sourceDir, + OutputFile: cmd.String("out"), + BuildCmd: cmd.String("build-cmd"), + StartCmd: cmd.String("start-cmd"), + Envs: cmd.StringSlice("env"), + }) + }, + }, + }, + // Default action (no subcommand): run as frontend. + // This handles the case where BuildKit invokes the binary directly. + Action: func(ctx context.Context, cmd *cli.Command) error { + return grpcclient.RunFromEnvironment(ctx, Build) + }, + } + + if err := app.Run(context.Background(), os.Args); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} diff --git a/planner.go b/planner.go new file mode 100644 index 0000000..748288a --- /dev/null +++ b/planner.go @@ -0,0 +1,68 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/railwayapp/railpack/core" + "github.com/railwayapp/railpack/core/app" +) + +// PlannerOptions configures the embedded railpack plan generation. +type PlannerOptions struct { + SourceDir string + OutputFile string + BuildCmd string + StartCmd string + Envs []string +} + +// runPlanner runs railpack plan generation as a Go library call +// and writes the resulting plan JSON to the output file. +func runPlanner(opts PlannerOptions) error { + a, err := app.NewApp(opts.SourceDir) + if err != nil { + return fmt.Errorf("creating app: %w", err) + } + + env, err := app.FromEnvs(opts.Envs) + if err != nil { + return fmt.Errorf("creating environment: %w", err) + } + + genOpts := &core.GenerateBuildPlanOptions{ + BuildCommand: opts.BuildCmd, + StartCommand: opts.StartCmd, + } + + result := core.GenerateBuildPlan(a, env, genOpts) + if !result.Success { + return fmt.Errorf("plan generation failed: %v", result.Logs) + } + + planBytes, err := json.MarshalIndent(result.Plan, "", " ") + if err != nil { + return fmt.Errorf("marshaling plan: %w", err) + } + + if err := os.MkdirAll(getDir(opts.OutputFile), 0755); err != nil { + return fmt.Errorf("creating output directory: %w", err) + } + + if err := os.WriteFile(opts.OutputFile, planBytes, 0644); err != nil { + return fmt.Errorf("writing plan: %w", err) + } + + fmt.Fprintf(os.Stderr, "Plan written to %s\n", opts.OutputFile) + return nil +} + +func getDir(path string) string { + for i := len(path) - 1; i >= 0; i-- { + if path[i] == '/' { + return path[:i] + } + } + return "." +} diff --git a/test.json b/test.json new file mode 100644 index 0000000..c381959 --- /dev/null +++ b/test.json @@ -0,0 +1,4 @@ +{ + "repo": "https://github.com/heroku/node-js-getting-started.git", + "ref": "main" +} From 45915ee5162ec3491d2f222d66064674898415ee Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Wed, 15 Apr 2026 10:24:46 +1000 Subject: [PATCH 2/4] add a publish workflow --- .github/workflows/publish.yml | 59 +++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..d902e80 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,59 @@ +name: Publish + +on: + push: + branches: [main] + tags: ["v*"] + pull_request: + branches: [main] + +env: + IMAGE: ghcr.io/nitrictech/sugapack + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: "1.26" + - run: go test ./... -v + + publish: + needs: test + runs-on: ubuntu-latest + if: github.event_name == 'push' + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/metadata-action@v5 + id: meta + with: + images: ${{ env.IMAGE }} + tags: | + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }} + type=sha + + - uses: docker/build-push-action@v6 + with: + context: . + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max From 01a17b6481fb00cfc49759d8429a9d849684e9cc Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Wed, 15 Apr 2026 10:32:26 +1000 Subject: [PATCH 3/4] update the README --- README.md | 97 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/README.md b/README.md index 7e5e0ab..0bec0b6 100644 --- a/README.md +++ b/README.md @@ -1 +1,98 @@ # sugapack + +A BuildKit frontend that wraps [railpack](https://github.com/railwayapp/railpack) with remote git source support. Everything runs on the builder — zero local context transfer. + +## How it works + +Sugapack is a BuildKit gRPC frontend that accepts a small JSON config as input (not the full source): + +1. **Fetch source** — clones the repo via `llb.Git()` with optional token auth for private repos +2. **Generate plan** — runs railpack's plan generation (embedded as a Go library) on the fetched source +3. **Execute plan** — converts the plan to LLB using railpack's `build_llb` package, substituting the git source for local context + +## Usage + +### With Depot + +```bash +echo '{"repo":"https://github.com/user/repo.git","ref":"abc123"}' | \ + depot build -f - \ + --build-arg BUILDKIT_SYNTAX=ghcr.io/nitrictech/sugapack:latest \ + --save --platform linux/amd64 \ + /dev/null +``` + +### With buildctl + +```bash +buildctl build \ + --frontend gateway.v0 \ + --opt source=ghcr.io/nitrictech/sugapack:latest \ + --local dockerfile=. \ + --opt filename=config.json \ + --output type=image,name=my-app:latest +``` + +### Private repos + +Pass a git auth token as a BuildKit secret: + +```bash +echo '{"repo":"https://github.com/user/private-repo.git","ref":"main","authSecret":"GIT_AUTH_TOKEN"}' | \ + depot build -f - \ + --build-arg BUILDKIT_SYNTAX=ghcr.io/nitrictech/sugapack:latest \ + --secret id=GIT_AUTH_TOKEN \ + --save --platform linux/amd64 \ + /dev/null +``` + +## Config format + +The "Dockerfile" input is a JSON config: + +```json +{ + "repo": "https://github.com/user/repo.git", + "ref": "abc123def", + "context": "apps/web", + "authSecret": "GIT_AUTH_TOKEN", + "railpack": { + "buildCmd": "npm run build", + "startCmd": "npm start", + "envs": { + "NODE_ENV": "production" + } + } +} +``` + +| Field | Required | Description | +|-------|----------|-------------| +| `repo` | yes | Git repository URL (HTTPS) | +| `ref` | no | Commit SHA, branch, or tag (default: `main`) | +| `context` | no | Subdirectory within the repo to use as build context | +| `authSecret` | no | BuildKit secret ID containing a git auth token | +| `railpack.buildCmd` | no | Override the build command | +| `railpack.startCmd` | no | Override the start command | +| `railpack.envs` | no | Additional environment variables for plan generation | + +## Local development + +Requires Docker and [buildctl](https://github.com/moby/buildkit). + +```bash +# Run tests +make test + +# Start local infra (buildkitd + registry) and run a full build +make dry-run + +# Custom config +make dry-run TEST_CONFIG=myapp.json + +# Private repo +GIT_AUTH_TOKEN=ghp_xxx make dry-run-private + +# Tear down +make clean +``` From 7f10a1a05b2b2cce31d794ce105d6bf6b4455c46 Mon Sep 17 00:00:00 2001 From: Tim Holm Date: Wed, 15 Apr 2026 10:53:49 +1000 Subject: [PATCH 4/4] add license --- LICENSE | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c5b9f0c --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Nitric Technologies + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE.