Skip to content

Commit

Permalink
Merge pull request #2314 from buildpacks/jjbustamante/fix-pack-2299
Browse files Browse the repository at this point in the history
Add new flag `--append-image-name-suffix` to append suffix to the image name when pushing a multi-arch buildpack or builder
  • Loading branch information
jjbustamante authored Jan 17, 2025
2 parents 0adeb45 + 0c432c9 commit c7f5b1c
Show file tree
Hide file tree
Showing 6 changed files with 216 additions and 42 deletions.
41 changes: 24 additions & 17 deletions internal/commands/builder_create.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,14 @@ import (

// BuilderCreateFlags define flags provided to the CreateBuilder command
type BuilderCreateFlags struct {
Publish bool
BuilderTomlPath string
Registry string
Policy string
Flatten []string
Targets []string
Label map[string]string
Publish bool
AppendImageNameSuffix bool
BuilderTomlPath string
Registry string
Policy string
Flatten []string
Targets []string
Label map[string]string
}

// CreateBuilder creates a builder image, based on a builder config
Expand Down Expand Up @@ -97,18 +98,23 @@ Creating a custom builder allows you to control what buildpacks are used and wha
logger.Infof("Pro tip: use --targets flag OR [[targets]] in builder.toml to specify the desired platform")
}

if !flags.Publish && flags.AppendImageNameSuffix {
logger.Warnf("--append-image-name-suffix will be ignored, use combined with --publish")
}

imageName := args[0]
if err := pack.CreateBuilder(cmd.Context(), client.CreateBuilderOptions{
RelativeBaseDir: relativeBaseDir,
BuildConfigEnv: envMap,
BuilderName: imageName,
Config: builderConfig,
Publish: flags.Publish,
Registry: flags.Registry,
PullPolicy: pullPolicy,
Flatten: toFlatten,
Labels: flags.Label,
Targets: multiArchCfg.Targets(),
RelativeBaseDir: relativeBaseDir,
BuildConfigEnv: envMap,
BuilderName: imageName,
Config: builderConfig,
Publish: flags.Publish,
AppendImageNameSuffix: flags.AppendImageNameSuffix && flags.Publish,
Registry: flags.Registry,
PullPolicy: pullPolicy,
Flatten: toFlatten,
Labels: flags.Label,
Targets: multiArchCfg.Targets(),
}); err != nil {
return err
}
Expand All @@ -124,6 +130,7 @@ Creating a custom builder allows you to control what buildpacks are used and wha
}
cmd.Flags().StringVarP(&flags.BuilderTomlPath, "config", "c", "", "Path to builder TOML file (required)")
cmd.Flags().BoolVar(&flags.Publish, "publish", false, "Publish the builder directly to the container registry specified in <image-name>, instead of the daemon.")
cmd.Flags().BoolVar(&flags.AppendImageNameSuffix, "append-image-name-suffix", false, "When publishing to a registry that doesn't allow overwrite existing tags use this flag to append a [os]-[arch] suffix to <image-name>")
cmd.Flags().StringVar(&flags.Policy, "pull-policy", "", "Pull policy to use. Accepted values are always, never, and if-not-present. The default is always")
cmd.Flags().StringArrayVar(&flags.Flatten, "flatten", nil, "List of buildpacks to flatten together into a single layer (format: '<buildpack-id>@<buildpack-version>,<buildpack-id>@<buildpack-version>'")
cmd.Flags().StringToStringVarP(&flags.Label, "label", "l", nil, "Labels to add to the builder image, in the form of '<name>=<value>'")
Expand Down
49 changes: 28 additions & 21 deletions internal/commands/buildpack_package.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,17 @@ import (

// BuildpackPackageFlags define flags provided to the BuildpackPackage command
type BuildpackPackageFlags struct {
PackageTomlPath string
Format string
Policy string
BuildpackRegistry string
Path string
FlattenExclude []string
Targets []string
Label map[string]string
Publish bool
Flatten bool
PackageTomlPath string
Format string
Policy string
BuildpackRegistry string
Path string
FlattenExclude []string
Targets []string
Label map[string]string
Publish bool
Flatten bool
AppendImageNameSuffix bool
}

// BuildpackPackager packages buildpacks
Expand Down Expand Up @@ -130,18 +131,23 @@ func BuildpackPackage(logger logging.Logger, cfg config.Config, packager Buildpa
defer clean(filesToClean)
}

if !flags.Publish && flags.AppendImageNameSuffix {
logger.Warnf("--append-image-name-suffix will be ignored, use combined with --publish")
}

if err := packager.PackageBuildpack(cmd.Context(), client.PackageBuildpackOptions{
RelativeBaseDir: relativeBaseDir,
Name: name,
Format: flags.Format,
Config: bpPackageCfg,
Publish: flags.Publish,
PullPolicy: pullPolicy,
Registry: flags.BuildpackRegistry,
Flatten: flags.Flatten,
FlattenExclude: flags.FlattenExclude,
Labels: flags.Label,
Targets: multiArchCfg.Targets(),
RelativeBaseDir: relativeBaseDir,
Name: name,
Format: flags.Format,
Config: bpPackageCfg,
Publish: flags.Publish,
AppendImageNameSuffix: flags.AppendImageNameSuffix && flags.Publish,
PullPolicy: pullPolicy,
Registry: flags.BuildpackRegistry,
Flatten: flags.Flatten,
FlattenExclude: flags.FlattenExclude,
Labels: flags.Label,
Targets: multiArchCfg.Targets(),
}); err != nil {
return err
}
Expand All @@ -163,6 +169,7 @@ func BuildpackPackage(logger logging.Logger, cfg config.Config, packager Buildpa
cmd.Flags().StringVarP(&flags.PackageTomlPath, "config", "c", "", "Path to package TOML config")
cmd.Flags().StringVarP(&flags.Format, "format", "f", "", `Format to save package as ("image" or "file")`)
cmd.Flags().BoolVar(&flags.Publish, "publish", false, `Publish the buildpack directly to the container registry specified in <name>, instead of the daemon (applies to "--format=image" only).`)
cmd.Flags().BoolVar(&flags.AppendImageNameSuffix, "append-image-name-suffix", false, "When publishing to a registry that doesn't allow overwrite existing tags use this flag to append a [os]-[arch] suffix to package <name>")
cmd.Flags().StringVar(&flags.Policy, "pull-policy", "", "Pull policy to use. Accepted values are always, never, and if-not-present. The default is always")
cmd.Flags().StringVarP(&flags.Path, "path", "p", "", "Path to the Buildpack that needs to be packaged")
cmd.Flags().StringVarP(&flags.BuildpackRegistry, "buildpack-registry", "r", "", "Buildpack Registry name")
Expand Down
24 changes: 24 additions & 0 deletions internal/name/name.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"strings"

"github.com/buildpacks/pack/pkg/dist"

gname "github.com/google/go-containerregistry/pkg/name"

"github.com/buildpacks/pack/internal/style"
Expand Down Expand Up @@ -49,6 +51,24 @@ func TranslateRegistry(name string, registryMirrors map[string]string, logger Lo
return refName, nil
}

func AppendSuffix(name string, target dist.Target) (string, error) {
reference, err := gname.ParseReference(name, gname.WeakValidation)
if err != nil {
return "", err
}

suffixPlatformTag := targetToTag(target)
if suffixPlatformTag != "" {
if reference.Identifier() == "latest" {
return fmt.Sprintf("%s:%s", reference.Context(), suffixPlatformTag), nil
}
if !strings.Contains(reference.Identifier(), ":") {
return fmt.Sprintf("%s:%s-%s", reference.Context(), reference.Identifier(), suffixPlatformTag), nil
}
}
return name, nil
}

func getMirror(repo gname.Repository, registryMirrors map[string]string) (string, bool) {
mirror, ok := registryMirrors["*"]
if ok {
Expand All @@ -58,3 +78,7 @@ func getMirror(repo gname.Repository, registryMirrors map[string]string) (string
mirror, ok = registryMirrors[repo.RegistryStr()]
return mirror, ok
}

func targetToTag(target dist.Target) string {
return strings.Join(target.ValuesAsSlice(), "-")
}
109 changes: 109 additions & 0 deletions internal/name/name_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"io"
"testing"

"github.com/buildpacks/pack/pkg/dist"

"github.com/sclevine/spec"
"github.com/sclevine/spec/report"

Expand Down Expand Up @@ -79,4 +81,111 @@ func testTranslateRegistry(t *testing.T, when spec.G, it spec.S) {
assert.Equal(output, expected)
})
})

when("#AppendSuffix", func() {
when("[os] is provided", func() {
when("[arch]] is provided", func() {
when("[arch-variant] is provided", func() {
when("tag is provided", func() {
it("append [os]-[arch]-[arch-variant] to the given tag", func() {
input := "my.registry.com/my-repo/my-image:some-tag"
target := dist.Target{
OS: "linux",
Arch: "amd64",
ArchVariant: "v6",
}

result, err := name.AppendSuffix(input, target)
assert.Nil(err)
assert.Equal(result, "my.registry.com/my-repo/my-image:some-tag-linux-amd64-v6")
})
})
when("tag is not provided", func() {
it("add tag: [os]-[arch]-[arch-variant] to the given <image>", func() {
input := "my.registry.com/my-repo/my-image"
target := dist.Target{
OS: "linux",
Arch: "amd64",
ArchVariant: "v6",
}

result, err := name.AppendSuffix(input, target)
assert.Nil(err)
assert.Equal(result, "my.registry.com/my-repo/my-image:linux-amd64-v6")
})
})
})
when("[arch-variant] is not provided", func() {
when("tag is provided", func() {
// my.registry.com/my-repo/my-image:some-tag
it("append [os]-[arch] to the given tag", func() {
input := "my.registry.com/my-repo/my-image:some-tag"
target := dist.Target{
OS: "linux",
Arch: "amd64",
}

result, err := name.AppendSuffix(input, target)
assert.Nil(err)
assert.Equal(result, "my.registry.com/my-repo/my-image:some-tag-linux-amd64")
})
})
when("tag is NOT provided", func() {
// my.registry.com/my-repo/my-image
it("add tag: [os]-[arch] to the given <image>", func() {
input := "my.registry.com/my-repo/my-image"
target := dist.Target{
OS: "linux",
Arch: "amd64",
}

result, err := name.AppendSuffix(input, target)
assert.Nil(err)
assert.Equal(result, "my.registry.com/my-repo/my-image:linux-amd64")
})
})
})
})

when("[arch] is not provided", func() {
when("tag is provided", func() {
// my.registry.com/my-repo/my-image:some-tag
it("append [os] to the given tag", func() {
input := "my.registry.com/my-repo/my-image:some-tag"
target := dist.Target{
OS: "linux",
}

result, err := name.AppendSuffix(input, target)
assert.Nil(err)
assert.Equal(result, "my.registry.com/my-repo/my-image:some-tag-linux")
})
})
when("tag is not provided", func() {
// my.registry.com/my-repo/my-image
it("add tag: [os] to the given <image>", func() {
input := "my.registry.com/my-repo/my-image"
target := dist.Target{
OS: "linux",
}

result, err := name.AppendSuffix(input, target)
assert.Nil(err)
assert.Equal(result, "my.registry.com/my-repo/my-image:linux")
})
})
})
})

when("[os] is not provided", func() {
it("doesn't append anything and return the same <image> name", func() {
input := "my.registry.com/my-repo/my-image"
target := dist.Target{}

result, err := name.AppendSuffix(input, target)
assert.Nil(err)
assert.Equal(result, input)
})
})
})
}
20 changes: 17 additions & 3 deletions pkg/client/create_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"sort"
"strings"

"github.com/buildpacks/pack/internal/name"

"github.com/Masterminds/semver"
"github.com/buildpacks/imgutil"
"github.com/pkg/errors"
Expand Down Expand Up @@ -43,6 +45,10 @@ type CreateBuilderOptions struct {
// Requires BuilderName to be a valid registry location.
Publish bool

// Append [os]-[arch] suffix to the image tag when publishing a multi-arch to a registry
// Requires Publish to be true
AppendImageNameSuffix bool

// Buildpack registry name. Defines where all registry buildpacks will be pulled from.
Registry string

Expand Down Expand Up @@ -98,7 +104,7 @@ func (c *Client) createBuilderTarget(ctx context.Context, opts CreateBuilderOpti
return "", err
}

bldr, err := c.createBaseBuilder(ctx, opts, target)
bldr, err := c.createBaseBuilder(ctx, opts, target, multiArch)
if err != nil {
return "", errors.Wrap(err, "failed to create builder")
}
Expand Down Expand Up @@ -197,7 +203,7 @@ func (c *Client) validateRunImageConfig(ctx context.Context, opts CreateBuilderO
return nil
}

func (c *Client) createBaseBuilder(ctx context.Context, opts CreateBuilderOptions, target *dist.Target) (*builder.Builder, error) {
func (c *Client) createBaseBuilder(ctx context.Context, opts CreateBuilderOptions, target *dist.Target, multiArch bool) (*builder.Builder, error) {
baseImage, err := c.imageFetcher.Fetch(ctx, opts.Config.Build.Image, image.FetchOptions{Daemon: !opts.Publish, PullPolicy: opts.PullPolicy, Target: target})
if err != nil {
return nil, errors.Wrap(err, "fetch build image")
Expand All @@ -213,7 +219,15 @@ func (c *Client) createBaseBuilder(ctx context.Context, opts CreateBuilderOption
builderOpts = append(builderOpts, builder.WithLabels(opts.Labels))
}

bldr, err := builder.New(baseImage, opts.BuilderName, builderOpts...)
builderName := opts.BuilderName
if multiArch && opts.AppendImageNameSuffix {
builderName, err = name.AppendSuffix(builderName, *target)
if err != nil {
return nil, errors.Wrap(err, "invalid image name")
}
}

bldr, err := builder.New(baseImage, builderName, builderOpts...)
if err != nil {
return nil, errors.Wrap(err, "invalid build-image")
}
Expand Down
15 changes: 14 additions & 1 deletion pkg/client/package_buildpack.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"fmt"
"path/filepath"

"github.com/buildpacks/pack/internal/name"

"github.com/pkg/errors"

pubbldpkg "github.com/buildpacks/pack/buildpackage"
Expand Down Expand Up @@ -47,6 +49,10 @@ type PackageBuildpackOptions struct {
// specified in the Name variable.
Publish bool

// Append [os]-[arch] suffix to the image tag when publishing a multi-arch to a registry
// Requires Publish to be true
AppendImageNameSuffix bool

// Strategy for updating images before packaging.
PullPolicy image.PullPolicy

Expand Down Expand Up @@ -192,7 +198,14 @@ func (c *Client) packageBuildpackTarget(ctx context.Context, opts PackageBuildpa
return digest, err
}
case FormatImage:
img, err := packageBuilder.SaveAsImage(opts.Name, opts.Publish, target, opts.Labels)
packageName := opts.Name
if multiArch && opts.AppendImageNameSuffix {
packageName, err = name.AppendSuffix(packageName, target)
if err != nil {
return "", errors.Wrap(err, "invalid image name")
}
}
img, err := packageBuilder.SaveAsImage(packageName, opts.Publish, target, opts.Labels)
if err != nil {
return digest, errors.Wrapf(err, "saving image")
}
Expand Down

0 comments on commit c7f5b1c

Please sign in to comment.