github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/rebase_test.go (about)

     1  package client
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"os"
     7  	"path/filepath"
     8  	"testing"
     9  
    10  	"github.com/buildpacks/imgutil/fakes"
    11  	"github.com/buildpacks/lifecycle/auth"
    12  	"github.com/heroku/color"
    13  	"github.com/sclevine/spec"
    14  	"github.com/sclevine/spec/report"
    15  
    16  	ifakes "github.com/buildpacks/pack/internal/fakes"
    17  	"github.com/buildpacks/pack/pkg/image"
    18  	"github.com/buildpacks/pack/pkg/logging"
    19  	h "github.com/buildpacks/pack/testhelpers"
    20  )
    21  
    22  func TestRebase(t *testing.T) {
    23  	color.Disable(true)
    24  	defer color.Disable(false)
    25  	spec.Run(t, "rebase_factory", testRebase, spec.Parallel(), spec.Report(report.Terminal{}))
    26  }
    27  
    28  func testRebase(t *testing.T, when spec.G, it spec.S) {
    29  	when("#Rebase", func() {
    30  		var (
    31  			fakeImageFetcher   *ifakes.FakeImageFetcher
    32  			subject            *Client
    33  			fakeAppImage       *fakes.Image
    34  			fakeRunImage       *fakes.Image
    35  			fakeRunImageMirror *fakes.Image
    36  			out                bytes.Buffer
    37  		)
    38  
    39  		it.Before(func() {
    40  			fakeImageFetcher = ifakes.NewFakeImageFetcher()
    41  
    42  			fakeAppImage = fakes.NewImage("some/app", "", &fakeIdentifier{name: "app-image"})
    43  			h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.lifecycle.metadata",
    44  				`{"stack":{"runImage":{"image":"some/run", "mirrors":["example.com/some/run"]}}}`))
    45  			h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.stack.id", "io.buildpacks.stacks.jammy"))
    46  			fakeImageFetcher.LocalImages["some/app"] = fakeAppImage
    47  
    48  			fakeRunImage = fakes.NewImage("some/run", "run-image-top-layer-sha", &fakeIdentifier{name: "run-image-digest"})
    49  			h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "io.buildpacks.stacks.jammy"))
    50  			fakeImageFetcher.LocalImages["some/run"] = fakeRunImage
    51  
    52  			fakeRunImageMirror = fakes.NewImage("example.com/some/run", "mirror-top-layer-sha", &fakeIdentifier{name: "mirror-digest"})
    53  			h.AssertNil(t, fakeRunImageMirror.SetLabel("io.buildpacks.stack.id", "io.buildpacks.stacks.jammy"))
    54  			fakeImageFetcher.LocalImages["example.com/some/run"] = fakeRunImageMirror
    55  
    56  			keychain, err := auth.DefaultKeychain("pack-test/dummy")
    57  			h.AssertNil(t, err)
    58  
    59  			fakeLogger := logging.NewLogWithWriters(&out, &out)
    60  			subject = &Client{
    61  				logger:       fakeLogger,
    62  				imageFetcher: fakeImageFetcher,
    63  				keychain:     keychain,
    64  			}
    65  		})
    66  
    67  		it.After(func() {
    68  			h.AssertNilE(t, fakeAppImage.Cleanup())
    69  			h.AssertNilE(t, fakeRunImage.Cleanup())
    70  			h.AssertNilE(t, fakeRunImageMirror.Cleanup())
    71  		})
    72  
    73  		when("#Rebase", func() {
    74  			when("run image is provided by the user", func() {
    75  				when("the image has a label with a run image specified", func() {
    76  					var fakeCustomRunImage *fakes.Image
    77  
    78  					it.Before(func() {
    79  						fakeCustomRunImage = fakes.NewImage("custom/run", "custom-base-top-layer-sha", &fakeIdentifier{name: "custom-base-digest"})
    80  						h.AssertNil(t, fakeCustomRunImage.SetLabel("io.buildpacks.stack.id", "io.buildpacks.stacks.jammy"))
    81  						fakeImageFetcher.LocalImages["custom/run"] = fakeCustomRunImage
    82  					})
    83  
    84  					it.After(func() {
    85  						h.AssertNilE(t, fakeCustomRunImage.Cleanup())
    86  					})
    87  
    88  					when("--force", func() {
    89  						it("uses the run image provided by the user", func() {
    90  							h.AssertNil(t, subject.Rebase(context.TODO(),
    91  								RebaseOptions{
    92  									RunImage: "custom/run",
    93  									RepoName: "some/app",
    94  									Force:    true,
    95  								}))
    96  							h.AssertEq(t, fakeAppImage.Base(), "custom/run")
    97  							lbl, _ := fakeAppImage.Label("io.buildpacks.lifecycle.metadata")
    98  							h.AssertContains(t, lbl, `"runImage":{"topLayer":"custom-base-top-layer-sha","reference":"custom-base-digest"`)
    99  						})
   100  					})
   101  
   102  					it("errors", func() {
   103  						h.AssertError(t, subject.Rebase(context.TODO(),
   104  							RebaseOptions{
   105  								RunImage: "custom/run",
   106  								RepoName: "some/app",
   107  							}), "new base image 'custom/run' not found in existing run image metadata")
   108  					})
   109  				})
   110  			})
   111  
   112  			when("run image is NOT provided by the user", func() {
   113  				when("the image has a label with a run image specified", func() {
   114  					it("uses the run image provided in the App image label", func() {
   115  						h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   116  							RepoName: "some/app",
   117  						}))
   118  						h.AssertEq(t, fakeAppImage.Base(), "some/run")
   119  						lbl, _ := fakeAppImage.Label("io.buildpacks.lifecycle.metadata")
   120  						h.AssertContains(t, lbl, `"runImage":{"topLayer":"run-image-top-layer-sha","reference":"run-image-digest"`)
   121  					})
   122  				})
   123  
   124  				when("the image has a label with a run image mirrors specified", func() {
   125  					when("there are no user provided mirrors", func() {
   126  						it.Before(func() {
   127  							fakeImageFetcher.LocalImages["example.com/some/app"] = fakeAppImage
   128  						})
   129  
   130  						it("chooses a matching mirror from the app image label", func() {
   131  							h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   132  								RepoName: "example.com/some/app",
   133  							}))
   134  							h.AssertEq(t, fakeAppImage.Base(), "example.com/some/run")
   135  							lbl, _ := fakeAppImage.Label("io.buildpacks.lifecycle.metadata")
   136  							h.AssertContains(t, lbl, `"runImage":{"topLayer":"mirror-top-layer-sha","reference":"mirror-digest"`)
   137  						})
   138  					})
   139  
   140  					when("there are user provided mirrors", func() {
   141  						var (
   142  							fakeLocalMirror *fakes.Image
   143  						)
   144  						it.Before(func() {
   145  							fakeImageFetcher.LocalImages["example.com/some/app"] = fakeAppImage
   146  							fakeLocalMirror = fakes.NewImage("example.com/some/local-run", "local-mirror-top-layer-sha", &fakeIdentifier{name: "local-mirror-digest"})
   147  							h.AssertNil(t, fakeLocalMirror.SetLabel("io.buildpacks.stack.id", "io.buildpacks.stacks.jammy"))
   148  							fakeImageFetcher.LocalImages["example.com/some/local-run"] = fakeLocalMirror
   149  						})
   150  
   151  						it.After(func() {
   152  							h.AssertNilE(t, fakeLocalMirror.Cleanup())
   153  						})
   154  						when("--force", func() {
   155  							it("chooses a matching local mirror first", func() {
   156  								h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   157  									RepoName: "example.com/some/app",
   158  									AdditionalMirrors: map[string][]string{
   159  										"some/run": {"example.com/some/local-run"},
   160  									},
   161  									Force: true,
   162  								}))
   163  								h.AssertEq(t, fakeAppImage.Base(), "example.com/some/local-run")
   164  								lbl, _ := fakeAppImage.Label("io.buildpacks.lifecycle.metadata")
   165  								h.AssertContains(t, lbl, `"runImage":{"topLayer":"local-mirror-top-layer-sha","reference":"local-mirror-digest"`)
   166  							})
   167  						})
   168  					})
   169  					when("there is a label and it has a run image and no stack", func() {
   170  						it("reads the run image from the label", func() {
   171  							h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.lifecycle.metadata",
   172  								`{"runImage":{"image":"some/run", "mirrors":["example.com/some/run"]}}`))
   173  							h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   174  								RepoName: "some/app",
   175  							}))
   176  							h.AssertEq(t, fakeAppImage.Base(), "some/run")
   177  						})
   178  					})
   179  					when("there is neither runImage nor stack", func() {
   180  						it("fails gracefully", func() {
   181  							h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.lifecycle.metadata", `{}`))
   182  							h.AssertError(t, subject.Rebase(context.TODO(), RebaseOptions{RepoName: "some/app"}),
   183  								"run image must be specified")
   184  						})
   185  					})
   186  				})
   187  
   188  				when("the image does not have a label with a run image specified", func() {
   189  					it("returns an error", func() {
   190  						h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.lifecycle.metadata", "{}"))
   191  						err := subject.Rebase(context.TODO(), RebaseOptions{
   192  							RepoName: "some/app",
   193  						})
   194  						h.AssertError(t, err, "run image must be specified")
   195  					})
   196  				})
   197  			})
   198  
   199  			when("publish", func() {
   200  				var (
   201  					fakeRemoteRunImage *fakes.Image
   202  				)
   203  
   204  				it.Before(func() {
   205  					fakeRemoteRunImage = fakes.NewImage("some/run", "remote-top-layer-sha", &fakeIdentifier{name: "remote-digest"})
   206  					h.AssertNil(t, fakeRemoteRunImage.SetLabel("io.buildpacks.stack.id", "io.buildpacks.stacks.jammy"))
   207  					fakeImageFetcher.RemoteImages["some/run"] = fakeRemoteRunImage
   208  				})
   209  
   210  				it.After(func() {
   211  					h.AssertNilE(t, fakeRemoteRunImage.Cleanup())
   212  				})
   213  
   214  				when("is false", func() {
   215  					when("pull policy is always", func() {
   216  						it("updates the local image", func() {
   217  							h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   218  								RepoName:   "some/app",
   219  								PullPolicy: image.PullAlways,
   220  							}))
   221  							h.AssertEq(t, fakeAppImage.Base(), "some/run")
   222  							lbl, _ := fakeAppImage.Label("io.buildpacks.lifecycle.metadata")
   223  							h.AssertContains(t, lbl, `"runImage":{"topLayer":"remote-top-layer-sha","reference":"remote-digest"`)
   224  						})
   225  					})
   226  
   227  					when("pull policy is never", func() {
   228  						it("uses local image", func() {
   229  							h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   230  								RepoName:   "some/app",
   231  								PullPolicy: image.PullNever,
   232  							}))
   233  							h.AssertEq(t, fakeAppImage.Base(), "some/run")
   234  							lbl, _ := fakeAppImage.Label("io.buildpacks.lifecycle.metadata")
   235  							h.AssertContains(t, lbl, `"runImage":{"topLayer":"run-image-top-layer-sha","reference":"run-image-digest"`)
   236  						})
   237  					})
   238  				})
   239  
   240  				when("report directory is set", func() {
   241  					it("writes the report", func() {
   242  						tmpdir := t.TempDir()
   243  						h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   244  							RepoName:             "some/app",
   245  							ReportDestinationDir: tmpdir,
   246  						}))
   247  						_, err := os.Stat(filepath.Join(tmpdir, "report.toml"))
   248  						h.AssertNil(t, err)
   249  					})
   250  				})
   251  
   252  				when("is true", func() {
   253  					it.Before(func() {
   254  						fakeImageFetcher.RemoteImages["some/app"] = fakeAppImage
   255  					})
   256  
   257  					when("skip pull is anything", func() {
   258  						it("uses remote image", func() {
   259  							h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   260  								RepoName: "some/app",
   261  								Publish:  true,
   262  							}))
   263  							h.AssertEq(t, fakeAppImage.Base(), "some/run")
   264  							lbl, _ := fakeAppImage.Label("io.buildpacks.lifecycle.metadata")
   265  							h.AssertContains(t, lbl, `"runImage":{"topLayer":"remote-top-layer-sha","reference":"remote-digest"`)
   266  							args := fakeImageFetcher.FetchCalls["some/run"]
   267  							h.AssertEq(t, args.Platform, "linux/amd64")
   268  						})
   269  					})
   270  				})
   271  			})
   272  			when("previous image is provided", func() {
   273  				it("fetches the image using the previous image name", func() {
   274  					h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   275  						RepoName:      "new/app",
   276  						PreviousImage: "some/app",
   277  					}))
   278  					args := fakeImageFetcher.FetchCalls["some/app"]
   279  					h.AssertNotNil(t, args)
   280  					h.AssertEq(t, args.Daemon, true)
   281  				})
   282  			})
   283  
   284  			when("previous image is set to new image name", func() {
   285  				it("returns error if Fetch function fails", func() {
   286  					err := subject.Rebase(context.TODO(), RebaseOptions{
   287  						RepoName:      "some/app",
   288  						PreviousImage: "new/app",
   289  					})
   290  					h.AssertError(t, err, "image 'new/app' does not exist on the daemon: not found")
   291  				})
   292  			})
   293  
   294  			when("previous image is not provided", func() {
   295  				it("fetches the image using the repo name", func() {
   296  					h.AssertNil(t, subject.Rebase(context.TODO(), RebaseOptions{
   297  						RepoName: "some/app",
   298  					}))
   299  					args := fakeImageFetcher.FetchCalls["some/app"]
   300  					h.AssertNotNil(t, args)
   301  					h.AssertEq(t, args.Daemon, true)
   302  				})
   303  			})
   304  		})
   305  	})
   306  }
   307  
   308  type fakeIdentifier struct {
   309  	name string
   310  }
   311  
   312  func (f *fakeIdentifier) String() string {
   313  	return f.name
   314  }