github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/initializer/build/builders_test.go (about)

     1  /*
     2  Copyright 2020 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 build
    18  
    19  import (
    20  	"testing"
    21  
    22  	"github.com/google/go-cmp/cmp"
    23  
    24  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/buildpacks"
    25  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/jib"
    26  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/docker"
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/initializer/prompt"
    28  	tag "github.com/GoogleContainerTools/skaffold/pkg/skaffold/tag/util"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/warnings"
    30  	"github.com/GoogleContainerTools/skaffold/testutil"
    31  )
    32  
    33  func TestResolveBuilderImages(t *testing.T) {
    34  	tests := []struct {
    35  		description            string
    36  		buildConfigs           []InitBuilder
    37  		images                 []string
    38  		force                  bool
    39  		shouldMakeChoice       bool
    40  		shouldErr              bool
    41  		expectedInfos          []ArtifactInfo
    42  		expectedGeneratedInfos []GeneratedArtifactInfo
    43  	}{
    44  		{
    45  			description:      "nothing to choose from",
    46  			buildConfigs:     []InitBuilder{},
    47  			images:           []string{},
    48  			shouldMakeChoice: false,
    49  			expectedInfos:    nil,
    50  		},
    51  		{
    52  			description:      "don't prompt for single dockerfile and image",
    53  			buildConfigs:     []InitBuilder{docker.ArtifactConfig{File: "Dockerfile1"}},
    54  			images:           []string{"image1"},
    55  			shouldMakeChoice: false,
    56  			expectedInfos: []ArtifactInfo{
    57  				{
    58  					Builder:   docker.ArtifactConfig{File: "Dockerfile1"},
    59  					ImageName: "image1",
    60  				},
    61  			},
    62  		},
    63  		{
    64  			description:      "prompt for multiple builders and images",
    65  			buildConfigs:     []InitBuilder{docker.ArtifactConfig{File: "Dockerfile1"}, jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"}, jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibMaven), Project: "project", File: "pom.xml"}},
    66  			images:           []string{"image1", "image2"},
    67  			shouldMakeChoice: true,
    68  			expectedInfos: []ArtifactInfo{
    69  				{
    70  					Builder:   docker.ArtifactConfig{File: "Dockerfile1"},
    71  					ImageName: "image1",
    72  				},
    73  				{
    74  					Builder:   jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"},
    75  					ImageName: "image2",
    76  				},
    77  			},
    78  			expectedGeneratedInfos: []GeneratedArtifactInfo{
    79  				{
    80  					ArtifactInfo: ArtifactInfo{
    81  						Builder:   jib.ArtifactConfig{BuilderName: "Jib Maven Plugin", File: "pom.xml", Project: "project"},
    82  						ImageName: "pom-xml-image",
    83  					},
    84  					ManifestPath: "deployment.yaml",
    85  				},
    86  			},
    87  		},
    88  		{
    89  			description:      "successful force",
    90  			buildConfigs:     []InitBuilder{jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"}},
    91  			images:           []string{"image1"},
    92  			shouldMakeChoice: false,
    93  			force:            true,
    94  			expectedInfos: []ArtifactInfo{
    95  				{
    96  					Builder:   jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"},
    97  					ImageName: "image1",
    98  				},
    99  			},
   100  		},
   101  		{
   102  			description:      "successful force - 1 image 2 builders",
   103  			buildConfigs:     []InitBuilder{docker.ArtifactConfig{File: "Dockerfile1"}, jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"}},
   104  			images:           []string{"image1"},
   105  			shouldMakeChoice: true,
   106  			force:            true,
   107  			expectedInfos: []ArtifactInfo{
   108  				{
   109  					Builder:   docker.ArtifactConfig{File: "Dockerfile1"},
   110  					ImageName: "image1",
   111  				},
   112  			},
   113  		},
   114  		{
   115  			description:      "error with ambiguous force",
   116  			buildConfigs:     []InitBuilder{docker.ArtifactConfig{File: "Dockerfile1"}, jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"}},
   117  			images:           []string{"image1", "image2"},
   118  			shouldMakeChoice: false,
   119  			force:            true,
   120  			shouldErr:        true,
   121  		},
   122  		{
   123  			description:  "one unresolved image",
   124  			buildConfigs: []InitBuilder{docker.ArtifactConfig{File: "foo"}},
   125  			images:       []string{},
   126  			expectedGeneratedInfos: []GeneratedArtifactInfo{
   127  				{
   128  					ArtifactInfo: ArtifactInfo{
   129  						Builder:   docker.ArtifactConfig{File: "foo"},
   130  						ImageName: "foo-image",
   131  					},
   132  					ManifestPath: "deployment.yaml",
   133  				},
   134  			},
   135  			shouldMakeChoice: false,
   136  			force:            false,
   137  			shouldErr:        false,
   138  		},
   139  	}
   140  	for _, test := range tests {
   141  		testutil.Run(t, test.description, func(t *testutil.T) {
   142  			t.Override(&prompt.ChooseBuildersFunc, func(choices []string) ([]string, error) {
   143  				return choices, nil
   144  			})
   145  			// Overrides prompt.BuildConfigFunc to choose first option rather than using the interactive menu
   146  			t.Override(&prompt.BuildConfigFunc, func(image string, choices []string) (string, error) {
   147  				if !test.shouldMakeChoice {
   148  					t.FailNow()
   149  				}
   150  				return choices[0], nil
   151  			})
   152  
   153  			initializer := &defaultBuildInitializer{
   154  				builders:         test.buildConfigs,
   155  				force:            test.force,
   156  				unresolvedImages: test.images,
   157  			}
   158  			err := initializer.resolveBuilderImages()
   159  			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedInfos, initializer.artifactInfos, cmp.AllowUnexported())
   160  			t.CheckDeepEqual(test.expectedGeneratedInfos, initializer.generatedArtifactInfos, cmp.AllowUnexported())
   161  		})
   162  	}
   163  }
   164  
   165  func TestAutoSelectBuilders(t *testing.T) {
   166  	tests := []struct {
   167  		description              string
   168  		builderConfigs           []InitBuilder
   169  		images                   []string
   170  		expectedInfos            []ArtifactInfo
   171  		expectedBuildersLeft     []InitBuilder
   172  		expectedUnresolvedImages []string
   173  	}{
   174  		{
   175  			description: "no automatic matches",
   176  			builderConfigs: []InitBuilder{
   177  				docker.ArtifactConfig{File: "Dockerfile"},
   178  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"},
   179  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibMaven), File: "pom.xml", Image: "not a k8s image"},
   180  			},
   181  			images:        []string{"image1", "image2"},
   182  			expectedInfos: nil,
   183  			expectedBuildersLeft: []InitBuilder{
   184  				docker.ArtifactConfig{File: "Dockerfile"},
   185  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle"},
   186  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibMaven), File: "pom.xml", Image: "not a k8s image"},
   187  			},
   188  			expectedUnresolvedImages: []string{"image1", "image2"},
   189  		},
   190  		{
   191  			description: "automatic jib matches",
   192  			builderConfigs: []InitBuilder{
   193  				docker.ArtifactConfig{File: "Dockerfile"},
   194  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle", Image: "image1"},
   195  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibMaven), File: "pom.xml", Image: "image2"},
   196  			},
   197  			images: []string{"image1", "image2", "image3"},
   198  			expectedInfos: []ArtifactInfo{
   199  				{
   200  					Builder:   jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle", Image: "image1"},
   201  					ImageName: "image1",
   202  				},
   203  				{
   204  					Builder:   jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibMaven), File: "pom.xml", Image: "image2"},
   205  					ImageName: "image2",
   206  				},
   207  			},
   208  			expectedBuildersLeft:     []InitBuilder{docker.ArtifactConfig{File: "Dockerfile"}},
   209  			expectedUnresolvedImages: []string{"image3"},
   210  		},
   211  		{
   212  			description: "multiple matches for one image",
   213  			builderConfigs: []InitBuilder{
   214  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle", Image: "image1"},
   215  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibMaven), File: "pom.xml", Image: "image1"},
   216  			},
   217  			images:        []string{"image1", "image2"},
   218  			expectedInfos: nil,
   219  			expectedBuildersLeft: []InitBuilder{
   220  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibGradle), File: "build.gradle", Image: "image1"},
   221  				jib.ArtifactConfig{BuilderName: jib.PluginName(jib.JibMaven), File: "pom.xml", Image: "image1"},
   222  			},
   223  			expectedUnresolvedImages: []string{"image1", "image2"},
   224  		},
   225  		{
   226  			description:              "show unique image names",
   227  			builderConfigs:           nil,
   228  			images:                   []string{"image1", "image1"},
   229  			expectedInfos:            nil,
   230  			expectedBuildersLeft:     nil,
   231  			expectedUnresolvedImages: []string{"image1"},
   232  		},
   233  	}
   234  
   235  	for _, test := range tests {
   236  		testutil.Run(t, test.description, func(t *testutil.T) {
   237  			pairs, builderConfigs, unresolvedImages := matchBuildersToImages(test.builderConfigs, test.images)
   238  
   239  			t.CheckDeepEqual(test.expectedInfos, pairs)
   240  			t.CheckDeepEqual(test.expectedBuildersLeft, builderConfigs)
   241  			t.CheckDeepEqual(test.expectedUnresolvedImages, unresolvedImages)
   242  		})
   243  	}
   244  }
   245  
   246  func TestProcessCliArtifacts(t *testing.T) {
   247  	tests := []struct {
   248  		description        string
   249  		artifacts          []string
   250  		shouldErr          bool
   251  		expectedInfos      []ArtifactInfo
   252  		expectedWorkspaces []string
   253  	}{
   254  		{
   255  			description: "Invalid pairs",
   256  			artifacts:   []string{"invalid"},
   257  			shouldErr:   true,
   258  		},
   259  		{
   260  			description: "Invalid builder",
   261  			artifacts:   []string{`{"builder":"Not real","payload":{},"image":"image"}`},
   262  			shouldErr:   true,
   263  		},
   264  		{
   265  			description: "Valid (backwards compatibility)",
   266  			artifacts: []string{
   267  				`/path/to/Dockerfile=image1`,
   268  				`/path/to/Dockerfile2=image2`,
   269  			},
   270  			expectedInfos: []ArtifactInfo{
   271  				{
   272  					Builder:   docker.ArtifactConfig{File: "/path/to/Dockerfile"},
   273  					ImageName: "image1",
   274  				},
   275  				{
   276  					Builder:   docker.ArtifactConfig{File: "/path/to/Dockerfile2"},
   277  					ImageName: "image2",
   278  				},
   279  			},
   280  		},
   281  		{
   282  			description: "Valid",
   283  			artifacts: []string{
   284  				`{"builder":"Docker","payload":{"path":"/path/to/Dockerfile"},"image":"image1", "context": "path/to/docker/workspace"}`,
   285  				`{"builder":"Jib Gradle Plugin","payload":{"path":"/path/to/build.gradle"},"image":"image2", "context":"path/to/jib/workspace"}`,
   286  				`{"builder":"Jib Maven Plugin","payload":{"path":"/path/to/pom.xml","project":"project-name","image":"testImage"},"image":"image3"}`,
   287  				`{"builder":"Buildpacks","payload":{"path":"/path/to/package.json"},"image":"image4"}`,
   288  			},
   289  			expectedInfos: []ArtifactInfo{
   290  				{
   291  					Builder:   docker.ArtifactConfig{File: "/path/to/Dockerfile"},
   292  					ImageName: "image1",
   293  					Workspace: "path/to/docker/workspace",
   294  				},
   295  				{
   296  					Builder:   jib.ArtifactConfig{BuilderName: "Jib Gradle Plugin", File: "/path/to/build.gradle"},
   297  					ImageName: "image2",
   298  					Workspace: "path/to/jib/workspace",
   299  				},
   300  				{
   301  					Builder:   jib.ArtifactConfig{BuilderName: "Jib Maven Plugin", File: "/path/to/pom.xml", Project: "project-name", Image: "testImage"},
   302  					ImageName: "image3",
   303  				},
   304  				{
   305  					Builder:   buildpacks.ArtifactConfig{File: "/path/to/package.json"},
   306  					ImageName: "image4",
   307  				},
   308  			},
   309  		},
   310  	}
   311  
   312  	for _, test := range tests {
   313  		testutil.Run(t, test.description, func(t *testutil.T) {
   314  			pairs, err := processCliArtifacts(test.artifacts)
   315  
   316  			t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expectedInfos, pairs)
   317  		})
   318  	}
   319  }
   320  
   321  func TestStripImageTags(t *testing.T) {
   322  	tests := []struct {
   323  		description      string
   324  		taggedImages     []string
   325  		expectedImages   []string
   326  		expectedWarnings []string
   327  	}{
   328  		{
   329  			description:      "empty",
   330  			taggedImages:     nil,
   331  			expectedImages:   nil,
   332  			expectedWarnings: nil,
   333  		},
   334  		{
   335  			description: "tags are removed",
   336  			taggedImages: []string{
   337  				"gcr.io/testproject/testimage:latest",
   338  				"testdockerhublib/bla:v1.0",
   339  				"registrywithport:5000/image:v2.3",
   340  			},
   341  			expectedImages: []string{
   342  				"gcr.io/testproject/testimage",
   343  				"testdockerhublib/bla",
   344  				"registrywithport:5000/image",
   345  			},
   346  			expectedWarnings: nil,
   347  		},
   348  		{
   349  			description: "invalid image names are skipped with warning",
   350  			taggedImages: []string{
   351  				"gcr.io/testproject/testimage:latest",
   352  				"{{ REPOSITORY }}/{{IMAGE}}",
   353  			},
   354  			expectedImages: []string{
   355  				"gcr.io/testproject/testimage",
   356  			},
   357  			expectedWarnings: nil,
   358  		},
   359  		{
   360  			description: "images with digest are ignored",
   361  			taggedImages: []string{
   362  				"gcr.io/testregistry/testimage@sha256:16a019b0fa168b31fbecb3f909f55a5342e39f346cae919b7ff0b22f40029876",
   363  			},
   364  			expectedImages: nil,
   365  			expectedWarnings: []string{
   366  				"Ignoring image referenced by digest: [gcr.io/testregistry/testimage@sha256:16a019b0fa168b31fbecb3f909f55a5342e39f346cae919b7ff0b22f40029876]",
   367  			},
   368  		},
   369  	}
   370  
   371  	for _, test := range tests {
   372  		testutil.Run(t, test.description, func(t *testutil.T) {
   373  			fakeWarner := &warnings.Collect{}
   374  			t.Override(&warnings.Printf, fakeWarner.Warnf)
   375  
   376  			images := tag.StripTags(test.taggedImages, true)
   377  
   378  			t.CheckDeepEqual(test.expectedImages, images)
   379  			t.CheckDeepEqual(test.expectedWarnings, fakeWarner.Warnings)
   380  		})
   381  	}
   382  }