From c88f29bf6a001c78cb7afba9ad7cc78f6e0d88fa Mon Sep 17 00:00:00 2001 From: Daniel Mikusa Date: Sat, 6 Jul 2024 10:34:20 -0400 Subject: [PATCH] Adds multi-arch support This adds two options for multi-arch support: 1. It by default will build for the local architecture, as determined by `runtime.GOARCH`. This means you'll get a single-arch image, but it'll be for the correct architecture for your system. 2. If you specify the `--publish` flag, it will build a full multi-arch image. This requires being published to a registry through, so it's not enabled by default. Signed-off-by: Daniel Mikusa --- commands/package_buildpack.go | 6 +++++ packager/buildpack.go | 47 ++++++++++++++++++++++++++++------- packager/buildpack_test.go | 32 +++++++++++++++++++++++- 3 files changed, 75 insertions(+), 10 deletions(-) diff --git a/commands/package_buildpack.go b/commands/package_buildpack.go index a01b5ac..66a03ca 100644 --- a/commands/package_buildpack.go +++ b/commands/package_buildpack.go @@ -47,6 +47,10 @@ func PackageBundleCommand() *cobra.Command { p.InferBuildpackVersion() } + if p.RegistryName == "" { + p.RegistryName = p.BuildpackID + } + err := p.Execute() if err != nil { log.Fatal(err) @@ -61,6 +65,8 @@ func PackageBundleCommand() *cobra.Command { packageBuildpackCmd.Flags().BoolVar(&p.IncludeDependencies, "include-dependencies", false, "whether to include dependencies (default: false)") packageBuildpackCmd.Flags().StringArrayVar(&p.DependencyFilters, "dependency-filter", []string{}, "one or more filters that are applied to exclude dependencies") packageBuildpackCmd.Flags().BoolVar(&p.StrictDependencyFilters, "strict-filters", false, "require filter to match all data or just some data (default: false)") + packageBuildpackCmd.Flags().StringVar(&p.RegistryName, "registry-name", "", "prefix for the registry to publish to (default: your buildpack id)") + packageBuildpackCmd.Flags().BoolVar(&p.Publish, "publish", false, "publish the buildpack to a buildpack registry (default: false)") return packageBuildpackCmd } diff --git a/packager/buildpack.go b/packager/buildpack.go index de4b325..ea1a7af 100644 --- a/packager/buildpack.go +++ b/packager/buildpack.go @@ -22,6 +22,7 @@ import ( "io" "os" "path/filepath" + "runtime" "strings" "github.com/buildpacks/libcnb/v2" @@ -51,6 +52,12 @@ type BundleBuildpack struct { // IncludeDependencies indicates whether to include dependencies in build package. IncludeDependencies bool + // RegistryName is the prefix to use when publishing the buildpack + RegistryName string + + // Publish indicates whether to publish the buildpack to the registry + Publish bool + executor effect.Executor exitHandler libcnb.ExitHandler } @@ -173,17 +180,30 @@ func (p *BundleBuildpack) ExecutePackage(tmpDir string) error { pullPolicy = "if-not-present" } + imageName := p.BuildpackID + if p.RegistryName != "" { + imageName = p.RegistryName + } + + args := []string{ + "buildpack", + "package", + imageName, + "--pull-policy", pullPolicy, + } + + if p.Publish { + args = append(args, "--publish") + } else { + args = append(args, "--target", archFromSystem()) + } + err := p.executor.Execute(effect.Execution{ Command: "pack", - Args: []string{ - "buildpack", - "package", - p.BuildpackID, - "--pull-policy", pullPolicy, - }, - Stdout: os.Stdout, - Stderr: os.Stderr, - Dir: tmpDir, + Args: args, + Stdout: os.Stdout, + Stderr: os.Stderr, + Dir: tmpDir, }) if err != nil { return fmt.Errorf("unable to execute `pack buildpack package` command\n%w", err) @@ -239,3 +259,12 @@ func (p *BundleBuildpack) Execute() error { return nil } + +func archFromSystem() string { + archFromEnv, ok := os.LookupEnv("BP_ARCH") + if !ok { + archFromEnv = runtime.GOARCH + } + + return "linux/" + archFromEnv +} diff --git a/packager/buildpack_test.go b/packager/buildpack_test.go index 403d5ac..61aed27 100644 --- a/packager/buildpack_test.go +++ b/packager/buildpack_test.go @@ -246,6 +246,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { it.Before(func() { mockExecutor = &mocks.Executor{} + + t.Setenv("BP_ARCH", "amd64") }) it("defaults pull policy to if-not-present", func() { @@ -256,6 +258,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { e.Args[2] == "some-id" && e.Args[3] == "--pull-policy" && e.Args[4] == "if-not-present" && + e.Args[5] == "--target" && + e.Args[6] == "linux/amd64" && e.Dir == "/some/path" })).Return(nil) @@ -265,6 +269,26 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { Expect(p.ExecutePackage("/some/path")).To(Succeed()) }) + it("include registry prefix if set", func() { + mockExecutor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool { + return e.Command == "pack" && + e.Args[0] == "buildpack" && + e.Args[1] == "package" && + e.Args[2] == "docker.io/some-other-id/image" && + e.Args[3] == "--pull-policy" && + e.Args[4] == "if-not-present" && + e.Args[5] == "--publish" && + e.Dir == "/some/path" + })).Return(nil) + + p := packager.NewBundleBuildpackForTests(mockExecutor, nil) + p.BuildpackID = "some-id" + p.RegistryName = "docker.io/some-other-id/image" + p.Publish = true + + Expect(p.ExecutePackage("/some/path")).To(Succeed()) + }) + context("BP_PULL_POLICY is set", func() { it.Before(func() { t.Setenv("BP_PULL_POLICY", "always") @@ -278,6 +302,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { e.Args[2] == "some-id" && e.Args[3] == "--pull-policy" && e.Args[4] == "always" && + e.Args[5] == "--target" && + e.Args[6] == "linux/amd64" && e.Dir == "/some/path" })).Return(nil) @@ -298,9 +324,11 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { it.Before(func() { mockExecutor = &mocks.Executor{} mockExitHandler = exMocks.ExitHandler{} + + t.Setenv("BP_ARCH", "amd64") }) - it("", func() { + it("compiles the package", func() { mockExecutor.On("Execute", mock.MatchedBy(func(e effect.Execution) bool { return e.Command == "pack" && e.Args[0] == "buildpack" && @@ -308,6 +336,8 @@ func testBuildpack(t *testing.T, context spec.G, it spec.S) { e.Args[2] == "some-id" && e.Args[3] == "--pull-policy" && e.Args[4] == "if-not-present" && + e.Args[5] == "--target" && + e.Args[6] == "linux/amd64" && e.Dir == "/some/path" })).Return(nil)