github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/deploy/kubectl/kubectl_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 kubectl 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "testing" 28 "time" 29 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/config" 31 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/label" 32 deployutil "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/util" 33 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/graph" 34 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/client" 35 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/kubernetes/manifest" 36 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/runner/runcontext" 37 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/schema/latest" 38 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/util" 39 "github.com/GoogleContainerTools/skaffold/testutil" 40 ) 41 42 func TestKubectlDeploy(t *testing.T) { 43 tests := []struct { 44 description string 45 kubectl latest.KubectlDeploy 46 builds []graph.Artifact 47 commands util.Command 48 shouldErr bool 49 forceDeploy bool 50 waitForDeletions bool 51 skipSkaffoldNamespaceOption bool 52 envs map[string]string 53 }{ 54 { 55 description: "no manifest", 56 kubectl: latest.KubectlDeploy{}, 57 commands: testutil.CmdRunOut("kubectl version --client -ojson", KubectlVersion112), 58 waitForDeletions: true, 59 }, 60 { 61 description: "deploy success (disable validation)", 62 kubectl: latest.KubectlDeploy{ 63 Manifests: []string{"deployment.yaml"}, 64 Flags: latest.KubectlFlags{ 65 DisableValidation: true, 66 }, 67 }, 68 commands: testutil. 69 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 70 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml --validate=false", DeploymentWebYAML). 71 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 72 AndRun("kubectl --context kubecontext --namespace testNamespace apply -f - --validate=false"), 73 builds: []graph.Artifact{{ 74 ImageName: "leeroy-web", 75 Tag: "leeroy-web:v1", 76 }}, 77 waitForDeletions: true, 78 }, 79 { 80 description: "deploy success (forced)", 81 kubectl: latest.KubectlDeploy{ 82 Manifests: []string{"deployment.yaml"}, 83 }, 84 commands: testutil. 85 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 86 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 87 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 88 AndRun("kubectl --context kubecontext --namespace testNamespace apply -f - --force --grace-period=0"), 89 builds: []graph.Artifact{{ 90 ImageName: "leeroy-web", 91 Tag: "leeroy-web:v1", 92 }}, 93 forceDeploy: true, 94 waitForDeletions: true, 95 }, 96 { 97 description: "deploy success", 98 kubectl: latest.KubectlDeploy{ 99 Manifests: []string{"deployment.yaml"}, 100 }, 101 commands: testutil. 102 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 103 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 104 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 105 AndRun("kubectl --context kubecontext --namespace testNamespace apply -f -"), 106 builds: []graph.Artifact{{ 107 ImageName: "leeroy-web", 108 Tag: "leeroy-web:v1", 109 }}, 110 waitForDeletions: true, 111 }, 112 { 113 description: "deploy success (kubectl v1.18)", 114 kubectl: latest.KubectlDeploy{ 115 Manifests: []string{"deployment.yaml"}, 116 }, 117 commands: testutil. 118 CmdRunOut("kubectl version --client -ojson", KubectlVersion118). 119 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run=client -oyaml -f deployment.yaml", DeploymentWebYAML). 120 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 121 AndRun("kubectl --context kubecontext --namespace testNamespace apply -f -"), 122 builds: []graph.Artifact{{ 123 ImageName: "leeroy-web", 124 Tag: "leeroy-web:v1", 125 }}, 126 waitForDeletions: true, 127 }, 128 { 129 description: "deploy success (default namespace)", 130 kubectl: latest.KubectlDeploy{ 131 Manifests: []string{"deployment.yaml"}, 132 DefaultNamespace: &TestNamespace2, 133 }, 134 commands: testutil. 135 CmdRunOut("kubectl version --client -ojson", KubectlVersion118). 136 AndRunOut("kubectl --context kubecontext --namespace testNamespace2 create --dry-run=client -oyaml -f deployment.yaml", DeploymentWebYAML). 137 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace2 get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 138 AndRun("kubectl --context kubecontext --namespace testNamespace2 apply -f -"), 139 builds: []graph.Artifact{{ 140 ImageName: "leeroy-web", 141 Tag: "leeroy-web:v1", 142 }}, 143 waitForDeletions: true, 144 skipSkaffoldNamespaceOption: true, 145 }, 146 { 147 description: "deploy success (default namespace with env template)", 148 kubectl: latest.KubectlDeploy{ 149 Manifests: []string{"deployment.yaml"}, 150 DefaultNamespace: &TestNamespace2FromEnvTemplate, 151 }, 152 commands: testutil. 153 CmdRunOut("kubectl version --client -ojson", KubectlVersion118). 154 AndRunOut("kubectl --context kubecontext --namespace testNamespace2 create --dry-run=client -oyaml -f deployment.yaml", DeploymentWebYAML). 155 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace2 get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 156 AndRun("kubectl --context kubecontext --namespace testNamespace2 apply -f -"), 157 builds: []graph.Artifact{{ 158 ImageName: "leeroy-web", 159 Tag: "leeroy-web:v1", 160 }}, 161 waitForDeletions: true, 162 skipSkaffoldNamespaceOption: true, 163 envs: map[string]string{ 164 "MYENV": "Namesp", 165 }, 166 }, 167 { 168 description: "http manifest", 169 kubectl: latest.KubectlDeploy{ 170 Manifests: []string{"deployment.yaml", "http://remote.yaml"}, 171 }, 172 commands: testutil. 173 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 174 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml -f http://remote.yaml", DeploymentWebYAML). 175 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 176 AndRun("kubectl --context kubecontext --namespace testNamespace apply -f -"), 177 builds: []graph.Artifact{{ 178 ImageName: "leeroy-web", 179 Tag: "leeroy-web:v1", 180 }}, 181 waitForDeletions: true, 182 }, 183 { 184 description: "deploy command error", 185 kubectl: latest.KubectlDeploy{ 186 Manifests: []string{"deployment.yaml"}, 187 }, 188 commands: testutil. 189 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 190 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 191 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 192 AndRunErr("kubectl --context kubecontext --namespace testNamespace apply -f -", fmt.Errorf("")), 193 builds: []graph.Artifact{{ 194 ImageName: "leeroy-web", 195 Tag: "leeroy-web:v1", 196 }}, 197 shouldErr: true, 198 waitForDeletions: true, 199 }, 200 { 201 description: "additional flags", 202 kubectl: latest.KubectlDeploy{ 203 Manifests: []string{"deployment.yaml"}, 204 Flags: latest.KubectlFlags{ 205 Global: []string{"-v=0"}, 206 Apply: []string{"--overwrite=true"}, 207 Delete: []string{"ignored"}, 208 }, 209 }, 210 commands: testutil. 211 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 212 AndRunOut("kubectl --context kubecontext --namespace testNamespace create -v=0 --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 213 AndRunInputOut("kubectl --context kubecontext --namespace testNamespace get -v=0 -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 214 AndRunErr("kubectl --context kubecontext --namespace testNamespace apply -v=0 --overwrite=true -f -", fmt.Errorf("")), 215 builds: []graph.Artifact{{ 216 ImageName: "leeroy-web", 217 Tag: "leeroy-web:v1", 218 }}, 219 shouldErr: true, 220 waitForDeletions: true, 221 }, 222 } 223 for _, test := range tests { 224 testutil.Run(t, test.description, func(t *testutil.T) { 225 t.SetEnvs(test.envs) 226 t.Override(&util.DefaultExecCommand, test.commands) 227 t.Override(&client.Client, deployutil.MockK8sClient) 228 t.NewTempDir(). 229 Write("deployment.yaml", DeploymentWebYAML). 230 Touch("empty.ignored"). 231 Chdir() 232 233 skaffoldNamespaceOption := "" 234 if !test.skipSkaffoldNamespaceOption { 235 skaffoldNamespaceOption = TestNamespace 236 } 237 238 k, err := NewDeployer(&kubectlConfig{ 239 workingDir: ".", 240 force: test.forceDeploy, 241 waitForDeletions: config.WaitForDeletions{ 242 Enabled: test.waitForDeletions, 243 Delay: 0 * time.Second, 244 Max: 10 * time.Second, 245 }, 246 RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{ 247 Namespace: skaffoldNamespaceOption}}, 248 }, &label.DefaultLabeller{}, &test.kubectl) 249 t.RequireNoError(err) 250 251 err = k.Deploy(context.Background(), ioutil.Discard, test.builds) 252 253 t.CheckError(test.shouldErr, err) 254 }) 255 } 256 } 257 258 func TestKubectlCleanup(t *testing.T) { 259 tests := []struct { 260 description string 261 kubectl latest.KubectlDeploy 262 commands util.Command 263 shouldErr bool 264 dryRun bool 265 }{ 266 { 267 description: "cleanup dry-run", 268 kubectl: latest.KubectlDeploy{ 269 Manifests: []string{"deployment.yaml"}, 270 }, 271 commands: testutil. 272 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 273 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 274 AndRun("kubectl --context kubecontext --namespace testNamespace delete --dry-run --ignore-not-found=true --wait=false -f -"), 275 dryRun: true, 276 }, 277 { 278 description: "cleanup success", 279 kubectl: latest.KubectlDeploy{ 280 Manifests: []string{"deployment.yaml"}, 281 }, 282 commands: testutil. 283 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 284 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 285 AndRun("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true --wait=false -f -"), 286 }, 287 { 288 description: "cleanup success (kubectl v1.18)", 289 kubectl: latest.KubectlDeploy{ 290 Manifests: []string{"deployment.yaml"}, 291 }, 292 commands: testutil. 293 CmdRunOut("kubectl version --client -ojson", KubectlVersion118). 294 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run=client -oyaml -f deployment.yaml", DeploymentWebYAML). 295 AndRun("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true --wait=false -f -"), 296 }, 297 { 298 description: "cleanup error", 299 kubectl: latest.KubectlDeploy{ 300 Manifests: []string{"deployment.yaml"}, 301 }, 302 commands: testutil. 303 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 304 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 305 AndRunErr("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true --wait=false -f -", errors.New("BUG")), 306 shouldErr: true, 307 }, 308 { 309 description: "additional flags", 310 kubectl: latest.KubectlDeploy{ 311 Manifests: []string{"deployment.yaml"}, 312 Flags: latest.KubectlFlags{ 313 Global: []string{"-v=0"}, 314 Apply: []string{"ignored"}, 315 Delete: []string{"--grace-period=1"}, 316 }, 317 }, 318 commands: testutil. 319 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 320 AndRunOut("kubectl --context kubecontext --namespace testNamespace create -v=0 --dry-run -oyaml -f deployment.yaml", DeploymentWebYAML). 321 AndRun("kubectl --context kubecontext --namespace testNamespace delete -v=0 --grace-period=1 --ignore-not-found=true --wait=false -f -"), 322 }, 323 } 324 for _, test := range tests { 325 testutil.Run(t, test.description, func(t *testutil.T) { 326 t.Override(&util.DefaultExecCommand, test.commands) 327 t.NewTempDir(). 328 Write("deployment.yaml", DeploymentWebYAML). 329 Chdir() 330 331 k, err := NewDeployer(&kubectlConfig{ 332 workingDir: ".", 333 RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: TestNamespace}}, 334 }, &label.DefaultLabeller{}, &test.kubectl) 335 t.RequireNoError(err) 336 337 err = k.Cleanup(context.Background(), ioutil.Discard, test.dryRun) 338 339 t.CheckError(test.shouldErr, err) 340 }) 341 } 342 } 343 344 func TestKubectlDeployerRemoteCleanup(t *testing.T) { 345 tests := []struct { 346 description string 347 kubectl latest.KubectlDeploy 348 commands util.Command 349 }{ 350 { 351 description: "cleanup success", 352 kubectl: latest.KubectlDeploy{ 353 RemoteManifests: []string{"pod/leeroy-web"}, 354 }, 355 commands: testutil. 356 CmdRun("kubectl --context kubecontext --namespace testNamespace get pod/leeroy-web -o yaml"). 357 AndRun("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true --wait=false -f -"). 358 AndRunInput("kubectl --context kubecontext --namespace testNamespace apply -f -", DeploymentWebYAML), 359 }, 360 { 361 description: "cleanup error", 362 kubectl: latest.KubectlDeploy{ 363 RemoteManifests: []string{"anotherNamespace:pod/leeroy-web"}, 364 }, 365 commands: testutil. 366 CmdRun("kubectl --context kubecontext --namespace anotherNamespace get pod/leeroy-web -o yaml"). 367 AndRun("kubectl --context kubecontext --namespace testNamespace delete --ignore-not-found=true --wait=false -f -"). 368 AndRunInput("kubectl --context kubecontext --namespace anotherNamespace apply -f -", DeploymentWebYAML), 369 }, 370 } 371 for _, test := range tests { 372 testutil.Run(t, "cleanup remote", func(t *testutil.T) { 373 t.Override(&util.DefaultExecCommand, test.commands) 374 t.NewTempDir(). 375 Write("deployment.yaml", DeploymentWebYAML). 376 Chdir() 377 378 k, err := NewDeployer(&kubectlConfig{ 379 workingDir: ".", 380 RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: TestNamespace}}, 381 }, &label.DefaultLabeller{}, &test.kubectl) 382 t.RequireNoError(err) 383 384 err = k.Cleanup(context.Background(), ioutil.Discard, false) 385 386 t.CheckNoError(err) 387 }) 388 } 389 } 390 391 func TestKubectlRedeploy(t *testing.T) { 392 testutil.Run(t, "", func(t *testutil.T) { 393 t.Override(&client.Client, deployutil.MockK8sClient) 394 tmpDir := t.NewTempDir(). 395 Write("deployment-web.yaml", DeploymentWebYAML). 396 Write("deployment-app.yaml", DeploymentAppYAML) 397 398 t.Override(&util.DefaultExecCommand, testutil. 399 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 400 AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), DeploymentAppYAML+"\n"+DeploymentWebYAML). 401 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentAppYAMLv1+"\n---\n"+DeploymentWebYAMLv1, ""). 402 AndRunInput("kubectl --context kubecontext apply -f -", DeploymentAppYAMLv1+"\n---\n"+DeploymentWebYAMLv1). 403 AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), DeploymentAppYAML+"\n"+DeploymentWebYAML). 404 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentAppYAMLv2+"\n---\n"+DeploymentWebYAMLv1, ""). 405 AndRunInput("kubectl --context kubecontext apply -f -", DeploymentAppYAMLv2). 406 AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-app.yaml")+" -f "+tmpDir.Path("deployment-web.yaml"), DeploymentAppYAML+"\n"+DeploymentWebYAML). 407 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentAppYAMLv2+"\n---\n"+DeploymentWebYAMLv1, ""), 408 ) 409 410 deployer, err := NewDeployer(&kubectlConfig{ 411 workingDir: ".", 412 waitForDeletions: config.WaitForDeletions{ 413 Enabled: true, 414 Delay: 0 * time.Millisecond, 415 Max: 10 * time.Second}, 416 }, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-app.yaml"), tmpDir.Path("deployment-web.yaml")}}) 417 t.RequireNoError(err) 418 419 // Deploy one manifest 420 err = deployer.Deploy(context.Background(), ioutil.Discard, []graph.Artifact{ 421 {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, 422 {ImageName: "leeroy-app", Tag: "leeroy-app:v1"}, 423 }) 424 t.CheckNoError(err) 425 426 // Deploy one manifest since only one image is updated 427 err = deployer.Deploy(context.Background(), ioutil.Discard, []graph.Artifact{ 428 {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, 429 {ImageName: "leeroy-app", Tag: "leeroy-app:v2"}, 430 }) 431 t.CheckNoError(err) 432 433 // Deploy zero manifest since no image is updated 434 err = deployer.Deploy(context.Background(), ioutil.Discard, []graph.Artifact{ 435 {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, 436 {ImageName: "leeroy-app", Tag: "leeroy-app:v2"}, 437 }) 438 t.CheckNoError(err) 439 }) 440 } 441 442 func TestKubectlWaitForDeletions(t *testing.T) { 443 testutil.Run(t, "", func(t *testutil.T) { 444 t.Override(&client.Client, deployutil.MockK8sClient) 445 tmpDir := t.NewTempDir().Write("deployment-web.yaml", DeploymentWebYAML) 446 447 t.Override(&util.DefaultExecCommand, testutil. 448 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 449 AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-web.yaml"), DeploymentWebYAML). 450 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, `{ 451 "items":[ 452 {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, 453 {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-app"}}, 454 {"metadata":{"name":"leeroy-front"}} 455 ] 456 }`). 457 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, `{ 458 "items":[ 459 {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, 460 {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-app"}}, 461 {"metadata":{"name":"leeroy-front"}} 462 ] 463 }`). 464 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, `{ 465 "items":[ 466 {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, 467 {"metadata":{"name":"leeroy-front"}} 468 ] 469 }`). 470 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, ""). 471 AndRunInput("kubectl --context kubecontext apply -f -", DeploymentWebYAMLv1), 472 ) 473 474 deployer, err := NewDeployer(&kubectlConfig{ 475 workingDir: tmpDir.Root(), 476 waitForDeletions: config.WaitForDeletions{ 477 Enabled: true, 478 Delay: 0 * time.Millisecond, 479 Max: 10 * time.Second, 480 }, 481 }, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-web.yaml")}}) 482 t.RequireNoError(err) 483 484 var out bytes.Buffer 485 err = deployer.Deploy(context.Background(), &out, []graph.Artifact{ 486 {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, 487 }) 488 489 t.CheckNoError(err) 490 t.CheckDeepEqual(` - 2 resources are marked for deletion, waiting for completion: "leeroy-web", "leeroy-app" 491 - "leeroy-web" is marked for deletion, waiting for completion 492 `, out.String()) 493 }) 494 } 495 496 func TestKubectlWaitForDeletionsFails(t *testing.T) { 497 testutil.Run(t, "", func(t *testutil.T) { 498 tmpDir := t.NewTempDir().Write("deployment-web.yaml", DeploymentWebYAML) 499 500 t.Override(&client.Client, deployutil.MockK8sClient) 501 t.Override(&util.DefaultExecCommand, testutil. 502 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 503 AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment-web.yaml"), DeploymentWebYAML). 504 AndRunInputOut("kubectl --context kubecontext get -f - --ignore-not-found -ojson", DeploymentWebYAMLv1, `{ 505 "items":[ 506 {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-web"}}, 507 {"metadata":{"deletionTimestamp":"2020-07-24T12:40:32Z","name":"leeroy-app"}} 508 ] 509 }`), 510 ) 511 512 deployer, err := NewDeployer(&kubectlConfig{ 513 workingDir: tmpDir.Root(), 514 waitForDeletions: config.WaitForDeletions{ 515 Enabled: true, 516 Delay: 10 * time.Second, 517 Max: 100 * time.Millisecond, 518 }, 519 }, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: []string{tmpDir.Path("deployment-web.yaml")}}) 520 t.RequireNoError(err) 521 522 err = deployer.Deploy(context.Background(), ioutil.Discard, []graph.Artifact{ 523 {ImageName: "leeroy-web", Tag: "leeroy-web:v1"}, 524 }) 525 526 t.CheckErrorContains(`2 resources failed to complete their deletion before a new deployment: "leeroy-web", "leeroy-app"`, err) 527 }) 528 } 529 530 func TestDependencies(t *testing.T) { 531 tests := []struct { 532 description string 533 manifests []string 534 expected []string 535 }{ 536 { 537 description: "no manifest", 538 manifests: []string(nil), 539 expected: []string(nil), 540 }, 541 { 542 description: "missing manifest file", 543 manifests: []string{"missing.yaml"}, 544 expected: []string(nil), 545 }, 546 { 547 description: "ignore non-manifest", 548 manifests: []string{"*.ignored"}, 549 expected: []string(nil), 550 }, 551 { 552 description: "single manifest", 553 manifests: []string{"deployment.yaml"}, 554 expected: []string{"deployment.yaml"}, 555 }, 556 { 557 description: "keep manifests order", 558 manifests: []string{"01_name.yaml", "00_service.yaml"}, 559 expected: []string{"01_name.yaml", "00_service.yaml"}, 560 }, 561 { 562 description: "sort children", 563 manifests: []string{"01/*.yaml", "00/*.yaml"}, 564 expected: []string{filepath.Join("01", "a.yaml"), filepath.Join("01", "b.yaml"), filepath.Join("00", "a.yaml"), filepath.Join("00", "b.yaml")}, 565 }, 566 { 567 description: "http manifest", 568 manifests: []string{"deployment.yaml", "http://remote.yaml"}, 569 expected: []string{"deployment.yaml"}, 570 }, 571 } 572 for _, test := range tests { 573 testutil.Run(t, test.description, func(t *testutil.T) { 574 t.NewTempDir(). 575 Touch("deployment.yaml", "01_name.yaml", "00_service.yaml", "empty.ignored"). 576 Touch("01/a.yaml", "01/b.yaml"). 577 Touch("00/b.yaml", "00/a.yaml"). 578 Chdir() 579 580 k, err := NewDeployer(&kubectlConfig{}, &label.DefaultLabeller{}, &latest.KubectlDeploy{Manifests: test.manifests}) 581 t.RequireNoError(err) 582 583 dependencies, err := k.Dependencies() 584 585 t.CheckNoError(err) 586 t.CheckDeepEqual(test.expected, dependencies) 587 }) 588 } 589 } 590 591 func TestKubectlRender(t *testing.T) { 592 tests := []struct { 593 description string 594 builds []graph.Artifact 595 input string 596 expected string 597 }{ 598 { 599 description: "normal render", 600 builds: []graph.Artifact{ 601 { 602 ImageName: "gcr.io/k8s-skaffold/skaffold", 603 Tag: "gcr.io/k8s-skaffold/skaffold:test", 604 }, 605 }, 606 input: `apiVersion: v1 607 kind: Pod 608 metadata: 609 namespace: default 610 spec: 611 containers: 612 - image: gcr.io/k8s-skaffold/skaffold 613 name: skaffold 614 `, 615 expected: `apiVersion: v1 616 kind: Pod 617 metadata: 618 namespace: default 619 spec: 620 containers: 621 - image: gcr.io/k8s-skaffold/skaffold:test 622 name: skaffold 623 `, 624 }, 625 { 626 description: "two artifacts", 627 builds: []graph.Artifact{ 628 { 629 ImageName: "gcr.io/project/image1", 630 Tag: "gcr.io/project/image1:tag1", 631 }, 632 { 633 ImageName: "gcr.io/project/image2", 634 Tag: "gcr.io/project/image2:tag2", 635 }, 636 }, 637 input: `apiVersion: v1 638 kind: Pod 639 metadata: 640 namespace: default 641 spec: 642 containers: 643 - image: gcr.io/project/image1 644 name: image1 645 - image: gcr.io/project/image2 646 name: image2 647 `, 648 expected: `apiVersion: v1 649 kind: Pod 650 metadata: 651 namespace: default 652 spec: 653 containers: 654 - image: gcr.io/project/image1:tag1 655 name: image1 656 - image: gcr.io/project/image2:tag2 657 name: image2 658 `, 659 }, 660 { 661 description: "no artifacts", 662 builds: nil, 663 input: `apiVersion: v1 664 kind: Pod 665 metadata: 666 namespace: default 667 spec: 668 containers: 669 - image: image1:tag1 670 name: image1 671 - image: image2:tag2 672 name: image2 673 `, 674 expected: `apiVersion: v1 675 kind: Pod 676 metadata: 677 namespace: default 678 spec: 679 containers: 680 - image: gcr.io/project/image1:tag1 681 name: image1 682 - image: gcr.io/project/image2:tag2 683 name: image2 684 `, 685 }, 686 } 687 for _, test := range tests { 688 testutil.Run(t, test.description, func(t *testutil.T) { 689 tmpDir := t.NewTempDir().Write("deployment.yaml", test.input) 690 t.Override(&util.DefaultExecCommand, testutil. 691 CmdRunOut("kubectl version --client -ojson", KubectlVersion112). 692 AndRunOut("kubectl --context kubecontext create --dry-run -oyaml -f "+tmpDir.Path("deployment.yaml"), test.input)) 693 deployer, err := NewDeployer(&kubectlConfig{ 694 workingDir: ".", 695 defaultRepo: "gcr.io/project", 696 multiLevelRepo: util.BoolPtr(true), 697 }, &label.DefaultLabeller{}, &latest.KubectlDeploy{ 698 Manifests: []string{tmpDir.Path("deployment.yaml")}, 699 }) 700 t.RequireNoError(err) 701 var b bytes.Buffer 702 err = deployer.Render(context.Background(), &b, test.builds, true, "") 703 t.CheckNoError(err) 704 t.CheckDeepEqual(test.expected, b.String(), testutil.YamlObj(t.T)) 705 }) 706 } 707 } 708 709 func TestGCSManifests(t *testing.T) { 710 tests := []struct { 711 description string 712 kubectl latest.KubectlDeploy 713 commands util.Command 714 shouldErr bool 715 skipRender bool 716 }{ 717 { 718 description: "manifest from GCS", 719 kubectl: latest.KubectlDeploy{ 720 Manifests: []string{"gs://dev/deployment.yaml"}, 721 }, 722 commands: testutil. 723 CmdRunOut(fmt.Sprintf("gsutil cp -r %s %s", "gs://dev/deployment.yaml", manifest.ManifestTmpDir), "log"). 724 AndRunOut("kubectl version --client -ojson", KubectlVersion112). 725 AndRunOut("kubectl --context kubecontext --namespace testNamespace create --dry-run -oyaml -f "+filepath.Join(manifest.ManifestTmpDir, "deployment.yaml"), DeploymentWebYAML). 726 AndRun("kubectl --context kubecontext --namespace testNamespace apply -f -"), 727 skipRender: true, 728 }} 729 for _, test := range tests { 730 testutil.Run(t, test.description, func(t *testutil.T) { 731 t.Override(&client.Client, deployutil.MockK8sClient) 732 t.Override(&util.DefaultExecCommand, test.commands) 733 if err := os.MkdirAll(manifest.ManifestTmpDir, os.ModePerm); err != nil { 734 t.Fatal(err) 735 } 736 if err := ioutil.WriteFile(manifest.ManifestTmpDir+"/deployment.yaml", []byte(DeploymentWebYAML), os.ModePerm); err != nil { 737 t.Fatal(err) 738 } 739 k, err := NewDeployer(&kubectlConfig{ 740 workingDir: ".", 741 skipRender: test.skipRender, 742 RunContext: runcontext.RunContext{Opts: config.SkaffoldOptions{Namespace: TestNamespace}}, 743 }, &label.DefaultLabeller{}, &test.kubectl) 744 t.RequireNoError(err) 745 746 err = k.Deploy(context.Background(), ioutil.Discard, nil) 747 748 t.CheckError(test.shouldErr, err) 749 }) 750 } 751 } 752 753 func TestHasRunnableHooks(t *testing.T) { 754 tests := []struct { 755 description string 756 cfg latest.KubectlDeploy 757 expected bool 758 }{ 759 { 760 description: "no hooks defined", 761 cfg: latest.KubectlDeploy{}, 762 }, 763 { 764 description: "has pre-deploy hook defined", 765 cfg: latest.KubectlDeploy{ 766 LifecycleHooks: latest.DeployHooks{PreHooks: []latest.DeployHookItem{{}}}, 767 }, 768 expected: true, 769 }, 770 { 771 description: "has post-deploy hook defined", 772 cfg: latest.KubectlDeploy{ 773 LifecycleHooks: latest.DeployHooks{PostHooks: []latest.DeployHookItem{{}}}, 774 }, 775 expected: true, 776 }, 777 } 778 for _, test := range tests { 779 testutil.Run(t, test.description, func(t *testutil.T) { 780 k, err := NewDeployer(&kubectlConfig{}, &label.DefaultLabeller{}, &test.cfg) 781 t.RequireNoError(err) 782 actual := k.HasRunnableHooks() 783 t.CheckDeepEqual(test.expected, actual) 784 }) 785 } 786 } 787 788 type kubectlConfig struct { 789 runcontext.RunContext // Embedded to provide the default values. 790 workingDir string 791 defaultRepo string 792 multiLevelRepo *bool 793 skipRender bool 794 force bool 795 waitForDeletions config.WaitForDeletions 796 } 797 798 func (c *kubectlConfig) GetKubeContext() string { return "kubecontext" } 799 func (c *kubectlConfig) GetKubeNamespace() string { return c.Opts.Namespace } 800 func (c *kubectlConfig) WorkingDir() string { return c.workingDir } 801 func (c *kubectlConfig) SkipRender() bool { return c.skipRender } 802 func (c *kubectlConfig) ForceDeploy() bool { return c.force } 803 func (c *kubectlConfig) DefaultRepo() *string { return &c.defaultRepo } 804 func (c *kubectlConfig) MultiLevelRepo() *bool { return c.multiLevelRepo } 805 func (c *kubectlConfig) WaitForDeletions() config.WaitForDeletions { return c.waitForDeletions } 806 func (c *kubectlConfig) PortForwardResources() []*latest.PortForwardResource { return nil }