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 039a4cec7..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: @@ -125,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) } @@ -243,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" @@ -253,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) @@ -293,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 53b69ab94..ab4fe5f03 100644 --- a/internal/builder/writer/human_readable_test.go +++ b/internal/builder/writer/human_readable_test.go @@ -315,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, @@ -349,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, @@ -574,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() 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 7019472a9..66983d080 100644 --- a/internal/builder/writer/structured_format.go +++ b/internal/builder/writer/structured_format.go @@ -38,7 +38,7 @@ 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"` @@ -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/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,