github.com/buildpacks/pack@v0.33.3-0.20240516162812-884dd1837311/internal/commands/buildpack_inspect_test.go (about) 1 package commands_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "testing" 7 8 "github.com/buildpacks/lifecycle/api" 9 "github.com/golang/mock/gomock" 10 "github.com/heroku/color" 11 "github.com/pkg/errors" 12 "github.com/sclevine/spec" 13 "github.com/sclevine/spec/report" 14 "github.com/spf13/cobra" 15 16 "github.com/buildpacks/pack/internal/commands" 17 "github.com/buildpacks/pack/internal/commands/testmocks" 18 "github.com/buildpacks/pack/internal/config" 19 "github.com/buildpacks/pack/pkg/buildpack" 20 "github.com/buildpacks/pack/pkg/client" 21 "github.com/buildpacks/pack/pkg/dist" 22 "github.com/buildpacks/pack/pkg/image" 23 "github.com/buildpacks/pack/pkg/logging" 24 h "github.com/buildpacks/pack/testhelpers" 25 ) 26 27 const complexOutputSection = `Stacks: 28 ID: io.buildpacks.stacks.first-stack 29 Mixins: 30 (omitted) 31 ID: io.buildpacks.stacks.second-stack 32 Mixins: 33 (omitted) 34 35 Buildpacks: 36 ID NAME VERSION HOMEPAGE 37 some/first-inner-buildpack - 1.0.0 first-inner-buildpack-homepage 38 some/second-inner-buildpack - 2.0.0 second-inner-buildpack-homepage 39 some/third-inner-buildpack - 3.0.0 third-inner-buildpack-homepage 40 some/top-buildpack top 0.0.1 top-buildpack-homepage 41 42 Detection Order: 43 └ Group #1: 44 └ some/top-buildpack@0.0.1 45 ├ Group #1: 46 │ ├ some/first-inner-buildpack@1.0.0 47 │ │ ├ Group #1: 48 │ │ │ ├ some/first-inner-buildpack@1.0.0 [cyclic] 49 │ │ │ └ some/third-inner-buildpack@3.0.0 50 │ │ └ Group #2: 51 │ │ └ some/third-inner-buildpack@3.0.0 52 │ └ some/second-inner-buildpack@2.0.0 53 └ Group #2: 54 └ some/first-inner-buildpack@1.0.0 55 ├ Group #1: 56 │ ├ some/first-inner-buildpack@1.0.0 [cyclic] 57 │ └ some/third-inner-buildpack@3.0.0 58 └ Group #2: 59 └ some/third-inner-buildpack@3.0.0` 60 61 const simpleOutputSection = `Stacks: 62 ID: io.buildpacks.stacks.first-stack 63 Mixins: 64 (omitted) 65 ID: io.buildpacks.stacks.second-stack 66 Mixins: 67 (omitted) 68 69 Buildpacks: 70 ID NAME VERSION HOMEPAGE 71 some/single-buildpack some 0.0.1 single-buildpack-homepage 72 some/buildpack-no-homepage - 0.0.2 - 73 74 Detection Order: 75 └ Group #1: 76 └ some/single-buildpack@0.0.1` 77 78 const inspectOutputTemplate = `Inspecting buildpack: '%s' 79 80 %s 81 82 %s 83 ` 84 85 const depthOutputSection = ` 86 Detection Order: 87 └ Group #1: 88 └ some/top-buildpack@0.0.1 89 ├ Group #1: 90 │ ├ some/first-inner-buildpack@1.0.0 91 │ └ some/second-inner-buildpack@2.0.0 92 └ Group #2: 93 └ some/first-inner-buildpack@1.0.0` 94 95 const simpleMixinOutputSection = ` 96 ID: io.buildpacks.stacks.first-stack 97 Mixins: 98 mixin1 99 mixin2 100 build:mixin3 101 build:mixin4 102 ID: io.buildpacks.stacks.second-stack 103 Mixins: 104 mixin1 105 mixin2` 106 107 func TestBuildpackInspectCommand(t *testing.T) { 108 color.Disable(true) 109 defer color.Disable(false) 110 spec.Run(t, "BuildpackInspectCommand", testBuildpackInspectCommand, spec.Sequential(), spec.Report(report.Terminal{})) 111 } 112 113 func testBuildpackInspectCommand(t *testing.T, when spec.G, it spec.S) { 114 apiVersion, err := api.NewVersion("0.2") 115 if err != nil { 116 t.Fail() 117 } 118 119 var ( 120 command *cobra.Command 121 logger logging.Logger 122 outBuf bytes.Buffer 123 mockController *gomock.Controller 124 mockClient *testmocks.MockPackClient 125 cfg config.Config 126 complexInfo *client.BuildpackInfo 127 simpleInfo *client.BuildpackInfo 128 assert = h.NewAssertionManager(t) 129 ) 130 131 it.Before(func() { 132 mockController = gomock.NewController(t) 133 mockClient = testmocks.NewMockPackClient(mockController) 134 logger = logging.NewLogWithWriters(&outBuf, &outBuf) 135 136 cfg = config.Config{ 137 DefaultRegistryName: "default-registry", 138 } 139 140 complexInfo = &client.BuildpackInfo{ 141 BuildpackMetadata: buildpack.Metadata{ 142 ModuleInfo: dist.ModuleInfo{ 143 ID: "some/top-buildpack", 144 Version: "0.0.1", 145 Homepage: "top-buildpack-homepage", 146 Name: "top", 147 }, 148 Stacks: []dist.Stack{ 149 {ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}}, 150 {ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}}, 151 }, 152 }, 153 Buildpacks: []dist.ModuleInfo{ 154 { 155 ID: "some/first-inner-buildpack", 156 Version: "1.0.0", 157 Homepage: "first-inner-buildpack-homepage", 158 }, 159 { 160 ID: "some/second-inner-buildpack", 161 Version: "2.0.0", 162 Homepage: "second-inner-buildpack-homepage", 163 }, 164 { 165 ID: "some/third-inner-buildpack", 166 Version: "3.0.0", 167 Homepage: "third-inner-buildpack-homepage", 168 }, 169 { 170 ID: "some/top-buildpack", 171 Version: "0.0.1", 172 Homepage: "top-buildpack-homepage", 173 Name: "top", 174 }, 175 }, 176 Order: dist.Order{ 177 { 178 Group: []dist.ModuleRef{ 179 { 180 ModuleInfo: dist.ModuleInfo{ 181 ID: "some/top-buildpack", 182 Version: "0.0.1", 183 Homepage: "top-buildpack-homepage", 184 Name: "top", 185 }, 186 Optional: false, 187 }, 188 }, 189 }, 190 }, 191 BuildpackLayers: dist.ModuleLayers{ 192 "some/first-inner-buildpack": { 193 "1.0.0": { 194 API: apiVersion, 195 Stacks: []dist.Stack{ 196 {ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}}, 197 {ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}}, 198 }, 199 Order: dist.Order{ 200 { 201 Group: []dist.ModuleRef{ 202 { 203 ModuleInfo: dist.ModuleInfo{ 204 ID: "some/first-inner-buildpack", 205 Version: "1.0.0", 206 }, 207 Optional: false, 208 }, 209 { 210 ModuleInfo: dist.ModuleInfo{ 211 ID: "some/third-inner-buildpack", 212 Version: "3.0.0", 213 }, 214 Optional: false, 215 }, 216 }, 217 }, 218 { 219 Group: []dist.ModuleRef{ 220 { 221 ModuleInfo: dist.ModuleInfo{ 222 ID: "some/third-inner-buildpack", 223 Version: "3.0.0", 224 }, 225 Optional: false, 226 }, 227 }, 228 }, 229 }, 230 LayerDiffID: "sha256:first-inner-buildpack-diff-id", 231 Homepage: "first-inner-buildpack-homepage", 232 }, 233 }, 234 "some/second-inner-buildpack": { 235 "2.0.0": { 236 API: apiVersion, 237 Stacks: []dist.Stack{ 238 {ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}}, 239 {ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}}, 240 }, 241 LayerDiffID: "sha256:second-inner-buildpack-diff-id", 242 Homepage: "second-inner-buildpack-homepage", 243 }, 244 }, 245 "some/third-inner-buildpack": { 246 "3.0.0": { 247 API: apiVersion, 248 Stacks: []dist.Stack{ 249 {ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}}, 250 {ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}}, 251 }, 252 LayerDiffID: "sha256:third-inner-buildpack-diff-id", 253 Homepage: "third-inner-buildpack-homepage", 254 }, 255 }, 256 "some/top-buildpack": { 257 "0.0.1": { 258 API: apiVersion, 259 Order: dist.Order{ 260 { 261 Group: []dist.ModuleRef{ 262 { 263 ModuleInfo: dist.ModuleInfo{ 264 ID: "some/first-inner-buildpack", 265 Version: "1.0.0", 266 }, 267 Optional: false, 268 }, 269 { 270 ModuleInfo: dist.ModuleInfo{ 271 ID: "some/second-inner-buildpack", 272 Version: "2.0.0", 273 }, 274 Optional: false, 275 }, 276 }, 277 }, 278 { 279 Group: []dist.ModuleRef{ 280 { 281 ModuleInfo: dist.ModuleInfo{ 282 ID: "some/first-inner-buildpack", 283 Version: "1.0.0", 284 }, 285 Optional: false, 286 }, 287 }, 288 }, 289 }, 290 LayerDiffID: "sha256:top-buildpack-diff-id", 291 Homepage: "top-buildpack-homepage", 292 Name: "top", 293 }, 294 }, 295 }, 296 } 297 298 simpleInfo = &client.BuildpackInfo{ 299 BuildpackMetadata: buildpack.Metadata{ 300 ModuleInfo: dist.ModuleInfo{ 301 ID: "some/single-buildpack", 302 Version: "0.0.1", 303 Homepage: "single-homepage-homepace", 304 Name: "some", 305 }, 306 Stacks: []dist.Stack{ 307 {ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}}, 308 {ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}}, 309 }, 310 }, 311 Buildpacks: []dist.ModuleInfo{ 312 { 313 ID: "some/single-buildpack", 314 Version: "0.0.1", 315 Name: "some", 316 Homepage: "single-buildpack-homepage", 317 }, 318 { 319 ID: "some/buildpack-no-homepage", 320 Version: "0.0.2", 321 }, 322 }, 323 Order: dist.Order{ 324 { 325 Group: []dist.ModuleRef{ 326 { 327 ModuleInfo: dist.ModuleInfo{ 328 ID: "some/single-buildpack", 329 Version: "0.0.1", 330 Homepage: "single-buildpack-homepage", 331 }, 332 Optional: false, 333 }, 334 }, 335 }, 336 }, 337 BuildpackLayers: dist.ModuleLayers{ 338 "some/single-buildpack": { 339 "0.0.1": { 340 API: apiVersion, 341 Stacks: []dist.Stack{ 342 {ID: "io.buildpacks.stacks.first-stack", Mixins: []string{"mixin1", "mixin2", "build:mixin3", "build:mixin4"}}, 343 {ID: "io.buildpacks.stacks.second-stack", Mixins: []string{"mixin1", "mixin2"}}, 344 }, 345 LayerDiffID: "sha256:single-buildpack-diff-id", 346 Homepage: "single-buildpack-homepage", 347 Name: "some", 348 }, 349 }, 350 }, 351 } 352 353 command = commands.BuildpackInspect(logger, cfg, mockClient) 354 }) 355 356 when("BuildpackInspect", func() { 357 when("inspecting an image", func() { 358 when("both remote and local image are present", func() { 359 it.Before(func() { 360 complexInfo.Location = buildpack.PackageLocator 361 simpleInfo.Location = buildpack.PackageLocator 362 363 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 364 BuildpackName: "test/buildpack", 365 Daemon: true, 366 Registry: "default-registry", 367 }).Return(complexInfo, nil) 368 369 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 370 BuildpackName: "test/buildpack", 371 Daemon: false, 372 Registry: "default-registry", 373 }).Return(simpleInfo, nil) 374 }) 375 376 it("succeeds", func() { 377 command.SetArgs([]string{"test/buildpack"}) 378 assert.Nil(command.Execute()) 379 380 localOutputSection := fmt.Sprintf(inspectOutputTemplate, 381 "test/buildpack", 382 "LOCAL IMAGE:", 383 complexOutputSection) 384 385 remoteOutputSection := fmt.Sprintf("%s\n\n%s", 386 "REMOTE IMAGE:", 387 simpleOutputSection) 388 389 assert.AssertTrimmedContains(outBuf.String(), localOutputSection) 390 assert.AssertTrimmedContains(outBuf.String(), remoteOutputSection) 391 }) 392 }) 393 394 when("only a local image is present", func() { 395 it.Before(func() { 396 complexInfo.Location = buildpack.PackageLocator 397 simpleInfo.Location = buildpack.PackageLocator 398 399 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 400 BuildpackName: "only-local-test/buildpack", 401 Daemon: true, 402 Registry: "default-registry", 403 }).Return(complexInfo, nil) 404 405 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 406 BuildpackName: "only-local-test/buildpack", 407 Daemon: false, 408 Registry: "default-registry", 409 }).Return(nil, errors.Wrap(image.ErrNotFound, "remote image not found!")) 410 }) 411 412 it("displays output for local image", func() { 413 command.SetArgs([]string{"only-local-test/buildpack"}) 414 assert.Nil(command.Execute()) 415 416 expectedOutput := fmt.Sprintf(inspectOutputTemplate, 417 "only-local-test/buildpack", 418 "LOCAL IMAGE:", 419 complexOutputSection) 420 421 assert.AssertTrimmedContains(outBuf.String(), expectedOutput) 422 }) 423 }) 424 425 when("only a remote image is present", func() { 426 it.Before(func() { 427 complexInfo.Location = buildpack.PackageLocator 428 simpleInfo.Location = buildpack.PackageLocator 429 430 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 431 BuildpackName: "only-remote-test/buildpack", 432 Daemon: false, 433 Registry: "default-registry", 434 }).Return(complexInfo, nil) 435 436 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 437 BuildpackName: "only-remote-test/buildpack", 438 Daemon: true, 439 Registry: "default-registry", 440 }).Return(nil, errors.Wrap(image.ErrNotFound, "remote image not found!")) 441 }) 442 443 it("displays output for remote image", func() { 444 command.SetArgs([]string{"only-remote-test/buildpack"}) 445 assert.Nil(command.Execute()) 446 447 expectedOutput := fmt.Sprintf(inspectOutputTemplate, 448 "only-remote-test/buildpack", 449 "REMOTE IMAGE:", 450 complexOutputSection) 451 452 assert.AssertTrimmedContains(outBuf.String(), expectedOutput) 453 }) 454 }) 455 }) 456 457 when("inspecting a buildpack uri", func() { 458 it.Before(func() { 459 simpleInfo.Location = buildpack.URILocator 460 }) 461 462 when("uri is a local path", func() { 463 it.Before(func() { 464 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 465 BuildpackName: "/path/to/test/buildpack", 466 Daemon: true, 467 Registry: "default-registry", 468 }).Return(simpleInfo, nil) 469 }) 470 471 it("succeeds", func() { 472 command.SetArgs([]string{"/path/to/test/buildpack"}) 473 assert.Nil(command.Execute()) 474 475 expectedOutput := fmt.Sprintf(inspectOutputTemplate, 476 "/path/to/test/buildpack", 477 "LOCAL ARCHIVE:", 478 simpleOutputSection) 479 480 assert.TrimmedEq(outBuf.String(), expectedOutput) 481 }) 482 }) 483 484 when("uri is an http or https location", func() { 485 it.Before(func() { 486 simpleInfo.Location = buildpack.URILocator 487 }) 488 when("uri is a local path", func() { 489 it.Before(func() { 490 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 491 BuildpackName: "https://path/to/test/buildpack", 492 Daemon: true, 493 Registry: "default-registry", 494 }).Return(simpleInfo, nil) 495 }) 496 497 it("succeeds", func() { 498 command.SetArgs([]string{"https://path/to/test/buildpack"}) 499 assert.Nil(command.Execute()) 500 501 expectedOutput := fmt.Sprintf(inspectOutputTemplate, 502 "https://path/to/test/buildpack", 503 "REMOTE ARCHIVE:", 504 simpleOutputSection) 505 506 assert.TrimmedEq(outBuf.String(), expectedOutput) 507 }) 508 }) 509 }) 510 }) 511 512 when("inspecting a buildpack on the registry", func() { 513 it.Before(func() { 514 simpleInfo.Location = buildpack.RegistryLocator 515 }) 516 517 when("using the default registry", func() { 518 it.Before(func() { 519 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 520 BuildpackName: "urn:cnb:registry:test/buildpack", 521 Daemon: true, 522 Registry: "default-registry", 523 }).Return(simpleInfo, nil) 524 }) 525 it("succeeds", func() { 526 command.SetArgs([]string{"urn:cnb:registry:test/buildpack"}) 527 assert.Nil(command.Execute()) 528 529 expectedOutput := fmt.Sprintf(inspectOutputTemplate, 530 "urn:cnb:registry:test/buildpack", 531 "REGISTRY IMAGE:", 532 simpleOutputSection) 533 534 assert.TrimmedEq(outBuf.String(), expectedOutput) 535 }) 536 }) 537 538 when("using a user provided registry", func() { 539 it.Before(func() { 540 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 541 BuildpackName: "urn:cnb:registry:test/buildpack", 542 Daemon: true, 543 Registry: "some-registry", 544 }).Return(simpleInfo, nil) 545 }) 546 547 it("succeeds", func() { 548 command.SetArgs([]string{"urn:cnb:registry:test/buildpack", "-r", "some-registry"}) 549 assert.Nil(command.Execute()) 550 551 expectedOutput := fmt.Sprintf(inspectOutputTemplate, 552 "urn:cnb:registry:test/buildpack", 553 "REGISTRY IMAGE:", 554 simpleOutputSection) 555 556 assert.TrimmedEq(outBuf.String(), expectedOutput) 557 }) 558 }) 559 }) 560 561 when("a depth flag is passed", func() { 562 it.Before(func() { 563 complexInfo.Location = buildpack.URILocator 564 565 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 566 BuildpackName: "/other/path/to/test/buildpack", 567 Daemon: true, 568 Registry: "default-registry", 569 }).Return(complexInfo, nil) 570 }) 571 572 it("displays detection order to specified depth", func() { 573 command.SetArgs([]string{"/other/path/to/test/buildpack", "-d", "2"}) 574 assert.Nil(command.Execute()) 575 576 assert.AssertTrimmedContains(outBuf.String(), depthOutputSection) 577 }) 578 }) 579 }) 580 581 when("verbose flag is passed", func() { 582 it.Before(func() { 583 simpleInfo.Location = buildpack.URILocator 584 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 585 BuildpackName: "/another/path/to/test/buildpack", 586 Daemon: true, 587 Registry: "default-registry", 588 }).Return(simpleInfo, nil) 589 }) 590 591 it("displays all mixins", func() { 592 command.SetArgs([]string{"/another/path/to/test/buildpack", "-v"}) 593 assert.Nil(command.Execute()) 594 595 assert.AssertTrimmedContains(outBuf.String(), simpleMixinOutputSection) 596 }) 597 }) 598 599 when("failure cases", func() { 600 when("unable to inspect buildpack image", func() { 601 it.Before(func() { 602 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 603 BuildpackName: "failure-case/buildpack", 604 Daemon: true, 605 Registry: "default-registry", 606 }).Return(&client.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "unable to inspect local failure-case/buildpack")) 607 608 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 609 BuildpackName: "failure-case/buildpack", 610 Daemon: false, 611 Registry: "default-registry", 612 }).Return(&client.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "unable to inspect remote failure-case/buildpack")) 613 }) 614 615 it("errors", func() { 616 command.SetArgs([]string{"failure-case/buildpack"}) 617 err := command.Execute() 618 assert.Error(err) 619 }) 620 }) 621 when("unable to inspect buildpack archive", func() { 622 it.Before(func() { 623 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 624 BuildpackName: "http://path/to/failure-case/buildpack", 625 Daemon: true, 626 Registry: "default-registry", 627 }).Return(&client.BuildpackInfo{}, errors.New("error inspecting local archive")) 628 629 it("errors", func() { 630 command.SetArgs([]string{"http://path/to/failure-case/buildpack"}) 631 err := command.Execute() 632 633 assert.Error(err) 634 assert.Contains(err.Error(), "error inspecting local archive") 635 }) 636 }) 637 }) 638 when("unable to inspect both remote and local images", func() { 639 it.Before(func() { 640 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 641 BuildpackName: "image-failure-case/buildpack", 642 Daemon: true, 643 Registry: "default-registry", 644 }).Return(&client.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "error inspecting local archive")) 645 646 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 647 BuildpackName: "image-failure-case/buildpack", 648 Daemon: false, 649 Registry: "default-registry", 650 }).Return(&client.BuildpackInfo{}, errors.Wrap(image.ErrNotFound, "error inspecting remote archive")) 651 }) 652 653 it("errors", func() { 654 command.SetArgs([]string{"image-failure-case/buildpack"}) 655 err := command.Execute() 656 657 assert.Error(err) 658 assert.Contains(err.Error(), "error writing buildpack output: \"error inspecting local archive: not found, error inspecting remote archive: not found\"") 659 }) 660 }) 661 662 when("unable to inspect buildpack on registry", func() { 663 it.Before(func() { 664 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 665 BuildpackName: "urn:cnb:registry:registry-failure/buildpack", 666 Daemon: true, 667 Registry: "some-registry", 668 }).Return(&client.BuildpackInfo{}, errors.New("error inspecting registry image")) 669 670 mockClient.EXPECT().InspectBuildpack(client.InspectBuildpackOptions{ 671 BuildpackName: "urn:cnb:registry:registry-failure/buildpack", 672 Daemon: false, 673 Registry: "some-registry", 674 }).Return(&client.BuildpackInfo{}, errors.New("error inspecting registry image")) 675 }) 676 677 it("errors", func() { 678 command.SetArgs([]string{"urn:cnb:registry:registry-failure/buildpack", "-r", "some-registry"}) 679 680 err := command.Execute() 681 assert.Error(err) 682 assert.Contains(err.Error(), "error inspecting registry image") 683 }) 684 }) 685 }) 686 }