github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/buildpacks/build_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 buildpacks 18 19 import ( 20 "context" 21 "io" 22 "io/ioutil" 23 "testing" 24 25 pack "github.com/buildpacks/pack/pkg/client" 26 packcfg "github.com/buildpacks/pack/pkg/image" 27 "github.com/docker/docker/api/types" 28 "github.com/google/go-cmp/cmp" 29 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 32 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 35 "github.com/GoogleContainerTools/skaffold/testutil" 36 ) 37 38 type fakePack struct { 39 Opts pack.BuildOptions 40 } 41 42 func (f *fakePack) runPack(_ context.Context, _ io.Writer, _ docker.LocalDaemon, opts pack.BuildOptions) error { 43 f.Opts = opts 44 return nil 45 } 46 47 func TestBuild(t *testing.T) { 48 tests := []struct { 49 description string 50 artifact *latest.Artifact 51 tag string 52 api *testutil.FakeAPIClient 53 files map[string]string 54 pushImages bool 55 shouldErr bool 56 mode config.RunMode 57 resolver ArtifactResolver 58 expectedOptions *pack.BuildOptions 59 }{ 60 { 61 description: "success for debug", 62 artifact: buildpacksArtifact("my/builder", "my/run"), 63 tag: "img:tag", 64 mode: config.RunModes.Debug, 65 api: &testutil.FakeAPIClient{}, 66 resolver: mockArtifactResolver{}, 67 expectedOptions: &pack.BuildOptions{ 68 AppPath: ".", 69 Builder: "my/builder", 70 RunImage: "my/run", 71 Env: debugModeArgs, 72 Image: "img:latest", 73 }, 74 }, 75 { 76 description: "success for build", 77 artifact: buildpacksArtifact("my/builder", "my/run"), 78 tag: "img:tag", 79 mode: config.RunModes.Build, 80 api: &testutil.FakeAPIClient{}, 81 resolver: mockArtifactResolver{}, 82 expectedOptions: &pack.BuildOptions{ 83 AppPath: ".", 84 Builder: "my/builder", 85 RunImage: "my/run", 86 PullPolicy: packcfg.PullNever, 87 Env: nonDebugModeArgs, 88 Image: "img:latest", 89 }, 90 }, 91 92 { 93 description: "success with buildpacks for debug", 94 artifact: withTrustedBuilder(withBuildpacks([]string{"my/buildpack", "my/otherBuildpack"}, buildpacksArtifact("my/otherBuilder", "my/otherRun"))), 95 tag: "img:tag", 96 api: &testutil.FakeAPIClient{}, 97 resolver: mockArtifactResolver{}, 98 mode: config.RunModes.Debug, 99 expectedOptions: &pack.BuildOptions{ 100 AppPath: ".", 101 Builder: "my/otherBuilder", 102 RunImage: "my/otherRun", 103 Buildpacks: []string{"my/buildpack", "my/otherBuildpack"}, 104 Env: debugModeArgs, 105 Image: "img:latest", 106 }, 107 }, 108 { 109 description: "success with buildpacks for build", 110 artifact: withTrustedBuilder(withBuildpacks([]string{"my/buildpack", "my/otherBuildpack"}, buildpacksArtifact("my/otherBuilder", "my/otherRun"))), 111 tag: "img:tag", 112 api: &testutil.FakeAPIClient{}, 113 resolver: mockArtifactResolver{}, 114 mode: config.RunModes.Build, 115 expectedOptions: &pack.BuildOptions{ 116 AppPath: ".", 117 Builder: "my/otherBuilder", 118 RunImage: "my/otherRun", 119 Buildpacks: []string{"my/buildpack", "my/otherBuildpack"}, 120 PullPolicy: packcfg.PullNever, 121 Env: nonDebugModeArgs, 122 Image: "img:latest", 123 }, 124 }, 125 { 126 description: "project.toml", 127 artifact: buildpacksArtifact("my/builder2", "my/run2"), 128 tag: "img:tag", 129 api: &testutil.FakeAPIClient{}, 130 resolver: mockArtifactResolver{}, 131 mode: config.RunModes.Build, 132 files: map[string]string{ 133 "project.toml": `[[build.env]] 134 name = "GOOGLE_RUNTIME_VERSION" 135 value = "14.3.0" 136 [[build.buildpacks]] 137 id = "my/buildpack" 138 [[build.buildpacks]] 139 id = "my/otherBuildpack" 140 version = "1.0" 141 `, 142 }, 143 expectedOptions: &pack.BuildOptions{ 144 AppPath: ".", 145 Builder: "my/builder2", 146 RunImage: "my/run2", 147 Buildpacks: []string{"my/buildpack", "my/otherBuildpack@1.0"}, 148 Env: addDefaultArgs(config.RunModes.Build, map[string]string{ 149 "GOOGLE_RUNTIME_VERSION": "14.3.0", 150 }), 151 Image: "img:latest", 152 }, 153 }, 154 { 155 description: "Buildpacks in skaffold.yaml override those in project.toml", 156 artifact: withBuildpacks([]string{"my/buildpack", "my/otherBuildpack"}, buildpacksArtifact("my/builder3", "my/run3")), 157 tag: "img:tag", 158 api: &testutil.FakeAPIClient{}, 159 resolver: mockArtifactResolver{}, 160 files: map[string]string{ 161 "project.toml": `[[build.buildpacks]] 162 id = "my/ignored" 163 `, 164 }, 165 expectedOptions: &pack.BuildOptions{ 166 AppPath: ".", 167 Builder: "my/builder3", 168 RunImage: "my/run3", 169 Buildpacks: []string{"my/buildpack", "my/otherBuildpack"}, 170 Env: nonDebugModeArgs, 171 Image: "img:latest", 172 }, 173 }, 174 { 175 description: "Combine env from skaffold.yaml and project.toml", 176 artifact: withEnv([]string{"KEY1=VALUE1"}, buildpacksArtifact("my/builder4", "my/run4")), 177 tag: "img:tag", 178 mode: config.RunModes.Build, 179 api: &testutil.FakeAPIClient{}, 180 resolver: mockArtifactResolver{}, 181 files: map[string]string{ 182 "project.toml": `[[build.env]] 183 name = "KEY2" 184 value = "VALUE2" 185 `, 186 }, 187 expectedOptions: &pack.BuildOptions{ 188 AppPath: ".", 189 Builder: "my/builder4", 190 RunImage: "my/run4", 191 Env: addDefaultArgs(config.RunModes.Build, map[string]string{ 192 "KEY1": "VALUE1", 193 "KEY2": "VALUE2", 194 }), 195 Image: "img:latest", 196 }, 197 }, 198 { 199 description: "dev mode", 200 artifact: withSync(&latest.Sync{Auto: util.BoolPtr(true)}, buildpacksArtifact("another/builder", "another/run")), 201 tag: "img:tag", 202 api: &testutil.FakeAPIClient{}, 203 resolver: mockArtifactResolver{}, 204 mode: config.RunModes.Dev, 205 expectedOptions: &pack.BuildOptions{ 206 AppPath: ".", 207 Builder: "another/builder", 208 RunImage: "another/run", 209 Env: addDefaultArgs(config.RunModes.Build, map[string]string{ 210 "GOOGLE_DEVMODE": "1", 211 }), 212 Image: "img:latest", 213 }, 214 }, 215 { 216 description: "dev mode but no sync", 217 artifact: buildpacksArtifact("my/other-builder", "my/run"), 218 tag: "img:tag", 219 api: &testutil.FakeAPIClient{}, 220 resolver: mockArtifactResolver{}, 221 mode: config.RunModes.Dev, 222 expectedOptions: &pack.BuildOptions{ 223 AppPath: ".", 224 Builder: "my/other-builder", 225 RunImage: "my/run", 226 Env: nonDebugModeArgs, 227 Image: "img:latest", 228 }, 229 }, 230 { 231 description: "invalid ref", 232 artifact: buildpacksArtifact("my/builder", "my/run"), 233 tag: "in valid ref", 234 api: &testutil.FakeAPIClient{}, 235 resolver: mockArtifactResolver{}, 236 shouldErr: true, 237 }, 238 { 239 description: "push error", 240 artifact: buildpacksArtifact("my/builder", "my/run"), 241 tag: "img:tag", 242 pushImages: true, 243 api: &testutil.FakeAPIClient{ 244 ErrImagePush: true, 245 }, 246 resolver: mockArtifactResolver{}, 247 shouldErr: true, 248 }, 249 { 250 description: "invalid env", 251 artifact: withEnv([]string{"INVALID"}, buildpacksArtifact("my/builder", "my/run")), 252 tag: "img:tag", 253 api: &testutil.FakeAPIClient{}, 254 resolver: mockArtifactResolver{}, 255 shouldErr: true, 256 }, 257 { 258 description: "invalid project.toml", 259 artifact: buildpacksArtifact("my/builder2", "my/run2"), 260 tag: "img:tag", 261 api: &testutil.FakeAPIClient{}, 262 resolver: mockArtifactResolver{}, 263 files: map[string]string{ 264 "project.toml": `INVALID`, 265 }, 266 shouldErr: true, 267 }, 268 } 269 for _, test := range tests { 270 testutil.Run(t, test.description, func(t *testutil.T) { 271 t.NewTempDir().Touch("file").WriteFiles(test.files).Chdir() 272 pack := &fakePack{} 273 t.Override(&runPackBuildFunc, pack.runPack) 274 275 test.api. 276 Add(test.artifact.BuildpackArtifact.Builder, "builderImageID"). 277 Add(test.artifact.BuildpackArtifact.RunImage, "runImageID"). 278 Add("img:latest", "builtImageID") 279 localDocker := fakeLocalDaemon(test.api) 280 281 builder := NewArtifactBuilder(localDocker, test.pushImages, test.mode, test.resolver) 282 _, err := builder.Build(context.Background(), ioutil.Discard, test.artifact, test.tag, platform.Matcher{}) 283 284 t.CheckError(test.shouldErr, err) 285 if test.expectedOptions != nil { 286 t.CheckDeepEqual(*test.expectedOptions, pack.Opts, ignoreField("ProjectDescriptor.SchemaVersion"), ignoreField("TrustBuilder")) 287 } 288 }) 289 } 290 } 291 292 func TestBuildWithArtifactDependencies(t *testing.T) { 293 tests := []struct { 294 description string 295 artifact *latest.Artifact 296 tag string 297 api *testutil.FakeAPIClient 298 files map[string]string 299 pushImages bool 300 shouldErr bool 301 mode config.RunMode 302 resolver ArtifactResolver 303 expectedOptions *pack.BuildOptions 304 }{ 305 { 306 description: "custom builder image only with no push", 307 artifact: withRequiredArtifacts([]*latest.ArtifactDependency{{ImageName: "builder-image", Alias: "BUILDER_IMAGE"}}, buildpacksArtifact("BUILDER_IMAGE", "my/run")), 308 tag: "img:tag", 309 pushImages: false, 310 mode: config.RunModes.Build, 311 api: &testutil.FakeAPIClient{}, 312 resolver: mockArtifactResolver{m: map[string]string{"builder-image": "my/custom-builder"}}, 313 expectedOptions: &pack.BuildOptions{ 314 AppPath: ".", 315 Builder: "my/custom-builder", 316 RunImage: "my/run", 317 PullPolicy: packcfg.PullIfNotPresent, 318 Env: nonDebugModeArgs, 319 Image: "img:latest", 320 }, 321 }, 322 { 323 description: "custom run image only with no push", 324 artifact: withRequiredArtifacts([]*latest.ArtifactDependency{{ImageName: "run-image", Alias: "RUN_IMAGE"}}, buildpacksArtifact("my/builder", "RUN_IMAGE")), 325 tag: "img:tag", 326 pushImages: false, 327 mode: config.RunModes.Build, 328 api: &testutil.FakeAPIClient{}, 329 resolver: mockArtifactResolver{m: map[string]string{"run-image": "my/custom-run"}}, 330 expectedOptions: &pack.BuildOptions{ 331 AppPath: ".", 332 Builder: "my/builder", 333 RunImage: "my/custom-run", 334 PullPolicy: packcfg.PullIfNotPresent, 335 Env: nonDebugModeArgs, 336 Image: "img:latest", 337 }, 338 }, 339 { 340 description: "custom builder image only with push", 341 artifact: withRequiredArtifacts([]*latest.ArtifactDependency{{ImageName: "builder-image", Alias: "BUILDER_IMAGE"}}, buildpacksArtifact("BUILDER_IMAGE", "my/run")), 342 tag: "img:tag", 343 pushImages: true, 344 mode: config.RunModes.Build, 345 api: &testutil.FakeAPIClient{}, 346 resolver: mockArtifactResolver{m: map[string]string{"builder-image": "my/custom-builder"}}, 347 expectedOptions: &pack.BuildOptions{ 348 AppPath: ".", 349 Builder: "my/custom-builder", 350 RunImage: "my/run", 351 PullPolicy: packcfg.PullIfNotPresent, 352 Env: nonDebugModeArgs, 353 Image: "img:latest", 354 }, 355 }, 356 { 357 description: "custom run image only with push", 358 artifact: withRequiredArtifacts([]*latest.ArtifactDependency{{ImageName: "run-image", Alias: "RUN_IMAGE"}}, buildpacksArtifact("my/builder", "RUN_IMAGE")), 359 tag: "img:tag", 360 pushImages: true, 361 mode: config.RunModes.Build, 362 api: &testutil.FakeAPIClient{}, 363 resolver: mockArtifactResolver{m: map[string]string{"run-image": "my/custom-run"}}, 364 expectedOptions: &pack.BuildOptions{ 365 AppPath: ".", 366 Builder: "my/builder", 367 RunImage: "my/custom-run", 368 PullPolicy: packcfg.PullIfNotPresent, 369 Env: nonDebugModeArgs, 370 Image: "img:latest", 371 }, 372 }, 373 { 374 description: "custom run image and custom builder image with push", 375 artifact: withRequiredArtifacts([]*latest.ArtifactDependency{{ImageName: "run-image", Alias: "RUN_IMAGE"}, {ImageName: "builder-image", Alias: "BUILDER_IMAGE"}}, buildpacksArtifact("BUILDER_IMAGE", "RUN_IMAGE")), 376 tag: "img:tag", 377 pushImages: true, 378 mode: config.RunModes.Build, 379 api: &testutil.FakeAPIClient{}, 380 resolver: mockArtifactResolver{m: map[string]string{"builder-image": "my/custom-builder", "run-image": "my/custom-run"}}, 381 expectedOptions: &pack.BuildOptions{ 382 AppPath: ".", 383 Builder: "my/custom-builder", 384 RunImage: "my/custom-run", 385 PullPolicy: packcfg.PullNever, 386 Env: nonDebugModeArgs, 387 Image: "img:latest", 388 }, 389 }, 390 { 391 description: "custom run image and custom builder image with no push", 392 artifact: withRequiredArtifacts([]*latest.ArtifactDependency{{ImageName: "run-image", Alias: "RUN_IMAGE"}, {ImageName: "builder-image", Alias: "BUILDER_IMAGE"}}, buildpacksArtifact("BUILDER_IMAGE", "RUN_IMAGE")), 393 tag: "img:tag", 394 pushImages: false, 395 mode: config.RunModes.Build, 396 api: &testutil.FakeAPIClient{}, 397 resolver: mockArtifactResolver{m: map[string]string{"builder-image": "my/custom-builder", "run-image": "my/custom-run"}}, 398 expectedOptions: &pack.BuildOptions{ 399 AppPath: ".", 400 Builder: "my/custom-builder", 401 RunImage: "my/custom-run", 402 PullPolicy: packcfg.PullNever, 403 Env: nonDebugModeArgs, 404 Image: "img:latest", 405 }, 406 }, 407 } 408 for _, test := range tests { 409 testutil.Run(t, test.description, func(t *testutil.T) { 410 t.NewTempDir().Touch("file").WriteFiles(test.files).Chdir() 411 pack := &fakePack{} 412 t.Override(&runPackBuildFunc, pack.runPack) 413 images.images = map[imageTuple]bool{} 414 test.api. 415 Add(test.artifact.BuildpackArtifact.Builder, "builderImageID"). 416 Add(test.artifact.BuildpackArtifact.RunImage, "runImageID"). 417 Add("img:latest", "builtImageID") 418 t.Override(&docker.DefaultAuthHelper, testAuthHelper{}) 419 420 localDocker := fakeLocalDaemon(test.api) 421 422 builder := NewArtifactBuilder(localDocker, test.pushImages, test.mode, test.resolver) 423 _, err := builder.Build(context.Background(), ioutil.Discard, test.artifact, test.tag, platform.Matcher{}) 424 425 t.CheckError(test.shouldErr, err) 426 if test.expectedOptions != nil { 427 t.CheckDeepEqual(*test.expectedOptions, pack.Opts, ignoreField("ProjectDescriptor.SchemaVersion"), ignoreField("TrustBuilder")) 428 } 429 }) 430 } 431 } 432 func buildpacksArtifact(builder, runImage string) *latest.Artifact { 433 return &latest.Artifact{ 434 Workspace: ".", 435 ArtifactType: latest.ArtifactType{ 436 BuildpackArtifact: &latest.BuildpackArtifact{ 437 Builder: builder, 438 RunImage: runImage, 439 ProjectDescriptor: "project.toml", 440 Dependencies: &latest.BuildpackDependencies{ 441 Paths: []string{"."}, 442 }, 443 }, 444 }, 445 } 446 } 447 448 func withEnv(env []string, artifact *latest.Artifact) *latest.Artifact { 449 artifact.BuildpackArtifact.Env = env 450 return artifact 451 } 452 453 func withSync(sync *latest.Sync, artifact *latest.Artifact) *latest.Artifact { 454 artifact.Sync = sync 455 return artifact 456 } 457 458 func withTrustedBuilder(artifact *latest.Artifact) *latest.Artifact { 459 artifact.BuildpackArtifact.TrustBuilder = true 460 return artifact 461 } 462 463 func withRequiredArtifacts(deps []*latest.ArtifactDependency, artifact *latest.Artifact) *latest.Artifact { 464 artifact.Dependencies = deps 465 return artifact 466 } 467 468 func withBuildpacks(buildpacks []string, artifact *latest.Artifact) *latest.Artifact { 469 artifact.BuildpackArtifact.Buildpacks = buildpacks 470 return artifact 471 } 472 473 type mockArtifactResolver struct { 474 m map[string]string 475 } 476 477 func (r mockArtifactResolver) GetImageTag(imageName string) (string, bool) { 478 if r.m == nil { 479 return "", false 480 } 481 val, found := r.m[imageName] 482 return val, found 483 } 484 485 type testAuthHelper struct{} 486 487 func (t testAuthHelper) GetAuthConfig(string) (types.AuthConfig, error) { 488 return types.AuthConfig{}, nil 489 } 490 func (t testAuthHelper) GetAllAuthConfigs(context.Context) (map[string]types.AuthConfig, error) { 491 return nil, nil 492 } 493 494 func ignoreField(path string) cmp.Option { 495 return cmp.FilterPath(func(p cmp.Path) bool { 496 return p.String() == path 497 }, cmp.Ignore()) 498 }