Skip to content

Commit 0f0918d

Browse files
authored
Merge pull request #1596 from jjbustamante/enhancement/issue-1548-imagee-in-oci-layout-format
`pack build` command to export to OCI layout format on disk
2 parents 95c8060 + fd9dc9e commit 0f0918d

25 files changed

Lines changed: 768 additions & 29 deletions

acceptance/acceptance_test.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -579,16 +579,21 @@ func testWithoutSpecificBuilderRequirement(
579579
pack.RunSuccessfully("config", "default-builder", "paketobuildpacks/builder:base")
580580

581581
output := pack.RunSuccessfully("report")
582-
583582
version := pack.Version()
584583

584+
layoutRepoDir := filepath.Join(pack.Home(), "layout-repo")
585+
if runtime.GOOS == "windows" {
586+
layoutRepoDir = strings.ReplaceAll(layoutRepoDir, `\`, `\\`)
587+
}
588+
585589
expectedOutput := pack.FixtureManager().TemplateFixture(
586590
"report_output.txt",
587591
map[string]interface{}{
588592
"DefaultBuilder": "[REDACTED]",
589593
"Version": version,
590594
"OS": runtime.GOOS,
591595
"Arch": runtime.GOARCH,
596+
"LayoutRepoDir": layoutRepoDir,
592597
},
593598
)
594599
assert.Equal(output, expectedOutput)
@@ -598,16 +603,21 @@ func testWithoutSpecificBuilderRequirement(
598603
pack.RunSuccessfully("config", "default-builder", "paketobuildpacks/builder:base")
599604

600605
output := pack.RunSuccessfully("report", "--explicit")
601-
602606
version := pack.Version()
603607

608+
layoutRepoDir := filepath.Join(pack.Home(), "layout-repo")
609+
if runtime.GOOS == "windows" {
610+
layoutRepoDir = strings.ReplaceAll(layoutRepoDir, `\`, `\\`)
611+
}
612+
604613
expectedOutput := pack.FixtureManager().TemplateFixture(
605614
"report_output.txt",
606615
map[string]interface{}{
607616
"DefaultBuilder": "paketobuildpacks/builder:base",
608617
"Version": version,
609618
"OS": runtime.GOOS,
610619
"Arch": runtime.GOARCH,
620+
"LayoutRepoDir": layoutRepoDir,
611621
},
612622
)
613623
assert.Equal(output, expectedOutput)

acceptance/invoke/pack.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,10 @@ func (i *PackInvoker) StartWithWriter(combinedOutput *bytes.Buffer, name string,
140140
}
141141
}
142142

143+
func (i *PackInvoker) Home() string {
144+
return i.home
145+
}
146+
143147
type InterruptCmd struct {
144148
testObject *testing.T
145149
assert h.AssertionManager

acceptance/testdata/pack_fixtures/report_output.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ Pack:
22
Version: {{ .Version }}
33
OS/Arch: {{ .OS }}/{{ .Arch }}
44

5-
Default Lifecycle Version: 0.15.2
5+
Default Lifecycle Version: 0.16.0
66

7-
Supported Platform APIs: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.10
7+
Supported Platform APIs: 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 0.10, 0.11, 0.12
88

99
Config:
1010
default-builder-image = "{{ .DefaultBuilder }}"
1111
experimental = true
12+
layout-repo-dir = "{{ .LayoutRepoDir }}"

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ require (
55
github.com/Masterminds/semver v1.5.0
66
github.com/Microsoft/go-winio v0.6.0
77
github.com/apex/log v1.9.0
8-
github.com/buildpacks/imgutil v0.0.0-20230120191822-4d50b9a7e215
8+
github.com/buildpacks/imgutil v0.0.0-20230221152838-4cf98dd677d2
99
github.com/buildpacks/lifecycle v0.16.0
1010
github.com/docker/cli v23.0.1+incompatible
1111
github.com/docker/docker v20.10.23+incompatible

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7
264264
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
265265
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
266266
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
267-
github.com/buildpacks/imgutil v0.0.0-20230120191822-4d50b9a7e215 h1:V/fmMFCX0jA73zqnKxmHnYq2dsWefpdTkytsorowbG0=
268-
github.com/buildpacks/imgutil v0.0.0-20230120191822-4d50b9a7e215/go.mod h1:zL5lZzgFuv9l36n52FjomVrUHpyuZf6r1UHKaZ4LeSQ=
267+
github.com/buildpacks/imgutil v0.0.0-20230221152838-4cf98dd677d2 h1:UjLEI78jFKLQwpFI2rpgKOZyXKW1cQdy7Wf+8Z6Lu1M=
268+
github.com/buildpacks/imgutil v0.0.0-20230221152838-4cf98dd677d2/go.mod h1:zL5lZzgFuv9l36n52FjomVrUHpyuZf6r1UHKaZ4LeSQ=
269269
github.com/buildpacks/lifecycle v0.16.0 h1:Q80RNP1JImJbkOXY/z/rWD9spqgEkTe/5/JypkOxJZ8=
270270
github.com/buildpacks/lifecycle v0.16.0/go.mod h1:fiM5EwiDImyWA5kZ2fTNy0+bC4izwiCMR9rNsXbQnFc=
271271
github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=

internal/build/lifecycle_execution.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"fmt"
66
"math/rand"
7+
"path/filepath"
78
"strconv"
89

910
"github.com/buildpacks/lifecycle/api"
@@ -333,7 +334,15 @@ func (l *LifecycleExecution) Create(ctx context.Context, buildCache, launchCache
333334
withEnv,
334335
}
335336

336-
if l.opts.Publish {
337+
if l.opts.Layout {
338+
var err error
339+
opts, err = l.appendLayoutOperations(opts)
340+
if err != nil {
341+
return err
342+
}
343+
}
344+
345+
if l.opts.Publish || l.opts.Layout {
337346
authConfig, err := auth.BuildEnvVar(authn.DefaultKeychain, l.opts.Image.String(), l.opts.RunImage, l.opts.CacheImage, l.opts.PreviousImage)
338347
if err != nil {
339348
return err
@@ -737,6 +746,12 @@ func (l *LifecycleExecution) hasExtensions() bool {
737746
return len(l.opts.Builder.OrderExtensions()) > 0
738747
}
739748

749+
func (l *LifecycleExecution) appendLayoutOperations(opts []PhaseConfigProviderOperation) ([]PhaseConfigProviderOperation, error) {
750+
layoutDir := filepath.Join(paths.RootDir, "layout-repo")
751+
opts = append(opts, WithEnv("CNB_USE_LAYOUT=true", "CNB_LAYOUT_DIR="+layoutDir, "CNB_EXPERIMENTAL_MODE=warn"))
752+
return opts, nil
753+
}
754+
740755
func prependArg(arg string, args []string) []string {
741756
return append([]string{arg}, args...)
742757
}

internal/build/lifecycle_execution_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"github.com/sclevine/spec"
2323
"github.com/sclevine/spec/report"
2424

25+
"github.com/buildpacks/pack/internal/paths"
26+
2527
"github.com/buildpacks/pack/internal/build"
2628
"github.com/buildpacks/pack/internal/build/fakes"
2729
"github.com/buildpacks/pack/internal/cache"
@@ -48,6 +50,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
4850
providedClearCache bool
4951
providedPublish bool
5052
providedUseCreator bool
53+
providedLayout bool
5154
providedDockerHost string
5255
providedNetworkMode = "some-network-mode"
5356
providedRunImage = "some-run-image"
@@ -81,6 +84,7 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
8184
opts.RunImage = providedRunImage
8285
opts.UseCreator = providedUseCreator
8386
opts.Volumes = providedVolumes
87+
opts.Layout = providedLayout
8488

8589
targetImageRef, err := name.ParseReference(providedTargetImage)
8690
h.AssertNil(t, err)
@@ -1106,6 +1110,18 @@ func testLifecycleExecution(t *testing.T, when spec.G, it spec.S) {
11061110
})
11071111
})
11081112
})
1113+
1114+
when("layout", func() {
1115+
providedLayout = true
1116+
layoutRepo := filepath.Join(paths.RootDir, "layout-repo")
1117+
platformAPI = api.MustParse("0.12")
1118+
1119+
it("configures the phase with oci layout environment variables", func() {
1120+
h.AssertSliceContains(t, configProvider.ContainerConfig().Env, "CNB_USE_LAYOUT=true")
1121+
h.AssertSliceContains(t, configProvider.ContainerConfig().Env, fmt.Sprintf("CNB_LAYOUT_DIR=%s", layoutRepo))
1122+
h.AssertSliceContains(t, configProvider.ContainerConfig().Env, "CNB_EXPERIMENTAL_MODE=warn")
1123+
})
1124+
})
11091125
})
11101126

11111127
when("#Detect", func() {

internal/build/lifecycle_executor.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ var (
2929
api.MustParse("0.8"),
3030
api.MustParse("0.9"),
3131
api.MustParse("0.10"),
32+
api.MustParse("0.11"),
33+
api.MustParse("0.12"),
3234
}
3335
)
3436

@@ -79,6 +81,7 @@ type LifecycleOptions struct {
7981
TrustBuilder bool
8082
UseCreator bool
8183
Interactive bool
84+
Layout bool
8285
Termui Termui
8386
DockerHost string
8487
Cache cache.CacheOpts

internal/builder/lifecycle.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import (
1414

1515
// A snapshot of the latest tested lifecycle version values
1616
const (
17-
DefaultLifecycleVersion = "0.15.2"
17+
DefaultLifecycleVersion = "0.16.0"
1818
DefaultBuildpackAPIVersion = "0.2"
1919
)
2020

internal/commands/build.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type BuildFlags struct {
2626
ClearCache bool
2727
TrustBuilder bool
2828
Interactive bool
29+
Sparse bool
2930
DockerHost string
3031
CacheImage string
3132
Cache cache.CacheOpts
@@ -68,11 +69,12 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
6869
"be provided directly to build using `--builder`, or can be set using the `set-default-builder` command. For more " +
6970
"on how to use `pack build`, see: https://buildpacks.io/docs/app-developer-guide/build-an-app/.",
7071
RunE: logError(logger, func(cmd *cobra.Command, args []string) error {
71-
if err := validateBuildFlags(&flags, cfg, packClient, logger); err != nil {
72+
inputImageName := client.ParseInputImageReference(args[0])
73+
if err := validateBuildFlags(&flags, cfg, inputImageName, logger); err != nil {
7274
return err
7375
}
7476

75-
imageName := args[0]
77+
inputPreviousImage := client.ParseInputImageReference(flags.PreviousImage)
7678

7779
descriptor, actualDescriptorPath, err := parseProjectToml(flags.AppPath, flags.DescriptorPath)
7880
if err != nil {
@@ -150,7 +152,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
150152
AdditionalTags: flags.AdditionalTags,
151153
RunImage: flags.RunImage,
152154
Env: env,
153-
Image: imageName,
155+
Image: inputImageName.Name(),
154156
Publish: flags.Publish,
155157
DockerHost: flags.DockerHost,
156158
PullPolicy: pullPolicy,
@@ -171,17 +173,23 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
171173
Workspace: flags.Workspace,
172174
LifecycleImage: lifecycleImage,
173175
GroupID: gid,
174-
PreviousImage: flags.PreviousImage,
176+
PreviousImage: inputPreviousImage.Name(),
175177
Interactive: flags.Interactive,
176178
SBOMDestinationDir: flags.SBOMDestinationDir,
177179
ReportDestinationDir: flags.ReportDestinationDir,
178180
CreationTime: dateTime,
179181
PreBuildpacks: flags.PreBuildpacks,
180182
PostBuildpacks: flags.PostBuildpacks,
183+
LayoutConfig: &client.LayoutConfig{
184+
Sparse: flags.Sparse,
185+
InputImage: inputImageName,
186+
PreviousInputImage: inputPreviousImage,
187+
LayoutRepoDir: cfg.LayoutRepositoryDir,
188+
},
181189
}); err != nil {
182190
return errors.Wrap(err, "failed to build")
183191
}
184-
logger.Infof("Successfully built image %s", style.Symbol(imageName))
192+
logger.Infof("Successfully built image %s", style.Symbol(inputImageName.Name()))
185193
return nil
186194
}),
187195
}
@@ -248,12 +256,14 @@ This option may set DOCKER_HOST environment variable for the build container if
248256
cmd.Flags().StringVar(&buildFlags.SBOMDestinationDir, "sbom-output-dir", "", "Path to export SBoM contents.\nOmitting the flag will yield no SBoM content.")
249257
cmd.Flags().StringVar(&buildFlags.ReportDestinationDir, "report-output-dir", "", "Path to export build report.toml.\nOmitting the flag yield no report file.")
250258
cmd.Flags().BoolVar(&buildFlags.Interactive, "interactive", false, "Launch a terminal UI to depict the build process")
259+
cmd.Flags().BoolVar(&buildFlags.Sparse, "sparse", false, "Use this flag to avoid saving on disk the run-image layers when the application image is exported to OCI layout format")
251260
if !cfg.Experimental {
252261
cmd.Flags().MarkHidden("interactive")
262+
cmd.Flags().MarkHidden("sparse")
253263
}
254264
}
255265

256-
func validateBuildFlags(flags *BuildFlags, cfg config.Config, packClient PackClient, logger logging.Logger) error {
266+
func validateBuildFlags(flags *BuildFlags, cfg config.Config, inputImageRef client.InputImageReference, logger logging.Logger) error {
257267
if flags.Registry != "" && !cfg.Experimental {
258268
return client.NewExperimentError("Support for buildpack registries is currently experimental.")
259269
}
@@ -282,6 +292,10 @@ func validateBuildFlags(flags *BuildFlags, cfg config.Config, packClient PackCli
282292
return client.NewExperimentError("Interactive mode is currently experimental.")
283293
}
284294

295+
if inputImageRef.Layout() && !cfg.Experimental {
296+
return client.NewExperimentError("Exporting to OCI layout is currently experimental.")
297+
}
298+
285299
return nil
286300
}
287301

0 commit comments

Comments
 (0)