diff --git a/internal/commands/rebase.go b/internal/commands/rebase.go index 0eed5f728..d0622f9db 100644 --- a/internal/commands/rebase.go +++ b/internal/commands/rebase.go @@ -49,6 +49,7 @@ func Rebase(logger logging.Logger, cfg config.Config, pack PackClient) *cobra.Co cmd.Flags().BoolVar(&opts.Publish, "publish", false, "Publish the rebased application image directly to the container registry specified in , instead of the daemon. The previous application image must also reside in the registry.") cmd.Flags().StringVar(&opts.RunImage, "run-image", "", "Run image to use for rebasing") cmd.Flags().StringVar(&policy, "pull-policy", "", "Pull policy to use. Accepted values are always, never, and if-not-present. The default is always") + cmd.Flags().StringVar(&opts.PreviousImage, "previous-image", "", "Image to rebase. Set to a particular tag reference, digest reference, or (when performing a daemon build) image ID. Use this flag in combination with to avoid replacing the original image.") cmd.Flags().StringVar(&opts.ReportDestinationDir, "report-output-dir", "", "Path to export build report.toml.\nOmitting the flag yield no report file.") cmd.Flags().BoolVar(&opts.Force, "force", false, "Perform rebase operation without target validation (only available for API >= 0.12)") diff --git a/internal/commands/rebase_test.go b/internal/commands/rebase_test.go index 8f235c409..a3bdf05f5 100644 --- a/internal/commands/rebase_test.go +++ b/internal/commands/rebase_test.go @@ -158,6 +158,44 @@ func testRebaseCommand(t *testing.T, when spec.G, it spec.S) { }) }) }) + when("image name and previous image are provided", func() { + var expectedOpts client.RebaseOptions + + it.Before(func() { + runImage := "test/image" + testMirror1 := "example.com/some/run1" + testMirror2 := "example.com/some/run2" + + cfg.RunImages = []config.RunImage{{ + Image: runImage, + Mirrors: []string{testMirror1, testMirror2}, + }} + command = commands.Rebase(logger, cfg, mockClient) + + repoName = "test/repo-image" + previousImage := "example.com/previous-image:tag" // Example of previous image with tag + opts := client.RebaseOptions{ + RepoName: repoName, + Publish: false, + PullPolicy: image.PullAlways, + RunImage: "", + AdditionalMirrors: map[string][]string{ + runImage: {testMirror1, testMirror2}, + }, + PreviousImage: previousImage, + } + expectedOpts = opts + }) + + it("works", func() { + mockClient.EXPECT(). + Rebase(gomock.Any(), gomock.Eq(expectedOpts)). + Return(nil) + + command.SetArgs([]string{repoName, "--previous-image", "example.com/previous-image:tag"}) + h.AssertNil(t, command.Execute()) + }) + }) }) }) } diff --git a/pkg/client/rebase.go b/pkg/client/rebase.go index 8f3f8cf5b..f493f6184 100644 --- a/pkg/client/rebase.go +++ b/pkg/client/rebase.go @@ -46,6 +46,9 @@ type RebaseOptions struct { // Pass-through force flag to lifecycle rebase command to skip target data // validated (will not have any effect if API < 0.12). Force bool + + // Image reference to use as the previous image for rebase. + PreviousImage string } // Rebase updates the run image layers in an app image. @@ -56,7 +59,13 @@ func (c *Client) Rebase(ctx context.Context, opts RebaseOptions) error { return errors.Wrapf(err, "invalid image name '%s'", opts.RepoName) } - appImage, err := c.imageFetcher.Fetch(ctx, opts.RepoName, image.FetchOptions{Daemon: !opts.Publish, PullPolicy: opts.PullPolicy}) + repoName := opts.RepoName + + if opts.PreviousImage != "" { + repoName = opts.PreviousImage + } + + appImage, err := c.imageFetcher.Fetch(ctx, repoName, image.FetchOptions{Daemon: !opts.Publish, PullPolicy: opts.PullPolicy}) if err != nil { return err } @@ -114,7 +123,7 @@ func (c *Client) Rebase(ctx context.Context, opts RebaseOptions) error { c.logger.Infof("Rebasing %s on run image %s", style.Symbol(appImage.Name()), style.Symbol(baseImage.Name())) rebaser := &phase.Rebaser{Logger: c.logger, PlatformAPI: build.SupportedPlatformAPIVersions.Latest(), Force: opts.Force} - report, err := rebaser.Rebase(appImage, baseImage, appImage.Name(), nil) + report, err := rebaser.Rebase(appImage, baseImage, opts.RepoName, nil) if err != nil { return err } diff --git a/pkg/client/rebase_test.go b/pkg/client/rebase_test.go index 8068f231d..ba6a9df86 100644 --- a/pkg/client/rebase_test.go +++ b/pkg/client/rebase_test.go @@ -267,6 +267,38 @@ func testRebase(t *testing.T, when spec.G, it spec.S) { }) }) }) + when("previous image is provided", func() { + it("fetches the image using the previous image name", func() { + h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{ + RepoName: "new/app", + PreviousImage: "some/app", + })) + args := fakeImageFetcher.FetchCalls["some/app"] + h.AssertNotNil(t, args) + h.AssertEq(t, args.Daemon, true) + }) + }) + + when("previous image is set to new image name", func() { + it("returns error if Fetch function fails", func() { + err := subject.Rebase(context.TODO(), RebaseOptions{ + RepoName: "some/app", + PreviousImage: "new/app", + }) + h.AssertError(t, err, "image 'new/app' does not exist on the daemon: not found") + }) + }) + + when("previous image is not provided", func() { + it("fetches the image using the repo name", func() { + h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{ + RepoName: "some/app", + })) + args := fakeImageFetcher.FetchCalls["some/app"] + h.AssertNotNil(t, args) + h.AssertEq(t, args.Daemon, true) + }) + }) }) }) }