diff --git a/acceptance/acceptance_test.go b/acceptance/acceptance_test.go index 4ced93112b..adf3b18c88 100644 --- a/acceptance/acceptance_test.go +++ b/acceptance/acceptance_test.go @@ -775,8 +775,6 @@ func testAcceptance( "base_image_top_layer": h.TopLayerDiffID(t, runImageMirror), "run_image_local_mirror": localRunImageMirror, "run_image_mirror": runImageMirror, - "show_reference": lifecycle.ShouldShowReference(), - "show_processes": lifecycle.ShouldShowProcesses(), }, ) @@ -973,12 +971,6 @@ func testAcceptance( when("--default-process", func() { it("sets the default process from those in the process list", func() { - h.SkipUnless(t, pack.Supports("build --default-process"), "--default-process flag is not supported") - h.SkipUnless(t, - lifecycle.SupportsFeature(config.DefaultProcess), - "skipping default process. Lifecycle does not support it", - ) - pack.RunSuccessfully( "build", repoName, "--default-process", "hello", @@ -1360,8 +1352,6 @@ func testAcceptance( "base_image_ref": strings.Join([]string{runImageMirror, h.Digest(t, runImageMirror)}, "@"), "base_image_top_layer": h.TopLayerDiffID(t, runImageMirror), "run_image_mirror": runImageMirror, - "show_reference": lifecycle.ShouldShowReference(), - "show_processes": lifecycle.ShouldShowReference(), }, ) diff --git a/acceptance/config/asset_manager.go b/acceptance/config/asset_manager.go index a0db7d6bea..a3e570cc31 100644 --- a/acceptance/config/asset_manager.go +++ b/acceptance/config/asset_manager.go @@ -11,8 +11,9 @@ import ( "regexp" "testing" + "github.com/buildpacks/lifecycle/api" + acceptanceOS "github.com/buildpacks/pack/acceptance/os" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/blob" "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/style" diff --git a/acceptance/config/lifecycle_asset.go b/acceptance/config/lifecycle_asset.go index 789c6e7ae7..1a4f53cbcc 100644 --- a/acceptance/config/lifecycle_asset.go +++ b/acceptance/config/lifecycle_asset.go @@ -58,42 +58,22 @@ func (l *LifecycleAsset) ShouldShowReference() bool { return !l.SemVer().LessThan(semver.MustParse("0.5.0")) } -func (l *LifecycleAsset) ShouldShowProcesses() bool { - return !l.pre060() -} - type LifecycleFeature int const ( - DefaultProcess LifecycleFeature = iota - CreatorInLifecycle - DetailedCacheLogging + CreatorInLifecycle LifecycleFeature = iota ) var lifecycleFeatureTests = map[LifecycleFeature]func(l *LifecycleAsset) bool{ - DefaultProcess: func(l *LifecycleAsset) bool { - return l.atLeast070() - }, CreatorInLifecycle: func(l *LifecycleAsset) bool { return l.atLeast074() }, - DetailedCacheLogging: func(l *LifecycleAsset) bool { - return !l.pre060() - }, } func (l *LifecycleAsset) SupportsFeature(f LifecycleFeature) bool { return lifecycleFeatureTests[f](l) } -func (l *LifecycleAsset) pre060() bool { - return l.SemVer().LessThan(semver.MustParse("0.6.0")) -} - -func (l *LifecycleAsset) atLeast070() bool { - return !l.SemVer().LessThan(semver.MustParse("0.7.0")) -} - func (l *LifecycleAsset) atLeast074() bool { return !l.SemVer().LessThan(semver.MustParse("0.7.4")) } diff --git a/acceptance/testdata/pack_fixtures/inspect_image_local_output.txt b/acceptance/testdata/pack_fixtures/inspect_image_local_output.txt index 5b235e0cac..bc5af8b4b4 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_local_output.txt +++ b/acceptance/testdata/pack_fixtures/inspect_image_local_output.txt @@ -8,9 +8,7 @@ LOCAL: Stack: pack.test.stack Base Image: -{{- if .show_reference}} Reference: {{.base_image_id}} -{{- end }} Top Layer: {{.base_image_top_layer}} Run Images: @@ -21,11 +19,9 @@ Run Images: Buildpacks: ID VERSION simple/layers simple-layers-version -{{- if .show_processes}} Processes: TYPE SHELL COMMAND ARGS web (default) bash ./run 8080 hello echo hello world -{{- end }} diff --git a/acceptance/testdata/pack_fixtures/inspect_image_published_output.txt b/acceptance/testdata/pack_fixtures/inspect_image_published_output.txt index c37989c01e..98fe4909cf 100644 --- a/acceptance/testdata/pack_fixtures/inspect_image_published_output.txt +++ b/acceptance/testdata/pack_fixtures/inspect_image_published_output.txt @@ -5,9 +5,7 @@ REMOTE: Stack: pack.test.stack Base Image: -{{- if .show_reference}} Reference: {{.base_image_ref}} -{{- end }} Top Layer: {{.base_image_top_layer}} Run Images: @@ -17,13 +15,11 @@ Run Images: Buildpacks: ID VERSION simple/layers simple-layers-version -{{- if .show_processes}} Processes: TYPE SHELL COMMAND ARGS web (default) bash ./run 8080 hello echo hello world -{{- end }} LOCAL: @@ -31,9 +27,7 @@ LOCAL: Stack: pack.test.stack Base Image: -{{- if .show_reference}} Reference: {{.base_image_ref}} -{{- end }} Top Layer: {{.base_image_top_layer}} Run Images: @@ -43,11 +37,9 @@ Run Images: Buildpacks: ID VERSION simple/layers simple-layers-version -{{- if .show_processes}} Processes: TYPE SHELL COMMAND ARGS web (default) bash ./run 8080 hello echo hello world -{{- end }} diff --git a/build.go b/build.go index bc5be0f22c..53dc2714a3 100644 --- a/build.go +++ b/build.go @@ -18,7 +18,6 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/pkg/errors" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/archive" "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/builder" @@ -127,17 +126,14 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { } defer c.docker.ImageRemove(context.Background(), ephemeralBuilder.Name(), types.ImageRemoveOptions{Force: true}) - lcPlatformAPIVersion := ephemeralBuilder.LifecycleDescriptor().API.PlatformVersion - supportsPlatform := false - for _, v := range build.SupportedPlatformAPIVersions { - if api.MustParse(v).SupportsVersion(lcPlatformAPIVersion) { - supportsPlatform = true - break - } - } - if !supportsPlatform { - c.logger.Debugf("pack %s supports Platform API version(s): %s", Version, strings.Join(build.SupportedPlatformAPIVersions, ", ")) - c.logger.Debugf("Builder %s has Platform API version: %s", style.Symbol(opts.Builder), lcPlatformAPIVersion) + builderPlatformAPIs := append( + ephemeralBuilder.LifecycleDescriptor().APIs.Platform.Deprecated, + ephemeralBuilder.LifecycleDescriptor().APIs.Platform.Supported..., + ) + + if !supportsPlatformAPI(builderPlatformAPIs) { + c.logger.Debugf("pack %s supports Platform API(s): %s", Version, strings.Join(build.SupportedPlatformAPIVersions.AsStrings(), ", ")) + c.logger.Debugf("Builder %s supports Platform API(s): %s", style.Symbol(opts.Builder), strings.Join(builderPlatformAPIs.AsStrings(), ", ")) return errors.Errorf("Builder %s is incompatible with this version of pack", style.Symbol(opts.Builder)) } @@ -208,6 +204,20 @@ func (c *Client) Build(ctx context.Context, opts BuildOptions) error { return nil } +// supportsPlatformAPI determines whether pack can build using the builder based on the builder's supported Platform API versions. +func supportsPlatformAPI(builderPlatformAPIs builder.APISet) bool { + for _, packSupportedAPI := range build.SupportedPlatformAPIVersions { + for _, builderSupportedAPI := range builderPlatformAPIs { + supportsPlatform := packSupportedAPI.Compare(builderSupportedAPI) == 0 + if supportsPlatform { + return true + } + } + } + + return false +} + func (c *Client) processBuilderName(builderName string) (name.Reference, error) { if builderName == "" { return nil, errors.New("builder is a required parameter if the client has no default builder") diff --git a/build_test.go b/build_test.go index 8bd104d41e..c04519bbd3 100644 --- a/build_test.go +++ b/build_test.go @@ -20,13 +20,13 @@ import ( "github.com/Masterminds/semver" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/lifecycle/api" "github.com/docker/docker/client" "github.com/heroku/color" "github.com/onsi/gomega/ghttp" "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/blob" "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/builder" @@ -330,9 +330,13 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { Version: *semver.MustParse(builder.DefaultLifecycleVersion), }, }, - API: builder.LifecycleAPI{ - BuildpackVersion: api.MustParse("0.3"), - PlatformVersion: api.MustParse("0.2"), + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{ + Supported: builder.APISet{api.MustParse("0.2"), api.MustParse("0.3"), api.MustParse("0.4")}, + }, + Platform: builder.APIVersions{ + Supported: builder.APISet{api.MustParse("0.3"), api.MustParse("0.4")}, + }, }, }, }, @@ -1596,7 +1600,7 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { when("Lifecycle option", func() { when("Platform API", func() { - for _, supportedPlatformAPI := range []string{"0.2", "0.3"} { + for _, supportedPlatformAPI := range []string{"0.3", "0.4"} { var ( supportedPlatformAPI = supportedPlatformAPI compatibleBuilder *fakes.Image @@ -1626,9 +1630,13 @@ func testBuild(t *testing.T, when spec.G, it spec.S) { Version: *semver.MustParse(builder.DefaultLifecycleVersion), }, }, - API: builder.LifecycleAPI{ - BuildpackVersion: api.MustParse("0.3"), - PlatformVersion: api.MustParse(supportedPlatformAPI), + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{ + Supported: builder.APISet{api.MustParse("0.2"), api.MustParse("0.3"), api.MustParse("0.4")}, + }, + Platform: builder.APIVersions{ + Supported: builder.APISet{api.MustParse(supportedPlatformAPI)}, + }, }, }, }, @@ -1889,9 +1897,13 @@ func newFakeBuilderImage(t *testing.T, tmpDir, builderName, defaultBuilderStackI Version: *semver.MustParse(lifecycleVersion), }, }, - API: builder.LifecycleAPI{ - BuildpackVersion: api.MustParse("0.3"), - PlatformVersion: api.MustParse("0.2"), + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{ + Supported: builder.APISet{api.MustParse("0.2"), api.MustParse("0.3"), api.MustParse("0.4")}, + }, + Platform: builder.APIVersions{ + Supported: builder.APISet{api.MustParse("0.3"), api.MustParse("0.4")}, + }, }, }, }, diff --git a/create_builder_test.go b/create_builder_test.go index e52b0a2910..3b2076f0d2 100644 --- a/create_builder_test.go +++ b/create_builder_test.go @@ -11,6 +11,7 @@ import ( "testing" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/lifecycle/api" "github.com/golang/mock/gomock" "github.com/heroku/color" "github.com/pkg/errors" @@ -20,7 +21,6 @@ import ( "github.com/buildpacks/pack" pubbldr "github.com/buildpacks/pack/builder" pubbldpkg "github.com/buildpacks/pack/buildpackage" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/blob" "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/dist" @@ -76,7 +76,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { mockDownloader.EXPECT().Download(gomock.Any(), "https://example.fake/bp-one.tgz").Return(blob.NewBlob(filepath.Join("testdata", "buildpack")), nil).AnyTimes() mockDownloader.EXPECT().Download(gomock.Any(), "some/buildpack/dir").Return(blob.NewBlob(filepath.Join("testdata", "buildpack")), nil).AnyTimes() - mockDownloader.EXPECT().Download(gomock.Any(), "file:///some-lifecycle").Return(blob.NewBlob(filepath.Join("testdata", "lifecycle")), nil).AnyTimes() + mockDownloader.EXPECT().Download(gomock.Any(), "file:///some-lifecycle").Return(blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil).AnyTimes() mockDownloader.EXPECT().Download(gomock.Any(), "file:///some-lifecycle-platform-0-1").Return(blob.NewBlob(filepath.Join("testdata", "lifecycle-platform-0.1")), nil).AnyTimes() var err error @@ -137,10 +137,6 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", gomock.Any(), gomock.Any()).Return(fakeBuildImage, nil) } - var configureBuilderWithLifecycleAPIv0_1 = func() { - opts.Config.Lifecycle = pubbldr.LifecycleConfig{URI: "file:///some-lifecycle-platform-0-1"} - } - var successfullyCreateBuilder = func() *builder.Builder { t.Helper() @@ -403,7 +399,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { gomock.Any(), "https://github.com/buildpacks/lifecycle/releases/download/v3.4.5/lifecycle-v3.4.5+linux.x86-64.tgz", ).Return( - blob.NewBlob(filepath.Join("testdata", "lifecycle")), nil, + blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, ) err := subject.CreateBuilder(context.TODO(), opts) @@ -426,7 +422,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { builder.DefaultLifecycleVersion, ), ).Return( - blob.NewBlob(filepath.Join("testdata", "lifecycle")), nil, + blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4")), nil, ) err := subject.CreateBuilder(context.TODO(), opts) @@ -484,11 +480,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { it("should embed the lifecycle", func() { prepareFetcherWithBuildImage() prepareFetcherWithRunImages() - - bldr := successfullyCreateBuilder() - - h.AssertEq(t, bldr.LifecycleDescriptor().Info.Version.String(), "3.4.5") - h.AssertEq(t, bldr.LifecycleDescriptor().API.PlatformVersion.String(), "0.2") + successfullyCreateBuilder() layerTar, err := fakeBuildImage.FindLayerWithPath("/cnb/lifecycle") h.AssertNil(t, err) @@ -499,63 +491,20 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) { h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/exporter") h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/launcher") }) - }) - when("creation succeeds for platform API < 0.2", func() { - it("should set basic metadata", func() { - configureBuilderWithLifecycleAPIv0_1() + it("should set lifecycle descriptor", func() { prepareFetcherWithBuildImage() prepareFetcherWithRunImages() - bldr := successfullyCreateBuilder() - h.AssertEq(t, bldr.Name(), "some/builder") - h.AssertEq(t, bldr.Description(), "Some description") - h.AssertEq(t, bldr.UID(), 1234) - h.AssertEq(t, bldr.GID(), 4321) - h.AssertEq(t, bldr.StackID, "some.stack.id") - }) - - it("should set buildpack and order metadata", func() { - configureBuilderWithLifecycleAPIv0_1() - prepareFetcherWithBuildImage() - prepareFetcherWithRunImages() - - bldr := successfullyCreateBuilder() - - bpInfo := dist.BuildpackInfo{ - ID: "bp.one", - Version: "1.2.3", - } - h.AssertEq(t, bldr.Order(), dist.Order{{ - Group: []dist.BuildpackRef{{ - BuildpackInfo: bpInfo, - Optional: false, - }}, - }}) - bpInfo.Homepage = "http://one.buildpack" - h.AssertEq(t, bldr.Buildpacks(), []dist.BuildpackInfo{bpInfo}) - }) - - it("should embed the lifecycle", func() { - configureBuilderWithLifecycleAPIv0_1() - prepareFetcherWithBuildImage() - prepareFetcherWithRunImages() - - bldr := successfullyCreateBuilder() - - h.AssertEq(t, bldr.LifecycleDescriptor().Info.Version.String(), "3.4.5") - h.AssertEq(t, bldr.LifecycleDescriptor().API.PlatformVersion.String(), "0.1") - - layerTar, err := fakeBuildImage.FindLayerWithPath("/cnb/lifecycle") - h.AssertNil(t, err) - h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/detector") - h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/restorer") - h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/analyzer") - h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/builder") - h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/exporter") - h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/cacher") - h.AssertTarHasFile(t, layerTar, "/cnb/lifecycle/launcher") + h.AssertEq(t, bldr.LifecycleDescriptor().Info.Version.String(), "0.0.0") + //nolint:deprecated + h.AssertEq(t, bldr.LifecycleDescriptor().API.BuildpackVersion.String(), "0.2") + h.AssertEq(t, bldr.LifecycleDescriptor().API.PlatformVersion.String(), "0.2") + h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Buildpack.Deprecated.AsStrings(), []string{}) + h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Buildpack.Supported.AsStrings(), []string{"0.2", "0.3", "0.4"}) + h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Platform.Deprecated.AsStrings(), []string{"0.2"}) + h.AssertEq(t, bldr.LifecycleDescriptor().APIs.Platform.Supported.AsStrings(), []string{"0.3", "0.4"}) }) }) diff --git a/inspect_builder_test.go b/inspect_builder_test.go index 1822bec920..a28bdcb76f 100644 --- a/inspect_builder_test.go +++ b/inspect_builder_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/lifecycle/api" "github.com/golang/mock/gomock" "github.com/google/go-cmp/cmp" "github.com/heroku/color" @@ -71,6 +72,34 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { } }) + when("only deprecated lifecycle apis are present", func() { + it.Before(func() { + h.AssertNil(t, builderImage.SetLabel( + "io.buildpacks.builder.metadata", + `{"lifecycle": {"version": "1.2.3", "api": {"buildpack": "1.2","platform": "2.3"}}}`, + )) + }) + + it("returns has both deprecated and new fields", func() { + builderInfo, err := subject.InspectBuilder("some/builder", useDaemon) + h.AssertNil(t, err) + + h.AssertEq(t, builderInfo.Lifecycle, builder.LifecycleDescriptor{ + Info: builder.LifecycleInfo{ + Version: builder.VersionMustParse("1.2.3"), + }, + API: builder.LifecycleAPI{ + BuildpackVersion: api.MustParse("1.2"), + PlatformVersion: api.MustParse("2.3"), + }, + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{Supported: builder.APISet{api.MustParse("1.2")}}, + Platform: builder.APIVersions{Supported: builder.APISet{api.MustParse("2.3")}}, + }, + }) + }) + }) + when("the builder image has appropriate metadata labels", func() { it.Before(func() { h.AssertNil(t, builderImage.SetLabel("io.buildpacks.builder.metadata", `{ @@ -90,7 +119,10 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { "homepage": "http://geocities.com/cool-bp" } ], - "lifecycle": {"version": "1.2.3"}, + "lifecycle": {"version": "1.2.3", "api": {"buildpack": "0.1","platform": "2.3"}, "apis": { + "buildpack": {"deprecated": ["0.1"], "supported": ["1.2", "1.3"]}, + "platform": {"deprecated": [], "supported": ["2.3", "2.4"]} + }}, "createdBy": {"name": "pack", "version": "1.2.3"} }`)) @@ -110,13 +142,11 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { Mixins: []string{"mixinOne", "mixinThree", "build:mixinTwo", "build:mixinFour"}, RunImage: "some/run-image", RunImageMirrors: []string{"gcr.io/some/default"}, - Buildpacks: []dist.BuildpackInfo{ - dist.BuildpackInfo{ - ID: "test.bp.one", - Version: "1.0.0", - Homepage: "http://geocities.com/cool-bp", - }, - }, + Buildpacks: []dist.BuildpackInfo{{ + ID: "test.bp.one", + Version: "1.0.0", + Homepage: "http://geocities.com/cool-bp", + }}, Order: dist.Order{ { Group: []dist.BuildpackRef{ @@ -135,6 +165,20 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { Info: builder.LifecycleInfo{ Version: builder.VersionMustParse("1.2.3"), }, + API: builder.LifecycleAPI{ + BuildpackVersion: api.MustParse("0.1"), + PlatformVersion: api.MustParse("2.3"), + }, + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{ + Deprecated: builder.APISet{api.MustParse("0.1")}, + Supported: builder.APISet{api.MustParse("1.2"), api.MustParse("1.3")}, + }, + Platform: builder.APIVersions{ + Deprecated: builder.APISet{}, + Supported: builder.APISet{api.MustParse("2.3"), api.MustParse("2.4")}, + }, + }, }, CreatedBy: builder.CreatorMetadata{ Name: "pack", @@ -142,7 +186,7 @@ func testInspectBuilder(t *testing.T, when spec.G, it spec.S) { }, } - if diff := cmp.Diff(*builderInfo, want); diff != "" { + if diff := cmp.Diff(want, *builderInfo); diff != "" { t.Errorf("InspectBuilder() mismatch (-want +got):\n%s", diff) } }) diff --git a/internal/api/version.go b/internal/api/version.go deleted file mode 100644 index c00f30d4c0..0000000000 --- a/internal/api/version.go +++ /dev/null @@ -1,125 +0,0 @@ -package api - -import ( - "fmt" - "regexp" - "strconv" - - "github.com/pkg/errors" - - "github.com/buildpacks/pack/internal/style" -) - -var regex = regexp.MustCompile(`^v?(\d+)\.(\d*)$`) - -type Version struct { - major, - minor uint64 -} - -func MustParse(v string) *Version { - version, err := NewVersion(v) - if err != nil { - panic(err) - } - - return version -} - -func NewVersion(v string) (*Version, error) { - matches := regex.FindAllStringSubmatch(v, -1) - if len(matches) == 0 { - return nil, errors.Errorf("could not parse %s as version", style.Symbol(v)) - } - - var ( - major, minor uint64 - err error - ) - if len(matches[0]) == 3 { - major, err = strconv.ParseUint(matches[0][1], 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "parsing major %s", style.Symbol(matches[0][1])) - } - - minor, err = strconv.ParseUint(matches[0][2], 10, 64) - if err != nil { - return nil, errors.Wrapf(err, "parsing minor %s", style.Symbol(matches[0][2])) - } - } else { - return nil, errors.Errorf("could not parse version %s", style.Symbol(v)) - } - - return &Version{major: major, minor: minor}, nil -} - -func (v *Version) String() string { - return fmt.Sprintf("%d.%d", v.major, v.minor) -} - -// MarshalText makes Version satisfy the encoding.TextMarshaler interface. -func (v *Version) MarshalText() ([]byte, error) { - return []byte(v.String()), nil -} - -// UnmarshalText makes Version satisfy the encoding.TextUnmarshaler interface. -func (v *Version) UnmarshalText(text []byte) error { - s := string(text) - - parsedVersion, err := NewVersion(s) - if err != nil { - return errors.Wrapf(err, "invalid api version %s", s) - } - - v.major = parsedVersion.major - v.minor = parsedVersion.minor - - return nil -} - -// SupportsVersion determines whether this version supports a given version. If comparing two pre-stable (major == 0) -// versions, minors must match exactly. Otherwise, this minor must be greater than or equal to the given minor. Majors -// must always match. -func (v *Version) SupportsVersion(o *Version) bool { - if v.Equal(o) { - return true - } - - if v.major != 0 { - return v.major == o.major && v.minor >= o.minor - } - - return false -} - -func (v *Version) Equal(o *Version) bool { - if o != nil { - return v.Compare(o) == 0 - } - - return o == nil && v == nil -} - -func (v *Version) Compare(o *Version) int { - if v.major != o.major { - if v.major < o.major { - return -1 - } - - if v.major > o.major { - return 1 - } - } - - if v.minor != o.minor { - if v.minor < o.minor { - return -1 - } - - if v.minor > o.minor { - return 1 - } - } - - return 0 -} diff --git a/internal/api/version_test.go b/internal/api/version_test.go deleted file mode 100644 index dc382683b1..0000000000 --- a/internal/api/version_test.go +++ /dev/null @@ -1,95 +0,0 @@ -package api_test - -import ( - "testing" - - "github.com/sclevine/spec" - "github.com/sclevine/spec/report" - - "github.com/buildpacks/pack/internal/api" - h "github.com/buildpacks/pack/testhelpers" -) - -func TestAPIVersion(t *testing.T) { - spec.Run(t, "APIVersion", testAPIVersion, spec.Parallel(), spec.Report(report.Terminal{})) -} - -func testAPIVersion(t *testing.T, when spec.G, it spec.S) { - when("#Equal", func() { - it("is equal to comparison", func() { - subject := api.MustParse("0.2") - comparison := api.MustParse("0.2") - - h.AssertEq(t, subject.Equal(comparison), true) - }) - - it("is not equal to comparison", func() { - subject := api.MustParse("0.2") - comparison := api.MustParse("0.3") - - h.AssertEq(t, subject.Equal(comparison), false) - }) - }) - - when("#SupportsVersion", func() { - when("pre-stable", func() { - it("matching minor value", func() { - subject := api.MustParse("0.2") - comparison := api.MustParse("0.2") - - h.AssertEq(t, subject.SupportsVersion(comparison), true) - }) - - it("subject minor > comparison minor", func() { - subject := api.MustParse("0.2") - comparison := api.MustParse("0.1") - - h.AssertEq(t, subject.SupportsVersion(comparison), false) - }) - - it("subject minor < comparison minor", func() { - subject := api.MustParse("0.1") - comparison := api.MustParse("0.2") - - h.AssertEq(t, subject.SupportsVersion(comparison), false) - }) - }) - - when("stable", func() { - it("matching major and minor", func() { - subject := api.MustParse("1.2") - comparison := api.MustParse("1.2") - - h.AssertEq(t, subject.SupportsVersion(comparison), true) - }) - - it("matching major but minor > comparison minor", func() { - subject := api.MustParse("1.2") - comparison := api.MustParse("1.1") - - h.AssertEq(t, subject.SupportsVersion(comparison), true) - }) - - it("matching major but minor < comparison minor", func() { - subject := api.MustParse("1.1") - comparison := api.MustParse("1.2") - - h.AssertEq(t, subject.SupportsVersion(comparison), false) - }) - - it("major < comparison major", func() { - subject := api.MustParse("1.0") - comparison := api.MustParse("2.0") - - h.AssertEq(t, subject.SupportsVersion(comparison), false) - }) - - it("major > comparison major", func() { - subject := api.MustParse("2.0") - comparison := api.MustParse("1.0") - - h.AssertEq(t, subject.SupportsVersion(comparison), false) - }) - }) - }) -} diff --git a/internal/build/build.go b/internal/build/build.go index f2d836deee..3e900dfde3 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -5,6 +5,7 @@ import ( "math/rand" "time" + "github.com/buildpacks/lifecycle/api" "github.com/docker/docker/client" "github.com/google/go-containerregistry/pkg/name" "github.com/pkg/errors" @@ -17,7 +18,7 @@ import ( var ( // SupportedPlatformAPIVersions lists the Platform API versions pack supports. - SupportedPlatformAPIVersions = []string{"0.2", "0.3"} + SupportedPlatformAPIVersions = builder.APISet{api.MustParse("0.3"), api.MustParse("0.4")} ) type Builder interface { @@ -164,7 +165,7 @@ func (l *Lifecycle) Setup(opts LifecycleOptions) { l.httpsProxy = opts.HTTPSProxy l.noProxy = opts.NoProxy l.version = opts.Builder.LifecycleDescriptor().Info.Version.String() - l.platformAPIVersion = opts.Builder.LifecycleDescriptor().API.PlatformVersion.String() + l.platformAPIVersion = latestVersion(opts.Builder.LifecycleDescriptor().APIs.Platform.Supported).String() l.defaultProcessType = opts.DefaultProcessType l.fileFilter = opts.FileFilter } @@ -187,3 +188,19 @@ func randString(n int) string { } return string(b) } + +func latestVersion(versions []*api.Version) *api.Version { + var latest *api.Version + for _, version := range versions { + switch { + case version == nil: + continue + case latest == nil: + latest = version + case version.Compare(latest) > 0: + latest = version + } + } + + return latest +} diff --git a/internal/build/fakes/fake_builder.go b/internal/build/fakes/fake_builder.go index 09fa5e447a..3e819593c5 100644 --- a/internal/build/fakes/fake_builder.go +++ b/internal/build/fakes/fake_builder.go @@ -2,8 +2,8 @@ package fakes import ( "github.com/Masterminds/semver" + "github.com/buildpacks/lifecycle/api" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/builder" ) @@ -37,9 +37,13 @@ func NewFakeBuilder(ops ...func(*FakeBuilder)) (*FakeBuilder, error) { ReturnForUID: 99, ReturnForGID: 99, ReturnForLifecycleDescriptor: builder.LifecycleDescriptor{ - API: builder.LifecycleAPI{ - BuildpackVersion: buildpackVersion, - PlatformVersion: platformAPIVersion, + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{ + Supported: builder.APISet{buildpackVersion}, + }, + Platform: builder.APIVersions{ + Supported: builder.APISet{platformAPIVersion}, + }, }, Info: builder.LifecycleInfo{ Version: &builder.Version{Version: *infoVersion}, diff --git a/internal/build/phases_test.go b/internal/build/phases_test.go index e3d80f13e8..f61a590576 100644 --- a/internal/build/phases_test.go +++ b/internal/build/phases_test.go @@ -16,7 +16,8 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/internal/api" + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/pack/internal/build" "github.com/buildpacks/pack/internal/build/fakes" ilogging "github.com/buildpacks/pack/internal/logging" @@ -711,53 +712,26 @@ func testPhases(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, fakePhase.RunCallCount, 1) }) - when("platform api <= 0.2", func() { - it("configures the phase with the expected arguments", func() { - platformAPIVersion, err := api.NewVersion("0.2") - h.AssertNil(t, err) - fakeBuilder, err := fakes.NewFakeBuilder(fakes.WithPlatformVersion(platformAPIVersion)) - h.AssertNil(t, err) - verboseLifecycle := newTestLifecycle(t, true, fakes.WithBuilder(fakeBuilder)) - fakePhaseFactory := fakes.NewFakePhaseFactory() - - err = verboseLifecycle.Build(context.Background(), "test", []string{}, fakePhaseFactory) - h.AssertNil(t, err) - - configProvider := fakePhaseFactory.NewCalledWithProvider - h.AssertEq(t, configProvider.Name(), "builder") - - h.AssertSliceNotContains(t, configProvider.ContainerConfig().Cmd, "-log-level", "debug") - h.AssertIncludeAllExpectedPatterns(t, - configProvider.ContainerConfig().Cmd, - []string{"-layers", "/layers"}, - []string{"-app", "/workspace"}, - []string{"-platform", "/platform"}, - ) - }) - }) - - when("platform api > 0.2", func() { - it("configures the phase with the expected arguments", func() { - platformAPIVersion, err := api.NewVersion("0.3") - h.AssertNil(t, err) - fakeBuilder, err := fakes.NewFakeBuilder(fakes.WithPlatformVersion(platformAPIVersion)) - h.AssertNil(t, err) - verboseLifecycle := newTestLifecycle(t, true, fakes.WithBuilder(fakeBuilder)) - fakePhaseFactory := fakes.NewFakePhaseFactory() + it("configures the phase with the expected arguments", func() { + platformAPIVersion, err := api.NewVersion("0.3") + h.AssertNil(t, err) + fakeBuilder, err := fakes.NewFakeBuilder(fakes.WithPlatformVersion(platformAPIVersion)) + h.AssertNil(t, err) + verboseLifecycle := newTestLifecycle(t, true, fakes.WithBuilder(fakeBuilder)) + fakePhaseFactory := fakes.NewFakePhaseFactory() - err = verboseLifecycle.Build(context.Background(), "test", []string{}, fakePhaseFactory) - h.AssertNil(t, err) + err = verboseLifecycle.Build(context.Background(), "test", []string{}, fakePhaseFactory) + h.AssertNil(t, err) - configProvider := fakePhaseFactory.NewCalledWithProvider - h.AssertEq(t, configProvider.Name(), "builder") - h.AssertIncludeAllExpectedPatterns(t, - configProvider.ContainerConfig().Cmd, - []string{"-log-level", "debug"}, - []string{"-layers", "/layers"}, - []string{"-app", "/workspace"}, - []string{"-platform", "/platform"}, - ) - }) + configProvider := fakePhaseFactory.NewCalledWithProvider + h.AssertEq(t, configProvider.Name(), "builder") + h.AssertIncludeAllExpectedPatterns(t, + configProvider.ContainerConfig().Cmd, + []string{"-log-level", "debug"}, + []string{"-layers", "/layers"}, + []string{"-app", "/workspace"}, + []string{"-platform", "/platform"}, + ) }) it("configures the phase with the expected network mode", func() { @@ -802,8 +776,9 @@ func testPhases(t *testing.T, when spec.G, it spec.S) { verboseLifecycle := newTestLifecycle(t, true) fakePhaseFactory := fakes.NewFakePhaseFactory() expectedRepoName := "some-repo-name" + expectedRunImage := "some-run-image" - err := verboseLifecycle.Export(context.Background(), expectedRepoName, "test", false, "test", "test", "test", fakePhaseFactory) + err := verboseLifecycle.Export(context.Background(), expectedRepoName, expectedRunImage, false, "test", "test", "test", fakePhaseFactory) h.AssertNil(t, err) configProvider := fakePhaseFactory.NewCalledWithProvider @@ -814,6 +789,7 @@ func testPhases(t *testing.T, when spec.G, it spec.S) { []string{"-cache-dir", "/cache"}, []string{"-layers", "/layers"}, []string{"-app", "/workspace"}, + []string{"-run-image", expectedRunImage}, []string{expectedRepoName}, ) }) @@ -907,6 +883,19 @@ func testPhases(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, len(configProvider.ContainerOps()), 1) h.AssertFunctionName(t, configProvider.ContainerOps()[0], "WriteStackToml") }) + + it("configures the phase with default process type", func() { + lifecycle := newTestLifecycle(t, true, func(options *build.LifecycleOptions) { + options.DefaultProcessType = "test-process" + }) + fakePhaseFactory := fakes.NewFakePhaseFactory() + expectedDefaultProc := []string{"-process-type", "test-process"} + + err := lifecycle.Export(context.Background(), "test", "test", true, "test", "test", "test", fakePhaseFactory) + h.AssertNil(t, err) + configProvider := fakePhaseFactory.NewCalledWithProvider + h.AssertIncludeAllExpectedPatterns(t, configProvider.ContainerConfig().Cmd, expectedDefaultProc) + }) }) when("publish is false", func() { @@ -1003,61 +992,9 @@ func testPhases(t *testing.T, when spec.G, it spec.S) { h.AssertEq(t, len(configProvider.ContainerOps()), 1) h.AssertFunctionName(t, configProvider.ContainerOps()[0], "WriteStackToml") }) - }) - when("platform api 0.2", func() { - it("uses -image", func() { - platformAPIVersion, err := api.NewVersion("0.2") - h.AssertNil(t, err) - fakeBuilder, err := fakes.NewFakeBuilder(fakes.WithPlatformVersion(platformAPIVersion)) - h.AssertNil(t, err) - lifecycle := newTestLifecycle(t, false, fakes.WithBuilder(fakeBuilder)) - fakePhaseFactory := fakes.NewFakePhaseFactory() - expectedRunImage := "some-run-image" - - err = lifecycle.Export(context.Background(), "test", expectedRunImage, false, "test", "test", "test", fakePhaseFactory) - h.AssertNil(t, err) - - configProvider := fakePhaseFactory.NewCalledWithProvider - h.AssertEq(t, configProvider.Name(), "exporter") - h.AssertIncludeAllExpectedPatterns(t, - configProvider.ContainerConfig().Cmd, - []string{"-image", expectedRunImage}, - ) - }) - }) - - when("platform api 0.3+", func() { - var ( - fakeBuilder *fakes.FakeBuilder - err error - ) - - it.Before(func() { - platformAPIVersion, err := api.NewVersion("0.3") - h.AssertNil(t, err) - fakeBuilder, err = fakes.NewFakeBuilder(fakes.WithPlatformVersion(platformAPIVersion)) - h.AssertNil(t, err) - }) - - it("uses -run-image instead of deprecated -image", func() { - lifecycle := newTestLifecycle(t, false, fakes.WithBuilder(fakeBuilder)) - fakePhaseFactory := fakes.NewFakePhaseFactory() - expectedRunImage := "some-run-image" - - err = lifecycle.Export(context.Background(), "test", expectedRunImage, false, "test", "test", "test", fakePhaseFactory) - h.AssertNil(t, err) - - configProvider := fakePhaseFactory.NewCalledWithProvider - h.AssertEq(t, configProvider.Name(), "exporter") - h.AssertIncludeAllExpectedPatterns(t, - configProvider.ContainerConfig().Cmd, - []string{"-run-image", expectedRunImage}, - ) - }) - - it("configures the phase with default arguments", func() { - lifecycle := newTestLifecycle(t, true, fakes.WithBuilder(fakeBuilder), func(options *build.LifecycleOptions) { + it("configures the phase with default process type", func() { + lifecycle := newTestLifecycle(t, true, func(options *build.LifecycleOptions) { options.DefaultProcessType = "test-process" }) fakePhaseFactory := fakes.NewFakePhaseFactory() diff --git a/internal/builder/builder.go b/internal/builder/builder.go index ef15542d44..607843190d 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -11,6 +11,7 @@ import ( "path/filepath" "regexp" "strconv" + "strings" "time" "github.com/BurntSushi/toml" @@ -113,15 +114,13 @@ func constructBuilder(img imgutil.Image, newName string, metadata Metadata) (*Bu } func constructLifecycleDescriptor(metadata Metadata) LifecycleDescriptor { - return LifecycleDescriptor{ + return *compatDescriptor(&LifecycleDescriptor{ Info: LifecycleInfo{ Version: metadata.Lifecycle.Version, }, - API: LifecycleAPI{ - PlatformVersion: metadata.Lifecycle.API.PlatformVersion, - BuildpackVersion: metadata.Lifecycle.API.BuildpackVersion, - }, - } + API: metadata.Lifecycle.API, + APIs: metadata.Lifecycle.APIs, + }) } func addImgLabelsToBuildr(bldr *Builder) error { @@ -274,8 +273,10 @@ func (b *Builder) Save(logger logging.Logger, creatorMetadata CreatorMetadata) e } if b.lifecycle != nil { - b.metadata.Lifecycle.LifecycleInfo = b.lifecycle.Descriptor().Info - b.metadata.Lifecycle.API = b.lifecycle.Descriptor().API + lifecycleDescriptor := b.lifecycle.Descriptor() + b.metadata.Lifecycle.LifecycleInfo = lifecycleDescriptor.Info + b.metadata.Lifecycle.API = lifecycleDescriptor.API + b.metadata.Lifecycle.APIs = lifecycleDescriptor.APIs lifecycleTar, err := b.lifecycleLayer(tmpDir) if err != nil { return err @@ -439,13 +440,22 @@ func validateBuildpacks(stackID string, mixins []string, lifecycleDescriptor Lif for _, bp := range bpsToValidate { bpd := bp.Descriptor() - if !bpd.API.SupportsVersion(lifecycleDescriptor.API.BuildpackVersion) { + // TODO: Should we warn when a buildpack API version is deprecated? + compatible := false + for _, version := range append(lifecycleDescriptor.APIs.Buildpack.Supported, lifecycleDescriptor.APIs.Buildpack.Deprecated...) { + compatible = version.Compare(bpd.API) == 0 + if compatible { + break + } + } + + if !compatible { return fmt.Errorf( - "buildpack %s (Buildpack API version %s) is incompatible with lifecycle %s (Buildpack API version %s)", + "buildpack %s (Buildpack API %s) is incompatible with lifecycle %s (Buildpack API(s) %s)", style.Symbol(bpd.Info.FullName()), bpd.API.String(), style.Symbol(lifecycleDescriptor.Info.Version.String()), - lifecycleDescriptor.API.BuildpackVersion.String(), + strings.Join(lifecycleDescriptor.APIs.Buildpack.Supported.AsStrings(), ", "), ) } diff --git a/internal/builder/builder_test.go b/internal/builder/builder_test.go index 3c59c97606..3cdf1a07a5 100644 --- a/internal/builder/builder_test.go +++ b/internal/builder/builder_test.go @@ -10,16 +10,15 @@ import ( "path/filepath" "testing" - "github.com/Masterminds/semver" "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/lifecycle/api" "github.com/golang/mock/gomock" "github.com/heroku/color" "github.com/sclevine/spec" "github.com/sclevine/spec/report" pubbldr "github.com/buildpacks/pack/builder" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/archive" "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/builder/testmocks" @@ -54,19 +53,22 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { logger = ilogging.NewLogWithWriters(&outBuf, &outBuf) baseImage = fakes.NewImage("base/image", "", nil) mockController = gomock.NewController(t) + + lifecycleTarReader := archive.ReadDirAsTar( + filepath.Join("testdata", "lifecycle", "platform-0.4"), + ".", 0, 0, 0755, true, nil, + ) + + descriptorContents, err := ioutil.ReadFile(filepath.Join("testdata", "lifecycle", "platform-0.4", "lifecycle.toml")) + h.AssertNil(t, err) + + lifecycleDescriptor, err := builder.ParseDescriptor(string(descriptorContents)) + h.AssertNil(t, err) + mockLifecycle = testmocks.NewMockLifecycle(mockController) - mockLifecycle.EXPECT().Open().Return(archive.ReadDirAsTar(filepath.Join("testdata", "lifecycle"), ".", 0, 0, 0755, true, nil), nil).AnyTimes() - mockLifecycle.EXPECT().Descriptor().Return(builder.LifecycleDescriptor{ - Info: builder.LifecycleInfo{ - Version: &builder.Version{Version: *semver.MustParse("1.2.3")}, - }, - API: builder.LifecycleAPI{ - PlatformVersion: api.MustParse("0.2"), - BuildpackVersion: api.MustParse("0.2"), - }, - }).AnyTimes() + mockLifecycle.EXPECT().Open().Return(lifecycleTarReader, nil).AnyTimes() + mockLifecycle.EXPECT().Descriptor().Return(*lifecycleDescriptor).AnyTimes() - var err error bp1v1, err = ifakes.NewFakeBuildpack(dist.BuildpackDescriptor{ API: api.MustParse("0.2"), Info: dist.BuildpackInfo{ @@ -562,7 +564,9 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { subject.AddBuildpack(bp) err = subject.Save(logger, builder.CreatorMetadata{}) - h.AssertError(t, err, "buildpack 'buildpack-1-id@buildpack-1-version-1' (Buildpack API version 0.1) is incompatible with lifecycle '1.2.3' (Buildpack API version 0.2)") + h.AssertError(t, + err, + "buildpack 'buildpack-1-id@buildpack-1-version-1' (Buildpack API 0.1) is incompatible with lifecycle '0.0.0' (Buildpack API(s) 0.2, 0.3, 0.4)") }) }) @@ -601,14 +605,12 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { when("#SetLifecycle", func() { it.Before(func() { - subject.SetLifecycle(mockLifecycle) - h.AssertNil(t, subject.Save(logger, builder.CreatorMetadata{})) h.AssertEq(t, baseImage.IsSaved(), true) }) it("should set the lifecycle version successfully", func() { - h.AssertEq(t, subject.LifecycleDescriptor().Info.Version.String(), "1.2.3") + h.AssertEq(t, subject.LifecycleDescriptor().Info.Version.String(), "0.0.0") }) it("should add the lifecycle binaries as an image layer", func() { @@ -663,9 +665,14 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { var metadata builder.Metadata h.AssertNil(t, json.Unmarshal([]byte(label), &metadata)) - h.AssertEq(t, metadata.Lifecycle.Version.String(), "1.2.3") - h.AssertEq(t, metadata.Lifecycle.API.PlatformVersion.String(), "0.2") + h.AssertEq(t, metadata.Lifecycle.Version.String(), "0.0.0") h.AssertEq(t, metadata.Lifecycle.API.BuildpackVersion.String(), "0.2") + h.AssertEq(t, metadata.Lifecycle.API.PlatformVersion.String(), "0.2") + h.AssertNotNil(t, metadata.Lifecycle.APIs) + h.AssertEq(t, metadata.Lifecycle.APIs.Buildpack.Deprecated.AsStrings(), []string{}) + h.AssertEq(t, metadata.Lifecycle.APIs.Buildpack.Supported.AsStrings(), []string{"0.2", "0.3", "0.4"}) + h.AssertEq(t, metadata.Lifecycle.APIs.Platform.Deprecated.AsStrings(), []string{"0.2"}) + h.AssertEq(t, metadata.Lifecycle.APIs.Platform.Supported.AsStrings(), []string{"0.3", "0.4"}) }) }) @@ -847,7 +854,12 @@ func testBuilder(t *testing.T, when spec.G, it spec.S) { it.Before(func() { h.AssertNil(t, baseImage.SetLabel( "io.buildpacks.builder.metadata", - `{"buildpacks": [{"id": "prev.id"}], "groups": [{"buildpacks": [{"id": "prev.id"}]}], "stack": {"runImage": {"image": "prev/run", "mirrors": ["prev/mirror"]}}, "lifecycle": {"version": "6.6.6", "api": {"buildpack": "0.2", "platform": "0.2"}}}`, + `{ +"buildpacks":[{"id":"prev.id"}], +"groups":[{"buildpacks":[{"id":"prev.id"}]}], +"stack":{"runImage":{"image":"prev/run","mirrors":["prev/mirror"]}}, +"lifecycle":{"version":"6.6.6","apis":{"buildpack":{"deprecated":["0.1"],"supported":["0.2","0.3"]},"platform":{"deprecated":[],"supported":["2.3","2.4"]}}} +}`, )) var err error diff --git a/internal/builder/descriptor.go b/internal/builder/descriptor.go new file mode 100644 index 0000000000..00160312e1 --- /dev/null +++ b/internal/builder/descriptor.go @@ -0,0 +1,101 @@ +package builder + +import ( + "github.com/BurntSushi/toml" + "github.com/buildpacks/lifecycle/api" + "github.com/pkg/errors" +) + +// LifecycleDescriptor contains information described in the lifecycle.toml +type LifecycleDescriptor struct { + Info LifecycleInfo `toml:"lifecycle"` + // Deprecated: Use `LifecycleAPIs` instead + API LifecycleAPI `toml:"api"` + APIs *LifecycleAPIs `toml:"apis"` +} + +// LifecycleInfo contains information about the lifecycle +type LifecycleInfo struct { + Version *Version `toml:"version" json:"version"` +} + +// LifecycleAPI describes which API versions the lifecycle satisfies +type LifecycleAPI struct { + BuildpackVersion *api.Version `toml:"buildpack" json:"buildpack"` + PlatformVersion *api.Version `toml:"platform" json:"platform"` +} + +// LifecycleAPIs describes the supported API versions per specification +type LifecycleAPIs struct { + Buildpack APIVersions `toml:"buildpack" json:"buildpack"` + Platform APIVersions `toml:"platform" json:"platform"` +} + +type APISet []*api.Version + +func (a APISet) AsStrings() []string { + verStrings := make([]string, len(a)) + for i, version := range a { + verStrings[i] = version.String() + } + + return verStrings +} + +// APIVersions describes the supported API versions +type APIVersions struct { + Deprecated APISet `toml:"deprecated" json:"deprecated"` + Supported APISet `toml:"supported" json:"supported"` +} + +// ParseDescriptor parses LifecycleDescriptor from toml formatted string. +func ParseDescriptor(contents string) (*LifecycleDescriptor, error) { + descriptor := &LifecycleDescriptor{} + _, err := toml.Decode(contents, &descriptor) + if err != nil { + return nil, errors.Wrap(err, "decoding descriptor") + } + + return compatDescriptor(descriptor), nil +} + +// compatDescriptor provides compatibility by mapping new fields to old and vice-versa +func compatDescriptor(descriptor *LifecycleDescriptor) *LifecycleDescriptor { + if descriptor.APIs != nil { + // select earliest value for deprecated parameters + descriptor.API.BuildpackVersion = findEarliestVersion( + append(descriptor.APIs.Buildpack.Deprecated, descriptor.APIs.Buildpack.Supported...), + ) + descriptor.API.PlatformVersion = findEarliestVersion( + append(descriptor.APIs.Platform.Deprecated, descriptor.APIs.Platform.Supported...), + ) + } else { + // fill supported with deprecated field + descriptor.APIs = &LifecycleAPIs{ + Buildpack: APIVersions{ + Supported: APISet{descriptor.API.BuildpackVersion}, + }, + Platform: APIVersions{ + Supported: APISet{descriptor.API.PlatformVersion}, + }, + } + } + + return descriptor +} + +func findEarliestVersion(versions []*api.Version) *api.Version { + var earliest *api.Version + for _, version := range versions { + switch { + case version == nil: + continue + case earliest == nil: + earliest = version + case version.Compare(earliest) < 0: + earliest = version + } + } + + return earliest +} diff --git a/internal/builder/lifecycle.go b/internal/builder/lifecycle.go index 5562f89717..d7e4565278 100644 --- a/internal/builder/lifecycle.go +++ b/internal/builder/lifecycle.go @@ -7,10 +7,8 @@ import ( "path" "regexp" - "github.com/BurntSushi/toml" "github.com/pkg/errors" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/archive" ) @@ -31,23 +29,6 @@ type Lifecycle interface { Descriptor() LifecycleDescriptor } -// LifecycleDescriptor contains information described in the lifecycle.toml -type LifecycleDescriptor struct { - Info LifecycleInfo `toml:"lifecycle"` - API LifecycleAPI `toml:"api"` -} - -// LifecycleInfo contains information about the lifecycle -type LifecycleInfo struct { - Version *Version `toml:"version" json:"version"` -} - -// LifecycleAPI describes which API versions the lifecycle satisfies -type LifecycleAPI struct { - BuildpackVersion *api.Version `toml:"buildpack" json:"buildpack"` - PlatformVersion *api.Version `toml:"platform" json:"platform"` -} - type lifecycle struct { descriptor LifecycleDescriptor Blob @@ -63,20 +44,19 @@ func NewLifecycle(blob Blob) (Lifecycle, error) { } defer br.Close() - var descriptor LifecycleDescriptor _, buf, err := archive.ReadTarEntry(br, "lifecycle.toml") - if err != nil && errors.Cause(err) == archive.ErrEntryNotExist { return nil, err } else if err != nil { - return nil, errors.Wrap(err, "decode lifecycle descriptor") + return nil, errors.Wrap(err, "reading lifecycle descriptor") } - _, err = toml.Decode(string(buf), &descriptor) + + lifecycleDescriptor, err := ParseDescriptor(string(buf)) if err != nil { - return nil, errors.Wrap(err, "decoding descriptor") + return nil, err } - lifecycle := &lifecycle{Blob: blob, descriptor: descriptor} + lifecycle := &lifecycle{Blob: blob, descriptor: *lifecycleDescriptor} if err = lifecycle.validateBinaries(); err != nil { return nil, errors.Wrap(err, "validating binaries") @@ -131,9 +111,7 @@ func (l *lifecycle) binaries() []string { "builder", "exporter", "launcher", - } - if l.Descriptor().API.PlatformVersion.Compare(api.MustParse("0.2")) < 0 { - binaries = append(binaries, "cacher") + "creator", } return binaries } diff --git a/internal/builder/lifecycle_test.go b/internal/builder/lifecycle_test.go index 9961bd6d65..f351fd062b 100644 --- a/internal/builder/lifecycle_test.go +++ b/internal/builder/lifecycle_test.go @@ -25,13 +25,31 @@ func TestLifecycle(t *testing.T) { func testLifecycle(t *testing.T, when spec.G, it spec.S) { when("#NewLifecycle", func() { - when("there is a descriptor file with platform version 0.2", func() { + when("platform api 0.3", func() { it("makes a lifecycle from a blob", func() { - lifecycle, err := builder.NewLifecycle(blob.NewBlob(filepath.Join("testdata", "lifecycle"))) + lifecycle, err := builder.NewLifecycle(blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.3"))) h.AssertNil(t, err) - h.AssertEq(t, lifecycle.Descriptor().Info.Version.String(), "1.2.3") + h.AssertEq(t, lifecycle.Descriptor().Info.Version.String(), "0.0.0") + h.AssertEq(t, lifecycle.Descriptor().API.BuildpackVersion.String(), "0.2") + h.AssertEq(t, lifecycle.Descriptor().API.PlatformVersion.String(), "0.3") + + // fill supported with deprecated field + h.AssertEq(t, lifecycle.Descriptor().APIs.Buildpack.Supported.AsStrings(), []string{"0.2"}) + h.AssertEq(t, lifecycle.Descriptor().APIs.Platform.Supported.AsStrings(), []string{"0.3"}) + }) + }) + + when("platform api 0.4", func() { + it("makes a lifecycle from a blob", func() { + lifecycle, err := builder.NewLifecycle(blob.NewBlob(filepath.Join("testdata", "lifecycle", "platform-0.4"))) + h.AssertNil(t, err) + h.AssertEq(t, lifecycle.Descriptor().Info.Version.String(), "0.0.0") + h.AssertEq(t, lifecycle.Descriptor().APIs.Buildpack.Supported.AsStrings(), []string{"0.2", "0.3", "0.4"}) + h.AssertEq(t, lifecycle.Descriptor().APIs.Platform.Supported.AsStrings(), []string{"0.3", "0.4"}) + + // select lowest value for deprecated parameters + h.AssertEq(t, lifecycle.Descriptor().API.BuildpackVersion.String(), "0.2") h.AssertEq(t, lifecycle.Descriptor().API.PlatformVersion.String(), "0.2") - h.AssertEq(t, lifecycle.Descriptor().API.BuildpackVersion.String(), "0.3") }) }) @@ -105,47 +123,6 @@ func testLifecycle(t *testing.T, when spec.G, it spec.S) { h.AssertError(t, err, "validating binaries") }) }) - - when("the lifecycle has platform version 0.1 and is missing cacher", func() { - var tmpDir string - - it.Before(func() { - var err error - tmpDir, err = ioutil.TempDir("", "") - h.AssertNil(t, err) - - h.AssertNil(t, ioutil.WriteFile(filepath.Join(tmpDir, "lifecycle.toml"), []byte(` -[api] - platform = "0.1" - buildpack = "0.3" - -[lifecycle] - version = "1.2.3" -`), os.ModePerm)) - - h.AssertNil(t, os.Mkdir(filepath.Join(tmpDir, "lifecycle"), os.ModePerm)) - - for _, f := range []string{ - "detector", - "restorer", - "analyzer", - "builder", - "exporter", - "launcher", - } { - h.AssertNil(t, ioutil.WriteFile(filepath.Join(tmpDir, "lifecycle", f), []byte("content"), os.ModePerm)) - } - }) - - it.After(func() { - h.AssertNil(t, os.RemoveAll(tmpDir)) - }) - - it("returns an error", func() { - _, err := builder.NewLifecycle(blob.NewBlob(tmpDir)) - h.AssertError(t, err, "validating binaries") - }) - }) }) } diff --git a/internal/builder/metadata.go b/internal/builder/metadata.go index 447142ee7d..7482256322 100644 --- a/internal/builder/metadata.go +++ b/internal/builder/metadata.go @@ -21,7 +21,9 @@ type CreatorMetadata struct { type LifecycleMetadata struct { LifecycleInfo - API LifecycleAPI `json:"api"` + // Deprecated: use APIs instead + API LifecycleAPI `json:"api"` + APIs *LifecycleAPIs `json:"apis"` } type StackMetadata struct { diff --git a/internal/builder/testdata/lifecycle/lifecycle.toml b/internal/builder/testdata/lifecycle/lifecycle.toml deleted file mode 100644 index 4e59c9ca33..0000000000 --- a/internal/builder/testdata/lifecycle/lifecycle.toml +++ /dev/null @@ -1,6 +0,0 @@ -[api] - platform = "0.2" - buildpack = "0.3" - -[lifecycle] - version = "1.2.3" diff --git a/internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/analyzer b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/analyzer similarity index 100% rename from internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/analyzer rename to internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/analyzer diff --git a/internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/builder b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/builder similarity index 100% rename from internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/builder rename to internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/builder diff --git a/internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/restorer b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/creator similarity index 100% rename from internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/restorer rename to internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/creator diff --git a/internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/detector b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/detector similarity index 100% rename from internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/detector rename to internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/detector diff --git a/internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/exporter b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/exporter similarity index 100% rename from internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/exporter rename to internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/exporter diff --git a/internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/launcher b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/launcher similarity index 100% rename from internal/builder/testdata/lifecycle/lifecycle-v1.2.3-arch/launcher rename to internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/launcher diff --git a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/restorer b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/restorer similarity index 100% rename from testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/restorer rename to internal/builder/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/restorer diff --git a/internal/builder/testdata/lifecycle/platform-0.3/lifecycle.toml b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle.toml new file mode 100644 index 0000000000..3ce93a1935 --- /dev/null +++ b/internal/builder/testdata/lifecycle/platform-0.3/lifecycle.toml @@ -0,0 +1,6 @@ +[lifecycle] +version = "0.0.0" + +[api] +buildpack = "0.2" +platform = "0.3" \ No newline at end of file diff --git a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/analyzer b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/analyzer similarity index 100% rename from testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/analyzer rename to internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/analyzer diff --git a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/builder b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/builder similarity index 100% rename from testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/builder rename to internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/builder diff --git a/testdata/lifecycle/lifecycle-v3.4.5-arch/restorer b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/creator similarity index 100% rename from testdata/lifecycle/lifecycle-v3.4.5-arch/restorer rename to internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/creator diff --git a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/detector b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/detector similarity index 100% rename from testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/detector rename to internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/detector diff --git a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/exporter b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/exporter similarity index 100% rename from testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/exporter rename to internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/exporter diff --git a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/launcher b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/launcher similarity index 100% rename from testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/launcher rename to internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/launcher diff --git a/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/restorer b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/restorer new file mode 100755 index 0000000000..f6d18366f2 --- /dev/null +++ b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/restorer @@ -0,0 +1 @@ +restorer \ No newline at end of file diff --git a/internal/builder/testdata/lifecycle/platform-0.4/lifecycle.toml b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle.toml new file mode 100644 index 0000000000..3fbda4fa10 --- /dev/null +++ b/internal/builder/testdata/lifecycle/platform-0.4/lifecycle.toml @@ -0,0 +1,11 @@ +[lifecycle] +version = "0.0.0" + +[apis] +[apis.buildpack] +deprecated = [] +supported = ["0.2", "0.3", "0.4"] + +[apis.platform] +deprecated = ["0.2"] +supported = ["0.3", "0.4"] \ No newline at end of file diff --git a/internal/buildpackage/builder_test.go b/internal/buildpackage/builder_test.go index e99705875e..a7b5dbe4c2 100644 --- a/internal/buildpackage/builder_test.go +++ b/internal/buildpackage/builder_test.go @@ -13,6 +13,7 @@ import ( "testing" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/lifecycle/api" "github.com/golang/mock/gomock" "github.com/google/go-containerregistry/pkg/v1/stream" "github.com/heroku/color" @@ -20,7 +21,6 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/buildpackage" "github.com/buildpacks/pack/internal/dist" ifakes "github.com/buildpacks/pack/internal/fakes" diff --git a/internal/buildpackage/oci_layout_package_test.go b/internal/buildpackage/oci_layout_package_test.go index 3c4939395e..775fbb1b0a 100644 --- a/internal/buildpackage/oci_layout_package_test.go +++ b/internal/buildpackage/oci_layout_package_test.go @@ -9,7 +9,8 @@ import ( "github.com/sclevine/spec" "github.com/sclevine/spec/report" - "github.com/buildpacks/pack/internal/api" + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/pack/internal/archive" "github.com/buildpacks/pack/internal/blob" "github.com/buildpacks/pack/internal/buildpackage" diff --git a/internal/commands/inspect_builder.go b/internal/commands/inspect_builder.go index f715f950e0..52cc3d60cd 100644 --- a/internal/commands/inspect_builder.go +++ b/internal/commands/inspect_builder.go @@ -12,12 +12,15 @@ import ( "github.com/spf13/cobra" "github.com/buildpacks/pack" + "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/config" "github.com/buildpacks/pack/internal/dist" "github.com/buildpacks/pack/internal/style" "github.com/buildpacks/pack/logging" ) +const none = "(none)" + func InspectBuilder(logger logging.Logger, cfg config.Config, client PackClient) *cobra.Command { cmd := &cobra.Command{ Use: "inspect-builder ", @@ -126,8 +129,12 @@ Stack: Lifecycle: Version: {{- if .Info.Lifecycle.Info.Version }} {{ .Info.Lifecycle.Info.Version }}{{- else }} (none){{- end }} - Buildpack API: {{- if .Info.Lifecycle.API.BuildpackVersion }} {{ .Info.Lifecycle.API.BuildpackVersion }}{{- else }} (none){{- end }} - Platform API: {{- if .Info.Lifecycle.API.PlatformVersion }} {{ .Info.Lifecycle.API.PlatformVersion }}{{- else }} (none){{- end }} + Buildpack APIs: + Deprecated: {{ .DeprecatedBuildpackAPIs }} + Supported: {{ .SupportedBuildpackAPIs }} + Platform APIs: + Deprecated: {{ .DeprecatedPlatformAPIs }} + Supported: {{ .SupportedPlatformAPIs }} Run Images: {{- if ne .RunImages "" }} @@ -183,15 +190,24 @@ Detection Order: lcDescriptor := &info.Lifecycle if lcDescriptor.Info.Version == nil { - warnings = append(warnings, fmt.Sprintf("%s does not specify lifecycle version", style.Symbol(imageName))) + warnings = append(warnings, fmt.Sprintf("%s does not specify a Lifecycle version", style.Symbol(imageName))) } - if lcDescriptor.API.BuildpackVersion == nil { - warnings = append(warnings, fmt.Sprintf("%s does not specify lifecycle buildpack api version", style.Symbol(imageName))) + deprecatedBuildpackAPIs := none + supportedBuildpackAPIs := none + deprecatedPlatformAPIs := none + supportedPlatformAPIs := none + if lcDescriptor.APIs != nil { + deprecatedBuildpackAPIs = stringifyAPISet(lcDescriptor.APIs.Buildpack.Deprecated) + supportedBuildpackAPIs = stringifyAPISet(lcDescriptor.APIs.Buildpack.Supported) + deprecatedPlatformAPIs = stringifyAPISet(lcDescriptor.APIs.Platform.Deprecated) + supportedPlatformAPIs = stringifyAPISet(lcDescriptor.APIs.Platform.Supported) } - - if lcDescriptor.API.PlatformVersion == nil { - warnings = append(warnings, fmt.Sprintf("%s does not specify lifecycle platform api version", style.Symbol(imageName))) + if supportedBuildpackAPIs == none { + warnings = append(warnings, fmt.Sprintf("%s does not specify supported Lifecycle Buildpack APIs", style.Symbol(imageName))) + } + if supportedPlatformAPIs == none { + warnings = append(warnings, fmt.Sprintf("%s does not specify supported Lifecycle Platform APIs", style.Symbol(imageName))) } trustedString := "No" @@ -200,12 +216,16 @@ Detection Order: } return warnings, tpl.Execute(writer, &struct { - Info pack.BuilderInfo - Buildpacks string - RunImages string - Order string - Verbose bool - Trusted string + Info pack.BuilderInfo + Buildpacks string + RunImages string + Order string + Verbose bool + Trusted string + DeprecatedBuildpackAPIs string + SupportedBuildpackAPIs string + DeprecatedPlatformAPIs string + SupportedPlatformAPIs string }{ info, bps, @@ -213,9 +233,21 @@ Detection Order: order, verbose, trustedString, + deprecatedBuildpackAPIs, + supportedBuildpackAPIs, + deprecatedPlatformAPIs, + supportedPlatformAPIs, }) } +func stringifyAPISet(versions builder.APISet) string { + if len(versions) == 0 { + return none + } + + return strings.Join(versions.AsStrings(), ", ") +} + // TODO: present buildpack order (inc. nested) [https://github.com/buildpacks/pack/issues/253]. func buildpacksOutput(bps []dist.BuildpackInfo) (string, error) { buf := &bytes.Buffer{} diff --git a/internal/commands/inspect_builder_test.go b/internal/commands/inspect_builder_test.go index bd804942b4..e57735fd64 100644 --- a/internal/commands/inspect_builder_test.go +++ b/internal/commands/inspect_builder_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/Masterminds/semver" + "github.com/buildpacks/lifecycle/api" "github.com/golang/mock/gomock" "github.com/heroku/color" "github.com/sclevine/spec" @@ -13,7 +14,6 @@ import ( "github.com/spf13/cobra" "github.com/buildpacks/pack" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/builder" "github.com/buildpacks/pack/internal/commands" "github.com/buildpacks/pack/internal/commands/testmocks" @@ -62,9 +62,15 @@ func testInspectBuilderCommand(t *testing.T, when spec.G, it spec.S) { Version: *semver.MustParse("6.7.8"), }, }, - API: builder.LifecycleAPI{ - BuildpackVersion: api.MustParse("5.6"), - PlatformVersion: api.MustParse("7.8"), + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{ + Deprecated: nil, + Supported: builder.APISet{api.MustParse("1.2"), api.MustParse("2.3")}, + }, + Platform: builder.APIVersions{ + Deprecated: builder.APISet{api.MustParse("0.1"), api.MustParse("1.2")}, + Supported: builder.APISet{api.MustParse("4.5")}, + }, }, }, CreatedBy: builder.CreatorMetadata{ @@ -89,9 +95,15 @@ func testInspectBuilderCommand(t *testing.T, when spec.G, it spec.S) { Version: *semver.MustParse("4.5.6"), }, }, - API: builder.LifecycleAPI{ - BuildpackVersion: api.MustParse("1.2"), - PlatformVersion: api.MustParse("3.4"), + APIs: &builder.LifecycleAPIs{ + Buildpack: builder.APIVersions{ + Deprecated: builder.APISet{api.MustParse("4.5"), api.MustParse("6.7")}, + Supported: builder.APISet{api.MustParse("8.9"), api.MustParse("10.11")}, + }, + Platform: builder.APIVersions{ + Deprecated: nil, + Supported: builder.APISet{api.MustParse("7.8")}, + }, }, }, CreatedBy: builder.CreatorMetadata{ @@ -115,8 +127,12 @@ Stack: Lifecycle: Version: 6.7.8 - Buildpack API: 5.6 - Platform API: 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) @@ -151,8 +167,12 @@ Stack: Lifecycle: Version: 4.5.6 - Buildpack API: 1.2 - Platform API: 3.4 + 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) @@ -291,9 +311,9 @@ Detection Order: it("missing lifecycle version logs a warning", func() { h.AssertNil(t, command.Execute()) - h.AssertContains(t, outBuf.String(), "Warning: 'some/image' does not specify lifecycle version") - h.AssertContains(t, outBuf.String(), "Warning: 'some/image' does not specify lifecycle buildpack api version") - h.AssertContains(t, outBuf.String(), "Warning: 'some/image' does not specify lifecycle platform api version") + h.AssertContains(t, outBuf.String(), "Warning: 'some/image' does not specify a Lifecycle version") + h.AssertContains(t, outBuf.String(), "Warning: 'some/image' does not specify supported Lifecycle Buildpack APIs") + h.AssertContains(t, outBuf.String(), "Warning: 'some/image' does not specify supported Lifecycle Platform APIs") }) }) diff --git a/internal/dist/buildpack.go b/internal/dist/buildpack.go index 74a5d2cf5b..2d81961b83 100644 --- a/internal/dist/buildpack.go +++ b/internal/dist/buildpack.go @@ -6,9 +6,9 @@ import ( "path" "github.com/BurntSushi/toml" + "github.com/buildpacks/lifecycle/api" "github.com/pkg/errors" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/archive" "github.com/buildpacks/pack/internal/style" ) diff --git a/internal/dist/buildpack_descriptor.go b/internal/dist/buildpack_descriptor.go index 6a57a1a7ac..4b95454ffa 100644 --- a/internal/dist/buildpack_descriptor.go +++ b/internal/dist/buildpack_descriptor.go @@ -5,7 +5,8 @@ import ( "sort" "strings" - "github.com/buildpacks/pack/internal/api" + "github.com/buildpacks/lifecycle/api" + "github.com/buildpacks/pack/internal/stringset" "github.com/buildpacks/pack/internal/style" ) diff --git a/internal/dist/dist.go b/internal/dist/dist.go index 88b0f59ae9..0297535977 100644 --- a/internal/dist/dist.go +++ b/internal/dist/dist.go @@ -1,6 +1,6 @@ package dist -import "github.com/buildpacks/pack/internal/api" +import "github.com/buildpacks/lifecycle/api" const BuildpackLayersLabel = "io.buildpacks.buildpack.layers" diff --git a/package_buildpack_test.go b/package_buildpack_test.go index 2352a4754e..07e93e2c14 100644 --- a/package_buildpack_test.go +++ b/package_buildpack_test.go @@ -11,6 +11,7 @@ import ( "github.com/buildpacks/imgutil" "github.com/buildpacks/imgutil/fakes" + "github.com/buildpacks/lifecycle/api" "github.com/golang/mock/gomock" "github.com/heroku/color" "github.com/sclevine/spec" @@ -18,7 +19,6 @@ import ( "github.com/buildpacks/pack" pubbldpkg "github.com/buildpacks/pack/buildpackage" - "github.com/buildpacks/pack/internal/api" "github.com/buildpacks/pack/internal/blob" "github.com/buildpacks/pack/internal/buildpackage" "github.com/buildpacks/pack/internal/dist" diff --git a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/cacher b/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/cacher deleted file mode 100755 index 3ad3632e14..0000000000 --- a/testdata/lifecycle-platform-0.1/lifecycle-v3.4.5-arch/cacher +++ /dev/null @@ -1 +0,0 @@ -cacher \ No newline at end of file diff --git a/testdata/lifecycle-platform-0.1/lifecycle.toml b/testdata/lifecycle-platform-0.1/lifecycle.toml deleted file mode 100644 index a04d093984..0000000000 --- a/testdata/lifecycle-platform-0.1/lifecycle.toml +++ /dev/null @@ -1,6 +0,0 @@ -[api] - platform = "0.1" - buildpack = "0.3" - -[lifecycle] - version = "3.4.5" diff --git a/testdata/lifecycle/lifecycle.toml b/testdata/lifecycle/lifecycle.toml deleted file mode 100644 index 2518f16ada..0000000000 --- a/testdata/lifecycle/lifecycle.toml +++ /dev/null @@ -1,6 +0,0 @@ -[api] - platform = "0.2" - buildpack = "0.3" - -[lifecycle] - version = "3.4.5" \ No newline at end of file diff --git a/testdata/lifecycle/lifecycle-v3.4.5-arch/analyzer b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/analyzer similarity index 100% rename from testdata/lifecycle/lifecycle-v3.4.5-arch/analyzer rename to testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/analyzer diff --git a/testdata/lifecycle/lifecycle-v3.4.5-arch/builder b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/builder similarity index 100% rename from testdata/lifecycle/lifecycle-v3.4.5-arch/builder rename to testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/builder diff --git a/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/creator b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/creator new file mode 100755 index 0000000000..f6d18366f2 --- /dev/null +++ b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/creator @@ -0,0 +1 @@ +restorer \ No newline at end of file diff --git a/testdata/lifecycle/lifecycle-v3.4.5-arch/detector b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/detector similarity index 100% rename from testdata/lifecycle/lifecycle-v3.4.5-arch/detector rename to testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/detector diff --git a/testdata/lifecycle/lifecycle-v3.4.5-arch/exporter b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/exporter similarity index 100% rename from testdata/lifecycle/lifecycle-v3.4.5-arch/exporter rename to testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/exporter diff --git a/testdata/lifecycle/lifecycle-v3.4.5-arch/launcher b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/launcher similarity index 100% rename from testdata/lifecycle/lifecycle-v3.4.5-arch/launcher rename to testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/launcher diff --git a/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/restorer b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/restorer new file mode 100755 index 0000000000..f6d18366f2 --- /dev/null +++ b/testdata/lifecycle/platform-0.3/lifecycle-v0.0.0-arch/restorer @@ -0,0 +1 @@ +restorer \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.3/lifecycle.toml b/testdata/lifecycle/platform-0.3/lifecycle.toml new file mode 100644 index 0000000000..3ce93a1935 --- /dev/null +++ b/testdata/lifecycle/platform-0.3/lifecycle.toml @@ -0,0 +1,6 @@ +[lifecycle] +version = "0.0.0" + +[api] +buildpack = "0.2" +platform = "0.3" \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/analyzer b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/analyzer new file mode 100755 index 0000000000..2c7cce34c1 --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/analyzer @@ -0,0 +1 @@ +analyzer \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/builder b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/builder new file mode 100755 index 0000000000..b05c21cd9d --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/builder @@ -0,0 +1 @@ +builder \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/creator b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/creator new file mode 100755 index 0000000000..f6d18366f2 --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/creator @@ -0,0 +1 @@ +restorer \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/detector b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/detector new file mode 100755 index 0000000000..4ca7e105c9 --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/detector @@ -0,0 +1 @@ +detector \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/exporter b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/exporter new file mode 100755 index 0000000000..76a0149ce4 --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/exporter @@ -0,0 +1 @@ +exporter \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/launcher b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/launcher new file mode 100755 index 0000000000..89f76d0bc2 --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/launcher @@ -0,0 +1 @@ +launcher \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/restorer b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/restorer new file mode 100755 index 0000000000..f6d18366f2 --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle-v0.0.0-arch/restorer @@ -0,0 +1 @@ +restorer \ No newline at end of file diff --git a/testdata/lifecycle/platform-0.4/lifecycle.toml b/testdata/lifecycle/platform-0.4/lifecycle.toml new file mode 100644 index 0000000000..3fbda4fa10 --- /dev/null +++ b/testdata/lifecycle/platform-0.4/lifecycle.toml @@ -0,0 +1,11 @@ +[lifecycle] +version = "0.0.0" + +[apis] +[apis.buildpack] +deprecated = [] +supported = ["0.2", "0.3", "0.4"] + +[apis.platform] +deprecated = ["0.2"] +supported = ["0.3", "0.4"] \ No newline at end of file diff --git a/testhelpers/arg_patterns.go b/testhelpers/arg_patterns.go index 569f072429..fbce27be97 100644 --- a/testhelpers/arg_patterns.go +++ b/testhelpers/arg_patterns.go @@ -10,6 +10,8 @@ import ( ) func AssertIncludeAllExpectedPatterns(t *testing.T, receivedArgs []string, expectedPatterns ...[]string) { + t.Helper() + missingPatterns := [][]string{} for _, expectedPattern := range expectedPatterns { @@ -62,6 +64,8 @@ func matchLocations(expectedArg string, receivedArgs []string) []int { } func assertSliceEmpty(t *testing.T, actual [][]string, msg string, msgArgs ...interface{}) { + t.Helper() + empty, err := sliceEmpty(actual) if err != nil {