Skip to content
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
15 changes: 15 additions & 0 deletions acceptance/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2699,6 +2699,21 @@ include = [ "*.jar", "media/mountain.jpg", "/media/person.png", ]
})
})
})

when("--platform", func() {
it.Before(func() {
h.SkipIf(t, !pack.SupportsFeature(invoke.PlatformOption), "")
})

it("uses the builder with the desired platform", func() {
output, _ := pack.Run(
"build", repoName,
"-p", filepath.Join("testdata", "mock_app"),
"--platform", "linux/not-exist-arch",
)
h.AssertContainsMatch(t, output, "Pulling image '.*test/builder.*' with platform 'linux/not-exist-arch")
})
})
})

when("build --buildpack <flattened buildpack>", func() {
Expand Down
4 changes: 4 additions & 0 deletions acceptance/invoke/pack.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ const (
FlattenBuilderCreationV2
FixesRunImageMetadata
ManifestCommands
PlatformOption
MultiPlatformBuildersAndBuildPackages
)

Expand Down Expand Up @@ -279,6 +280,9 @@ var featureTests = map[Feature]func(i *PackInvoker) bool{
ManifestCommands: func(i *PackInvoker) bool {
return i.atLeast("v0.34.0")
},
PlatformOption: func(i *PackInvoker) bool {
return i.atLeast("v0.34.0")
},
MultiPlatformBuildersAndBuildPackages: func(i *PackInvoker) bool {
return i.atLeast("v0.34.0")
},
Expand Down
5 changes: 5 additions & 0 deletions internal/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ type BuildFlags struct {
Builder string
Registry string
RunImage string
Platform string
Policy string
Network string
DescriptorPath string
Expand Down Expand Up @@ -132,6 +133,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
if err != nil {
return errors.Wrapf(err, "parsing pull policy %s", flags.Policy)
}

var lifecycleImage string
if flags.LifecycleImage != "" {
ref, err := name.ParseReference(flags.LifecycleImage)
Expand All @@ -140,6 +142,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
}
lifecycleImage = ref.Name()
}

var gid = -1
if cmd.Flags().Changed("gid") {
gid = flags.GID
Expand All @@ -165,6 +168,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
Image: inputImageName.Name(),
Publish: flags.Publish,
DockerHost: flags.DockerHost,
Platform: flags.Platform,
PullPolicy: pullPolicy,
ClearCache: flags.ClearCache,
TrustBuilder: func(string) bool {
Expand Down Expand Up @@ -257,6 +261,7 @@ Special value 'inherit' may be used in which case DOCKER_HOST environment variab
This option may set DOCKER_HOST environment variable for the build container if needed.
`)
cmd.Flags().StringVar(&buildFlags.LifecycleImage, "lifecycle-image", cfg.LifecycleImage, `Custom lifecycle image to use for analysis, restore, and export when builder is untrusted.`)
cmd.Flags().StringVar(&buildFlags.Platform, "platform", "", `Platform to build on (e.g., "linux/amd64").`)
cmd.Flags().StringVar(&buildFlags.Policy, "pull-policy", "", `Pull policy to use. Accepted values are always, never, and if-not-present. (default "always")`)
cmd.Flags().StringVarP(&buildFlags.Registry, "buildpack-registry", "r", cfg.DefaultRegistryName, "Buildpack Registry by name")
cmd.Flags().StringVar(&buildFlags.RunImage, "run-image", "", "Run image (defaults to default stack's run image)")
Expand Down
20 changes: 20 additions & 0 deletions internal/commands/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,17 @@ func testBuildCommand(t *testing.T, when spec.G, it spec.S) {
})
})

when("--platform", func() {
it("sets platform", func() {
mockClient.EXPECT().
Build(gomock.Any(), EqBuildOptionsWithPlatform("linux/amd64")).
Return(nil)

command.SetArgs([]string{"image", "--builder", "my-builder", "--platform", "linux/amd64"})
h.AssertNil(t, command.Execute())
})
})

when("--pull-policy", func() {
it("sets pull-policy=never", func() {
mockClient.EXPECT().
Expand Down Expand Up @@ -958,6 +969,15 @@ func EqBuildOptionsDefaultProcess(defaultProc string) gomock.Matcher {
}
}

func EqBuildOptionsWithPlatform(platform string) gomock.Matcher {
return buildOptionsMatcher{
description: fmt.Sprintf("Platform=%s", platform),
equals: func(o client.BuildOptions) bool {
return o.Platform == platform
},
}
}

func EqBuildOptionsWithPullPolicy(policy image.PullPolicy) gomock.Matcher {
return buildOptionsMatcher{
description: fmt.Sprintf("PullPolicy=%s", policy),
Expand Down
105 changes: 70 additions & 35 deletions pkg/client/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,9 @@ type BuildOptions struct {
// Process type that will be used when setting container start command.
DefaultProcessType string

// Platform is the desired platform to build on (e.g., linux/amd64)
Platform string

// Strategy for updating local images before a build.
PullPolicy image.PullPolicy

Expand Down Expand Up @@ -320,32 +323,54 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
return errors.Wrapf(err, "invalid builder '%s'", opts.Builder)
}

rawBuilderImage, err := c.imageFetcher.Fetch(ctx, builderRef.Name(), image.FetchOptions{Daemon: true, PullPolicy: opts.PullPolicy})
requestedTarget := func() *dist.Target {
if opts.Platform == "" {
return nil
}
parts := strings.Split(opts.Platform, "/")
switch len(parts) {
case 0:
return nil
case 1:
return &dist.Target{OS: parts[0]}
case 2:
return &dist.Target{OS: parts[0], Arch: parts[1]}
default:
return &dist.Target{OS: parts[0], Arch: parts[1], ArchVariant: parts[2]}
}
}()

rawBuilderImage, err := c.imageFetcher.Fetch(
ctx,
builderRef.Name(),
image.FetchOptions{
Daemon: true,
Target: requestedTarget,
PullPolicy: opts.PullPolicy},
)
if err != nil {
return errors.Wrapf(err, "failed to fetch builder image '%s'", builderRef.Name())
}

builderOS, err := rawBuilderImage.OS()
if err != nil {
return errors.Wrapf(err, "getting builder OS")
}

builderArch, err := rawBuilderImage.Architecture()
if err != nil {
return errors.Wrapf(err, "getting builder architecture")
var targetToUse *dist.Target
if requestedTarget != nil {
targetToUse = requestedTarget
} else {
targetToUse, err = getTargetFromBuilder(rawBuilderImage)
if err != nil {
return err
}
}

bldr, err := c.getBuilder(rawBuilderImage)
if err != nil {
return errors.Wrapf(err, "invalid builder %s", style.Symbol(opts.Builder))
}

target := &dist.Target{OS: builderOS, Arch: builderArch}

fetchOptions := image.FetchOptions{
Daemon: !opts.Publish,
PullPolicy: opts.PullPolicy,
Target: target,
Target: targetToUse,
}
runImageName := c.resolveRunImage(opts.RunImage, imgRegistry, builderRef.Context().RegistryStr(), bldr.DefaultRunImage(), opts.AdditionalMirrors, opts.Publish, fetchOptions)

Expand Down Expand Up @@ -374,12 +399,12 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
return err
}

fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Image(), bldr.Buildpacks(), bldr.Order(), bldr.StackID, opts)
fetchedBPs, order, err := c.processBuildpacks(ctx, bldr.Buildpacks(), bldr.Order(), bldr.StackID, opts, targetToUse)
if err != nil {
return err
}

fetchedExs, orderExtensions, err := c.processExtensions(ctx, bldr.Image(), bldr.Extensions(), bldr.OrderExtensions(), bldr.StackID, opts)
fetchedExs, orderExtensions, err := c.processExtensions(ctx, bldr.Extensions(), opts, targetToUse)
if err != nil {
return err
}
Expand Down Expand Up @@ -420,7 +445,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
image.FetchOptions{
Daemon: true,
PullPolicy: opts.PullPolicy,
Target: target,
Target: targetToUse,
},
)
if err != nil {
Expand Down Expand Up @@ -492,7 +517,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
defer c.docker.ImageRemove(context.Background(), ephemeralBuilder.Name(), types.RemoveOptions{Force: true})

if len(bldr.OrderExtensions()) > 0 || len(ephemeralBuilder.OrderExtensions()) > 0 {
if builderOS == "windows" {
if targetToUse.OS == "windows" {
return fmt.Errorf("builder contains image extensions which are not supported for Windows builds")
}
if !(opts.PullPolicy == image.PullAlways) {
Expand All @@ -504,7 +529,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
opts.ContainerConfig.Volumes = appendLayoutVolumes(opts.ContainerConfig.Volumes, pathsConfig)
}

processedVolumes, warnings, err := processVolumes(builderOS, opts.ContainerConfig.Volumes)
processedVolumes, warnings, err := processVolumes(targetToUse.OS, opts.ContainerConfig.Volumes)
if err != nil {
return err
}
Expand Down Expand Up @@ -735,6 +760,26 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error {
return c.logImageNameAndSha(ctx, opts.Publish, imageRef)
}

func getTargetFromBuilder(builderImage imgutil.Image) (*dist.Target, error) {
builderOS, err := builderImage.OS()
if err != nil {
return nil, fmt.Errorf("failed to get builder OS: %w", err)
}
builderArch, err := builderImage.Architecture()
if err != nil {
return nil, fmt.Errorf("failed to get builder architecture: %w", err)
}
builderArchVariant, err := builderImage.Variant()
if err != nil {
return nil, fmt.Errorf("failed to get builder architecture variant: %w", err)
}
return &dist.Target{
OS: builderOS,
Arch: builderArch,
ArchVariant: builderArchVariant,
}, nil
}

func extractSupportedLifecycleApis(labels map[string]string) ([]string, error) {
// sample contents of labels:
// {io.buildpacks.builder.metadata:\"{\"lifecycle\":{\"version\":\"0.15.3\"},\"api\":{\"buildpack\":\"0.2\",\"platform\":\"0.3\"}}",
Expand Down Expand Up @@ -1087,7 +1132,7 @@ func (c *Client) processProxyConfig(config *ProxyConfig) ProxyConfig {
// ----------
// - group:
// - A
func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Image, builderBPs []dist.ModuleInfo, builderOrder dist.Order, stackID string, opts BuildOptions) (fetchedBPs []buildpack.BuildModule, order dist.Order, err error) {
func (c *Client) processBuildpacks(ctx context.Context, builderBPs []dist.ModuleInfo, builderOrder dist.Order, stackID string, opts BuildOptions, targetToUse *dist.Target) (fetchedBPs []buildpack.BuildModule, order dist.Order, err error) {
relativeBaseDir := opts.RelativeBaseDir
declaredBPs := opts.Buildpacks

Expand Down Expand Up @@ -1130,7 +1175,7 @@ func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Ima
order = newOrder
}
default:
newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderImage, builderBPs, opts, buildpack.KindBuildpack)
newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderBPs, opts, buildpack.KindBuildpack, targetToUse)
if err != nil {
return fetchedBPs, order, err
}
Expand Down Expand Up @@ -1164,7 +1209,7 @@ func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Ima
if len(preBuildpacks) > 0 || len(postBuildpacks) > 0 {
order = builderOrder
for _, bp := range preBuildpacks {
newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderImage, builderBPs, opts, buildpack.KindBuildpack)
newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderBPs, opts, buildpack.KindBuildpack, targetToUse)
if err != nil {
return fetchedBPs, order, err
}
Expand All @@ -1173,7 +1218,7 @@ func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Ima
}

for _, bp := range postBuildpacks {
newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderImage, builderBPs, opts, buildpack.KindBuildpack)
newFetchedBPs, moduleInfo, err := c.fetchBuildpack(ctx, bp, relativeBaseDir, builderBPs, opts, buildpack.KindBuildpack, targetToUse)
if err != nil {
return fetchedBPs, order, err
}
Expand All @@ -1186,7 +1231,7 @@ func (c *Client) processBuildpacks(ctx context.Context, builderImage imgutil.Ima
return fetchedBPs, order, nil
}

func (c *Client) fetchBuildpack(ctx context.Context, bp string, relativeBaseDir string, builderImage imgutil.Image, builderBPs []dist.ModuleInfo, opts BuildOptions, kind string) ([]buildpack.BuildModule, *dist.ModuleInfo, error) {
func (c *Client) fetchBuildpack(ctx context.Context, bp string, relativeBaseDir string, builderBPs []dist.ModuleInfo, opts BuildOptions, kind string, targetToUse *dist.Target) ([]buildpack.BuildModule, *dist.ModuleInfo, error) {
pullPolicy := opts.PullPolicy
publish := opts.Publish
registry := opts.Registry
Expand All @@ -1206,19 +1251,9 @@ func (c *Client) fetchBuildpack(ctx context.Context, bp string, relativeBaseDir
Version: version,
}
default:
builderOS, err := builderImage.OS()
if err != nil {
return nil, nil, errors.Wrapf(err, "getting builder OS")
}

builderArch, err := builderImage.Architecture()
if err != nil {
return nil, nil, errors.Wrapf(err, "getting builder architecture")
}

downloadOptions := buildpack.DownloadOptions{
RegistryName: registry,
Target: &dist.Target{OS: builderOS, Arch: builderArch},
Target: targetToUse,
RelativeBaseDir: relativeBaseDir,
Daemon: !publish,
PullPolicy: pullPolicy,
Expand Down Expand Up @@ -1322,7 +1357,7 @@ func prependBuildpackToOrder(order dist.Order, bpInfo dist.ModuleInfo) (newOrder
return newOrder
}

func (c *Client) processExtensions(ctx context.Context, builderImage imgutil.Image, builderExs []dist.ModuleInfo, builderOrder dist.Order, stackID string, opts BuildOptions) (fetchedExs []buildpack.BuildModule, orderExtensions dist.Order, err error) {
func (c *Client) processExtensions(ctx context.Context, builderExs []dist.ModuleInfo, opts BuildOptions, targetToUse *dist.Target) (fetchedExs []buildpack.BuildModule, orderExtensions dist.Order, err error) {
relativeBaseDir := opts.RelativeBaseDir
declaredExs := opts.Extensions

Expand All @@ -1339,7 +1374,7 @@ func (c *Client) processExtensions(ctx context.Context, builderImage imgutil.Ima
case buildpack.FromBuilderLocator:
return nil, nil, errors.New("from builder is not supported for extensions")
default:
newFetchedExs, moduleInfo, err := c.fetchBuildpack(ctx, ex, relativeBaseDir, builderImage, builderExs, opts, buildpack.KindExtension)
newFetchedExs, moduleInfo, err := c.fetchBuildpack(ctx, ex, relativeBaseDir, builderExs, opts, buildpack.KindExtension, targetToUse)
if err != nil {
return fetchedExs, orderExtensions, err
}
Expand Down
Loading