github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/pkg/client/inspect_image_test.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "testing" 9 10 "github.com/buildpacks/imgutil/fakes" 11 "github.com/buildpacks/lifecycle/launch" 12 "github.com/buildpacks/lifecycle/platform/files" 13 "github.com/golang/mock/gomock" 14 "github.com/google/go-cmp/cmp" 15 "github.com/google/go-cmp/cmp/cmpopts" 16 "github.com/heroku/color" 17 "github.com/sclevine/spec" 18 "github.com/sclevine/spec/report" 19 20 "github.com/buildpacks/pack/pkg/image" 21 "github.com/buildpacks/pack/pkg/logging" 22 "github.com/buildpacks/pack/pkg/testmocks" 23 h "github.com/buildpacks/pack/testhelpers" 24 ) 25 26 func TestInspectImage(t *testing.T) { 27 color.Disable(true) 28 defer color.Disable(false) 29 spec.Run(t, "InspectImage", testInspectImage, spec.Parallel(), spec.Report(report.Terminal{})) 30 } 31 32 // PlatformAPI should be ignored because it is not set in the metadata label 33 var ignorePlatformAPI = []cmp.Option{ 34 cmpopts.IgnoreFields(launch.Process{}, "PlatformAPI"), 35 cmpopts.IgnoreFields(launch.RawCommand{}, "PlatformAPI"), 36 } 37 38 func testInspectImage(t *testing.T, when spec.G, it spec.S) { 39 var ( 40 subject *Client 41 mockImageFetcher *testmocks.MockImageFetcher 42 mockDockerClient *testmocks.MockCommonAPIClient 43 mockController *gomock.Controller 44 mockImage *testmocks.MockImage 45 mockImageNoRebasable *testmocks.MockImage 46 mockImageRebasableWithoutLabel *testmocks.MockImage 47 mockImageWithExtension *testmocks.MockImage 48 out bytes.Buffer 49 ) 50 51 it.Before(func() { 52 mockController = gomock.NewController(t) 53 mockImageFetcher = testmocks.NewMockImageFetcher(mockController) 54 mockDockerClient = testmocks.NewMockCommonAPIClient(mockController) 55 56 var err error 57 subject, err = NewClient(WithLogger(logging.NewLogWithWriters(&out, &out)), WithFetcher(mockImageFetcher), WithDockerClient(mockDockerClient)) 58 h.AssertNil(t, err) 59 60 mockImage = testmocks.NewImage("some/image", "", nil) 61 h.AssertNil(t, mockImage.SetWorkingDir("/test-workdir")) 62 h.AssertNil(t, mockImage.SetLabel("io.buildpacks.stack.id", "test.stack.id")) 63 h.AssertNil(t, mockImage.SetLabel("io.buildpacks.rebasable", "true")) 64 h.AssertNil(t, mockImage.SetLabel( 65 "io.buildpacks.lifecycle.metadata", 66 `{ 67 "stack": { 68 "runImage": { 69 "image": "some-run-image", 70 "mirrors": [ 71 "some-mirror", 72 "other-mirror" 73 ] 74 } 75 }, 76 "runImage": { 77 "topLayer": "some-top-layer", 78 "reference": "some-run-image-reference" 79 } 80 }`, 81 )) 82 h.AssertNil(t, mockImage.SetLabel( 83 "io.buildpacks.build.metadata", 84 `{ 85 "bom": [ 86 { 87 "name": "some-bom-element" 88 } 89 ], 90 "buildpacks": [ 91 { 92 "id": "some-buildpack", 93 "version": "some-version" 94 }, 95 { 96 "id": "other-buildpack", 97 "version": "other-version" 98 } 99 ], 100 "processes": [ 101 { 102 "type": "other-process", 103 "command": "/other/process", 104 "args": ["opt", "1"], 105 "direct": true 106 }, 107 { 108 "type": "web", 109 "command": "/start/web-process", 110 "args": ["-p", "1234"], 111 "direct": false 112 } 113 ], 114 "launcher": { 115 "version": "0.5.0" 116 } 117 }`, 118 )) 119 120 mockImageNoRebasable = testmocks.NewImage("some/imageNoRebasable", "", nil) 121 h.AssertNil(t, mockImageNoRebasable.SetWorkingDir("/test-workdir")) 122 h.AssertNil(t, mockImageNoRebasable.SetLabel("io.buildpacks.stack.id", "test.stack.id")) 123 h.AssertNil(t, mockImageNoRebasable.SetLabel("io.buildpacks.rebasable", "false")) 124 h.AssertNil(t, mockImageNoRebasable.SetLabel( 125 "io.buildpacks.lifecycle.metadata", 126 `{ 127 "stack": { 128 "runImage": { 129 "image": "some-run-image-no-rebasable", 130 "mirrors": [ 131 "some-mirror", 132 "other-mirror" 133 ] 134 } 135 }, 136 "runImage": { 137 "topLayer": "some-top-layer", 138 "reference": "some-run-image-reference" 139 } 140 }`, 141 )) 142 h.AssertNil(t, mockImageNoRebasable.SetLabel( 143 "io.buildpacks.build.metadata", 144 `{ 145 "bom": [ 146 { 147 "name": "some-bom-element" 148 } 149 ], 150 "buildpacks": [ 151 { 152 "id": "some-buildpack", 153 "version": "some-version" 154 }, 155 { 156 "id": "other-buildpack", 157 "version": "other-version" 158 } 159 ], 160 "processes": [ 161 { 162 "type": "other-process", 163 "command": "/other/process", 164 "args": ["opt", "1"], 165 "direct": true 166 }, 167 { 168 "type": "web", 169 "command": "/start/web-process", 170 "args": ["-p", "1234"], 171 "direct": false 172 } 173 ], 174 "launcher": { 175 "version": "0.5.0" 176 } 177 }`, 178 )) 179 180 mockImageRebasableWithoutLabel = testmocks.NewImage("some/imageRebasableWithoutLabel", "", nil) 181 h.AssertNil(t, mockImageNoRebasable.SetWorkingDir("/test-workdir")) 182 h.AssertNil(t, mockImageNoRebasable.SetLabel("io.buildpacks.stack.id", "test.stack.id")) 183 h.AssertNil(t, mockImageNoRebasable.SetLabel( 184 "io.buildpacks.lifecycle.metadata", 185 `{ 186 "stack": { 187 "runImage": { 188 "image": "some-run-image-no-rebasable", 189 "mirrors": [ 190 "some-mirror", 191 "other-mirror" 192 ] 193 } 194 }, 195 "runImage": { 196 "topLayer": "some-top-layer", 197 "reference": "some-run-image-reference" 198 } 199 }`, 200 )) 201 h.AssertNil(t, mockImageNoRebasable.SetLabel( 202 "io.buildpacks.build.metadata", 203 `{ 204 "bom": [ 205 { 206 "name": "some-bom-element" 207 } 208 ], 209 "buildpacks": [ 210 { 211 "id": "some-buildpack", 212 "version": "some-version" 213 }, 214 { 215 "id": "other-buildpack", 216 "version": "other-version" 217 } 218 ], 219 "processes": [ 220 { 221 "type": "other-process", 222 "command": "/other/process", 223 "args": ["opt", "1"], 224 "direct": true 225 }, 226 { 227 "type": "web", 228 "command": "/start/web-process", 229 "args": ["-p", "1234"], 230 "direct": false 231 } 232 ], 233 "launcher": { 234 "version": "0.5.0" 235 } 236 }`, 237 )) 238 239 mockImageWithExtension = testmocks.NewImage("some/imageWithExtension", "", nil) 240 h.AssertNil(t, mockImageWithExtension.SetWorkingDir("/test-workdir")) 241 h.AssertNil(t, mockImageWithExtension.SetLabel("io.buildpacks.stack.id", "test.stack.id")) 242 h.AssertNil(t, mockImageWithExtension.SetLabel("io.buildpacks.rebasable", "true")) 243 h.AssertNil(t, mockImageWithExtension.SetLabel( 244 "io.buildpacks.lifecycle.metadata", 245 `{ 246 "stack": { 247 "runImage": { 248 "image": "some-run-image", 249 "mirrors": [ 250 "some-mirror", 251 "other-mirror" 252 ] 253 } 254 }, 255 "runImage": { 256 "topLayer": "some-top-layer", 257 "reference": "some-run-image-reference" 258 } 259 }`, 260 )) 261 h.AssertNil(t, mockImageWithExtension.SetLabel( 262 "io.buildpacks.build.metadata", 263 `{ 264 "bom": [ 265 { 266 "name": "some-bom-element" 267 } 268 ], 269 "buildpacks": [ 270 { 271 "id": "some-buildpack", 272 "version": "some-version" 273 }, 274 { 275 "id": "other-buildpack", 276 "version": "other-version" 277 } 278 ], 279 "extensions": [ 280 { 281 "id": "some-extension", 282 "version": "some-version" 283 }, 284 { 285 "id": "other-extension", 286 "version": "other-version" 287 } 288 ], 289 "processes": [ 290 { 291 "type": "other-process", 292 "command": "/other/process", 293 "args": ["opt", "1"], 294 "direct": true 295 }, 296 { 297 "type": "web", 298 "command": "/start/web-process", 299 "args": ["-p", "1234"], 300 "direct": false 301 } 302 ], 303 "launcher": { 304 "version": "0.5.0" 305 } 306 }`, 307 )) 308 }) 309 310 it.After(func() { 311 mockController.Finish() 312 }) 313 314 when("the image exists", func() { 315 for _, useDaemon := range []bool{true, false} { 316 useDaemon := useDaemon 317 when(fmt.Sprintf("daemon is %t", useDaemon), func() { 318 it.Before(func() { 319 if useDaemon { 320 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/image", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}).Return(mockImage, nil).AnyTimes() 321 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/imageNoRebasable", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}).Return(mockImageNoRebasable, nil).AnyTimes() 322 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/imageRebasableWithoutLabel", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}).Return(mockImageRebasableWithoutLabel, nil).AnyTimes() 323 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/imageWithExtension", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}).Return(mockImageWithExtension, nil).AnyTimes() 324 } else { 325 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/image", image.FetchOptions{Daemon: false, PullPolicy: image.PullNever}).Return(mockImage, nil).AnyTimes() 326 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/imageNoRebasable", image.FetchOptions{Daemon: false, PullPolicy: image.PullNever}).Return(mockImageNoRebasable, nil).AnyTimes() 327 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/imageRebasableWithoutLabel", image.FetchOptions{Daemon: false, PullPolicy: image.PullNever}).Return(mockImageRebasableWithoutLabel, nil).AnyTimes() 328 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/imageWithExtension", image.FetchOptions{Daemon: false, PullPolicy: image.PullNever}).Return(mockImageWithExtension, nil).AnyTimes() 329 } 330 }) 331 332 it("returns the stack ID", func() { 333 info, err := subject.InspectImage("some/image", useDaemon) 334 h.AssertNil(t, err) 335 h.AssertEq(t, info.StackID, "test.stack.id") 336 }) 337 338 it("returns the stack ID with extension", func() { 339 infoWithExtension, err := subject.InspectImage("some/imageWithExtension", useDaemon) 340 h.AssertNil(t, err) 341 h.AssertEq(t, infoWithExtension.StackID, "test.stack.id") 342 }) 343 344 it("returns the stack from runImage.Image if set", func() { 345 h.AssertNil(t, mockImage.SetLabel( 346 "io.buildpacks.lifecycle.metadata", 347 `{ 348 "runImage": { 349 "topLayer": "some-top-layer", 350 "reference": "some-run-image-reference", 351 "image": "is everything" 352 } 353 }`, 354 )) 355 info, err := subject.InspectImage("some/image", useDaemon) 356 h.AssertNil(t, err) 357 h.AssertEq(t, info.Stack, 358 files.Stack{RunImage: files.RunImageForExport{Image: "is everything"}}) 359 }) 360 361 it("returns the stack", func() { 362 info, err := subject.InspectImage("some/image", useDaemon) 363 h.AssertNil(t, err) 364 h.AssertEq(t, info.Stack, 365 files.Stack{ 366 RunImage: files.RunImageForExport{ 367 Image: "some-run-image", 368 Mirrors: []string{ 369 "some-mirror", 370 "other-mirror", 371 }, 372 }, 373 }, 374 ) 375 }) 376 377 it("returns the stack with extension", func() { 378 infoWithExtension, err := subject.InspectImage("some/imageWithExtension", useDaemon) 379 h.AssertNil(t, err) 380 h.AssertEq(t, infoWithExtension.Stack, 381 files.Stack{ 382 RunImage: files.RunImageForExport{ 383 Image: "some-run-image", 384 Mirrors: []string{ 385 "some-mirror", 386 "other-mirror", 387 }, 388 }, 389 }, 390 ) 391 }) 392 393 it("returns the base image", func() { 394 info, err := subject.InspectImage("some/image", useDaemon) 395 h.AssertNil(t, err) 396 h.AssertEq(t, info.Base, 397 files.RunImageForRebase{ 398 TopLayer: "some-top-layer", 399 Reference: "some-run-image-reference", 400 }, 401 ) 402 }) 403 404 it("returns the base image with extension", func() { 405 infoWithExtension, err := subject.InspectImage("some/imageWithExtension", useDaemon) 406 h.AssertNil(t, err) 407 h.AssertEq(t, infoWithExtension.Base, 408 files.RunImageForRebase{ 409 TopLayer: "some-top-layer", 410 Reference: "some-run-image-reference", 411 }, 412 ) 413 }) 414 415 it("returns the rebasable image", func() { 416 info, err := subject.InspectImage("some/image", useDaemon) 417 h.AssertNil(t, err) 418 h.AssertEq(t, info.Rebasable, true) 419 }) 420 421 it("returns the rebasable image true if the label has not been set", func() { 422 info, err := subject.InspectImage("some/imageRebasableWithoutLabel", useDaemon) 423 h.AssertNil(t, err) 424 h.AssertEq(t, info.Rebasable, true) 425 }) 426 427 it("returns the no rebasable image", func() { 428 info, err := subject.InspectImage("some/imageNoRebasable", useDaemon) 429 h.AssertNil(t, err) 430 h.AssertEq(t, info.Rebasable, false) 431 }) 432 433 it("returns the rebasable image with Extension", func() { 434 infoRebasableWithExtension, err := subject.InspectImage("some/imageWithExtension", useDaemon) 435 h.AssertNil(t, err) 436 h.AssertEq(t, infoRebasableWithExtension.Rebasable, true) 437 }) 438 439 it("returns the BOM", func() { 440 info, err := subject.InspectImage("some/image", useDaemon) 441 h.AssertNil(t, err) 442 443 rawBOM, err := json.Marshal(info.BOM) 444 h.AssertNil(t, err) 445 h.AssertContains(t, string(rawBOM), `[{"name":"some-bom-element"`) 446 }) 447 448 it("returns the BOM", func() { 449 infoWithExtension, err := subject.InspectImage("some/imageWithExtension", useDaemon) 450 h.AssertNil(t, err) 451 452 rawBOM, err := json.Marshal(infoWithExtension.BOM) 453 h.AssertNil(t, err) 454 h.AssertContains(t, string(rawBOM), `[{"name":"some-bom-element"`) 455 }) 456 457 it("returns the buildpacks", func() { 458 info, err := subject.InspectImage("some/image", useDaemon) 459 h.AssertNil(t, err) 460 461 h.AssertEq(t, len(info.Buildpacks), 2) 462 h.AssertEq(t, info.Buildpacks[0].ID, "some-buildpack") 463 h.AssertEq(t, info.Buildpacks[0].Version, "some-version") 464 h.AssertEq(t, info.Buildpacks[1].ID, "other-buildpack") 465 h.AssertEq(t, info.Buildpacks[1].Version, "other-version") 466 }) 467 468 it("returns the buildpacks with extension", func() { 469 infoWithExtension, err := subject.InspectImage("some/imageWithExtension", useDaemon) 470 h.AssertNil(t, err) 471 472 h.AssertEq(t, len(infoWithExtension.Buildpacks), 2) 473 h.AssertEq(t, infoWithExtension.Buildpacks[0].ID, "some-buildpack") 474 h.AssertEq(t, infoWithExtension.Buildpacks[0].Version, "some-version") 475 h.AssertEq(t, infoWithExtension.Buildpacks[1].ID, "other-buildpack") 476 h.AssertEq(t, infoWithExtension.Buildpacks[1].Version, "other-version") 477 }) 478 479 it("returns the extensions", func() { 480 infoWithExtension, err := subject.InspectImage("some/imageWithExtension", useDaemon) 481 h.AssertNil(t, err) 482 483 h.AssertEq(t, len(infoWithExtension.Extensions), 2) 484 h.AssertEq(t, infoWithExtension.Extensions[0].ID, "some-extension") 485 h.AssertEq(t, infoWithExtension.Extensions[0].Version, "some-version") 486 h.AssertEq(t, infoWithExtension.Extensions[1].ID, "other-extension") 487 h.AssertEq(t, infoWithExtension.Extensions[1].Version, "other-version") 488 }) 489 490 it("returns the processes setting the web process as default", func() { 491 info, err := subject.InspectImage("some/image", useDaemon) 492 h.AssertNil(t, err) 493 494 h.AssertEq(t, info.Processes, 495 ProcessDetails{ 496 DefaultProcess: &launch.Process{ 497 Type: "web", 498 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 499 Args: []string{"-p", "1234"}, 500 Direct: false, 501 WorkingDirectory: "/test-workdir", 502 }, 503 OtherProcesses: []launch.Process{ 504 { 505 Type: "other-process", 506 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 507 Args: []string{"opt", "1"}, 508 Direct: true, 509 WorkingDirectory: "/test-workdir", 510 }, 511 }, 512 }, 513 ignorePlatformAPI...) 514 }) 515 516 when("Platform API < 0.4", func() { 517 when("CNB_PROCESS_TYPE is set", func() { 518 it.Before(func() { 519 h.AssertNil(t, mockImage.SetEnv("CNB_PROCESS_TYPE", "other-process")) 520 }) 521 522 it("returns processes setting the correct default process", func() { 523 info, err := subject.InspectImage("some/image", useDaemon) 524 h.AssertNil(t, err) 525 526 h.AssertEq(t, info.Processes, 527 ProcessDetails{ 528 DefaultProcess: &launch.Process{ 529 Type: "other-process", 530 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 531 Args: []string{"opt", "1"}, 532 Direct: true, 533 WorkingDirectory: "/test-workdir", 534 }, 535 OtherProcesses: []launch.Process{ 536 { 537 Type: "web", 538 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 539 Args: []string{"-p", "1234"}, 540 Direct: false, 541 WorkingDirectory: "/test-workdir", 542 }, 543 }, 544 }, 545 ignorePlatformAPI...) 546 }) 547 }) 548 549 when("CNB_PROCESS_TYPE is set, but doesn't match an existing process", func() { 550 it.Before(func() { 551 h.AssertNil(t, mockImage.SetEnv("CNB_PROCESS_TYPE", "missing-process")) 552 }) 553 554 it("returns a nil default process", func() { 555 info, err := subject.InspectImage("some/image", useDaemon) 556 h.AssertNil(t, err) 557 558 h.AssertEq(t, info.Processes, 559 ProcessDetails{ 560 DefaultProcess: nil, 561 OtherProcesses: []launch.Process{ 562 { 563 Type: "other-process", 564 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 565 Args: []string{"opt", "1"}, 566 Direct: true, 567 WorkingDirectory: "/test-workdir", 568 }, 569 { 570 Type: "web", 571 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 572 Args: []string{"-p", "1234"}, 573 Direct: false, 574 WorkingDirectory: "/test-workdir", 575 }, 576 }, 577 }, 578 ignorePlatformAPI...) 579 }) 580 }) 581 582 it("returns a nil default process when CNB_PROCESS_TYPE is not set and there is no web process", func() { 583 h.AssertNil(t, mockImage.SetLabel( 584 "io.buildpacks.build.metadata", 585 `{ 586 "processes": [ 587 { 588 "type": "other-process", 589 "command": "/other/process", 590 "args": ["opt", "1"], 591 "direct": true 592 } 593 ] 594 }`, 595 )) 596 597 info, err := subject.InspectImage("some/image", useDaemon) 598 h.AssertNil(t, err) 599 600 h.AssertEq(t, info.Processes, 601 ProcessDetails{ 602 DefaultProcess: nil, 603 OtherProcesses: []launch.Process{ 604 { 605 Type: "other-process", 606 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 607 Args: []string{"opt", "1"}, 608 Direct: true, 609 WorkingDirectory: "/test-workdir", 610 }, 611 }, 612 }, 613 ignorePlatformAPI...) 614 }) 615 }) 616 617 when("Platform API >= 0.4 and <= 0.8", func() { 618 it.Before(func() { 619 h.AssertNil(t, mockImage.SetEnv("CNB_PLATFORM_API", "0.4")) 620 }) 621 622 when("CNB_PLATFORM_API set to bad value", func() { 623 it("errors", func() { 624 h.AssertNil(t, mockImage.SetEnv("CNB_PLATFORM_API", "not-semver")) 625 _, err := subject.InspectImage("some/image", useDaemon) 626 h.AssertError(t, err, "parsing platform api version") 627 }) 628 }) 629 630 when("Can't inspect Image entrypoint", func() { 631 it("errors", func() { 632 mockImage.EntrypointCall.Returns.Error = errors.New("some-error") 633 634 _, err := subject.InspectImage("some/image", useDaemon) 635 h.AssertError(t, err, "reading entrypoint") 636 }) 637 }) 638 639 when("ENTRYPOINT is empty", func() { 640 it("sets nil default process", func() { 641 info, err := subject.InspectImage("some/image", useDaemon) 642 h.AssertNil(t, err) 643 644 h.AssertEq(t, info.Processes, 645 ProcessDetails{ 646 DefaultProcess: nil, 647 OtherProcesses: []launch.Process{ 648 { 649 Type: "other-process", 650 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 651 Args: []string{"opt", "1"}, 652 Direct: true, 653 WorkingDirectory: "/test-workdir", 654 }, 655 { 656 Type: "web", 657 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 658 Args: []string{"-p", "1234"}, 659 Direct: false, 660 WorkingDirectory: "/test-workdir", 661 }, 662 }, 663 }, 664 ignorePlatformAPI...) 665 }) 666 }) 667 668 when("CNB_PROCESS_TYPE is set", func() { 669 it.Before(func() { 670 h.AssertNil(t, mockImage.SetEnv("CNB_PROCESS_TYPE", "other-process")) 671 672 mockImage.EntrypointCall.Returns.StringArr = []string{"/cnb/process/web"} 673 }) 674 675 it("ignores it and sets the correct default process", func() { 676 info, err := subject.InspectImage("some/image", useDaemon) 677 h.AssertNil(t, err) 678 679 h.AssertEq(t, info.Processes, 680 ProcessDetails{ 681 DefaultProcess: &launch.Process{ 682 Type: "web", 683 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 684 Args: []string{"-p", "1234"}, 685 Direct: false, 686 WorkingDirectory: "/test-workdir", 687 }, 688 OtherProcesses: []launch.Process{ 689 { 690 Type: "other-process", 691 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 692 Args: []string{"opt", "1"}, 693 Direct: true, 694 WorkingDirectory: "/test-workdir", 695 }, 696 }, 697 }, 698 ignorePlatformAPI...) 699 }) 700 }) 701 702 when("ENTRYPOINT is set, but doesn't match an existing process", func() { 703 it.Before(func() { 704 mockImage.EntrypointCall.Returns.StringArr = []string{"/cnb/process/unknown-process"} 705 }) 706 707 it("returns nil default default process", func() { 708 info, err := subject.InspectImage("some/image", useDaemon) 709 h.AssertNil(t, err) 710 711 h.AssertEq(t, info.Processes, 712 ProcessDetails{ 713 DefaultProcess: nil, 714 OtherProcesses: []launch.Process{ 715 { 716 Type: "other-process", 717 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 718 Args: []string{"opt", "1"}, 719 Direct: true, 720 WorkingDirectory: "/test-workdir", 721 }, 722 { 723 Type: "web", 724 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 725 Args: []string{"-p", "1234"}, 726 Direct: false, 727 WorkingDirectory: "/test-workdir", 728 }, 729 }, 730 }, 731 ignorePlatformAPI...) 732 }) 733 }) 734 735 when("ENTRYPOINT set to /cnb/lifecycle/launcher", func() { 736 it("returns a nil default process", func() { 737 mockImage.EntrypointCall.Returns.StringArr = []string{"/cnb/lifecycle/launcher"} 738 739 h.AssertNil(t, mockImage.SetLabel( 740 "io.buildpacks.build.metadata", 741 `{ 742 "processes": [ 743 { 744 "type": "other-process", 745 "command": "/other/process", 746 "args": ["opt", "1"], 747 "direct": true 748 } 749 ] 750 }`, 751 )) 752 753 info, err := subject.InspectImage("some/image", useDaemon) 754 h.AssertNil(t, err) 755 756 h.AssertEq(t, info.Processes, 757 ProcessDetails{ 758 DefaultProcess: nil, 759 OtherProcesses: []launch.Process{ 760 { 761 Type: "other-process", 762 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 763 Args: []string{"opt", "1"}, 764 Direct: true, 765 WorkingDirectory: "/test-workdir", 766 }, 767 }, 768 }, 769 ignorePlatformAPI...) 770 }) 771 }) 772 773 when("Inspecting Windows images", func() { 774 when(`ENTRYPOINT set to c:\cnb\lifecycle\launcher.exe`, func() { 775 it("sets default process to nil", func() { 776 mockImage.EntrypointCall.Returns.StringArr = []string{`c:\cnb\lifecycle\launcher.exe`} 777 778 info, err := subject.InspectImage("some/image", useDaemon) 779 h.AssertNil(t, err) 780 781 h.AssertEq(t, info.Processes, 782 ProcessDetails{ 783 DefaultProcess: nil, 784 OtherProcesses: []launch.Process{ 785 { 786 Type: "other-process", 787 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 788 Args: []string{"opt", "1"}, 789 Direct: true, 790 WorkingDirectory: "/test-workdir", 791 }, 792 { 793 Type: "web", 794 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 795 Args: []string{"-p", "1234"}, 796 Direct: false, 797 WorkingDirectory: "/test-workdir", 798 }, 799 }, 800 }, 801 ignorePlatformAPI...) 802 }) 803 }) 804 805 when("ENTRYPOINT is set, but doesn't match an existing process", func() { 806 it("sets default process to nil", func() { 807 mockImage.EntrypointCall.Returns.StringArr = []string{`c:\cnb\process\unknown-process.exe`} 808 809 info, err := subject.InspectImage("some/image", useDaemon) 810 h.AssertNil(t, err) 811 812 h.AssertEq(t, info.Processes, 813 ProcessDetails{ 814 DefaultProcess: nil, 815 OtherProcesses: []launch.Process{ 816 { 817 Type: "other-process", 818 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 819 Args: []string{"opt", "1"}, 820 Direct: true, 821 WorkingDirectory: "/test-workdir", 822 }, 823 { 824 Type: "web", 825 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 826 Args: []string{"-p", "1234"}, 827 Direct: false, 828 WorkingDirectory: "/test-workdir", 829 }, 830 }, 831 }, 832 ignorePlatformAPI...) 833 }) 834 }) 835 836 when("ENTRYPOINT is set, and matches an existing process", func() { 837 it("sets default process to defined process", func() { 838 mockImage.EntrypointCall.Returns.StringArr = []string{`c:\cnb\process\other-process.exe`} 839 840 info, err := subject.InspectImage("some/image", useDaemon) 841 h.AssertNil(t, err) 842 843 h.AssertEq(t, info.Processes, 844 ProcessDetails{ 845 DefaultProcess: &launch.Process{ 846 Type: "other-process", 847 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 848 Args: []string{"opt", "1"}, 849 Direct: true, 850 WorkingDirectory: "/test-workdir", 851 }, 852 OtherProcesses: []launch.Process{ 853 { 854 Type: "web", 855 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 856 Args: []string{"-p", "1234"}, 857 Direct: false, 858 WorkingDirectory: "/test-workdir", 859 }, 860 }, 861 }, 862 ignorePlatformAPI...) 863 }) 864 }) 865 }) 866 }) 867 868 when("Platform API > 0.8", func() { 869 when("working-dir is set", func() { 870 it("returns process with working directory if available", func() { 871 h.AssertNil(t, mockImage.SetLabel( 872 "io.buildpacks.build.metadata", 873 `{ 874 "processes": [ 875 { 876 "type": "other-process", 877 "command": "/other/process", 878 "args": ["opt", "1"], 879 "direct": true, 880 "working-dir": "/other-workdir" 881 } 882 ] 883 }`, 884 )) 885 886 info, err := subject.InspectImage("some/image", useDaemon) 887 h.AssertNil(t, err) 888 fmt.Print(info) 889 890 h.AssertEq(t, info.Processes, 891 ProcessDetails{ 892 DefaultProcess: nil, 893 OtherProcesses: []launch.Process{ 894 { 895 Type: "other-process", 896 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 897 Args: []string{"opt", "1"}, 898 Direct: true, 899 WorkingDirectory: "/other-workdir", 900 }, 901 }, 902 }, 903 ignorePlatformAPI...) 904 }) 905 }) 906 907 when("working-dir is not set", func() { 908 it("returns process with working directory from image", func() { 909 info, err := subject.InspectImage("some/image", useDaemon) 910 h.AssertNil(t, err) 911 912 h.AssertEq(t, info.Processes, 913 ProcessDetails{ 914 DefaultProcess: &launch.Process{ 915 Type: "web", 916 Command: launch.RawCommand{Entries: []string{"/start/web-process"}}, 917 Args: []string{"-p", "1234"}, 918 Direct: false, 919 WorkingDirectory: "/test-workdir", 920 }, 921 OtherProcesses: []launch.Process{ 922 { 923 Type: "other-process", 924 Command: launch.RawCommand{Entries: []string{"/other/process"}}, 925 Args: []string{"opt", "1"}, 926 Direct: true, 927 WorkingDirectory: "/test-workdir", 928 }, 929 }, 930 }, 931 ignorePlatformAPI...) 932 }) 933 }) 934 }) 935 }) 936 } 937 }) 938 939 when("the image doesn't exist", func() { 940 it("returns nil", func() { 941 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "not/some-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}).Return(nil, image.ErrNotFound) 942 943 info, err := subject.InspectImage("not/some-image", true) 944 h.AssertNil(t, err) 945 h.AssertNil(t, info) 946 }) 947 }) 948 949 when("there is an error fetching the image", func() { 950 it("returns the error", func() { 951 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "not/some-image", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}).Return(nil, errors.New("some-error")) 952 953 _, err := subject.InspectImage("not/some-image", true) 954 h.AssertError(t, err, "some-error") 955 }) 956 }) 957 958 when("the image is missing labels", func() { 959 it("returns empty data", func() { 960 mockImageFetcher.EXPECT(). 961 Fetch(gomock.Any(), "missing/labels", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}). 962 Return(fakes.NewImage("missing/labels", "", nil), nil) 963 info, err := subject.InspectImage("missing/labels", true) 964 h.AssertNil(t, err) 965 h.AssertEq(t, info, &ImageInfo{Rebasable: true}, ignorePlatformAPI...) 966 }) 967 }) 968 969 when("the image has malformed labels", func() { 970 var badImage *fakes.Image 971 972 it.Before(func() { 973 badImage = fakes.NewImage("bad/image", "", nil) 974 mockImageFetcher.EXPECT(). 975 Fetch(gomock.Any(), "bad/image", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}). 976 Return(badImage, nil) 977 }) 978 979 it("returns an error when layers md cannot parse", func() { 980 h.AssertNil(t, badImage.SetLabel("io.buildpacks.lifecycle.metadata", "not ---- json")) 981 _, err := subject.InspectImage("bad/image", true) 982 h.AssertError(t, err, "unmarshalling label 'io.buildpacks.lifecycle.metadata'") 983 }) 984 985 it("returns an error when build md cannot parse", func() { 986 h.AssertNil(t, badImage.SetLabel("io.buildpacks.build.metadata", "not ---- json")) 987 _, err := subject.InspectImage("bad/image", true) 988 h.AssertError(t, err, "unmarshalling label 'io.buildpacks.build.metadata'") 989 }) 990 }) 991 992 when("lifecycle version is 0.4.x or earlier", func() { 993 it("includes an empty base image reference", func() { 994 oldImage := fakes.NewImage("old/image", "", nil) 995 mockImageFetcher.EXPECT().Fetch(gomock.Any(), "old/image", image.FetchOptions{Daemon: true, PullPolicy: image.PullNever}).Return(oldImage, nil) 996 997 h.AssertNil(t, oldImage.SetLabel( 998 "io.buildpacks.lifecycle.metadata", 999 `{ 1000 "runImage": { 1001 "topLayer": "some-top-layer", 1002 "reference": "some-run-image-reference" 1003 } 1004 }`, 1005 )) 1006 h.AssertNil(t, oldImage.SetLabel( 1007 "io.buildpacks.build.metadata", 1008 `{ 1009 "launcher": { 1010 "version": "0.4.0" 1011 } 1012 }`, 1013 )) 1014 1015 info, err := subject.InspectImage("old/image", true) 1016 h.AssertNil(t, err) 1017 h.AssertEq(t, info.Base, 1018 files.RunImageForRebase{ 1019 TopLayer: "some-top-layer", 1020 Reference: "", 1021 }, 1022 ) 1023 }) 1024 }) 1025 }