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 }