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  }