Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rebaser changes for run image extension #1032

Merged
merged 6 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cmd/lifecycle/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ func FlagVersion(showVersion *bool) {
flagSet.BoolVar(showVersion, "version", false, "show version")
}

func FlagForceRebase(force *bool) {
flagSet.BoolVar(force, "force", *force, "execute rebase even if operation is unsafe")
}

// deprecated

func DeprecatedFlagRunImage(deprecatedRunImage *string) {
Expand Down
18 changes: 17 additions & 1 deletion cmd/lifecycle/rebaser.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ func (r *rebaseCmd) DefineFlags() {
if r.PlatformAPI.AtLeast("0.11") {
cli.FlagPreviousImage(&r.PreviousImageRef)
}

if r.PlatformAPI.AtLeast("0.12") {
cli.FlagForceRebase(&r.ForceRebase)
}
}

// Args validates arguments and flags, and fills in default values.
Expand Down Expand Up @@ -103,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 {
Expand Down Expand Up @@ -157,9 +162,20 @@ func (r *rebaseCmd) setAppImage() error {
}

if r.RunImageRef == "" {
if r.PlatformAPI.AtLeast("0.12") {
natalieparellano marked this conversation as resolved.
Show resolved Hide resolved
r.RunImageRef = md.RunImage.Reference
if r.RunImageRef != "" {
return nil
}
}

// for backwards compatibility, we need to fallback to the stack metadata
// fail if there is no run image metadata available from either location
if md.Stack.RunImage.Image == "" {
return cmd.FailErrCode(errors.New("-run-image is required when there is no stack metadata available"), cmd.CodeForInvalidArgs, "parse arguments")
return cmd.FailErrCode(errors.New("-run-image is required when there is no run image metadata available"), cmd.CodeForInvalidArgs, "parse arguments")
}

// for older platforms, we find the best mirror for the run image as this point
r.RunImageRef, err = md.Stack.BestRunImageMirror(registry)
if err != nil {
return err
Expand Down
6 changes: 6 additions & 0 deletions platform/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ const (
DefaultProjectMetadataFile = "project-metadata.toml"
)

// The following are configuration options for rebaser.
const (
// EnvForceRebase is used to force the rebaser to rebase the app image even if the operation is unsafe.
EnvForceRebase = "CNB_FORCE_REBASE"
)

var (
// DefaultLauncherPath is the default location of the launcher executable during the build.
// The launcher is exported in the output application image and is used to start application processes at runtime.
Expand Down
3 changes: 2 additions & 1 deletion platform/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
4 changes: 4 additions & 0 deletions platform/lifecycle_inputs.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type LifecycleInputs struct {
StackPath string
UID int
GID int
ForceRebase bool
SkipLayers bool
UseDaemon bool
UseLayout bool
Expand Down Expand Up @@ -131,6 +132,9 @@ func NewLifecycleInputs(platformAPI *api.Version) *LifecycleInputs {
LauncherPath: DefaultLauncherPath,
LauncherSBOMDir: DefaultBuildpacksioSBOMDir,
ProjectMetadataPath: envOrDefault(EnvProjectMetadataPath, filepath.Join(PlaceholderLayers, DefaultProjectMetadataFile)),

// Configuration options for rebasing
ForceRebase: boolEnv(EnvForceRebase),
}

if platformAPI.LessThan("0.6") {
Expand Down
4 changes: 4 additions & 0 deletions platform/lifecycle_inputs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func testLifecycleInputs(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, inputs.DefaultProcessType, "")
h.AssertEq(t, inputs.DeprecatedRunImageRef, "")
h.AssertEq(t, inputs.ExtensionsDir, platform.DefaultExtensionsDir)
h.AssertEq(t, inputs.ForceRebase, false)
h.AssertEq(t, inputs.GID, 0)
h.AssertEq(t, inputs.KanikoCacheTTL, platform.DefaultKanikoCacheTTL)
h.AssertEq(t, inputs.KanikoDir, "/kaniko")
Expand Down Expand Up @@ -72,6 +73,7 @@ func testLifecycleInputs(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, os.Setenv(platform.EnvCacheImage, "some-cache-image"))
h.AssertNil(t, os.Setenv(platform.EnvExtensionsDir, "some-extensions-dir"))
h.AssertNil(t, os.Setenv(platform.EnvGID, "5678"))
h.AssertNil(t, os.Setenv(platform.EnvForceRebase, "true"))
h.AssertNil(t, os.Setenv(platform.EnvGeneratedDir, "some-generated-dir"))
h.AssertNil(t, os.Setenv(platform.EnvGroupPath, "some-group-path"))
h.AssertNil(t, os.Setenv(platform.EnvKanikoCacheTTL, "1h0m0s"))
Expand Down Expand Up @@ -103,6 +105,7 @@ func testLifecycleInputs(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, os.Unsetenv(platform.EnvCacheDir))
h.AssertNil(t, os.Unsetenv(platform.EnvCacheImage))
h.AssertNil(t, os.Unsetenv(platform.EnvExtensionsDir))
h.AssertNil(t, os.Unsetenv(platform.EnvForceRebase))
h.AssertNil(t, os.Unsetenv(platform.EnvGID))
h.AssertNil(t, os.Unsetenv(platform.EnvGeneratedDir))
h.AssertNil(t, os.Unsetenv(platform.EnvGroupPath))
Expand Down Expand Up @@ -139,6 +142,7 @@ func testLifecycleInputs(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, inputs.DefaultProcessType, "some-process-type")
h.AssertEq(t, inputs.DeprecatedRunImageRef, "")
h.AssertEq(t, inputs.ExtensionsDir, "some-extensions-dir")
h.AssertEq(t, inputs.ForceRebase, true)
h.AssertEq(t, inputs.GID, 5678)
h.AssertEq(t, inputs.KanikoCacheTTL, 1*time.Hour)
h.AssertEq(t, inputs.KanikoDir, "/kaniko")
Expand Down
18 changes: 18 additions & 0 deletions rebaser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
type Rebaser struct {
Logger log.Logger
PlatformAPI *api.Version
Force bool
}

type RebaseReport struct {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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")
}
49 changes: 49 additions & 0 deletions rebaser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,55 @@ 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() {
Expand Down