From d02801355db0543ce896a9fec775ee797da20bca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Va=C5=A1ek?= Date: Mon, 14 Apr 2025 07:43:16 +0200 Subject: [PATCH] Make base jammy stack multi-arch (#2780) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matej VaĊĦek --- .github/workflows/update-builder.yaml | 3 + hack/cmd/update-builder/main.go | 163 ++++++++++++++++++++++---- 2 files changed, 143 insertions(+), 23 deletions(-) diff --git a/.github/workflows/update-builder.yaml b/.github/workflows/update-builder.yaml index 7960647a..71903742 100644 --- a/.github/workflows/update-builder.yaml +++ b/.github/workflows/update-builder.yaml @@ -13,6 +13,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: knative/actions/setup-go@main + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Build and Push env: GITHUB_TOKEN: ${{ github.token }} @@ -24,5 +26,6 @@ jobs: echo -e '\n[[registry]]\nlocation = "localhost:5000"\ninsecure = true\n' >> \ "$HOME/.config/containers/registries.conf" skopeo login ghcr.io -u gh-action -p "$GITHUB_TOKEN" + docker login ghcr.io -u gh-action -p "$GITHUB_TOKEN" make wf-update-builder diff --git a/hack/cmd/update-builder/main.go b/hack/cmd/update-builder/main.go index 042d6dd1..dd109995 100644 --- a/hack/cmd/update-builder/main.go +++ b/hack/cmd/update-builder/main.go @@ -97,10 +97,7 @@ func buildBuilderImage(ctx context.Context, variant, version, arch, builderTomlP return "", fmt.Errorf("cannot parse builder.toml: %w", err) } - err = fixupStacks(ctx, &builderConfig) - if err != nil { - return "", fmt.Errorf("cannot fix up stacks: %w", err) - } + fixupStacks(&builderConfig) // temporary fix, for some reason paketo does not distribute several buildpacks for ARM64 // we need ot fix that up @@ -263,6 +260,11 @@ func buildBuilderImageMultiArch(ctx context.Context, variant string) error { return fmt.Errorf("cannot download builder toml: %w", err) } + err = buildStack(ctx, variant, builderTomlPath) + if err != nil { + return fmt.Errorf("cannot build stack: %w", err) + } + remoteOpts := []remote.Option{ remote.WithAuthFromKeychain(DefaultKeychain), remote.WithContext(ctx), @@ -1023,32 +1025,16 @@ func fixupGoDistPkgRefs(buildpackToml, arch string) error { return nil } -func fixupStacks(ctx context.Context, builderConfig *builder.Config) error { - var err error - - oldBuild := builderConfig.Stack.BuildImage - parts := strings.Split(oldBuild, "/") - newBuilder := "localhost:5000/" + parts[len(parts)-1] - err = copyImage(ctx, oldBuild, newBuilder) - if err != nil { - return fmt.Errorf("cannot mirror build image: %w", err) - } +func fixupStacks(builderConfig *builder.Config) { + newBuilder := stackImageToMirror(builderConfig.Stack.BuildImage) builderConfig.Stack.BuildImage = newBuilder builderConfig.Build.Image = newBuilder - oldRun := builderConfig.Stack.RunImage - parts = strings.Split(oldRun, "/") - newRun := "ghcr.io/knative/" + parts[len(parts)-1] - err = copyImage(ctx, oldRun, newRun) - if err != nil { - return fmt.Errorf("cannot mirror build image: %w", err) - } - + newRun := stackImageToMirror(builderConfig.Stack.RunImage) builderConfig.Stack.RunImage = newRun builderConfig.Run.Images = []builder.RunImageConfig{{ Image: newRun, }} - return nil } func copyImage(ctx context.Context, srcRef, destRef string) error { @@ -1066,3 +1052,134 @@ func copyImage(ctx context.Context, srcRef, destRef string) error { } return nil } + +func stackImageToMirror(ref string) string { + parts := strings.Split(ref, "/") + lastPart := parts[len(parts)-1] + switch { + case strings.HasPrefix(lastPart, "build-"): + return "localhost:5000/" + lastPart + case strings.HasPrefix(lastPart, "run-"): + return "ghcr.io/knative/" + lastPart + default: + panic("non reachable") + } +} + +func buildStack(ctx context.Context, variant, builderTomlPath string) error { + var err error + + builderConfig, _, err := builder.ReadConfig(builderTomlPath) + if err != nil { + return fmt.Errorf("cannot parse builder.toml: %w", err) + } + + buildImage := builderConfig.Stack.BuildImage + runImage := builderConfig.Stack.RunImage + + if variant == "base" { + // For base stack we do not just do mirroring, we build it from the source. + // This is done in order to support arm64. Only tiny stack is multi-arch in the upstream. + err = buildBaseStack(ctx, buildImage, runImage) + if err != nil { + return fmt.Errorf("cannot build base stack: %w", err) + } + return nil + } + + err = copyImage(ctx, buildImage, stackImageToMirror(buildImage)) + if err != nil { + return fmt.Errorf("cannot mirror build image: %w", err) + } + + err = copyImage(ctx, runImage, stackImageToMirror(runImage)) + if err != nil { + return fmt.Errorf("cannot mirror run image: %w", err) + } + + return nil +} + +func buildBaseStack(ctx context.Context, buildImage, runImage string) error { + cli := newGHClient(ctx) + + parts := strings.Split(buildImage, ":") + stackVersion := parts[len(parts)-1] + + rel, resp, err := cli.Repositories.GetReleaseByTag(ctx, "paketo-buildpacks", "jammy-base-stack", "v"+stackVersion) + if err != nil { + return fmt.Errorf("cannot get release: %w", err) + } + defer func(Body io.ReadCloser) { + _ = Body.Close() + }(resp.Body) + + src, err := os.MkdirTemp("", "src-dir") + if err != nil { + return fmt.Errorf("cannot create temp dir: %w", err) + } + + err = downloadTarball(rel.GetTarballURL(), src) + if err != nil { + return fmt.Errorf("cannot download source tarball: %w", err) + } + + err = patchStack(filepath.Join(src, "stack", "stack.toml")) + if err != nil { + return fmt.Errorf("cannot patch stack toml: %w", err) + } + + script := fmt.Sprintf(` +set -ex +scripts/create.sh +.bin/jam publish-stack --build-ref %q --run-ref %q --build-archive build/build.oci --run-archive build/run.oci +`, stackImageToMirror(buildImage), stackImageToMirror(runImage)) + + cmd := exec.CommandContext(ctx, "sh", "-c", script) + cmd.Dir = src + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + err = cmd.Run() + if err != nil { + return fmt.Errorf("cannot build stack: %w", err) + } + + return nil +} + +func patchStack(stackTomlPath string) error { + input, err := os.ReadFile(stackTomlPath) + if err != nil { + return fmt.Errorf("cannot open stack toml: %w", err) + } + + var data any + err = toml.Unmarshal(input, &data) + if err != nil { + return fmt.Errorf("cannot decode data: %w", err) + } + + m := data.(map[string]any) + m["platforms"] = []string{"linux/amd64", "linux/arm64"} + + args := map[string]interface{}{ + "args": map[string]interface{}{ + "architecture": "arm64", + "sources": ` deb http://ports.ubuntu.com/ubuntu-ports/ jammy main universe multiverse + deb http://ports.ubuntu.com/ubuntu-ports/ jammy-updates main universe multiverse + deb http://ports.ubuntu.com/ubuntu-ports/ jammy-security main universe multiverse + `}, + } + + m["build"].(map[string]any)["platforms"] = map[string]any{"linux/arm64": args} + m["run"].(map[string]any)["platforms"] = map[string]any{"linux/arm64": args} + + output, err := toml.Marshal(data) + err = os.WriteFile(stackTomlPath, output, 0644) + if err != nil { + return fmt.Errorf("cannot write patched stack toml: %w", err) + } + + return nil +}