diff --git a/acceptance/testdata/pack_fixtures/inspect_builder_nested_depth_2_output.txt b/acceptance/testdata/pack_fixtures/inspect_builder_nested_depth_2_output.txt index f9e6df9b8..32de2b333 100644 --- a/acceptance/testdata/pack_fixtures/inspect_builder_nested_depth_2_output.txt +++ b/acceptance/testdata/pack_fixtures/inspect_builder_nested_depth_2_output.txt @@ -46,12 +46,6 @@ Detection Order: │ └ simple/nested-level-2@nested-l2-version └ read/env@read-env-version (optional) -Extensions: - (none) - -Detection Order (Extensions): - (none) - LOCAL: Created By: @@ -97,9 +91,3 @@ Detection Order: │ └ Group #1: │ └ simple/nested-level-2@nested-l2-version └ read/env@read-env-version (optional) - -Extensions: - (none) - -Detection Order (Extensions): - (none) diff --git a/acceptance/testdata/pack_fixtures/inspect_builder_nested_output.txt b/acceptance/testdata/pack_fixtures/inspect_builder_nested_output.txt index c90d1c848..5b6c2a5c3 100644 --- a/acceptance/testdata/pack_fixtures/inspect_builder_nested_output.txt +++ b/acceptance/testdata/pack_fixtures/inspect_builder_nested_output.txt @@ -48,12 +48,6 @@ Detection Order: │ └ simple/layers@simple-layers-version └ read/env@read-env-version (optional) -Extensions: - (none) - -Detection Order (Extensions): - (none) - LOCAL: Created By: @@ -101,9 +95,3 @@ Detection Order: │ └ Group #1: │ └ simple/layers@simple-layers-version └ read/env@read-env-version (optional) - -Extensions: - (none) - -Detection Order (Extensions): - (none) diff --git a/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_json.txt b/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_json.txt index 3c10a8727..1106dc3af 100644 --- a/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_json.txt +++ b/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_json.txt @@ -97,9 +97,7 @@ } ] } - ], - "extensions": null, - "order_extensions": null + ] }, "local_info": { "created_by": { @@ -196,8 +194,6 @@ } ] } - ], - "extensions": null, - "order_extensions": null + ] } } diff --git a/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_yaml.txt b/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_yaml.txt index 8dafce25c..5e0a3df8b 100644 --- a/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_yaml.txt +++ b/acceptance/testdata/pack_fixtures/inspect_builder_nested_output_yaml.txt @@ -58,8 +58,6 @@ remote_info: - id: read/env version: read-env-version optional: true - extensions: [] - order_extensions: [] local_info: created_by: name: Pack CLI @@ -116,5 +114,3 @@ local_info: - id: read/env version: read-env-version optional: true - extensions: [] - order_extensions: [] diff --git a/acceptance/testdata/pack_fixtures/inspect_builder_output.txt b/acceptance/testdata/pack_fixtures/inspect_builder_output.txt index 18e817d6f..6113a26f1 100644 --- a/acceptance/testdata/pack_fixtures/inspect_builder_output.txt +++ b/acceptance/testdata/pack_fixtures/inspect_builder_output.txt @@ -42,12 +42,6 @@ Detection Order: ├ simple/layers └ read/env@read-env-version (optional) -Extensions: - (none) - -Detection Order (Extensions): - (none) - LOCAL: Created By: @@ -89,9 +83,3 @@ Detection Order: └ Group #1: ├ simple/layers └ read/env@read-env-version (optional) - -Extensions: - (none) - -Detection Order (Extensions): - (none) diff --git a/cmd/cmd.go b/cmd/cmd.go index 96a7b3d8d..aacebe8a2 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -94,7 +94,6 @@ func NewPackCommand(logger ConfigurableLogger) (*cobra.Command, error) { rootCmd.AddCommand(commands.ListTrustedBuilders(logger, cfg)) rootCmd.AddCommand(commands.CreateBuilder(logger, cfg, packClient)) rootCmd.AddCommand(commands.PackageBuildpack(logger, cfg, packClient, buildpackage.NewConfigReader())) - rootCmd.AddCommand(commands.SuggestStacks(logger)) if cfg.Experimental { rootCmd.AddCommand(commands.AddBuildpackRegistry(logger, cfg, cfgPath)) diff --git a/go.mod b/go.mod index 5c222b68b..2c50d8c96 100644 --- a/go.mod +++ b/go.mod @@ -29,9 +29,9 @@ require ( github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/sclevine/spec v1.4.0 github.com/spf13/cobra v1.7.0 - golang.org/x/crypto v0.7.0 + golang.org/x/crypto v0.8.0 golang.org/x/mod v0.10.0 - golang.org/x/oauth2 v0.6.0 + golang.org/x/oauth2 v0.7.0 golang.org/x/sync v0.1.0 golang.org/x/term v0.7.0 golang.org/x/text v0.9.0 @@ -113,7 +113,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/vbatts/tar-split v0.11.2 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect - golang.org/x/net v0.8.0 // indirect + golang.org/x/net v0.9.0 // indirect golang.org/x/sys v0.7.0 // indirect golang.org/x/tools v0.7.0 // indirect google.golang.org/appengine v1.6.7 // indirect diff --git a/go.sum b/go.sum index e21bdea36..b2c0e39a4 100644 --- a/go.sum +++ b/go.sum @@ -360,8 +360,8 @@ golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ= +golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -385,11 +385,11 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM= +golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= +golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= +golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 755e52ec1..770f1454a 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -28,6 +28,8 @@ import ( "github.com/buildpacks/pack/pkg/buildpack" "github.com/buildpacks/pack/pkg/dist" "github.com/buildpacks/pack/pkg/logging" + + lifecycleplatform "github.com/buildpacks/lifecycle/platform" ) const ( @@ -234,9 +236,16 @@ func (b *Builder) Stack() StackMetadata { return b.metadata.Stack } -// RunImages returns the run image metadata +// RunImages returns all run image metadata func (b *Builder) RunImages() []RunImageMetadata { - return b.metadata.RunImages + return append(b.metadata.RunImages, b.Stack().RunImage) +} + +// DefaultRunImage returns the default run image metadata +func (b *Builder) DefaultRunImage() RunImageMetadata { + // run.images are ensured in builder.ValidateConfig() + // per the spec, we use the first one as the default + return b.RunImages()[0] } // Mixins returns the mixins of the builder @@ -360,7 +369,7 @@ func (b *Builder) Save(logger logging.Logger, creatorMetadata CreatorMetadata) e } } - if err := validateBuildpacks(b.StackID, b.Mixins(), b.LifecycleDescriptor(), b.Buildpacks(), b.additionalBuildpacks); err != nil { + if err := b.validateBuildpacks(); err != nil { return errors.Wrap(err, "validating buildpacks") } @@ -633,24 +642,20 @@ func hasElementWithVersion(moduleList []dist.ModuleInfo, version string) bool { return false } -func validateBuildpacks(stackID string, mixins []string, lifecycleDescriptor LifecycleDescriptor, allBuildpacks []dist.ModuleInfo, bpsToValidate []buildpack.BuildModule) error { +func (b *Builder) validateBuildpacks() error { bpLookup := map[string]interface{}{} - for _, bp := range allBuildpacks { + for _, bp := range b.Buildpacks() { bpLookup[bp.FullName()] = nil } - for _, bp := range bpsToValidate { + for _, bp := range b.additionalBuildpacks { bpd := bp.Descriptor() - if err := validateLifecycleCompat(bpd, lifecycleDescriptor); err != nil { + if err := validateLifecycleCompat(bpd, b.LifecycleDescriptor()); err != nil { return err } - if len(bpd.Stacks()) >= 1 { // standard buildpack - if err := bpd.EnsureStackSupport(stackID, mixins, false); err != nil { - return err - } - } else { // order buildpack + if len(bpd.Order()) > 0 { // order buildpack for _, g := range bpd.Order() { for _, r := range g.Group { if _, ok := bpLookup[r.FullName()]; !ok { @@ -661,6 +666,30 @@ func validateBuildpacks(stackID string, mixins []string, lifecycleDescriptor Lif } } } + } else if err := bpd.EnsureStackSupport(b.StackID, b.Mixins(), false); err != nil { + return err + } else { + buildOS, err := b.Image().OS() + if err != nil { + return err + } + buildArch, err := b.Image().Architecture() + if err != nil { + return err + } + buildDistroName, err := b.Image().Label(lifecycleplatform.OSDistributionNameLabel) + if err != nil { + return err + } + buildDistroVersion, err := b.Image().Label(lifecycleplatform.OSDistributionVersionLabel) + if err != nil { + return err + } + if err := bpd.EnsureTargetSupport(buildOS, buildArch, buildDistroName, buildDistroVersion); err != nil { + return err + } + + // TODO ensure at least one run-image } } @@ -903,7 +932,12 @@ func orderFileContents(order dist.Order, orderExt dist.Order) (string, error) { func (b *Builder) stackLayer(dest string) (string, error) { buf := &bytes.Buffer{} - err := toml.NewEncoder(buf).Encode(b.metadata.Stack) + var err error + if b.metadata.Stack.RunImage.Image != "" { + err = toml.NewEncoder(buf).Encode(b.metadata.Stack) + } else if len(b.metadata.RunImages) > 0 { + err = toml.NewEncoder(buf).Encode(b.metadata.RunImages[0]) + } if err != nil { return "", errors.Wrapf(err, "failed to marshal stack.toml") } diff --git a/internal/builder/builder_test.go b/internal/builder/builder_test.go index a5d5e7b1d..d0bee062c 100644 --- a/internal/builder/builder_test.go +++ b/internal/builder/builder_test.go @@ -766,10 +766,10 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { // - 1 from stackLayer // - 1 from runImageLayer h.AssertEq(t, baseImage.NumberOfAddedLayers(), 6) - oldSha256 := "4dc0072c61fc2bd7118bbc93a432eae0012082de094455cf0a9fed20e3c44789" - newSha256 := "29cb2bce4c2350f0e86f3dd30fa3810beb409b910126a18651de750f457fedfb" + oldSha256 := "2ba2e8563f7f43533ba26047a44f3e8bb7dd009043bd73a0e6aadb02c084955c" + newSha256 := "719faea06424d01bb5788ce63c1167e8d382b2d9df8fcf3a0a54ea9b2e3b4045" if runtime.GOOS == "windows" { - newSha256 = "eaed4a1617bba5738ae5672f6aefda8add7abb2f8630c75dc97a6232879d4ae4" + newSha256 = "d99d31efba72ebf98e8101ada9e89464566e943c05367c561b116c2cb86837c9" } h.AssertContains(t, outBuf.String(), fmt.Sprintf(`buildpack 'buildpack-1-id@buildpack-1-version-1' was previously defined with different contents and will be overwritten @@ -794,7 +794,7 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("adding buildpack that already exists on the image", func() { it("skips adding buildpack that already exists", func() { logger := logging.NewLogWithWriters(&outBuf, &outBuf, logging.WithVerbose()) - diffID := "4dc0072c61fc2bd7118bbc93a432eae0012082de094455cf0a9fed20e3c44789" + diffID := "2ba2e8563f7f43533ba26047a44f3e8bb7dd009043bd73a0e6aadb02c084955c" bpLayer := dist.ModuleLayers{ "buildpack-1-id": map[string]dist.ModuleLayerInfo{ "buildpack-1-version-1": { @@ -1618,6 +1618,23 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { ) }) }) + + when("#DefaultRunImage", func() { + it.Before(func() { + subject.SetRunImage(pubbldr.RunConfig{Images: []pubbldr.RunImageConfig{{ + Image: "some/run", + Mirrors: []string{"some/mirror", "other/mirror"}, + }}}) + h.AssertNil(t, subject.Save(logger, builder.CreatorMetadata{})) + h.AssertEq(t, baseImage.IsSaved(), true) + }) + + it("adds the run.toml to the image", func() { + actual := subject.DefaultRunImage() + h.AssertEq(t, actual.Image, "some/run") + h.AssertEq(t, actual.Mirrors, []string{"some/mirror", "other/mirror"}) + }) + }) }) when("builder exists", func() { diff --git a/internal/builder/inspect.go b/internal/builder/inspect.go index 9ad09b78c..c42a89213 100644 --- a/internal/builder/inspect.go +++ b/internal/builder/inspect.go @@ -15,8 +15,7 @@ type Info struct { Description string StackID string Mixins []string - RunImage string - RunImageMirrors []string + RunImages []pubbldr.RunImageConfig Buildpacks []dist.ModuleInfo Order pubbldr.DetectionOrder BuildpackLayers dist.ModuleLayers @@ -80,7 +79,7 @@ func (i *Inspector) Inspect(name string, daemon bool, orderDetectionDepth int) ( stackID, err := labelManager.StackID() if err != nil { - return Info{}, fmt.Errorf("reading image stack id: %w", err) + // TODO log warn } mixins, err := labelManager.Mixins() @@ -126,12 +125,31 @@ func (i *Inspector) Inspect(name string, daemon bool, orderDetectionDepth int) ( APIs: metadata.Lifecycle.APIs, }) + var runImages []pubbldr.RunImageConfig + for _, ri := range metadata.RunImages { + runImages = append(runImages, pubbldr.RunImageConfig{ + Image: ri.Image, + Mirrors: ri.Mirrors, + }) + } + addStackRunImage := true + for _, ri := range runImages { + if ri.Image == metadata.Stack.RunImage.Image { + addStackRunImage = false + } + } + if addStackRunImage && metadata.Stack.RunImage.Image != "" { + runImages = append(runImages, pubbldr.RunImageConfig{ + Image: metadata.Stack.RunImage.Image, + Mirrors: metadata.Stack.RunImage.Mirrors, + }) + } + return Info{ Description: metadata.Description, StackID: stackID, Mixins: append(commonMixins, buildMixins...), - RunImage: metadata.Stack.RunImage.Image, - RunImageMirrors: metadata.Stack.RunImage.Mirrors, + RunImages: runImages, Buildpacks: sortBuildPacksByID(uniqueBuildpacks(metadata.Buildpacks)), Order: detectionOrder, BuildpackLayers: layers, diff --git a/internal/builder/inspect_test.go b/internal/builder/inspect_test.go index 43102ce38..38411ba2c 100644 --- a/internal/builder/inspect_test.go +++ b/internal/builder/inspect_test.go @@ -23,6 +23,7 @@ const ( testBuilderDescription = "Test Builder Description" testStackID = "test-builder-stack-id" testRunImage = "test/run-image" + testStackRunImage = "test/stack-run-image" ) var ( @@ -75,13 +76,19 @@ var ( Stack: testStack, Lifecycle: inspectTestLifecycle, CreatedBy: testCreatorData, + RunImages: []builder.RunImageMetadata{ + { + Image: testRunImage, + Mirrors: testRunImageMirrors, + }, + }, } testMixins = []string{"build:mixinA", "mixinX", "mixinY"} expectedTestMixins = []string{"mixinX", "mixinY", "build:mixinA"} testRunImageMirrors = []string{"test/first-run-image-mirror", "test/second-run-image-mirror"} testStack = builder.StackMetadata{ RunImage: builder.RunImageMetadata{ - Image: testRunImage, + Image: testStackRunImage, Mirrors: testRunImageMirrors, }, } @@ -218,8 +225,11 @@ func testInspect(t *testing.T, when spec.G, it spec.S) { assert.Equal(info.Description, testBuilderDescription) assert.Equal(info.StackID, testStackID) assert.Equal(info.Mixins, expectedTestMixins) - assert.Equal(info.RunImage, testRunImage) - assert.Equal(info.RunImageMirrors, testRunImageMirrors) + assert.Equal(len(info.RunImages), 2) + assert.Equal(info.RunImages[0].Image, testRunImage) + assert.Equal(info.RunImages[1].Image, testStackRunImage) + assert.Equal(info.RunImages[0].Mirrors, testRunImageMirrors) + assert.Equal(info.RunImages[1].Mirrors, testRunImageMirrors) assert.Equal(info.Buildpacks, testBuildpacks) assert.Equal(info.Order, expectedDetectionTestOrder) assert.Equal(info.BuildpackLayers, testLayers) @@ -291,7 +301,7 @@ func testInspect(t *testing.T, when spec.G, it spec.S) { }) }) - when("label manager returns an error for `StackID`", func() { + when("label manager does not return an error for `StackID`", func() { it("returns the wrapped error", func() { expectedBaseError := errors.New("label not found") @@ -304,7 +314,7 @@ func testInspect(t *testing.T, when spec.G, it spec.S) { ) _, err := inspector.Inspect(testBuilderName, true, pubbldr.OrderDetectionNone) - assert.ErrorWithMessage(err, "reading image stack id: label not found") + assert.Nil(err) }) }) diff --git a/internal/builder/writer/human_readable.go b/internal/builder/writer/human_readable.go index 2a11ecd8b..dc88df881 100644 --- a/internal/builder/writer/human_readable.go +++ b/internal/builder/writer/human_readable.go @@ -47,8 +47,8 @@ Created By: Trusted: {{.Trusted}} -Stack: - ID: {{ .Info.Stack }} +{{ if ne .Info.Stack "" -}}Stack: + ID: {{ .Info.Stack }}{{ end -}} {{- if .Verbose}} {{- if ne (len .Info.Mixins) 0 }} Mixins: @@ -61,8 +61,12 @@ Stack: {{ .RunImages }} {{ .Buildpacks }} {{ .Order }} +{{- if ne .Extensions "" }} {{ .Extensions }} -{{ .OrderExtensions}}` +{{- end }} +{{- if ne .OrderExtensions "" }} +{{ .OrderExtensions }} +{{- end }}` ) type HumanReadable struct{} @@ -121,7 +125,7 @@ func writeBuilderInfo( var warnings []string - runImagesString, runImagesWarnings, err := runImagesOutput(info.RunImage, localRunImages, info.RunImageMirrors, sharedInfo.Name) + runImagesString, runImagesWarnings, err := runImagesOutput(info.RunImages, localRunImages, sharedInfo.Name) if err != nil { return fmt.Errorf("compiling run images output: %w", err) } @@ -129,9 +133,15 @@ func writeBuilderInfo( if err != nil { return fmt.Errorf("compiling detection order output: %w", err) } - orderExtString, orderExtWarnings, err := detectionOrderExtOutput(info.OrderExtensions, sharedInfo.Name) - if err != nil { - return fmt.Errorf("compiling detection order extensions output: %w", err) + + var orderExtString string + var orderExtWarnings []string + + if info.Extensions != nil { + orderExtString, orderExtWarnings, err = detectionOrderExtOutput(info.OrderExtensions, sharedInfo.Name) + if err != nil { + return fmt.Errorf("compiling detection order extensions output: %w", err) + } } buildpacksString, buildpacksWarnings, err := buildpacksOutput(info.Buildpacks, sharedInfo.Name) if err != nil { @@ -139,19 +149,26 @@ func writeBuilderInfo( } lifecycleString, lifecycleWarnings := lifecycleOutput(info.Lifecycle, sharedInfo.Name) - extensionsString, extensionsWarnings, err := extensionsOutput(info.Extensions, sharedInfo.Name) - if err != nil { - return fmt.Errorf("compiling extensions output: %w", err) + var extensionsString string + var extensionsWarnings []string + + if info.Extensions != nil { + extensionsString, extensionsWarnings, err = extensionsOutput(info.Extensions, sharedInfo.Name) + if err != nil { + return fmt.Errorf("compiling extensions output: %w", err) + } } warnings = append(warnings, runImagesWarnings...) warnings = append(warnings, orderWarnings...) warnings = append(warnings, buildpacksWarnings...) warnings = append(warnings, lifecycleWarnings...) - warnings = append(warnings, extensionsWarnings...) - warnings = append(warnings, orderExtWarnings...) - + if info.Extensions != nil { + warnings = append(warnings, extensionsWarnings...) + warnings = append(warnings, orderExtWarnings...) + } outputTemplate, _ := template.New("").Parse(outputTemplate) + err = outputTemplate.Execute( logger.Writer(), &struct { @@ -226,9 +243,8 @@ func stringFromBool(subject bool) string { } func runImagesOutput( - runImage string, + runImages []pubbldr.RunImageConfig, localRunImages []config.RunImage, - buildRunImages []string, builderName string, ) (string, []string, error) { output := "Run Images:\n" @@ -236,36 +252,39 @@ func runImagesOutput( tabWriterBuf := bytes.Buffer{} localMirrorTabWriter := tabwriter.NewWriter(&tabWriterBuf, writerMinWidth, writerTabWidth, defaultTabWidth, writerPadChar, writerFlags) - err := writeLocalMirrors(localMirrorTabWriter, runImage, localRunImages) + err := writeLocalMirrors(localMirrorTabWriter, runImages, localRunImages) if err != nil { return "", []string{}, fmt.Errorf("writing local mirrors: %w", err) } var warnings []string - if runImage != "" { - _, err = fmt.Fprintf(localMirrorTabWriter, " %s\n", runImage) - if err != nil { - return "", []string{}, fmt.Errorf("writing to tabwriter: %w", err) - } - } else { + if len(runImages) == 0 { warnings = append( warnings, fmt.Sprintf("%s does not specify a run image", builderName), "Users must build with an explicitly specified run image", ) - } - for _, m := range buildRunImages { - _, err = fmt.Fprintf(localMirrorTabWriter, " %s\n", m) - if err != nil { - return "", []string{}, fmt.Errorf("writing to tab writer: %w", err) + } else { + for _, runImage := range runImages { + if runImage.Image != "" { + _, err = fmt.Fprintf(localMirrorTabWriter, " %s\n", runImage.Image) + if err != nil { + return "", []string{}, fmt.Errorf("writing to tabwriter: %w", err) + } + } + for _, m := range runImage.Mirrors { + _, err = fmt.Fprintf(localMirrorTabWriter, " %s\n", m) + if err != nil { + return "", []string{}, fmt.Errorf("writing to tab writer: %w", err) + } + } + err = localMirrorTabWriter.Flush() + if err != nil { + return "", []string{}, fmt.Errorf("flushing tab writer: %w", err) + } } } - err = localMirrorTabWriter.Flush() - if err != nil { - return "", []string{}, fmt.Errorf("flushing tab writer: %w", err) - } - runImageOutput := tabWriterBuf.String() if runImageOutput == "" { runImageOutput = fmt.Sprintf(" %s\n", none) @@ -276,13 +295,15 @@ func runImagesOutput( return output, warnings, nil } -func writeLocalMirrors(logWriter io.Writer, runImage string, localRunImages []config.RunImage) error { +func writeLocalMirrors(logWriter io.Writer, runImages []pubbldr.RunImageConfig, localRunImages []config.RunImage) error { for _, i := range localRunImages { - if i.Image == runImage { - for _, m := range i.Mirrors { - _, err := fmt.Fprintf(logWriter, " %s\t(user-configured)\n", m) - if err != nil { - return fmt.Errorf("writing local mirror: %s: %w", m, err) + for _, ri := range runImages { + if i.Image == ri.Image { + for _, m := range i.Mirrors { + _, err := fmt.Fprintf(logWriter, " %s\t(user-configured)\n", m) + if err != nil { + return fmt.Errorf("writing local mirror: %s: %w", m, err) + } } } } diff --git a/internal/builder/writer/human_readable_test.go b/internal/builder/writer/human_readable_test.go index 875acdba8..ab4fe5f03 100644 --- a/internal/builder/writer/human_readable_test.go +++ b/internal/builder/writer/human_readable_test.go @@ -99,6 +99,58 @@ Detection Order (Extensions): ├ test.bp.two@test.bp.two.version (optional) └ test.bp.three@test.bp.three.version ` + expectedRemoteOutputWithoutExtensions = ` +REMOTE: + +Description: Some remote description + +Created By: + Name: Pack CLI + Version: 1.2.3 + +Trusted: No + +Stack: + ID: test.stack.id + +Lifecycle: + Version: 6.7.8 + Buildpack APIs: + Deprecated: (none) + Supported: 1.2, 2.3 + Platform APIs: + Deprecated: 0.1, 1.2 + Supported: 4.5 + +Run Images: + first/local (user-configured) + second/local (user-configured) + some/run-image + first/default + second/default + +Buildpacks: + ID NAME VERSION HOMEPAGE + test.top.nested - test.top.nested.version - + test.nested - http://geocities.com/top-bp + test.bp.one - test.bp.one.version http://geocities.com/cool-bp + test.bp.two - test.bp.two.version - + test.bp.three - test.bp.three.version - + +Detection Order: + ├ Group #1: + │ ├ test.top.nested@test.top.nested.version + │ │ └ Group #1: + │ │ ├ test.nested + │ │ │ └ Group #1: + │ │ │ └ test.bp.one@test.bp.one.version (optional) + │ │ ├ test.bp.three@test.bp.three.version (optional) + │ │ └ test.nested.two@test.nested.two.version + │ │ └ Group #2: + │ │ └ test.bp.one@test.bp.one.version (optional)[cyclic] + │ └ test.bp.two@test.bp.two.version (optional) + └ test.bp.three@test.bp.three.version +` expectedLocalOutput = ` LOCAL: @@ -164,6 +216,60 @@ Detection Order (Extensions): ├ test.bp.two@test.bp.two.version (optional) └ test.bp.three@test.bp.three.version ` + + expectedLocalOutputWithoutExtensions = ` +LOCAL: + +Description: Some local description + +Created By: + Name: Pack CLI + Version: 4.5.6 + +Trusted: No + +Stack: + ID: test.stack.id + +Lifecycle: + Version: 4.5.6 + Buildpack APIs: + Deprecated: 4.5, 6.7 + Supported: 8.9, 10.11 + Platform APIs: + Deprecated: (none) + Supported: 7.8 + +Run Images: + first/local (user-configured) + second/local (user-configured) + some/run-image + first/local-default + second/local-default + +Buildpacks: + ID NAME VERSION HOMEPAGE + test.top.nested - test.top.nested.version - + test.nested - http://geocities.com/top-bp + test.bp.one - test.bp.one.version http://geocities.com/cool-bp + test.bp.two - test.bp.two.version - + test.bp.three - test.bp.three.version - + +Detection Order: + ├ Group #1: + │ ├ test.top.nested@test.top.nested.version + │ │ └ Group #1: + │ │ ├ test.nested + │ │ │ └ Group #1: + │ │ │ └ test.bp.one@test.bp.one.version (optional) + │ │ ├ test.bp.three@test.bp.three.version (optional) + │ │ └ test.nested.two@test.nested.two.version + │ │ └ Group #2: + │ │ └ test.bp.one@test.bp.one.version (optional)[cyclic] + │ └ test.bp.two@test.bp.two.version (optional) + └ test.bp.three@test.bp.three.version +` + expectedVerboseStack = ` Stack: ID: test.stack.id @@ -184,10 +290,6 @@ Run Images: expectedEmptyBuildpacks = ` Buildpacks: (none) -` - expectedEmptyExtensions = ` -Extensions: - (none) ` expectedEmptyOrder = ` Detection Order: @@ -213,8 +315,7 @@ REMOTE: Description: "Some remote description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/default", "second/default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/default", "second/default"}}}, Buildpacks: buildpacks, Order: order, Extensions: extensions, @@ -247,8 +348,7 @@ REMOTE: Description: "Some local description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/local-default", "second/local-default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/local-default", "second/local-default"}}}, Buildpacks: buildpacks, Order: order, Extensions: extensions, @@ -472,10 +572,8 @@ REMOTE: when("no run images are specified", func() { it("displays run images as (none) and warns about unset run image", func() { - localInfo.RunImage = "" - localInfo.RunImageMirrors = []string{} - remoteInfo.RunImage = "" - remoteInfo.RunImageMirrors = []string{} + localInfo.RunImages = []pubbldr.RunImageConfig{} + remoteInfo.RunImages = []pubbldr.RunImageConfig{} emptyLocalRunImages := []config.RunImage{} humanReadableWriter := writer.NewHumanReadable() @@ -508,7 +606,7 @@ REMOTE: }) when("no extensions are specified", func() { - it("displays extensions as (none)", func() { + it("displays no extensions as (none)", func() { localInfo.Extensions = []dist.ModuleInfo{} remoteInfo.Extensions = []dist.ModuleInfo{} @@ -518,7 +616,9 @@ REMOTE: err := humanReadableWriter.Print(logger, localRunImages, localInfo, remoteInfo, nil, nil, sharedBuilderInfo) assert.Nil(err) - assert.Contains(outBuf.String(), expectedEmptyExtensions) + assert.Contains(outBuf.String(), "Inspecting builder: 'test-builder'") + assert.Contains(outBuf.String(), expectedRemoteOutputWithoutExtensions) + assert.Contains(outBuf.String(), expectedLocalOutputWithoutExtensions) }) }) diff --git a/internal/builder/writer/json_test.go b/internal/builder/writer/json_test.go index aed5427ec..a7d2b597d 100644 --- a/internal/builder/writer/json_test.go +++ b/internal/builder/writer/json_test.go @@ -285,8 +285,7 @@ func testJSON(t *testing.T, when spec.G, it spec.S) { Description: "Some remote description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/default", "second/default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/default", "second/default"}}}, Buildpacks: buildpacks, Order: order, Extensions: extensions, @@ -319,8 +318,7 @@ func testJSON(t *testing.T, when spec.G, it spec.S) { Description: "Some local description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/local-default", "second/local-default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/local-default", "second/local-default"}}}, Buildpacks: buildpacks, Order: order, Extensions: extensions, @@ -450,10 +448,8 @@ func testJSON(t *testing.T, when spec.G, it spec.S) { when("no run images are specified", func() { it("displays run images as empty list", func() { - localInfo.RunImage = "" - localInfo.RunImageMirrors = []string{} - remoteInfo.RunImage = "" - remoteInfo.RunImageMirrors = []string{} + localInfo.RunImages = []pubbldr.RunImageConfig{} + remoteInfo.RunImages = []pubbldr.RunImageConfig{} emptyLocalRunImages := []config.RunImage{} jsonWriter := writer.NewJSON() diff --git a/internal/builder/writer/structured_format.go b/internal/builder/writer/structured_format.go index b661f6bbc..66983d080 100644 --- a/internal/builder/writer/structured_format.go +++ b/internal/builder/writer/structured_format.go @@ -38,13 +38,13 @@ type Stack struct { type BuilderInfo struct { Description string `json:"description,omitempty" yaml:"description,omitempty" toml:"description,omitempty"` CreatedBy builder.CreatorMetadata `json:"created_by" yaml:"created_by" toml:"created_by"` - Stack Stack `json:"stack" yaml:"stack" toml:"stack"` + Stack *Stack `json:"stack,omitempty" yaml:"stack,omitempty" toml:"stack,omitempty"` Lifecycle Lifecycle `json:"lifecycle" yaml:"lifecycle" toml:"lifecycle"` RunImages []RunImage `json:"run_images" yaml:"run_images" toml:"run_images"` Buildpacks []dist.ModuleInfo `json:"buildpacks" yaml:"buildpacks" toml:"buildpacks"` pubbldr.DetectionOrder `json:"detection_order" yaml:"detection_order" toml:"detection_order"` - Extensions []dist.ModuleInfo `json:"extensions" yaml:"extensions" toml:"extensions"` - OrderExtensions pubbldr.DetectionOrder `json:"order_extensions" yaml:"order_extensions" toml:"order_extensions"` + Extensions []dist.ModuleInfo `json:"extensions,omitempty" yaml:"extensions,omitempty" toml:"extensions,omitempty"` + OrderExtensions pubbldr.DetectionOrder `json:"order_extensions,omitempty" yaml:"order_extensions,omitempty" toml:"order_extensions,omitempty"` } type StructuredFormat struct { @@ -69,7 +69,10 @@ func (w *StructuredFormat) Print( outputInfo := InspectOutput{SharedBuilderInfo: builderInfo} if local != nil { - stack := Stack{ID: local.Stack} + var stack *Stack + if local.Stack != "" { + stack = &Stack{ID: local.Stack} + } if logger.IsVerbose() { stack.Mixins = local.Mixins @@ -84,7 +87,7 @@ func (w *StructuredFormat) Print( BuildpackAPIs: local.Lifecycle.APIs.Buildpack, PlatformAPIs: local.Lifecycle.APIs.Platform, }, - RunImages: runImages(local.RunImage, localRunImages, local.RunImageMirrors), + RunImages: runImages(local.RunImages, localRunImages), Buildpacks: local.Buildpacks, DetectionOrder: local.Order, Extensions: local.Extensions, @@ -93,7 +96,10 @@ func (w *StructuredFormat) Print( } if remote != nil { - stack := Stack{ID: remote.Stack} + var stack *Stack + if remote.Stack != "" { + stack = &Stack{ID: remote.Stack} + } if logger.IsVerbose() { stack.Mixins = remote.Mixins @@ -108,7 +114,7 @@ func (w *StructuredFormat) Print( BuildpackAPIs: remote.Lifecycle.APIs.Buildpack, PlatformAPIs: remote.Lifecycle.APIs.Platform, }, - RunImages: runImages(remote.RunImage, localRunImages, remote.RunImageMirrors), + RunImages: runImages(remote.RunImages, localRunImages), Buildpacks: remote.Buildpacks, DetectionOrder: remote.Order, Extensions: remote.Extensions, @@ -133,23 +139,24 @@ func (w *StructuredFormat) Print( return nil } -func runImages(runImage string, localRunImages []config.RunImage, buildRunImages []string) []RunImage { - var images = []RunImage{} +func runImages(runImages []pubbldr.RunImageConfig, localRunImages []config.RunImage) []RunImage { + images := []RunImage{} for _, i := range localRunImages { - if i.Image == runImage { - for _, m := range i.Mirrors { - images = append(images, RunImage{Name: m, UserConfigured: true}) + for _, runImage := range runImages { + if i.Image == runImage.Image { + for _, m := range i.Mirrors { + images = append(images, RunImage{Name: m, UserConfigured: true}) + } } } } - if runImage != "" { - images = append(images, RunImage{Name: runImage}) - } - - for _, m := range buildRunImages { - images = append(images, RunImage{Name: m}) + for _, runImage := range runImages { + images = append(images, RunImage{Name: runImage.Image}) + for _, m := range runImage.Mirrors { + images = append(images, RunImage{Name: m}) + } } return images diff --git a/internal/builder/writer/toml_test.go b/internal/builder/writer/toml_test.go index 99cc96eec..92d6bdb93 100644 --- a/internal/builder/writer/toml_test.go +++ b/internal/builder/writer/toml_test.go @@ -271,8 +271,7 @@ default = false Description: "Some remote description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/default", "second/default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/default", "second/default"}}}, Buildpacks: buildpacks, Order: order, BuildpackLayers: dist.ModuleLayers{}, @@ -303,8 +302,7 @@ default = false Description: "Some local description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/local-default", "second/local-default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/local-default", "second/local-default"}}}, Buildpacks: buildpacks, Order: order, BuildpackLayers: dist.ModuleLayers{}, @@ -430,10 +428,8 @@ default = false when("no run images are specified", func() { it("omits run images from output", func() { - localInfo.RunImage = "" - localInfo.RunImageMirrors = []string{} - remoteInfo.RunImage = "" - remoteInfo.RunImageMirrors = []string{} + localInfo.RunImages = []pubbldr.RunImageConfig{} + remoteInfo.RunImages = []pubbldr.RunImageConfig{} emptyLocalRunImages := []config.RunImage{} tomlWriter := writer.NewTOML() diff --git a/internal/builder/writer/yaml_test.go b/internal/builder/writer/yaml_test.go index 5f4cfe7e3..e1450922b 100644 --- a/internal/builder/writer/yaml_test.go +++ b/internal/builder/writer/yaml_test.go @@ -194,8 +194,7 @@ func testYAML(t *testing.T, when spec.G, it spec.S) { Description: "Some remote description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/default", "second/default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/default", "second/default"}}}, Buildpacks: buildpacks, Order: order, Extensions: extensions, @@ -228,8 +227,7 @@ func testYAML(t *testing.T, when spec.G, it spec.S) { Description: "Some local description", Stack: "test.stack.id", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"first/local-default", "second/local-default"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"first/local-default", "second/local-default"}}}, Buildpacks: buildpacks, Order: order, Extensions: extensions, @@ -359,10 +357,8 @@ func testYAML(t *testing.T, when spec.G, it spec.S) { when("no run images are specified", func() { it("displays run images as empty list", func() { - localInfo.RunImage = "" - localInfo.RunImageMirrors = []string{} - remoteInfo.RunImage = "" - remoteInfo.RunImageMirrors = []string{} + localInfo.RunImages = []pubbldr.RunImageConfig{} + remoteInfo.RunImages = []pubbldr.RunImageConfig{} emptyLocalRunImages := []config.RunImage{} yamlWriter := writer.NewYAML() diff --git a/internal/commands/builder_inspect_test.go b/internal/commands/builder_inspect_test.go index a17552d10..f784f0b7c 100644 --- a/internal/commands/builder_inspect_test.go +++ b/internal/commands/builder_inspect_test.go @@ -6,6 +6,8 @@ import ( "regexp" "testing" + pubbldr "github.com/buildpacks/pack/builder" + "github.com/buildpacks/lifecycle/api" "github.com/heroku/color" "github.com/sclevine/spec" @@ -36,13 +38,13 @@ var ( expectedLocalInfo = &client.BuilderInfo{ Description: "test-local-builder", Stack: "local-stack", - RunImage: "local/image", + RunImages: []pubbldr.RunImageConfig{{Image: "local/image"}}, Lifecycle: minimalLifecycleDescriptor, } expectedRemoteInfo = &client.BuilderInfo{ Description: "test-remote-builder", Stack: "remote-stack", - RunImage: "remote/image", + RunImages: []pubbldr.RunImageConfig{{Image: "remote/image"}}, Lifecycle: minimalLifecycleDescriptor, } expectedLocalDisplay = "Sample output for local builder" diff --git a/internal/commands/stack.go b/internal/commands/stack.go index fcfdfda05..482cd6941 100644 --- a/internal/commands/stack.go +++ b/internal/commands/stack.go @@ -9,7 +9,8 @@ import ( func NewStackCommand(logger logging.Logger) *cobra.Command { command := cobra.Command{ Use: "stack", - Short: "Interact with stacks", + Short: "(deprecated) Interact with stacks", + Long: "(Deprecated)\nStacks are deprecated in favor of using BuildImages and RunImages directly, but will continue to be supported throughout all of 2023 and '24 if not longer. Please see our docs for more details- https://buildpacks.io/docs/concepts/components/stack", RunE: nil, } diff --git a/internal/commands/stack_suggest.go b/internal/commands/stack_suggest.go index 341e35a20..66eb92cd9 100644 --- a/internal/commands/stack_suggest.go +++ b/internal/commands/stack_suggest.go @@ -19,6 +19,11 @@ type suggestedStack struct { } var suggestedStacks = []suggestedStack{ + { + ID: "Deprecation Notice", + Description: "Stacks are deprecated in favor of using BuildImages and RunImages directly, but will continue to be supported throughout all of 2023 and 2024 if not longer. Please see our docs for more details- https://buildpacks.io/docs/concepts/components/stack", + Maintainer: "CNB", + }, { ID: "heroku-20", Description: "The official Heroku stack based on Ubuntu 20.04", @@ -53,7 +58,7 @@ func stackSuggest(logger logging.Logger) *cobra.Command { cmd := &cobra.Command{ Use: "suggest", Args: cobra.NoArgs, - Short: "List the recommended stacks", + Short: "(deprecated) List the recommended stacks", Example: "pack stack suggest", RunE: logError(logger, func(*cobra.Command, []string) error { Suggest(logger) diff --git a/internal/commands/stack_suggest_test.go b/internal/commands/stack_suggest_test.go index b1b1c1eae..242998afc 100644 --- a/internal/commands/stack_suggest_test.go +++ b/internal/commands/stack_suggest_test.go @@ -32,6 +32,12 @@ func testStacksSuggestCommand(t *testing.T, when spec.G, it spec.S) { h.AssertNil(t, command.Execute()) h.AssertEq(t, outBuf.String(), `Stacks maintained by the community: + Stack ID: Deprecation Notice + Description: Stacks are deprecated in favor of using BuildImages and RunImages directly, but will continue to be supported throughout all of 2023 and 2024 if not longer. Please see our docs for more details- https://buildpacks.io/docs/concepts/components/stack + Maintainer: CNB + Build Image: + Run Image: + Stack ID: heroku-20 Description: The official Heroku stack based on Ubuntu 20.04 Maintainer: Heroku diff --git a/internal/commands/stack_test.go b/internal/commands/stack_test.go new file mode 100644 index 000000000..6c2b571b9 --- /dev/null +++ b/internal/commands/stack_test.go @@ -0,0 +1,53 @@ +package commands + +import ( + "bytes" + "testing" + + "github.com/sclevine/spec" + "github.com/sclevine/spec/report" + "github.com/spf13/cobra" + + "github.com/buildpacks/pack/pkg/logging" + h "github.com/buildpacks/pack/testhelpers" +) + +func TestStackCommand(t *testing.T) { + spec.Run(t, "StackCommand", testStackCommand, spec.Parallel(), spec.Report(report.Terminal{})) +} + +func testStackCommand(t *testing.T, when spec.G, it spec.S) { + var ( + command *cobra.Command + outBuf bytes.Buffer + ) + + it.Before(func() { + command = NewStackCommand(logging.NewLogWithWriters(&outBuf, &outBuf)) + }) + + when("#Stack", func() { + it("displays stack information", func() { + command.SetArgs([]string{}) + bb := bytes.NewBufferString("") // In most tests we don't seem to need to this, not sure why it's necessary here. + command.SetOut(bb) + h.AssertNil(t, command.Execute()) + h.AssertEq(t, bb.String(), `(Deprecated) +Stacks are deprecated in favor of using BuildImages and RunImages directly, but will continue to be supported throughout all of 2023 and '24 if not longer. Please see our docs for more details- https://buildpacks.io/docs/concepts/components/stack + +Usage: + stack [command] + +Available Commands: + completion Generate the autocompletion script for the specified shell + help Help about any command + suggest (deprecated) List the recommended stacks + +Flags: + -h, --help help for stack + +Use "stack [command] --help" for more information about a command. +`) + }) + }) +} diff --git a/internal/commands/suggest_stacks.go b/internal/commands/suggest_stacks.go deleted file mode 100644 index a2c6d0b65..000000000 --- a/internal/commands/suggest_stacks.go +++ /dev/null @@ -1,26 +0,0 @@ -package commands - -import ( - "github.com/spf13/cobra" - - "github.com/buildpacks/pack/pkg/logging" -) - -// Deprecated: Use `stack suggest` instead -func SuggestStacks(logger logging.Logger) *cobra.Command { - cmd := &cobra.Command{ - Use: "suggest-stacks", - Args: cobra.NoArgs, - Short: "Display list of recommended stacks", - Example: "pack suggest-stacks", - RunE: logError(logger, func(cmd *cobra.Command, args []string) error { - deprecationWarning(logger, "suggest-stacks", "stack suggest") - Suggest(logger) - return nil - }), - Hidden: true, - } - - AddHelpFlag(cmd, "suggest-stacks") - return cmd -} diff --git a/internal/commands/suggest_stacks_test.go b/internal/commands/suggest_stacks_test.go deleted file mode 100644 index 1dc799b74..000000000 --- a/internal/commands/suggest_stacks_test.go +++ /dev/null @@ -1,66 +0,0 @@ -package commands_test - -import ( - "bytes" - "testing" - - "github.com/heroku/color" - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - "github.com/spf13/cobra" - - "github.com/buildpacks/pack/internal/commands" - "github.com/buildpacks/pack/pkg/logging" - h "github.com/buildpacks/pack/testhelpers" -) - -func TestSuggestStacksCommand(t *testing.T) { - color.Disable(true) - defer color.Disable(false) - spec.Run(t, "Commands", testSuggestStacksCommand, spec.Parallel(), spec.Report(report.Terminal{})) -} - -func testSuggestStacksCommand(t *testing.T, when spec.G, it spec.S) { - var ( - command *cobra.Command - outBuf bytes.Buffer - ) - - it.Before(func() { - command = commands.SuggestStacks(logging.NewLogWithWriters(&outBuf, &outBuf)) - }) - - when("#SuggestStacks", func() { - it("displays stack information", func() { - command.SetArgs([]string{}) - h.AssertNil(t, command.Execute()) - h.AssertEq(t, outBuf.String(), `Warning: Command 'pack suggest-stacks' has been deprecated, please use 'pack stack suggest' instead -Stacks maintained by the community: - - Stack ID: heroku-20 - Description: The official Heroku stack based on Ubuntu 20.04 - Maintainer: Heroku - Build Image: heroku/heroku:20-cnb-build - Run Image: heroku/heroku:20-cnb - - Stack ID: io.buildpacks.stacks.bionic - Description: A minimal Paketo stack based on Ubuntu 18.04 - Maintainer: Paketo Project - Build Image: paketobuildpacks/build:base-cnb - Run Image: paketobuildpacks/run:base-cnb - - Stack ID: io.buildpacks.stacks.bionic - Description: A large Paketo stack based on Ubuntu 18.04 - Maintainer: Paketo Project - Build Image: paketobuildpacks/build:full-cnb - Run Image: paketobuildpacks/run:full-cnb - - Stack ID: io.paketo.stacks.tiny - Description: A tiny Paketo stack based on Ubuntu 18.04, similar to distroless - Maintainer: Paketo Project - Build Image: paketobuildpacks/build:tiny-cnb - Run Image: paketobuildpacks/run:tiny-cnb -`) - }) - }) -} diff --git a/pkg/buildpack/builder_test.go b/pkg/buildpack/builder_test.go index 44db5511b..5af8b6f1a 100644 --- a/pkg/buildpack/builder_test.go +++ b/pkg/buildpack/builder_test.go @@ -764,7 +764,7 @@ func testPackageBuilder(t *testing.T, when spec.G, it spec.S) { // buildpackage metadata h.ContentContains(`"io.buildpacks.buildpackage.metadata":"{\"id\":\"bp.1.id\",\"version\":\"bp.1.version\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}]}"`), // buildpack layers metadata - h.ContentContains(`"io.buildpacks.buildpack.layers":"{\"bp.1.id\":{\"bp.1.version\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}],\"layerDiffID\":\"sha256:9fa0bb03eebdd0f8e4b6d6f50471b44be83dba750624dfce15dac45975c5707b\"}}`), + h.ContentContains(`"io.buildpacks.buildpack.layers":"{\"bp.1.id\":{\"bp.1.version\":{\"api\":\"0.2\",\"stacks\":[{\"id\":\"stack.id.1\"},{\"id\":\"stack.id.2\"}],\"layerDiffID\":\"sha256:44447e95b06b73496d1891de5afb01936e9999b97ea03dad6337d9f5610807a7\"}}`), // image os h.ContentContains(`"os":"linux"`), ) diff --git a/pkg/buildpack/buildpack.go b/pkg/buildpack/buildpack.go index 96ef94d71..d32c63158 100644 --- a/pkg/buildpack/buildpack.go +++ b/pkg/buildpack/buildpack.go @@ -34,11 +34,13 @@ type BuildModule interface { type Descriptor interface { API() *api.Version EnsureStackSupport(stackID string, providedMixins []string, validateRunStageMixins bool) error + EnsureTargetSupport(os, arch, distroName, distroVersion string) error EscapedID() string Info() dist.ModuleInfo Kind() string Order() dist.Order Stacks() []dist.Stack + Targets() []dist.Target } type Blob interface { @@ -74,6 +76,9 @@ func FromBuildpackRootBlob(blob Blob, layerWriterFactory archive.TarWriterFactor if err := readDescriptor(KindBuildpack, &descriptor, blob); err != nil { return nil, err } + if err := detectPlatformSpecificValues(&descriptor, blob); err != nil { + return nil, err + } if err := validateBuildpackDescriptor(descriptor); err != nil { return nil, err } @@ -117,6 +122,35 @@ func readDescriptor(kind string, descriptor interface{}, blob Blob) error { return nil } +func detectPlatformSpecificValues(descriptor *dist.BuildpackDescriptor, blob Blob) error { + if val, err := hasFile(blob, path.Join("bin", "build")); val { + descriptor.WithLinuxBuild = true + } else if err != nil { + return err + } + if val, err := hasFile(blob, path.Join("bin", "build.bat")); val { + descriptor.WithWindowsBuild = true + } else if err != nil { + return err + } + if val, err := hasFile(blob, path.Join("bin", "build.exe")); val { + descriptor.WithWindowsBuild = true + } else if err != nil { + return err + } + return nil +} + +func hasFile(blob Blob, file string) (bool, error) { + rc, err := blob.Open() + if err != nil { + return false, errors.Wrapf(err, "open %s", "buildpack bin/") + } + defer rc.Close() + _, _, err = archive.ReadTarEntry(rc, file) + return err == nil, nil +} + func buildpackFrom(descriptor Descriptor, blob Blob, layerWriterFactory archive.TarWriterFactory) (BuildModule, error) { return &buildModule{ descriptor: descriptor, @@ -245,19 +279,11 @@ func validateBuildpackDescriptor(bpd dist.BuildpackDescriptor) error { return errors.Errorf("%s is required", style.Symbol("buildpack.version")) } - if len(bpd.Order()) == 0 && len(bpd.Stacks()) == 0 { - return errors.Errorf( - "buildpack %s: must have either %s or an %s defined", - style.Symbol(bpd.Info().FullName()), - style.Symbol("stacks"), - style.Symbol("order"), - ) - } - - if len(bpd.Order()) >= 1 && len(bpd.Stacks()) >= 1 { + if len(bpd.Order()) >= 1 && (len(bpd.Stacks()) >= 1 || len(bpd.Targets()) >= 1) { return errors.Errorf( - "buildpack %s: cannot have both %s and an %s defined", + "buildpack %s: cannot have both %s/%s and an %s defined", style.Symbol(bpd.Info().FullName()), + style.Symbol("targets"), style.Symbol("stacks"), style.Symbol("order"), ) diff --git a/pkg/buildpack/buildpack_test.go b/pkg/buildpack/buildpack_test.go index 365bc350f..d7bc44e27 100644 --- a/pkg/buildpack/buildpack_test.go +++ b/pkg/buildpack/buildpack_test.go @@ -2,6 +2,7 @@ package buildpack_test import ( "errors" + "fmt" "io" "os" "path/filepath" @@ -104,6 +105,8 @@ id = "some.stack.id" ) h.AssertNil(t, err) + h.AssertNil(t, bp.Descriptor().EnsureTargetSupport(dist.DefaultTargetOSLinux, dist.DefaultTargetArch, "", "")) + tarPath := writeBlobToFile(bp) defer os.Remove(tarPath) @@ -143,6 +146,82 @@ id = "some.stack.id" ) }) + it("translates blob to windows bat distribution format", func() { + bp, err := buildpack.FromBuildpackRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` +api = "0.9" + +[buildpack] +id = "bp.one" +version = "1.2.3" +`)) + + tarBuilder.AddDir("bin", 0700, time.Now()) + tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build.bat", 0700, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, + }, + archive.DefaultTarWriterFactory(), + ) + h.AssertNil(t, err) + + bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) + h.AssertTrue(t, bpDescriptor.WithWindowsBuild) + h.AssertFalse(t, bpDescriptor.WithLinuxBuild) + + tarPath := writeBlobToFile(bp) + defer os.Remove(tarPath) + + h.AssertOnTarEntry(t, tarPath, + "/cnb/buildpacks/bp.one/1.2.3/bin/build.bat", + h.HasFileMode(0755), + h.HasModTime(archive.NormalizedDateTime), + h.ContentEquals("build-contents"), + ) + }) + + it("translates blob to windows exe distribution format", func() { + bp, err := buildpack.FromBuildpackRootBlob( + &readerBlob{ + openFn: func() io.ReadCloser { + tarBuilder := archive.TarBuilder{} + tarBuilder.AddFile("buildpack.toml", 0700, time.Now(), []byte(` +api = "0.3" + +[buildpack] +id = "bp.one" +version = "1.2.3" +`)) + + tarBuilder.AddDir("bin", 0700, time.Now()) + tarBuilder.AddFile("bin/detect", 0700, time.Now(), []byte("detect-contents")) + tarBuilder.AddFile("bin/build.exe", 0700, time.Now(), []byte("build-contents")) + return tarBuilder.Reader(archive.DefaultTarWriterFactory()) + }, + }, + archive.DefaultTarWriterFactory(), + ) + h.AssertNil(t, err) + + bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) + h.AssertTrue(t, bpDescriptor.WithWindowsBuild) + h.AssertFalse(t, bpDescriptor.WithLinuxBuild) + + tarPath := writeBlobToFile(bp) + defer os.Remove(tarPath) + + h.AssertOnTarEntry(t, tarPath, + "/cnb/buildpacks/bp.one/1.2.3/bin/build.exe", + h.HasFileMode(0755), + h.HasModTime(archive.NormalizedDateTime), + h.ContentEquals("build-contents"), + ) + }) + it("surfaces errors encountered while reading blob", func() { realBlob := &readerBlob{ openFn: func() io.ReadCloser { @@ -164,6 +243,7 @@ id = "some.stack.id" bp, err := buildpack.FromBuildpackRootBlob( &errorBlob{ realBlob: realBlob, + limit: 4, }, archive.DefaultTarWriterFactory(), ) @@ -173,7 +253,7 @@ id = "some.stack.id" h.AssertNil(t, err) _, err = io.Copy(io.Discard, bpReader) - h.AssertError(t, err, "error from errBlob") + h.AssertError(t, err, "error from errBlob (reached limit of 4)") }) when("calculating permissions", func() { @@ -229,6 +309,10 @@ id = "some.stack.id" ) h.AssertNil(t, err) + bpDescriptor := bp.Descriptor().(*dist.BuildpackDescriptor) + h.AssertFalse(t, bpDescriptor.WithWindowsBuild) + h.AssertTrue(t, bpDescriptor.WithLinuxBuild) + tarPath := writeBlobToFile(bp) defer os.Remove(tarPath) @@ -401,12 +485,12 @@ id = "some.stack.id" }, archive.DefaultTarWriterFactory(), ) - h.AssertError(t, err, "cannot have both 'stacks' and an 'order' defined") + h.AssertError(t, err, "cannot have both 'targets'/'stacks' and an 'order' defined") }) }) when("missing stacks and order", func() { - it("returns error", func() { + it("does not return an error", func() { _, err := buildpack.FromBuildpackRootBlob( &readerBlob{ openFn: func() io.ReadCloser { @@ -421,7 +505,7 @@ version = "1.2.3" }, archive.DefaultTarWriterFactory(), ) - h.AssertError(t, err, "must have either 'stacks' or an 'order' defined") + h.AssertNil(t, err) }) }) }) @@ -460,16 +544,17 @@ version = "1.2.3" } type errorBlob struct { - notFirst bool + count int + limit int realBlob buildpack.Blob } func (e *errorBlob) Open() (io.ReadCloser, error) { - if !e.notFirst { - e.notFirst = true + if e.count < e.limit { + e.count += 1 return e.realBlob.Open() } - return nil, errors.New("error from errBlob") + return nil, errors.New(fmt.Sprintf("error from errBlob (reached limit of %d)", e.limit)) } type readerBlob struct { diff --git a/pkg/buildpack/package.go b/pkg/buildpack/package.go index 62096292a..34643226b 100644 --- a/pkg/buildpack/package.go +++ b/pkg/buildpack/package.go @@ -67,8 +67,9 @@ func extractBuildpacks(pkg Package) (mainBP BuildModule, depBPs []BuildModule, e Homepage: bpInfo.Homepage, Name: bpInfo.Name, }, - WithStacks: bpInfo.Stacks, - WithOrder: bpInfo.Order, + WithStacks: bpInfo.Stacks, + WithTargets: bpInfo.Targets, + WithOrder: bpInfo.Order, } diffID := bpInfo.LayerDiffID // Allow use in closure diff --git a/pkg/client/build.go b/pkg/client/build.go index 7eaf2a966..5134c2998 100644 --- a/pkg/client/build.go +++ b/pkg/client/build.go @@ -323,7 +323,7 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return errors.Wrapf(err, "invalid builder %s", style.Symbol(opts.Builder)) } - runImageName := c.resolveRunImage(opts.RunImage, imgRegistry, builderRef.Context().RegistryStr(), bldr.Stack(), opts.AdditionalMirrors, opts.Publish) + runImageName := c.resolveRunImage(opts.RunImage, imgRegistry, builderRef.Context().RegistryStr(), bldr.DefaultRunImage(), opts.AdditionalMirrors, opts.Publish) fetchOptions := image.FetchOptions{Daemon: !opts.Publish, PullPolicy: opts.PullPolicy} if opts.Layout() { @@ -610,7 +610,7 @@ func (c *Client) getBuilder(img imgutil.Image) (*builder.Builder, error) { if err != nil { return nil, err } - if bldr.Stack().RunImage.Image == "" { + if bldr.Stack().RunImage.Image == "" && len(bldr.RunImages()) == 0 { return nil, errors.New("builder metadata is missing run-image") } @@ -709,8 +709,9 @@ func allBuildpacks(builderImage imgutil.Image, additionalBuildpacks []buildpack. ID: id, Version: ver, }, - WithStacks: bp.Stacks, - WithOrder: bp.Order, + WithStacks: bp.Stacks, + WithTargets: bp.Targets, + WithOrder: bp.Order, } all = append(all, &desc) } diff --git a/pkg/client/common.go b/pkg/client/common.go index b526edd95..d6b4901cd 100644 --- a/pkg/client/common.go +++ b/pkg/client/common.go @@ -28,7 +28,7 @@ func (c *Client) parseTagReference(imageName string) (name.Reference, error) { return ref, nil } -func (c *Client) resolveRunImage(runImage, imgRegistry, bldrRegistry string, stackInfo builder.StackMetadata, additionalMirrors map[string][]string, publish bool) string { +func (c *Client) resolveRunImage(runImage, imgRegistry, bldrRegistry string, runImageMetadata builder.RunImageMetadata, additionalMirrors map[string][]string, publish bool) string { if runImage != "" { c.logger.Debugf("Using provided run-image %s", style.Symbol(runImage)) return runImage @@ -41,15 +41,15 @@ func (c *Client) resolveRunImage(runImage, imgRegistry, bldrRegistry string, sta runImageName := getBestRunMirror( preferredRegistry, - stackInfo.RunImage.Image, - stackInfo.RunImage.Mirrors, - additionalMirrors[stackInfo.RunImage.Image], + runImageMetadata.Image, + runImageMetadata.Mirrors, + additionalMirrors[runImageMetadata.Image], ) switch { - case runImageName == stackInfo.RunImage.Image: + case runImageName == runImageMetadata.Image: c.logger.Debugf("Selected run image %s", style.Symbol(runImageName)) - case contains(stackInfo.RunImage.Mirrors, runImageName): + case contains(runImageMetadata.Mirrors, runImageName): c.logger.Debugf("Selected run image mirror %s", style.Symbol(runImageName)) default: c.logger.Debugf("Selected run image mirror %s from local config", style.Symbol(runImageName)) diff --git a/pkg/client/common_test.go b/pkg/client/common_test.go index 732c324b7..c52b1a37a 100644 --- a/pkg/client/common_test.go +++ b/pkg/client/common_test.go @@ -59,14 +59,14 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { when("passed specific run image", func() { it("selects that run image", func() { runImgFlag := "flag/passed-run-image" - runImageName := subject.resolveRunImage(runImgFlag, defaultRegistry, "", stackInfo, nil, false) + runImageName := subject.resolveRunImage(runImgFlag, defaultRegistry, "", stackInfo.RunImage, nil, false) assert.Equal(runImageName, runImgFlag) }) }) when("publish is true", func() { it("defaults to run-image in registry publishing to", func() { - runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo, nil, true) + runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, nil, true) assert.Equal(runImageName, gcrRunMirror) }) @@ -74,7 +74,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { configMirrors := map[string][]string{ runImageName: {defaultRegistry + "/unique-run-img"}, } - runImageName := subject.resolveRunImage("", defaultRegistry, "", stackInfo, configMirrors, true) + runImageName := subject.resolveRunImage("", defaultRegistry, "", stackInfo.RunImage, configMirrors, true) assert.NotEqual(runImageName, defaultMirror) assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) @@ -83,7 +83,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { configMirrors := map[string][]string{ runImageName: {defaultRegistry + "/unique-run-img"}, } - runImageName := subject.resolveRunImage("", "test.registry.io", "", stackInfo, configMirrors, true) + runImageName := subject.resolveRunImage("", "test.registry.io", "", stackInfo.RunImage, configMirrors, true) assert.NotEqual(runImageName, defaultMirror) assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) @@ -92,7 +92,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { // If publish is false, we are using the local daemon, and want to match to the builder registry when("publish is false", func() { it("defaults to run-image in registry publishing to", func() { - runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo, nil, false) + runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, nil, false) assert.Equal(runImageName, defaultMirror) assert.NotEqual(runImageName, gcrRunMirror) }) @@ -101,7 +101,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { configMirrors := map[string][]string{ runImageName: {defaultRegistry + "/unique-run-img"}, } - runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo, configMirrors, false) + runImageName := subject.resolveRunImage("", gcrRegistry, defaultRegistry, stackInfo.RunImage, configMirrors, false) assert.NotEqual(runImageName, defaultMirror) assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) @@ -110,7 +110,7 @@ func testCommon(t *testing.T, when spec.G, it spec.S) { configMirrors := map[string][]string{ runImageName: {defaultRegistry + "/unique-run-img"}, } - runImageName := subject.resolveRunImage("", defaultRegistry, "test.registry.io", stackInfo, configMirrors, false) + runImageName := subject.resolveRunImage("", defaultRegistry, "test.registry.io", stackInfo.RunImage, configMirrors, false) assert.NotEqual(runImageName, defaultMirror) assert.Equal(runImageName, defaultRegistry+"/unique-run-img") }) diff --git a/pkg/client/inspect_builder.go b/pkg/client/inspect_builder.go index a85051d0d..75eb542ec 100644 --- a/pkg/client/inspect_builder.go +++ b/pkg/client/inspect_builder.go @@ -22,11 +22,7 @@ type BuilderInfo struct { Mixins []string // RunImage provided by the builder. - RunImage string - - // List of all run image mirrors a builder will use to provide - // the RunImage. - RunImageMirrors []string + RunImages []pubbldr.RunImageConfig // All buildpacks included within the builder. Buildpacks []dist.ModuleInfo @@ -101,8 +97,7 @@ func (c *Client) InspectBuilder(name string, daemon bool, modifiers ...BuilderIn Description: info.Description, Stack: info.StackID, Mixins: info.Mixins, - RunImage: info.RunImage, - RunImageMirrors: info.RunImageMirrors, + RunImages: info.RunImages, Buildpacks: info.Buildpacks, Order: info.Order, BuildpackLayers: info.BuildpackLayers, diff --git a/pkg/client/inspect_builder_test.go b/pkg/client/inspect_builder_test.go index 0708dd747..6b666c499 100644 --- a/pkg/client/inspect_builder_test.go +++ b/pkg/client/inspect_builder_test.go @@ -140,7 +140,15 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { "buildpack": {"deprecated": ["0.1"], "supported": ["1.2", "1.3"]}, "platform": {"deprecated": [], "supported": ["2.3", "2.4"]} }}, - "createdBy": {"name": "pack", "version": "1.2.3"} + "createdBy": {"name": "pack", "version": "1.2.3"}, + "images": [ + { + "image": "some/run-image", + "mirrors": [ + "gcr.io/some/default" + ] + } + ] }`)) assert.Succeeds(builderImage.SetLabel( @@ -221,11 +229,10 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { assert.Nil(err) want := BuilderInfo{ - Description: "Some description", - Stack: "test.stack.id", - Mixins: []string{"mixinOne", "mixinThree", "build:mixinTwo", "build:mixinFour"}, - RunImage: "some/run-image", - RunImageMirrors: []string{"gcr.io/some/default"}, + Description: "Some description", + Stack: "test.stack.id", + Mixins: []string{"mixinOne", "mixinThree", "build:mixinTwo", "build:mixinFour"}, + RunImages: []pubbldr.RunImageConfig{{Image: "some/run-image", Mirrors: []string{"gcr.io/some/default"}}}, Buildpacks: []dist.ModuleInfo{ { ID: "test.bp.one", diff --git a/pkg/client/new_buildpack_test.go b/pkg/client/new_buildpack_test.go index 6c25c46f7..c2eb0eb6e 100644 --- a/pkg/client/new_buildpack_test.go +++ b/pkg/client/new_buildpack_test.go @@ -2,7 +2,6 @@ package client_test import ( "context" - "fmt" "os" "path/filepath" "runtime" @@ -132,6 +131,5 @@ func assertBuildpackToml(t *testing.T, path string, id string) { h.AssertNil(t, err) defer f.Close() - fmt.Printf("%s\n", buildpackDescriptor) h.AssertEq(t, buildpackDescriptor.Info().ID, "example/my-cnb") } diff --git a/pkg/client/rebase.go b/pkg/client/rebase.go index 03dbebbe4..8035f997e 100644 --- a/pkg/client/rebase.go +++ b/pkg/client/rebase.go @@ -70,11 +70,9 @@ func (c *Client) Rebase(ctx context.Context, opts RebaseOptions) error { opts.RunImage, imageRef.Context().RegistryStr(), "", - builder.StackMetadata{ - RunImage: builder.RunImageMetadata{ - Image: md.Stack.RunImage.Image, - Mirrors: md.Stack.RunImage.Mirrors, - }, + builder.RunImageMetadata{ + Image: md.Stack.RunImage.Image, + Mirrors: md.Stack.RunImage.Mirrors, }, opts.AdditionalMirrors, opts.Publish) diff --git a/pkg/dist/buildmodule.go b/pkg/dist/buildmodule.go index cc09d985a..75d10d44e 100644 --- a/pkg/dist/buildmodule.go +++ b/pkg/dist/buildmodule.go @@ -51,3 +51,14 @@ type Stack struct { ID string `json:"id" toml:"id"` Mixins []string `json:"mixins,omitempty" toml:"mixins,omitempty"` } + +type Target struct { + OS string `json:"os" toml:"os"` + Arch string `json:"arch" toml:"arch"` + Distributions []Distribution `json:"distributions,omitempty" toml:"distributions,omitempty"` +} + +type Distribution struct { + Name string `json:"name" toml:"name"` + Versions []string `json:"versions,omitempty" toml:"versions,omitempty"` +} diff --git a/pkg/dist/buildpack_descriptor.go b/pkg/dist/buildpack_descriptor.go index 1f1ab160c..e54e89c1b 100644 --- a/pkg/dist/buildpack_descriptor.go +++ b/pkg/dist/buildpack_descriptor.go @@ -12,10 +12,13 @@ import ( ) type BuildpackDescriptor struct { - WithAPI *api.Version `toml:"api"` - WithInfo ModuleInfo `toml:"buildpack"` - WithStacks []Stack `toml:"stacks"` - WithOrder Order `toml:"order"` + WithAPI *api.Version `toml:"api"` + WithInfo ModuleInfo `toml:"buildpack"` + WithStacks []Stack `toml:"stacks"` + WithTargets []Target `toml:"targets,omitempty"` + WithOrder Order `toml:"order"` + WithWindowsBuild bool + WithLinuxBuild bool } func (b *BuildpackDescriptor) EscapedID() string { @@ -24,7 +27,7 @@ func (b *BuildpackDescriptor) EscapedID() string { func (b *BuildpackDescriptor) EnsureStackSupport(stackID string, providedMixins []string, validateRunStageMixins bool) error { if len(b.Stacks()) == 0 { - return nil // Order buildpack, no validation required + return nil // Order buildpack or a buildpack using Targets, no validation required } bpMixins, err := b.findMixinsForStack(stackID) @@ -50,6 +53,40 @@ func (b *BuildpackDescriptor) EnsureStackSupport(stackID string, providedMixins return nil } +func (b *BuildpackDescriptor) EnsureTargetSupport(os, arch, distroName, distroVersion string) error { + if len(b.Targets()) == 0 { + if (!b.WithLinuxBuild && !b.WithWindowsBuild) || len(b.Stacks()) > 0 { // nolint + return nil // Order buildpack or stack buildpack, no validation required + } else if b.WithLinuxBuild && os == DefaultTargetOSLinux && arch == DefaultTargetArch { + return nil + } else if b.WithWindowsBuild && os == DefaultTargetOSWindows && arch == DefaultTargetArch { + return nil + } + } + for _, target := range b.Targets() { + if target.OS == os { + if target.Arch == "*" || arch == "" || target.Arch == arch { + if len(target.Distributions) == 0 || distroName == "" || distroVersion == "" { + return nil + } + for _, distro := range target.Distributions { + if distro.Name == distroName { + if len(distro.Versions) == 0 { + return nil + } + for _, version := range distro.Versions { + if version == distroVersion { + return nil + } + } + } + } + } + } + } + return fmt.Errorf("buildpack %s does not support target: (%s %s, %s@%s)", style.Symbol(b.Info().FullName()), os, arch, distroName, distroVersion) +} + func (b *BuildpackDescriptor) Kind() string { return "buildpack" } @@ -70,6 +107,10 @@ func (b *BuildpackDescriptor) Stacks() []Stack { return b.WithStacks } +func (b *BuildpackDescriptor) Targets() []Target { + return b.WithTargets +} + func (b *BuildpackDescriptor) findMixinsForStack(stackID string) ([]string, error) { for _, s := range b.Stacks() { if s.ID == stackID || s.ID == "*" { diff --git a/pkg/dist/buildpack_descriptor_test.go b/pkg/dist/buildpack_descriptor_test.go index 0b610145e..cbeb9d3e8 100644 --- a/pkg/dist/buildpack_descriptor_test.go +++ b/pkg/dist/buildpack_descriptor_test.go @@ -150,6 +150,120 @@ func testBuildpackDescriptor(t *testing.T, when spec.G, it spec.S) { }) }) + when("validating against run image target", func() { + it("succeeds with no distribution", func() { + bp := dist.BuildpackDescriptor{ + WithInfo: dist.ModuleInfo{ + ID: "some.buildpack.id", + Version: "some.buildpack.version", + }, + WithTargets: []dist.Target{{ + OS: "fake-os", + Arch: "fake-arch", + }}, + } + + h.AssertNil(t, bp.EnsureStackSupport("some.stack.id", []string{}, true)) + h.AssertNil(t, bp.EnsureTargetSupport("fake-os", "fake-arch", "fake-distro", "0.0")) + }) + + it("succeeds with no target and bin/build.exe", func() { + bp := dist.BuildpackDescriptor{ + WithInfo: dist.ModuleInfo{ + ID: "some.buildpack.id", + Version: "some.buildpack.version", + }, + WithWindowsBuild: true, + } + + h.AssertNil(t, bp.EnsureStackSupport("some.stack.id", []string{}, true)) + h.AssertNil(t, bp.EnsureTargetSupport("windows", "amd64", "fake-distro", "0.0")) + }) + + it("succeeds with no target and bin/build", func() { + bp := dist.BuildpackDescriptor{ + WithInfo: dist.ModuleInfo{ + ID: "some.buildpack.id", + Version: "some.buildpack.version", + }, + WithLinuxBuild: true, + } + + h.AssertNil(t, bp.EnsureStackSupport("some.stack.id", []string{}, true)) + h.AssertNil(t, bp.EnsureTargetSupport("linux", "amd64", "fake-distro", "0.0")) + }) + + it("returns an error when no match", func() { + bp := dist.BuildpackDescriptor{ + WithInfo: dist.ModuleInfo{ + ID: "some.buildpack.id", + Version: "some.buildpack.version", + }, + WithTargets: []dist.Target{{ + OS: "fake-os", + Arch: "fake-arch", + }}, + } + + h.AssertNil(t, bp.EnsureStackSupport("some.stack.id", []string{}, true)) + h.AssertError(t, bp.EnsureTargetSupport("some-other-os", "fake-arch", "fake-distro", "0.0"), + "buildpack 'some.buildpack.id@some.buildpack.version' does not support target: (some-other-os fake-arch, fake-distro@0.0)") + }) + + it("succeeds with distribution", func() { + bp := dist.BuildpackDescriptor{ + WithInfo: dist.ModuleInfo{ + ID: "some.buildpack.id", + Version: "some.buildpack.version", + }, + WithTargets: []dist.Target{{ + OS: "fake-os", + Arch: "fake-arch", + Distributions: []dist.Distribution{ + { + Name: "fake-distro", + Versions: []string{"0.1"}, + }, + { + Name: "another-distro", + Versions: []string{"0.22"}, + }, + }, + }}, + } + + h.AssertNil(t, bp.EnsureStackSupport("some.stack.id", []string{}, true)) + h.AssertNil(t, bp.EnsureTargetSupport("fake-os", "fake-arch", "fake-distro", "0.1")) + }) + + it("returns an error when no distribution matches", func() { + bp := dist.BuildpackDescriptor{ + WithInfo: dist.ModuleInfo{ + ID: "some.buildpack.id", + Version: "some.buildpack.version", + }, + WithTargets: []dist.Target{{ + OS: "fake-os", + Arch: "fake-arch", + Distributions: []dist.Distribution{ + { + Name: "fake-distro", + Versions: []string{"0.1"}, + }, + { + Name: "another-distro", + Versions: []string{"0.22"}, + }, + }, + }}, + } + + h.AssertNil(t, bp.EnsureStackSupport("some.stack.id", []string{}, true)) + h.AssertError(t, bp.EnsureTargetSupport("some-other-os", "fake-arch", "fake-distro", "0.0"), + "buildpack 'some.buildpack.id@some.buildpack.version' does not support target: (some-other-os fake-arch, fake-distro@0.0)") + }) + }) + when("#Kind", func() { it("returns 'buildpack'", func() { bpDesc := dist.BuildpackDescriptor{} diff --git a/pkg/dist/dist.go b/pkg/dist/dist.go index e3eccbbe7..56e80debb 100644 --- a/pkg/dist/dist.go +++ b/pkg/dist/dist.go @@ -8,6 +8,9 @@ const ( BuildpackLayersLabel = "io.buildpacks.buildpack.layers" ExtensionLayersLabel = "io.buildpacks.extension.layers" ExtensionMetadataLabel = "io.buildpacks.extension.metadata" + DefaultTargetOSLinux = "linux" + DefaultTargetOSWindows = "windows" + DefaultTargetArch = "amd64" ) type BuildpackURI struct { @@ -51,6 +54,7 @@ type ModuleLayers map[string]map[string]ModuleLayerInfo type ModuleLayerInfo struct { API *api.Version `json:"api"` Stacks []Stack `json:"stacks,omitempty"` + Targets []Target `json:"targets,omitempty"` Order Order `json:"order,omitempty"` LayerDiffID string `json:"layerDiffID"` Homepage string `json:"homepage,omitempty"` diff --git a/pkg/dist/extension_descriptor.go b/pkg/dist/extension_descriptor.go index 8714ff8e5..b968ebf44 100644 --- a/pkg/dist/extension_descriptor.go +++ b/pkg/dist/extension_descriptor.go @@ -15,6 +15,10 @@ func (e *ExtensionDescriptor) EnsureStackSupport(_ string, _ []string, _ bool) e return nil } +func (e *ExtensionDescriptor) EnsureTargetSupport(_, _, _, _ string) error { + return nil +} + func (e *ExtensionDescriptor) EscapedID() string { return strings.ReplaceAll(e.Info().ID, "/", "_") } @@ -38,3 +42,7 @@ func (e *ExtensionDescriptor) Order() Order { func (e *ExtensionDescriptor) Stacks() []Stack { return nil } + +func (e *ExtensionDescriptor) Targets() []Target { + return nil +} diff --git a/pkg/dist/layers.go b/pkg/dist/layers.go index 51ac3a86d..590454980 100644 --- a/pkg/dist/layers.go +++ b/pkg/dist/layers.go @@ -15,6 +15,7 @@ type Descriptor interface { Info() ModuleInfo Order() Order Stacks() []Stack + Targets() []Target } func LayerDiffID(layerTarPath string) (v1.Hash, error) { @@ -45,6 +46,7 @@ func AddToLayersMD(layerMD ModuleLayers, descriptor Descriptor, diffID string) { layerMD[info.ID][info.Version] = ModuleLayerInfo{ API: descriptor.API(), Stacks: descriptor.Stacks(), + Targets: descriptor.Targets(), Order: descriptor.Order(), LayerDiffID: diffID, Homepage: info.Homepage,