github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/build/cluster/pod_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 cluster
    18  
    19  import (
    20  	"testing"
    21  
    22  	specs "github.com/opencontainers/image-spec/specs-go/v1"
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/apimachinery/pkg/api/resource"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/build/kaniko"
    28  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/platform"
    29  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest"
    30  	"github.com/GoogleContainerTools/skaffold/pkg/skaffold/util"
    31  	"github.com/GoogleContainerTools/skaffold/testutil"
    32  )
    33  
    34  func TestKanikoArgs(t *testing.T) {
    35  	tests := []struct {
    36  		description        string
    37  		artifact           *latest.KanikoArtifact
    38  		insecureRegistries map[string]bool
    39  		tag                string
    40  		shouldErr          bool
    41  		expectedArgs       []string
    42  	}{
    43  		{
    44  			description: "simple build",
    45  			artifact: &latest.KanikoArtifact{
    46  				DockerfilePath: "Dockerfile",
    47  			},
    48  			expectedArgs: []string{},
    49  		},
    50  		{
    51  			description: "cache layers",
    52  			artifact: &latest.KanikoArtifact{
    53  				DockerfilePath: "Dockerfile",
    54  				Cache:          &latest.KanikoCache{},
    55  			},
    56  			expectedArgs: []string{kaniko.CacheFlag},
    57  		},
    58  		{
    59  			description: "cache layers to specific repo",
    60  			artifact: &latest.KanikoArtifact{
    61  				DockerfilePath: "Dockerfile",
    62  				Cache: &latest.KanikoCache{
    63  					Repo: "repo",
    64  				},
    65  			},
    66  			expectedArgs: []string{"--cache", kaniko.CacheRepoFlag, "repo"},
    67  		},
    68  		{
    69  			description: "cache path",
    70  			artifact: &latest.KanikoArtifact{
    71  				DockerfilePath: "Dockerfile",
    72  				Cache: &latest.KanikoCache{
    73  					HostPath: "/cache",
    74  				},
    75  			},
    76  			expectedArgs: []string{
    77  				kaniko.CacheFlag,
    78  				kaniko.CacheDirFlag, "/cache"},
    79  		},
    80  		{
    81  			description: "target",
    82  			artifact: &latest.KanikoArtifact{
    83  				DockerfilePath: "Dockerfile",
    84  				Target:         "target",
    85  			},
    86  			expectedArgs: []string{kaniko.TargetFlag, "target"},
    87  		},
    88  		{
    89  			description: "reproducible",
    90  			artifact: &latest.KanikoArtifact{
    91  				DockerfilePath: "Dockerfile",
    92  				Reproducible:   true,
    93  			},
    94  			expectedArgs: []string{kaniko.ReproducibleFlag},
    95  		},
    96  		{
    97  			description: "build args",
    98  			artifact: &latest.KanikoArtifact{
    99  				DockerfilePath: "Dockerfile",
   100  				BuildArgs: map[string]*string{
   101  					"nil_key":   nil,
   102  					"empty_key": util.StringPtr(""),
   103  					"value_key": util.StringPtr("value"),
   104  				},
   105  			},
   106  			expectedArgs: []string{
   107  				kaniko.BuildArgsFlag, "empty_key=",
   108  				kaniko.BuildArgsFlag, "nil_key",
   109  				kaniko.BuildArgsFlag, "value_key=value"},
   110  		},
   111  		{
   112  			description: "invalid build args",
   113  			artifact: &latest.KanikoArtifact{
   114  				DockerfilePath: "Dockerfile",
   115  				BuildArgs: map[string]*string{
   116  					"invalid": util.StringPtr("{{Invalid"),
   117  				},
   118  			},
   119  			shouldErr: true,
   120  		},
   121  		{
   122  			description: "insecure registries",
   123  			artifact: &latest.KanikoArtifact{
   124  				DockerfilePath: "Dockerfile",
   125  			},
   126  			insecureRegistries: map[string]bool{"localhost:4000": true},
   127  			expectedArgs:       []string{kaniko.InsecureRegistryFlag, "localhost:4000"},
   128  		},
   129  		{
   130  			description: "skip tls",
   131  			artifact: &latest.KanikoArtifact{
   132  				DockerfilePath: "Dockerfile",
   133  				SkipTLS:        true,
   134  			},
   135  			expectedArgs: []string{
   136  				kaniko.SkipTLSFlag,
   137  				kaniko.SkipTLSVerifyRegistryFlag, "gcr.io",
   138  			},
   139  		},
   140  		{
   141  			description: "invalid registry",
   142  			artifact: &latest.KanikoArtifact{
   143  				DockerfilePath: "Dockerfile",
   144  				SkipTLS:        true,
   145  			},
   146  			tag:       "!!!!",
   147  			shouldErr: true,
   148  		},
   149  	}
   150  	for _, test := range tests {
   151  		testutil.Run(t, test.description, func(t *testutil.T) {
   152  			commonArgs := []string{"--destination", "gcr.io/tag", "--dockerfile", "Dockerfile", "--context", "dir:///kaniko/buildcontext"}
   153  
   154  			tag := "gcr.io/tag"
   155  			if test.tag != "" {
   156  				tag = test.tag
   157  			}
   158  			args, err := kanikoArgs(test.artifact, tag, test.insecureRegistries)
   159  
   160  			t.CheckError(test.shouldErr, err)
   161  			if !test.shouldErr {
   162  				t.CheckDeepEqual(append(commonArgs, test.expectedArgs...), args)
   163  			}
   164  		})
   165  	}
   166  }
   167  
   168  func TestKanikoPodSpec(t *testing.T) {
   169  	artifact := &latest.KanikoArtifact{
   170  		Image:          "image",
   171  		DockerfilePath: "Dockerfile",
   172  		InitImage:      "init/image",
   173  		Env: []v1.EnvVar{{
   174  			Name:  "KEY",
   175  			Value: "VALUE",
   176  		}},
   177  		VolumeMounts: []v1.VolumeMount{
   178  			{
   179  				Name:      "cm-volume-1",
   180  				ReadOnly:  true,
   181  				MountPath: "/cm-test-mount-path",
   182  				SubPath:   "/subpath",
   183  			},
   184  			{
   185  				Name:      "secret-volume-1",
   186  				ReadOnly:  true,
   187  				MountPath: "/secret-test-mount-path",
   188  				SubPath:   "/subpath",
   189  			},
   190  		},
   191  	}
   192  
   193  	var runAsUser int64 = 0
   194  
   195  	builder := &Builder{
   196  		cfg: &mockBuilderContext{},
   197  		ClusterDetails: &latest.ClusterDetails{
   198  			Namespace:           "ns",
   199  			PullSecretName:      "secret",
   200  			PullSecretPath:      "kaniko-secret.json",
   201  			PullSecretMountPath: "/secret",
   202  			HTTPProxy:           "http://proxy",
   203  			HTTPSProxy:          "https://proxy",
   204  			ServiceAccountName:  "aVerySpecialSA",
   205  			RunAsUser:           &runAsUser,
   206  			Resources: &latest.ResourceRequirements{
   207  				Requests: &latest.ResourceRequirement{
   208  					CPU: "0.1",
   209  				},
   210  				Limits: &latest.ResourceRequirement{
   211  					CPU: "0.5",
   212  				},
   213  			},
   214  			Volumes: []v1.Volume{
   215  				{
   216  					Name: "cm-volume-1",
   217  					VolumeSource: v1.VolumeSource{
   218  						ConfigMap: &v1.ConfigMapVolumeSource{
   219  							LocalObjectReference: v1.LocalObjectReference{
   220  								Name: "cm-1",
   221  							},
   222  						},
   223  					},
   224  				},
   225  				{
   226  					Name: "secret-volume-1",
   227  					VolumeSource: v1.VolumeSource{
   228  						Secret: &v1.SecretVolumeSource{
   229  							SecretName: "secret-1",
   230  						},
   231  					},
   232  				},
   233  			},
   234  			Tolerations: []v1.Toleration{
   235  				{
   236  					Key:               "app",
   237  					Operator:          "Equal",
   238  					Value:             "skaffold",
   239  					Effect:            "NoSchedule",
   240  					TolerationSeconds: nil,
   241  				},
   242  			},
   243  			NodeSelector: map[string]string{"kubernetes.io/os": "linux"},
   244  		},
   245  	}
   246  	matcher := platform.Matcher{Platforms: []specs.Platform{{OS: "linux", Architecture: "arm64"}}}
   247  	pod, _ := builder.kanikoPodSpec(artifact, "tag", matcher)
   248  
   249  	expectedPod := &v1.Pod{
   250  		ObjectMeta: metav1.ObjectMeta{
   251  			Annotations:  map[string]string{"test": "test"},
   252  			GenerateName: "kaniko-",
   253  			Labels:       map[string]string{"skaffold-kaniko": "skaffold-kaniko"},
   254  			Namespace:    "ns",
   255  		},
   256  		Spec: v1.PodSpec{
   257  			InitContainers: []v1.Container{{
   258  				Name:    initContainer,
   259  				Image:   "init/image",
   260  				Command: []string{"sh", "-c", "while [ ! -f /tmp/complete ]; do sleep 1; done"},
   261  				VolumeMounts: []v1.VolumeMount{{
   262  					Name:      kaniko.DefaultEmptyDirName,
   263  					MountPath: kaniko.DefaultEmptyDirMountPath,
   264  				}, {
   265  					Name:      "cm-volume-1",
   266  					ReadOnly:  true,
   267  					MountPath: "/cm-secret-mount-path",
   268  					SubPath:   "/subpath",
   269  				}, {
   270  					Name:      "secret-volume-1",
   271  					ReadOnly:  true,
   272  					MountPath: "/secret-secret-mount-path",
   273  					SubPath:   "/subpath",
   274  				}},
   275  				Resources: v1.ResourceRequirements{
   276  					Requests: map[v1.ResourceName]resource.Quantity{
   277  						v1.ResourceCPU: resource.MustParse("0.1"),
   278  					},
   279  					Limits: v1.ResourceList{
   280  						v1.ResourceCPU: resource.MustParse("0.5"),
   281  					},
   282  				},
   283  			}},
   284  			Containers: []v1.Container{{
   285  				Name:            kaniko.DefaultContainerName,
   286  				Image:           "image",
   287  				Args:            []string{"--dockerfile", "Dockerfile", "--context", "dir:///kaniko/buildcontext", "--destination", "tag", "-v", "info"},
   288  				ImagePullPolicy: v1.PullIfNotPresent,
   289  				Env: []v1.EnvVar{{
   290  					Name:  "UPSTREAM_CLIENT_TYPE",
   291  					Value: "UpstreamClient(skaffold-)",
   292  				}, {
   293  					Name:  "KEY",
   294  					Value: "VALUE",
   295  				}, {
   296  					Name:  "HTTP_PROXY",
   297  					Value: "http://proxy",
   298  				}, {
   299  					Name:  "HTTPS_PROXY",
   300  					Value: "https://proxy",
   301  				}, {
   302  					Name:  "GOOGLE_APPLICATION_CREDENTIALS",
   303  					Value: "/secret/kaniko-secret.json",
   304  				}},
   305  				VolumeMounts: []v1.VolumeMount{
   306  					{
   307  						Name:      kaniko.DefaultEmptyDirName,
   308  						MountPath: kaniko.DefaultEmptyDirMountPath,
   309  					},
   310  					{
   311  						Name:      kaniko.DefaultSecretName,
   312  						MountPath: "/secret",
   313  					},
   314  					{
   315  						Name:      "cm-volume-1",
   316  						ReadOnly:  true,
   317  						MountPath: "/cm-secret-mount-path",
   318  						SubPath:   "/subpath",
   319  					},
   320  					{
   321  						Name:      "secret-volume-1",
   322  						ReadOnly:  true,
   323  						MountPath: "/secret-secret-mount-path",
   324  						SubPath:   "/subpath",
   325  					},
   326  				},
   327  				Resources: v1.ResourceRequirements{
   328  					Requests: map[v1.ResourceName]resource.Quantity{
   329  						v1.ResourceCPU: resource.MustParse("0.1"),
   330  					},
   331  					Limits: v1.ResourceList{
   332  						v1.ResourceCPU: resource.MustParse("0.5"),
   333  					},
   334  				},
   335  			}},
   336  			ServiceAccountName: "aVerySpecialSA",
   337  			SecurityContext: &v1.PodSecurityContext{
   338  				RunAsUser: &runAsUser,
   339  			},
   340  			RestartPolicy: v1.RestartPolicyNever,
   341  			Volumes: []v1.Volume{
   342  				{
   343  					Name: kaniko.DefaultEmptyDirName,
   344  					VolumeSource: v1.VolumeSource{
   345  						EmptyDir: &v1.EmptyDirVolumeSource{},
   346  					},
   347  				},
   348  				{
   349  					Name: kaniko.DefaultSecretName,
   350  					VolumeSource: v1.VolumeSource{
   351  						Secret: &v1.SecretVolumeSource{
   352  							SecretName: "secret",
   353  						},
   354  					},
   355  				},
   356  				{
   357  					Name: "cm-volume-1",
   358  					VolumeSource: v1.VolumeSource{
   359  						ConfigMap: &v1.ConfigMapVolumeSource{
   360  							LocalObjectReference: v1.LocalObjectReference{
   361  								Name: "cm-1",
   362  							},
   363  						},
   364  					},
   365  				},
   366  				{
   367  					Name: "secret-volume-1",
   368  					VolumeSource: v1.VolumeSource{
   369  						Secret: &v1.SecretVolumeSource{
   370  							SecretName: "secret-1",
   371  						},
   372  					},
   373  				},
   374  			},
   375  			Tolerations: []v1.Toleration{
   376  				{
   377  					Key:               "app",
   378  					Operator:          "Equal",
   379  					Value:             "skaffold",
   380  					Effect:            "NoSchedule",
   381  					TolerationSeconds: nil,
   382  				},
   383  			},
   384  			NodeSelector: map[string]string{"kubernetes.io/os": "linux", "kubernetes.io/arch": "arm64"},
   385  		},
   386  	}
   387  
   388  	testutil.CheckDeepEqual(t, expectedPod.Spec.Containers[0].Env, pod.Spec.Containers[0].Env)
   389  }
   390  
   391  func TestResourceRequirements(t *testing.T) {
   392  	tests := []struct {
   393  		description string
   394  		initial     *latest.ResourceRequirements
   395  		expected    v1.ResourceRequirements
   396  	}{
   397  		{
   398  			description: "no resource specified",
   399  			initial:     &latest.ResourceRequirements{},
   400  			expected:    v1.ResourceRequirements{},
   401  		},
   402  		{
   403  			description: "with resource specified",
   404  			initial: &latest.ResourceRequirements{
   405  				Requests: &latest.ResourceRequirement{
   406  					CPU:              "0.5",
   407  					Memory:           "1000",
   408  					ResourceStorage:  "1000",
   409  					EphemeralStorage: "1000",
   410  				},
   411  				Limits: &latest.ResourceRequirement{
   412  					CPU:              "1.0",
   413  					Memory:           "2000",
   414  					ResourceStorage:  "1000",
   415  					EphemeralStorage: "1000",
   416  				},
   417  			},
   418  			expected: v1.ResourceRequirements{
   419  				Requests: v1.ResourceList{
   420  					v1.ResourceCPU:              resource.MustParse("0.5"),
   421  					v1.ResourceMemory:           resource.MustParse("1000"),
   422  					v1.ResourceStorage:          resource.MustParse("1000"),
   423  					v1.ResourceEphemeralStorage: resource.MustParse("1000"),
   424  				},
   425  				Limits: v1.ResourceList{
   426  					v1.ResourceCPU:              resource.MustParse("1.0"),
   427  					v1.ResourceMemory:           resource.MustParse("2000"),
   428  					v1.ResourceStorage:          resource.MustParse("1000"),
   429  					v1.ResourceEphemeralStorage: resource.MustParse("1000"),
   430  				},
   431  			},
   432  		},
   433  	}
   434  
   435  	for _, test := range tests {
   436  		testutil.Run(t, test.description, func(t *testutil.T) {
   437  			actual := resourceRequirements(test.initial)
   438  			t.CheckDeepEqual(test.expected, actual)
   439  		})
   440  	}
   441  }