diff --git a/cmd/lifecycle/rebaser.go b/cmd/lifecycle/rebaser.go index 195e57c82..f2f5b2b3d 100644 --- a/cmd/lifecycle/rebaser.go +++ b/cmd/lifecycle/rebaser.go @@ -107,6 +107,7 @@ func (r *rebaseCmd) Exec() error { rebaser := &lifecycle.Rebaser{ Logger: cmd.DefaultLogger, PlatformAPI: r.PlatformAPI, + Force: r.ForceRebase, } report, err := rebaser.Rebase(r.appImage, newBaseImage, r.OutputImageRef, r.AdditionalTags) if err != nil { diff --git a/platform/labels.go b/platform/labels.go index 63de14bf0..97eca5e52 100644 --- a/platform/labels.go +++ b/platform/labels.go @@ -3,7 +3,8 @@ package platform const ( BuildMetadataLabel = "io.buildpacks.build.metadata" LayerMetadataLabel = "io.buildpacks.lifecycle.metadata" + MixinsLabel = "io.buildpacks.stack.mixins" ProjectMetadataLabel = "io.buildpacks.project.metadata" + RebaseableLabel = "io.buildpacks.rebasable" StackIDLabel = "io.buildpacks.stack.id" - MixinsLabel = "io.buildpacks.stack.mixins" ) diff --git a/rebaser.go b/rebaser.go index d89a9ca49..67560b38e 100644 --- a/rebaser.go +++ b/rebaser.go @@ -19,6 +19,7 @@ import ( type Rebaser struct { Logger log.Logger PlatformAPI *api.Version + Force bool } type RebaseReport struct { @@ -53,6 +54,10 @@ func (r *Rebaser) Rebase(workingImage imgutil.Image, newBaseImage imgutil.Image, return RebaseReport{}, fmt.Errorf("incompatible stack: '%s' is not compatible with '%s'", newBaseStackID, appStackID) } + if err := r.validateRebaseable(workingImage); err != nil { + return RebaseReport{}, err + } + if err := validateMixins(workingImage, newBaseImage); err != nil { return RebaseReport{}, err } @@ -123,6 +128,19 @@ func validateMixins(appImg, newBaseImg imgutil.Image) error { return nil } +func (r *Rebaser) validateRebaseable(appImg imgutil.Image) error { + if r.PlatformAPI.AtLeast("0.12") { + rebaseable, err := appImg.Label(platform.RebaseableLabel) + if err != nil { + return errors.Wrap(err, "get app image rebaseable label") + } + if !r.Force && rebaseable == "false" { + return fmt.Errorf("app image is not marked as rebaseable") + } + } + return nil +} + func (r *Rebaser) supportsManifestSize() bool { return r.PlatformAPI.AtLeast("0.6") } diff --git a/rebaser_test.go b/rebaser_test.go index bbab40772..e187ad1cd 100644 --- a/rebaser_test.go +++ b/rebaser_test.go @@ -250,6 +250,56 @@ func testRebaser(t *testing.T, when spec.G, it spec.S) { }) }) + when("platform API is >= 0.12", func() { + it.Before(func() { + rebaser.PlatformAPI = api.MustParse("0.12") + }) + + when("validating rebasable", func() { + when("rebaseable label is false", func() { + it.Before(func() { + h.AssertNil(t, fakeAppImage.SetLabel(platform.RebaseableLabel, "false")) + }) + + it("returns an error", func() { + _, err := rebaser.Rebase(fakeAppImage, fakeNewBaseImage, fakeAppImage.Name(), additionalNames) + h.AssertError(t, err, "app image is not marked as rebaseable") + }) + + when("force is true", func() { + it("allows rebase", func() { + rebaser.Force = true + _, err := rebaser.Rebase(fakeAppImage, fakeNewBaseImage, fakeAppImage.Name(), additionalNames) + h.AssertNil(t, err) + }) + }) + }) + + when("rebaseable label is not false", func() { + it.Before(func() { + h.AssertNil(t, fakeAppImage.SetLabel(platform.RebaseableLabel, "true")) + }) + + it("allows rebase", func() { + _, err := rebaser.Rebase(fakeAppImage, fakeNewBaseImage, fakeAppImage.Name(), additionalNames) + h.AssertNil(t, err) + }) + }) + + when("rebaseable label is empty", func() { + it.Before(func() { + h.AssertNil(t, fakeAppImage.SetLabel(platform.RebaseableLabel, "")) + }) + + it("allows rebase", func() { + _, err := rebaser.Rebase(fakeAppImage, fakeNewBaseImage, fakeAppImage.Name(), additionalNames) + h.AssertNil(t, err) + }) + }) + }) + + }) + when("validating mixins", func() { when("there are no mixin labels", func() { it("allows rebase", func() {