Skip to content

Make base jammy stack multi-arch #2780

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/update-builder.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -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

163 changes: 140 additions & 23 deletions hack/cmd/update-builder/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand 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),
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}
Loading