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 }