github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/local/local_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 local 18 19 import ( 20 "context" 21 "errors" 22 "io/ioutil" 23 "testing" 24 25 "github.com/docker/docker/api/types" 26 "github.com/docker/docker/client" 27 28 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build" 29 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/bazel" 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/buildpacks" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/custom" 32 dockerbuilder "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/docker" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/jib" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" 35 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/constants" 36 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker" 37 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 38 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform" 39 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext" 40 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 41 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 42 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/warnings" 43 "github.com/GoogleContainerTools/skaffold/testutil" 44 testEvent "github.com/GoogleContainerTools/skaffold/testutil/event" 45 ) 46 47 type testAuthHelper struct{} 48 49 func (t testAuthHelper) GetAuthConfig(string) (types.AuthConfig, error) { 50 return types.AuthConfig{}, nil 51 } 52 func (t testAuthHelper) GetAllAuthConfigs(context.Context) (map[string]types.AuthConfig, error) { 53 return nil, nil 54 } 55 56 func TestLocalRun(t *testing.T) { 57 tests := []struct { 58 description string 59 api *testutil.FakeAPIClient 60 tag string 61 artifact *latest.Artifact 62 expected string 63 expectedWarnings []string 64 expectedPushed map[string]string 65 pushImages bool 66 shouldErr bool 67 }{ 68 { 69 description: "single build (local)", 70 artifact: &latest.Artifact{ 71 ImageName: "gcr.io/test/image", 72 ArtifactType: latest.ArtifactType{ 73 DockerArtifact: &latest.DockerArtifact{}, 74 }, 75 }, 76 tag: "gcr.io/test/image:tag", 77 api: &testutil.FakeAPIClient{}, 78 pushImages: false, 79 expected: "gcr.io/test/image:1", 80 }, 81 { 82 description: "error getting image digest", 83 artifact: &latest.Artifact{ 84 ImageName: "gcr.io/test/image", 85 ArtifactType: latest.ArtifactType{ 86 DockerArtifact: &latest.DockerArtifact{}, 87 }, 88 }, 89 tag: "gcr.io/test/image:tag", 90 api: &testutil.FakeAPIClient{ 91 ErrImageInspect: true, 92 }, 93 shouldErr: true, 94 }, 95 { 96 description: "single build (remote)", 97 artifact: &latest.Artifact{ 98 ImageName: "gcr.io/test/image", 99 ArtifactType: latest.ArtifactType{ 100 DockerArtifact: &latest.DockerArtifact{}, 101 }, 102 }, 103 tag: "gcr.io/test/image:tag", 104 api: &testutil.FakeAPIClient{}, 105 pushImages: true, 106 expected: "gcr.io/test/image:tag@sha256:51ae7fa00c92525c319404a3a6d400e52ff9372c5a39cb415e0486fe425f3165", 107 expectedPushed: map[string]string{ 108 "gcr.io/test/image:tag": "sha256:51ae7fa00c92525c319404a3a6d400e52ff9372c5a39cb415e0486fe425f3165", 109 }, 110 }, 111 { 112 description: "error build", 113 artifact: &latest.Artifact{ 114 ImageName: "gcr.io/test/image", 115 ArtifactType: latest.ArtifactType{ 116 DockerArtifact: &latest.DockerArtifact{}, 117 }, 118 }, 119 tag: "gcr.io/test/image:tag", 120 api: &testutil.FakeAPIClient{ 121 ErrImageBuild: true, 122 }, 123 shouldErr: true, 124 }, 125 { 126 description: "Don't push on build error", 127 artifact: &latest.Artifact{ 128 ImageName: "gcr.io/test/image", 129 ArtifactType: latest.ArtifactType{ 130 DockerArtifact: &latest.DockerArtifact{}, 131 }, 132 }, 133 tag: "gcr.io/test/image:tag", 134 pushImages: true, 135 api: &testutil.FakeAPIClient{ 136 ErrImageBuild: true, 137 }, 138 shouldErr: true, 139 }, 140 { 141 description: "unknown artifact type", 142 artifact: &latest.Artifact{}, 143 api: &testutil.FakeAPIClient{}, 144 shouldErr: true, 145 }, 146 { 147 description: "cache-from images already pulled", 148 artifact: &latest.Artifact{ 149 ImageName: "gcr.io/test/image", 150 ArtifactType: latest.ArtifactType{ 151 DockerArtifact: &latest.DockerArtifact{ 152 CacheFrom: []string{"pull1", "pull2"}, 153 }, 154 }, 155 }, 156 api: (&testutil.FakeAPIClient{}).Add("pull1", "imageID1").Add("pull2", "imageID2"), 157 tag: "gcr.io/test/image:tag", 158 expected: "gcr.io/test/image:1", 159 }, 160 { 161 description: "pull cache-from images", 162 artifact: &latest.Artifact{ 163 ImageName: "gcr.io/test/image", 164 ArtifactType: latest.ArtifactType{ 165 DockerArtifact: &latest.DockerArtifact{ 166 CacheFrom: []string{"pull1", "pull2"}, 167 }, 168 }, 169 }, 170 api: (&testutil.FakeAPIClient{}).Add("pull1", "imageid").Add("pull2", "anotherimageid"), 171 tag: "gcr.io/test/image:tag", 172 expected: "gcr.io/test/image:1", 173 }, 174 { 175 description: "ignore cache-from pull error", 176 artifact: &latest.Artifact{ 177 ImageName: "gcr.io/test/image", 178 ArtifactType: latest.ArtifactType{ 179 DockerArtifact: &latest.DockerArtifact{ 180 CacheFrom: []string{"pull1"}, 181 }, 182 }, 183 }, 184 api: (&testutil.FakeAPIClient{ 185 ErrImagePull: true, 186 }).Add("pull1", ""), 187 tag: "gcr.io/test/image:tag", 188 expected: "gcr.io/test/image:1", 189 expectedWarnings: []string{"cacheFrom image couldn't be pulled: pull1\n"}, 190 }, 191 { 192 description: "error checking cache-from image", 193 artifact: &latest.Artifact{ 194 ImageName: "gcr.io/test/image", 195 ArtifactType: latest.ArtifactType{ 196 DockerArtifact: &latest.DockerArtifact{ 197 CacheFrom: []string{"pull"}, 198 }, 199 }, 200 }, 201 api: &testutil.FakeAPIClient{ 202 ErrImageInspect: true, 203 }, 204 tag: "gcr.io/test/image:tag", 205 shouldErr: true, 206 }, 207 { 208 description: "fail fast docker not found", 209 artifact: &latest.Artifact{ 210 ImageName: "gcr.io/test/image", 211 ArtifactType: latest.ArtifactType{ 212 DockerArtifact: &latest.DockerArtifact{}, 213 }, 214 }, 215 tag: "gcr.io/test/image:tag", 216 api: &testutil.FakeAPIClient{ 217 ErrVersion: true, 218 }, 219 pushImages: false, 220 shouldErr: true, 221 }, 222 } 223 for _, test := range tests { 224 testutil.Run(t, test.description, func(t *testutil.T) { 225 t.Override(&docker.DefaultAuthHelper, testAuthHelper{}) 226 fakeWarner := &warnings.Collect{} 227 t.Override(&warnings.Printf, fakeWarner.Warnf) 228 t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) { 229 return fakeLocalDaemon(test.api), nil 230 }) 231 t.Override(&docker.EvalBuildArgs, func(_ config.RunMode, _ string, _ string, args map[string]*string, _ map[string]*string) (map[string]*string, error) { 232 return args, nil 233 }) 234 testEvent.InitializeState([]latest.Pipeline{{ 235 Deploy: latest.DeployConfig{}, 236 Build: latest.BuildConfig{ 237 BuildType: latest.BuildType{ 238 LocalBuild: &latest.LocalBuild{}, 239 }, 240 }}}) 241 242 builder, err := NewBuilder(context.Background(), &mockBuilderContext{artifactStore: build.NewArtifactStore()}, &latest.LocalBuild{ 243 Push: util.BoolPtr(test.pushImages), 244 Concurrency: &constants.DefaultLocalConcurrency, 245 }) 246 t.CheckNoError(err) 247 ab := builder.Build(context.Background(), ioutil.Discard, test.artifact) 248 res, err := ab(context.Background(), ioutil.Discard, test.artifact, test.tag, platform.Matcher{}) 249 t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, res) 250 t.CheckDeepEqual(test.expectedWarnings, fakeWarner.Warnings) 251 t.CheckDeepEqual(test.expectedPushed, test.api.Pushed()) 252 }) 253 } 254 } 255 256 type dummyLocalDaemon struct { 257 docker.LocalDaemon 258 } 259 260 func TestNewBuilder(t *testing.T) { 261 dummyDaemon := dummyLocalDaemon{} 262 263 tests := []struct { 264 description string 265 shouldErr bool 266 expectedPush bool 267 cluster config.Cluster 268 pushFlag config.BoolOrUndefined 269 localBuild latest.LocalBuild 270 localDockerFn func(context.Context, docker.Config) (docker.LocalDaemon, error) 271 }{ 272 { 273 description: "failed to get docker client", 274 localDockerFn: func(context.Context, docker.Config) (docker.LocalDaemon, error) { 275 return nil, errors.New("dummy docker error") 276 }, 277 shouldErr: true, 278 }, 279 { 280 description: "pushImages becomes cluster.PushImages when local:push and --push is not defined", 281 localDockerFn: func(context.Context, docker.Config) (docker.LocalDaemon, error) { 282 return dummyDaemon, nil 283 }, 284 cluster: config.Cluster{PushImages: true}, 285 expectedPush: true, 286 }, 287 { 288 description: "pushImages becomes config (local:push) when --push is not defined", 289 localDockerFn: func(context.Context, docker.Config) (docker.LocalDaemon, error) { 290 return dummyDaemon, nil 291 }, 292 cluster: config.Cluster{PushImages: true}, 293 localBuild: latest.LocalBuild{ 294 Push: util.BoolPtr(false), 295 }, 296 shouldErr: false, 297 expectedPush: false, 298 }, 299 { 300 description: "pushImages defined in flags (--push=false), ignores cluster.PushImages", 301 localDockerFn: func(context.Context, docker.Config) (docker.LocalDaemon, error) { 302 return dummyDaemon, nil 303 }, 304 cluster: config.Cluster{PushImages: true}, 305 pushFlag: config.NewBoolOrUndefined(util.BoolPtr(false)), 306 shouldErr: false, 307 expectedPush: false, 308 }, 309 { 310 description: "pushImages defined in flags (--push=false), ignores config (local:push)", 311 localDockerFn: func(context.Context, docker.Config) (docker.LocalDaemon, error) { 312 return dummyDaemon, nil 313 }, 314 pushFlag: config.NewBoolOrUndefined(util.BoolPtr(false)), 315 localBuild: latest.LocalBuild{ 316 Push: util.BoolPtr(true), 317 }, 318 shouldErr: false, 319 expectedPush: false, 320 }, 321 } 322 for _, test := range tests { 323 testutil.Run(t, test.description, func(t *testutil.T) { 324 if test.localDockerFn != nil { 325 t.Override(&docker.NewAPIClient, test.localDockerFn) 326 } 327 328 builder, err := NewBuilder(context.Background(), &mockBuilderContext{ 329 local: test.localBuild, 330 cluster: test.cluster, 331 pushFlag: test.pushFlag, 332 }, &test.localBuild) 333 334 t.CheckError(test.shouldErr, err) 335 if !test.shouldErr { 336 t.CheckDeepEqual(test.expectedPush, builder.pushImages) 337 } 338 }) 339 } 340 } 341 342 func TestGetArtifactBuilder(t *testing.T) { 343 tests := []struct { 344 description string 345 artifact *latest.Artifact 346 expected string 347 shouldErr bool 348 }{ 349 { 350 description: "docker builder", 351 artifact: &latest.Artifact{ 352 ImageName: "gcr.io/test/image", 353 ArtifactType: latest.ArtifactType{ 354 DockerArtifact: &latest.DockerArtifact{}, 355 }, 356 }, 357 expected: "docker", 358 }, 359 { 360 description: "jib builder", 361 artifact: &latest.Artifact{ 362 ImageName: "gcr.io/test/image", 363 ArtifactType: latest.ArtifactType{ 364 JibArtifact: &latest.JibArtifact{}, 365 }, 366 }, 367 expected: "jib", 368 }, 369 { 370 description: "buildpacks builder", 371 artifact: &latest.Artifact{ 372 ImageName: "gcr.io/test/image", 373 ArtifactType: latest.ArtifactType{ 374 BuildpackArtifact: &latest.BuildpackArtifact{}, 375 }, 376 }, 377 expected: "buildpacks", 378 }, 379 { 380 description: "bazel builder", 381 artifact: &latest.Artifact{ 382 ImageName: "gcr.io/test/image", 383 ArtifactType: latest.ArtifactType{ 384 BazelArtifact: &latest.BazelArtifact{}, 385 }, 386 }, 387 expected: "bazel", 388 }, 389 { 390 description: "custom builder", 391 artifact: &latest.Artifact{ 392 ImageName: "gcr.io/test/image", 393 ArtifactType: latest.ArtifactType{ 394 CustomArtifact: &latest.CustomArtifact{}, 395 }, 396 }, 397 expected: "custom", 398 }, 399 } 400 for _, test := range tests { 401 testutil.Run(t, test.description, func(t *testutil.T) { 402 t.Override(&docker.NewAPIClient, func(context.Context, docker.Config) (docker.LocalDaemon, error) { 403 return fakeLocalDaemon(&testutil.FakeAPIClient{}), nil 404 }) 405 t.Override(&docker.EvalBuildArgs, func(_ config.RunMode, _ string, _ string, args map[string]*string, _ map[string]*string) (map[string]*string, error) { 406 return args, nil 407 }) 408 409 b, err := NewBuilder(context.Background(), &mockBuilderContext{artifactStore: build.NewArtifactStore()}, &latest.LocalBuild{Concurrency: &constants.DefaultLocalConcurrency}) 410 t.CheckNoError(err) 411 412 builder, err := newPerArtifactBuilder(b, test.artifact) 413 t.CheckNoError(err) 414 415 switch builder.(type) { 416 case *dockerbuilder.Builder: 417 t.CheckDeepEqual(test.expected, "docker") 418 case *bazel.Builder: 419 t.CheckDeepEqual(test.expected, "bazel") 420 case *buildpacks.Builder: 421 t.CheckDeepEqual(test.expected, "buildpacks") 422 case *custom.Builder: 423 t.CheckDeepEqual(test.expected, "custom") 424 case *jib.Builder: 425 t.CheckDeepEqual(test.expected, "jib") 426 } 427 }) 428 } 429 } 430 431 func fakeLocalDaemon(api client.CommonAPIClient) docker.LocalDaemon { 432 return docker.NewLocalDaemon(api, nil, false, nil) 433 } 434 435 type mockBuilderContext struct { 436 runcontext.RunContext // Embedded to provide the default values. 437 local latest.LocalBuild 438 mode config.RunMode 439 cluster config.Cluster 440 pushFlag config.BoolOrUndefined 441 artifactStore build.ArtifactStore 442 sourceDepsResolver func() graph.SourceDependenciesCache 443 } 444 445 func (c *mockBuilderContext) Mode() config.RunMode { 446 return c.mode 447 } 448 449 func (c *mockBuilderContext) GetCluster() config.Cluster { 450 return c.cluster 451 } 452 453 func (c *mockBuilderContext) PushImages() config.BoolOrUndefined { 454 return c.pushFlag 455 } 456 457 func (c *mockBuilderContext) ArtifactStore() build.ArtifactStore { 458 return c.artifactStore 459 } 460 461 func (c *mockBuilderContext) SourceDependenciesResolver() graph.SourceDependenciesCache { 462 if c.sourceDepsResolver != nil { 463 return c.sourceDepsResolver() 464 } 465 return nil 466 }