github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/jib/gradle_test.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package jib 18 19 import ( 20 "context" 21 "errors" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "runtime" 27 "strings" 28 "testing" 29 "time" 30 31 v1 "github.com/opencontainers/image-spec/specs-go/v1" 32 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform" 35 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 36 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 37 "github.com/GoogleContainerTools/skaffold/testutil" 38 ) 39 40 func TestBuildJibGradleToDocker(t *testing.T) { 41 tests := []struct { 42 description string 43 artifact *latest.JibArtifact 44 commands util.Command 45 shouldErr bool 46 expectedError string 47 }{ 48 { 49 description: "build", 50 artifact: &latest.JibArtifact{}, 51 commands: testutil.CmdRun( 52 "gradle fake-gradleBuildArgs-for-jibDockerBuild --image=img:tag", 53 ), 54 }, 55 { 56 description: "build with project", 57 artifact: &latest.JibArtifact{Project: "project"}, 58 commands: testutil.CmdRun( 59 "gradle fake-gradleBuildArgs-for-project-for-jibDockerBuild --image=img:tag", 60 ), 61 }, 62 { 63 description: "build with custom base image", 64 artifact: &latest.JibArtifact{BaseImage: "docker://busybox"}, 65 commands: testutil.CmdRun( 66 "gradle fake-gradleBuildArgs-for-jibDockerBuild -Djib.from.image=docker://busybox --image=img:tag", 67 ), 68 }, 69 { 70 description: "fail build", 71 artifact: &latest.JibArtifact{}, 72 commands: testutil.CmdRunErr( 73 "gradle fake-gradleBuildArgs-for-jibDockerBuild --image=img:tag", 74 errors.New("BUG"), 75 ), 76 shouldErr: true, 77 expectedError: "gradle build failed", 78 }, 79 } 80 81 for _, test := range tests { 82 testutil.Run(t, test.description, func(t *testutil.T) { 83 t.NewTempDir().Touch("build.gradle").Chdir() 84 t.Override(&gradleBuildArgsFunc, getGradleBuildArgsFuncFake(t, MinimumJibGradleVersion)) 85 t.Override(&util.DefaultExecCommand, test.commands) 86 api := (&testutil.FakeAPIClient{}).Add("img:tag", "imageID") 87 localDocker := fakeLocalDaemon(api) 88 89 builder := NewArtifactBuilder(localDocker, &mockConfig{}, false, false, nil) 90 result, err := builder.Build(context.Background(), ioutil.Discard, &latest.Artifact{ 91 ArtifactType: latest.ArtifactType{ 92 JibArtifact: test.artifact, 93 }, 94 }, "img:tag", platform.Matcher{}) 95 96 t.CheckError(test.shouldErr, err) 97 if test.shouldErr { 98 t.CheckErrorContains(test.expectedError, err) 99 } else { 100 t.CheckDeepEqual("imageID", result) 101 } 102 }) 103 } 104 } 105 106 func TestBuildJibGradleToRegistry(t *testing.T) { 107 tests := []struct { 108 description string 109 artifact *latest.JibArtifact 110 commands util.Command 111 shouldErr bool 112 expectedError string 113 }{ 114 { 115 description: "remote build", 116 artifact: &latest.JibArtifact{}, 117 commands: testutil.CmdRun( 118 "gradle fake-gradleBuildArgs-for-jib --image=img:tag", 119 ), 120 }, 121 { 122 description: "build with project", 123 artifact: &latest.JibArtifact{Project: "project"}, 124 commands: testutil.CmdRun( 125 "gradle fake-gradleBuildArgs-for-project-for-jib --image=img:tag", 126 ), 127 }, 128 { 129 description: "build with custom base image", 130 artifact: &latest.JibArtifact{BaseImage: "docker://busybox"}, 131 commands: testutil.CmdRun( 132 "gradle fake-gradleBuildArgs-for-jib -Djib.from.image=docker://busybox --image=img:tag", 133 ), 134 }, 135 { 136 description: "fail build", 137 artifact: &latest.JibArtifact{}, 138 commands: testutil.CmdRunErr( 139 "gradle fake-gradleBuildArgs-for-jib --image=img:tag", 140 errors.New("BUG"), 141 ), 142 shouldErr: true, 143 expectedError: "gradle build failed", 144 }, 145 } 146 147 for _, test := range tests { 148 testutil.Run(t, test.description, func(t *testutil.T) { 149 t.NewTempDir().Touch("build.gradle").Chdir() 150 t.Override(&gradleBuildArgsFunc, getGradleBuildArgsFuncFake(t, MinimumJibGradleVersion)) 151 t.Override(&util.DefaultExecCommand, test.commands) 152 t.Override(&docker.RemoteDigest, func(identifier string, _ docker.Config, _ []v1.Platform) (string, error) { 153 if identifier == "img:tag" { 154 return "digest", nil 155 } 156 return "", errors.New("unknown remote tag") 157 }) 158 localDocker := fakeLocalDaemon(&testutil.FakeAPIClient{}) 159 160 builder := NewArtifactBuilder(localDocker, &mockConfig{}, true, false, nil) 161 result, err := builder.Build(context.Background(), ioutil.Discard, &latest.Artifact{ 162 ArtifactType: latest.ArtifactType{ 163 JibArtifact: test.artifact, 164 }, 165 }, "img:tag", platform.Matcher{}) 166 167 t.CheckError(test.shouldErr, err) 168 if test.shouldErr { 169 t.CheckErrorContains(test.expectedError, err) 170 } else { 171 t.CheckDeepEqual("digest", result) 172 } 173 }) 174 } 175 } 176 177 func TestMinimumGradleVersion(t *testing.T) { 178 testutil.CheckDeepEqual(t, "1.4.0", MinimumJibGradleVersion) 179 } 180 181 func TestGradleWrapperDefinition(t *testing.T) { 182 testutil.CheckDeepEqual(t, "gradle", GradleCommand.Executable) 183 testutil.CheckDeepEqual(t, "gradlew", GradleCommand.Wrapper) 184 } 185 186 func TestGetDependenciesGradle(t *testing.T) { 187 tmpDir := testutil.NewTempDir(t) 188 189 tmpDir.Touch("build", "dep1", "dep2") 190 build := tmpDir.Path("build") 191 dep1 := tmpDir.Path("dep1") 192 dep2 := tmpDir.Path("dep2") 193 194 ctx := context.Background() 195 196 tests := []struct { 197 description string 198 stdout string 199 modTime time.Time 200 expected []string 201 err error 202 }{ 203 { 204 description: "failure", 205 stdout: "", 206 modTime: time.Unix(0, 0), 207 err: errors.New("error"), 208 }, 209 { 210 description: "success", 211 stdout: fmt.Sprintf("BEGIN JIB JSON\n{\"build\":[\"%s\"],\"inputs\":[\"%s\"],\"ignore\":[]}", build, dep1), 212 modTime: time.Unix(0, 0), 213 expected: []string{"build", "dep1"}, 214 }, 215 { 216 // Expected output differs from stdout since build file hasn't change, thus gradle command won't run 217 description: "success", 218 stdout: fmt.Sprintf("BEGIN JIB JSON\n{\"build\":[\"%s\"],\"inputs\":[\"%s\", \"%s\"],\"ignore\":[]}", build, dep1, dep2), 219 modTime: time.Unix(0, 0), 220 expected: []string{"build", "dep1"}, 221 }, 222 { 223 description: "success", 224 stdout: fmt.Sprintf("BEGIN JIB JSON\n{\"build\":[\"%s\"],\"inputs\":[\"%s\", \"%s\"],\"ignore\":[]}", build, dep1, dep2), 225 modTime: time.Unix(10000, 0), 226 expected: []string{"build", "dep1", "dep2"}, 227 }, 228 } 229 for _, test := range tests { 230 testutil.Run(t, test.description, func(t *testutil.T) { 231 t.Override(&util.DefaultExecCommand, testutil.CmdRunOutErr( 232 strings.Join(getCommandGradle(ctx, tmpDir.Root(), &latest.JibArtifact{Project: "gradle-test"}).Args, " "), 233 test.stdout, 234 test.err, 235 )) 236 237 // Change build file mod time 238 if err := os.Chtimes(build, test.modTime, test.modTime); err != nil { 239 t.Fatal(err) 240 } 241 242 ws := tmpDir.Root() 243 deps, err := getDependenciesGradle(ctx, ws, &latest.JibArtifact{Project: "gradle-test"}) 244 if test.err != nil { 245 prefix := fmt.Sprintf("could not fetch dependencies for workspace %s: initial Jib dependency refresh failed: failed to get Jib dependencies: ", ws) 246 t.CheckErrorAndDeepEqual(true, err, prefix+test.err.Error(), err.Error()) 247 } else { 248 t.CheckDeepEqual(test.expected, deps) 249 } 250 }) 251 } 252 } 253 254 func TestGetCommandGradle(t *testing.T) { 255 ctx := context.Background() 256 257 tests := []struct { 258 description string 259 jibArtifact latest.JibArtifact 260 filesInWorkspace []string 261 expectedCmd func(workspace string) exec.Cmd 262 }{ 263 { 264 description: "gradle default", 265 jibArtifact: latest.JibArtifact{}, 266 filesInWorkspace: []string{}, 267 expectedCmd: func(workspace string) exec.Cmd { 268 return GradleCommand.CreateCommand(ctx, workspace, []string{"_skaffoldFailIfJibOutOfDate", "-Djib.requiredVersion=" + MinimumJibGradleVersion, ":_jibSkaffoldFilesV2", "-q", "--console=plain"}) 269 }, 270 }, 271 { 272 description: "gradle default with project", 273 jibArtifact: latest.JibArtifact{Project: "project"}, 274 filesInWorkspace: []string{}, 275 expectedCmd: func(workspace string) exec.Cmd { 276 return GradleCommand.CreateCommand(ctx, workspace, []string{"_skaffoldFailIfJibOutOfDate", "-Djib.requiredVersion=" + MinimumJibGradleVersion, ":project:_jibSkaffoldFilesV2", "-q", "--console=plain"}) 277 }, 278 }, 279 { 280 description: "gradle with wrapper", 281 jibArtifact: latest.JibArtifact{}, 282 filesInWorkspace: []string{"gradlew", "gradlew.cmd"}, 283 expectedCmd: func(workspace string) exec.Cmd { 284 return GradleCommand.CreateCommand(ctx, workspace, []string{"_skaffoldFailIfJibOutOfDate", "-Djib.requiredVersion=" + MinimumJibGradleVersion, ":_jibSkaffoldFilesV2", "-q", "--console=plain"}) 285 }, 286 }, 287 { 288 description: "gradle with wrapper and project", 289 jibArtifact: latest.JibArtifact{Project: "project"}, 290 filesInWorkspace: []string{"gradlew", "gradlew.cmd"}, 291 expectedCmd: func(workspace string) exec.Cmd { 292 return GradleCommand.CreateCommand(ctx, workspace, []string{"_skaffoldFailIfJibOutOfDate", "-Djib.requiredVersion=" + MinimumJibGradleVersion, ":project:_jibSkaffoldFilesV2", "-q", "--console=plain"}) 293 }, 294 }, 295 } 296 for _, test := range tests { 297 testutil.Run(t, test.description, func(t *testutil.T) { 298 tmpDir := t.NewTempDir(). 299 Touch(test.filesInWorkspace...) 300 301 cmd := getCommandGradle(ctx, tmpDir.Root(), &test.jibArtifact) 302 303 expectedCmd := test.expectedCmd(tmpDir.Root()) 304 t.CheckDeepEqual(expectedCmd.Path, cmd.Path) 305 t.CheckDeepEqual(expectedCmd.Args, cmd.Args) 306 t.CheckDeepEqual(expectedCmd.Dir, cmd.Dir) 307 }) 308 } 309 } 310 311 func TestGetSyncMapCommandGradle(t *testing.T) { 312 ctx := context.Background() 313 tests := []struct { 314 description string 315 workspace string 316 jibArtifact latest.JibArtifact 317 expectedCmd func(workspace string) exec.Cmd 318 }{ 319 { 320 description: "single module", 321 jibArtifact: latest.JibArtifact{}, 322 expectedCmd: func(workspace string) exec.Cmd { 323 return GradleCommand.CreateCommand(ctx, workspace, []string{"fake-gradleBuildArgs-for-_jibSkaffoldSyncMap-skipTests"}) 324 }, 325 }, 326 { 327 description: "multi module", 328 jibArtifact: latest.JibArtifact{Project: "project"}, 329 expectedCmd: func(workspace string) exec.Cmd { 330 return GradleCommand.CreateCommand(ctx, workspace, []string{"fake-gradleBuildArgs-for-project-for-_jibSkaffoldSyncMap-skipTests"}) 331 }, 332 }, 333 } 334 for _, test := range tests { 335 testutil.Run(t, test.description, func(t *testutil.T) { 336 t.Override(&gradleBuildArgsFunc, getGradleBuildArgsFuncFake(t, MinimumJibGradleVersionForSync)) 337 cmd := getSyncMapCommandGradle(ctx, test.workspace, &test.jibArtifact) 338 expectedCmd := test.expectedCmd(test.workspace) 339 t.CheckDeepEqual(expectedCmd.Path, cmd.Path) 340 t.CheckDeepEqual(expectedCmd.Args, cmd.Args) 341 t.CheckDeepEqual(expectedCmd.Dir, cmd.Dir) 342 }) 343 } 344 } 345 346 func TestGenerateGradleBuildArgs(t *testing.T) { 347 tests := []struct { 348 description string 349 in latest.JibArtifact 350 platforms platform.Matcher 351 expectedMinVersion string 352 deps []*latest.ArtifactDependency 353 image string 354 skipTests bool 355 pushImages bool 356 r ArtifactResolver 357 insecureRegistries map[string]bool 358 out []string 359 }{ 360 {description: "single module", image: "image", out: []string{"fake-gradleBuildArgs-for-testTask", "--image=image"}}, 361 {description: "single module without tests", image: "image", skipTests: true, out: []string{"fake-gradleBuildArgs-for-testTask-skipTests", "--image=image"}}, 362 {description: "multi module", in: latest.JibArtifact{Project: "project"}, image: "image", out: []string{"fake-gradleBuildArgs-for-project-for-testTask", "--image=image"}}, 363 {description: "multi module without tests", in: latest.JibArtifact{Project: "project"}, image: "image", skipTests: true, out: []string{"fake-gradleBuildArgs-for-project-for-testTask-skipTests", "--image=image"}}, 364 {description: "multi module without tests with insecure registries", in: latest.JibArtifact{Project: "project"}, image: "registry.tld/image", skipTests: true, insecureRegistries: map[string]bool{"registry.tld": true}, out: []string{"fake-gradleBuildArgs-for-project-for-testTask-skipTests", "-Djib.allowInsecureRegistries=true", "--image=registry.tld/image"}}, 365 {description: "single module with custom base image", in: latest.JibArtifact{BaseImage: "docker://busybox"}, image: "image", out: []string{"fake-gradleBuildArgs-for-testTask", "-Djib.from.image=docker://busybox", "--image=image"}}, 366 {description: "multi module with custom base image", in: latest.JibArtifact{Project: "project", BaseImage: "docker://busybox"}, image: "image", out: []string{"fake-gradleBuildArgs-for-project-for-testTask", "-Djib.from.image=docker://busybox", "--image=image"}}, 367 {description: "host platform", image: "image", platforms: platform.Matcher{Platforms: []v1.Platform{{OS: runtime.GOOS, Architecture: runtime.GOARCH}}}, out: []string{"fake-gradleBuildArgs-for-testTask", fmt.Sprintf("-Djib.from.platforms=%s/%s", runtime.GOOS, runtime.GOARCH), "--image=image"}}, 368 {description: "cross-platform", image: "image", platforms: platform.Matcher{Platforms: []v1.Platform{{OS: "freebsd", Architecture: "arm"}}}, out: []string{"fake-gradleBuildArgs-for-testTask", "-Djib.from.platforms=freebsd/arm", "--image=image"}, expectedMinVersion: MinimumJibGradleVersionForCrossPlatform}, 369 {description: "multi-platform", image: "image", platforms: platform.Matcher{Platforms: []v1.Platform{{OS: "linux", Architecture: "amd64"}, {OS: "darwin", Architecture: "arm64"}}}, out: []string{"fake-gradleBuildArgs-for-testTask", "-Djib.from.platforms=linux/amd64,darwin/arm64", "--image=image"}, expectedMinVersion: MinimumJibGradleVersionForCrossPlatform}, 370 { 371 description: "single module with local base image from required artifacts", 372 in: latest.JibArtifact{BaseImage: "alias"}, 373 image: "image", 374 deps: []*latest.ArtifactDependency{{ImageName: "img", Alias: "alias"}}, 375 r: mockArtifactResolver{m: map[string]string{"img": "img:tag"}}, 376 out: []string{"fake-gradleBuildArgs-for-testTask", "-Djib.from.image=docker://img:tag", "--image=image"}, 377 }, 378 { 379 description: "multi module with local base image from required artifacts", 380 in: latest.JibArtifact{Project: "project", BaseImage: "alias"}, 381 image: "image", 382 deps: []*latest.ArtifactDependency{{ImageName: "img", Alias: "alias"}}, 383 r: mockArtifactResolver{m: map[string]string{"img": "img:tag"}}, 384 out: []string{"fake-gradleBuildArgs-for-project-for-testTask", "-Djib.from.image=docker://img:tag", "--image=image"}, 385 }, { 386 description: "single module with remote base image from required artifacts", 387 in: latest.JibArtifact{BaseImage: "alias"}, 388 image: "image", 389 pushImages: true, 390 deps: []*latest.ArtifactDependency{{ImageName: "img", Alias: "alias"}}, 391 r: mockArtifactResolver{m: map[string]string{"img": "img:tag"}}, 392 out: []string{"fake-gradleBuildArgs-for-testTask", "-Djib.from.image=img:tag", "--image=image"}, 393 }, 394 { 395 description: "multi module with remote base image from required artifacts", 396 in: latest.JibArtifact{Project: "project", BaseImage: "alias"}, 397 image: "image", 398 pushImages: true, 399 deps: []*latest.ArtifactDependency{{ImageName: "img", Alias: "alias"}}, 400 r: mockArtifactResolver{m: map[string]string{"img": "img:tag"}}, 401 out: []string{"fake-gradleBuildArgs-for-project-for-testTask", "-Djib.from.image=img:tag", "--image=image"}, 402 }, 403 } 404 for _, test := range tests { 405 testutil.Run(t, test.description, func(t *testutil.T) { 406 minVersion := MinimumJibGradleVersion 407 if test.expectedMinVersion != "" { 408 minVersion = test.expectedMinVersion 409 } 410 t.Override(&gradleBuildArgsFunc, getGradleBuildArgsFuncFake(t, minVersion)) 411 command := GenerateGradleBuildArgs("testTask", test.image, &test.in, test.platforms, test.skipTests, test.pushImages, test.deps, test.r, test.insecureRegistries, false) 412 t.CheckDeepEqual(test.out, command) 413 }) 414 } 415 } 416 417 func TestGradleArgs(t *testing.T) { 418 tests := []struct { 419 description string 420 jibArtifact latest.JibArtifact 421 expected []string 422 }{ 423 { 424 description: "single module", 425 jibArtifact: latest.JibArtifact{}, 426 expected: []string{"_skaffoldFailIfJibOutOfDate", "-Djib.requiredVersion=test-version", ":testTask"}, 427 }, 428 { 429 description: "multi module", 430 jibArtifact: latest.JibArtifact{Project: "module"}, 431 expected: []string{"_skaffoldFailIfJibOutOfDate", "-Djib.requiredVersion=test-version", ":module:testTask"}, 432 }, 433 } 434 for _, test := range tests { 435 args := gradleArgs(&test.jibArtifact, "testTask", "test-version") 436 testutil.CheckDeepEqual(t, test.expected, args) 437 } 438 } 439 440 func TestGradleBuildArgs(t *testing.T) { 441 tests := []struct { 442 description string 443 jibArtifact latest.JibArtifact 444 skipTests bool 445 showColors bool 446 expected []string 447 }{ 448 { 449 description: "single module", 450 jibArtifact: latest.JibArtifact{}, 451 skipTests: false, 452 showColors: true, 453 expected: []string{"-Djib.console=plain", "fake-gradleArgs-for-testTask"}, 454 }, 455 { 456 description: "single module skip tests", 457 jibArtifact: latest.JibArtifact{}, 458 skipTests: true, 459 showColors: true, 460 expected: []string{"-Djib.console=plain", "fake-gradleArgs-for-testTask", "-x", "test"}, 461 }, 462 { 463 description: "single module plain console", 464 jibArtifact: latest.JibArtifact{}, 465 skipTests: true, 466 showColors: false, 467 expected: []string{"--console=plain", "fake-gradleArgs-for-testTask", "-x", "test"}, 468 }, 469 { 470 description: "single module with extra flags", 471 jibArtifact: latest.JibArtifact{Flags: []string{"--flag1", "--flag2"}}, 472 skipTests: false, 473 showColors: true, 474 expected: []string{"-Djib.console=plain", "fake-gradleArgs-for-testTask", "--flag1", "--flag2"}, 475 }, 476 { 477 description: "multi module", 478 jibArtifact: latest.JibArtifact{Project: "module"}, 479 skipTests: false, 480 showColors: true, 481 expected: []string{"-Djib.console=plain", "fake-gradleArgs-for-module-for-testTask"}, 482 }, 483 { 484 description: "single module skip tests", 485 jibArtifact: latest.JibArtifact{Project: "module"}, 486 skipTests: true, 487 showColors: true, 488 expected: []string{"-Djib.console=plain", "fake-gradleArgs-for-module-for-testTask", "-x", "test"}, 489 }, 490 { 491 description: "multi module with extra flags", 492 jibArtifact: latest.JibArtifact{Project: "module", Flags: []string{"--flag1", "--flag2"}}, 493 skipTests: false, 494 showColors: true, 495 expected: []string{"-Djib.console=plain", "fake-gradleArgs-for-module-for-testTask", "--flag1", "--flag2"}, 496 }, 497 } 498 for _, test := range tests { 499 testutil.Run(t, test.description, func(t *testutil.T) { 500 t.Override(&gradleArgsFunc, getGradleArgsFuncFake(t, "test-version")) 501 args := gradleBuildArgs("testTask", &test.jibArtifact, test.skipTests, test.showColors, "test-version") 502 t.CheckDeepEqual(test.expected, args) 503 }) 504 } 505 } 506 507 func getGradleArgsFuncFake(t *testutil.T, expectedMinimumVersion string) func(*latest.JibArtifact, string, string) []string { 508 return func(a *latest.JibArtifact, task string, minimumVersion string) []string { 509 t.CheckDeepEqual(expectedMinimumVersion, minimumVersion) 510 if a.Project == "" { 511 return []string{"fake-gradleArgs-for-" + task} 512 } 513 return []string{"fake-gradleArgs-for-" + a.Project + "-for-" + task} 514 } 515 } 516 517 // check that parameters are actually passed though 518 func getGradleBuildArgsFuncFake(t *testutil.T, expectedMinimumVersion string) func(string, *latest.JibArtifact, bool, bool, string) []string { 519 return func(task string, a *latest.JibArtifact, skipTests, showColors bool, minimumVersion string) []string { 520 t.CheckDeepEqual(expectedMinimumVersion, minimumVersion) 521 522 testString := "" 523 if skipTests { 524 testString = "-skipTests" 525 } 526 527 if a.Project == "" { 528 return []string{"fake-gradleBuildArgs-for-" + task + testString} 529 } 530 return []string{"fake-gradleBuildArgs-for-" + a.Project + "-for-" + task + testString} 531 } 532 } 533 534 type mockConfig struct { 535 Config 536 } 537 538 func (c *mockConfig) GetInsecureRegistries() map[string]bool { return nil } 539 540 type mockArtifactResolver struct { 541 m map[string]string 542 } 543 544 func (r mockArtifactResolver) GetImageTag(imageName string) (string, bool) { 545 val, found := r.m[imageName] 546 return val, found 547 }