github.com/GoogleContainerTools/skaffold@v1.39.18/pkg/skaffold/deploy/kpt/kpt_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 kpt 18 19 import ( 20 "bytes" 21 "context" 22 "errors" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "os" 27 "strings" 28 "testing" 29 30 "github.com/GoogleContainerTools/skaffold/pkg/skaffold/deploy/kubectl" 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 const ( 43 testPod = `apiVersion: v1 44 kind: Pod 45 metadata: 46 namespace: default 47 spec: 48 containers: 49 - image: gcr.io/project/image1 50 name: image1 51 ` 52 ) 53 54 // Test that kpt deployer manipulate manifests in the given order and no intermediate data is 55 // stored after each step: 56 // Step 1. `kpt fn source` (read in the manifest as stdin), 57 // Step 2. `kpt fn run` (validate, transform or generate the manifests via kpt functions), 58 // Step 3. `kpt fn sink` (to temp dir to run kuustomize build on), 59 // Step 4. `kustomize build` (if the temp dir from step 3 has a Kustomization hydrate the manifest), 60 // Step 5. `kpt fn sink` (store the stdout in a given dir). 61 62 func TestKpt_Deploy(t *testing.T) { 63 sanityCheck = func(ctx context.Context, dir string, buf io.Writer) error { return nil } 64 tests := []struct { 65 description string 66 builds []graph.Artifact 67 kpt latest.KptDeploy 68 hasKustomization func(string) bool 69 commands util.Command 70 expected []string 71 shouldErr bool 72 }{ 73 { 74 description: "no manifest", 75 kpt: latest.KptDeploy{ 76 Dir: ".", 77 }, 78 commands: testutil. 79 CmdRunOut("kpt fn source .", ``). 80 AndRunOut("kpt fn run", ``). 81 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``), 82 }, 83 { 84 description: "invalid manifest", 85 kpt: latest.KptDeploy{ 86 Dir: ".", 87 }, 88 commands: testutil. 89 CmdRunOut("kpt fn source .", ``). 90 AndRunOut("kpt fn run", `foo`). 91 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 92 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 93 shouldErr: true, 94 }, 95 { 96 description: "invalid user specified applyDir", 97 kpt: latest.KptDeploy{ 98 Dir: ".", 99 Live: latest.KptLive{ 100 Apply: latest.KptApplyInventory{ 101 Dir: "invalid_path", 102 }, 103 }, 104 }, 105 commands: testutil. 106 CmdRunOut("kpt fn source .", ``). 107 AndRunOut("kpt fn run", testPod). 108 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 109 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 110 shouldErr: true, 111 }, 112 113 { 114 description: "kustomization and specified kpt fn", 115 kpt: latest.KptDeploy{ 116 Dir: ".", 117 Fn: latest.KptFn{FnPath: "kpt-func.yaml"}, 118 Live: latest.KptLive{ 119 Apply: latest.KptApplyInventory{ 120 Dir: "valid_path", 121 }, 122 }, 123 }, 124 hasKustomization: func(dir string) bool { return dir == tmpKustomizeDir }, 125 commands: testutil. 126 CmdRunOut("kpt fn source .", ``). 127 AndRunOut("kpt fn run --fn-path kpt-func.yaml", testPod). 128 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 129 AndRunOut(fmt.Sprintf("kustomize build %v", tmpKustomizeDir), ``). 130 AndRun("kpt live apply valid_path --context kubecontext --namespace testNamespace"), 131 expected: []string{"default"}, 132 }, 133 { 134 description: "kpt live apply fails", 135 kpt: latest.KptDeploy{ 136 Dir: ".", 137 }, 138 commands: testutil. 139 CmdRunOut("kpt fn source .", ``). 140 AndRunOut("kpt fn run", testPod). 141 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 142 AndRunOut("kpt live init .kpt-hydrated --context kubecontext --namespace testNamespace", ``). 143 AndRunOut("kpt fn sink .kpt-hydrated", ``). 144 AndRunErr("kpt live apply .kpt-hydrated --context kubecontext --namespace testNamespace", errors.New("BUG")), 145 shouldErr: true, 146 }, 147 { 148 description: "user specifies reconcile timeout and poll period", 149 kpt: latest.KptDeploy{ 150 Dir: ".", 151 Live: latest.KptLive{ 152 Apply: latest.KptApplyInventory{ 153 Dir: "valid_path", 154 }, 155 Options: latest.KptApplyOptions{ 156 PollPeriod: "5s", 157 ReconcileTimeout: "2m", 158 }, 159 }, 160 }, 161 commands: testutil. 162 CmdRunOut("kpt fn source .", ``). 163 AndRunOut("kpt fn run", testPod). 164 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 165 AndRunOut("kpt fn sink valid_path", ``). 166 AndRun("kpt live apply valid_path --poll-period 5s --reconcile-timeout 2m --context kubecontext --namespace testNamespace"), 167 }, 168 { 169 description: "user specifies invalid reconcile timeout and poll period", 170 kpt: latest.KptDeploy{ 171 Dir: ".", 172 Live: latest.KptLive{ 173 Apply: latest.KptApplyInventory{ 174 Dir: "valid_path", 175 }, 176 Options: latest.KptApplyOptions{ 177 PollPeriod: "foo", 178 ReconcileTimeout: "bar", 179 }, 180 }, 181 }, 182 commands: testutil. 183 CmdRunOut("kpt fn source .", ``). 184 AndRunOut("kpt fn run", testPod). 185 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 186 AndRunOut("kpt fn sink valid_path", ``). 187 AndRun("kpt live apply valid_path --poll-period foo --reconcile-timeout bar --context kubecontext --namespace testNamespace"), 188 }, 189 { 190 description: "user specifies prune propagation policy and prune timeout", 191 kpt: latest.KptDeploy{ 192 Dir: ".", 193 Live: latest.KptLive{ 194 Apply: latest.KptApplyInventory{ 195 Dir: "valid_path", 196 }, 197 Options: latest.KptApplyOptions{ 198 PrunePropagationPolicy: "Orphan", 199 PruneTimeout: "2m", 200 }, 201 }, 202 }, 203 commands: testutil. 204 CmdRunOut("kpt fn source .", ``). 205 AndRunOut("kpt fn run", testPod). 206 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 207 AndRunOut("kpt fn sink valid_path", ``). 208 AndRun("kpt live apply valid_path --prune-propagation-policy Orphan --prune-timeout 2m --context kubecontext --namespace testNamespace"), 209 }, 210 { 211 description: "user specifies invalid prune propagation policy and prune timeout", 212 kpt: latest.KptDeploy{ 213 Dir: ".", 214 Live: latest.KptLive{ 215 Apply: latest.KptApplyInventory{ 216 Dir: "valid_path", 217 }, 218 Options: latest.KptApplyOptions{ 219 PrunePropagationPolicy: "foo", 220 PruneTimeout: "bar", 221 }, 222 }, 223 }, 224 commands: testutil. 225 CmdRunOut("kpt fn source .", ``). 226 AndRunOut("kpt fn run", testPod). 227 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 228 AndRunOut("kpt fn sink valid_path", ``). 229 AndRun("kpt live apply valid_path --prune-propagation-policy foo --prune-timeout bar --context kubecontext --namespace testNamespace"), 230 }, 231 } 232 for _, test := range tests { 233 testutil.Run(t, test.description, func(t *testutil.T) { 234 t.Override(&util.DefaultExecCommand, test.commands) 235 t.Override(&client.Client, deployutil.MockK8sClient) 236 t.NewTempDir().Chdir() 237 238 var err error 239 k, err := NewDeployer(&kptConfig{}, &label.DefaultLabeller{}, &test.kpt) 240 if err != nil { 241 t.Fatalf("unexpected error occurred attempting to create new kpt deployer") 242 } 243 if test.hasKustomization != nil { 244 k.hasKustomization = test.hasKustomization 245 } 246 247 if k.Live.Apply.Dir == "valid_path" { 248 // 0755 is a permission setting where the owner can read, write, and execute. 249 // Others can read and execute but not modify the directory. 250 t.CheckNoError(os.Mkdir(k.Live.Apply.Dir, 0755)) 251 } 252 253 err = k.Deploy(context.Background(), ioutil.Discard, test.builds) 254 t.CheckError(test.shouldErr, err) 255 }) 256 } 257 } 258 259 func TestKpt_Dependencies(t *testing.T) { 260 tests := []struct { 261 description string 262 kpt latest.KptDeploy 263 createFiles map[string]string 264 kustomizations map[string]string 265 expected []string 266 shouldErr bool 267 }{ 268 { 269 description: "bad dir", 270 kpt: latest.KptDeploy{ 271 Dir: "invalid_path", 272 }, 273 shouldErr: true, 274 }, 275 { 276 description: "empty dir and unspecified fnPath", 277 kpt: latest.KptDeploy{ 278 Dir: ".", 279 }, 280 }, 281 { 282 description: "dir", 283 kpt: latest.KptDeploy{ 284 Dir: ".", 285 }, 286 createFiles: map[string]string{ 287 "foo.yaml": "", 288 "README.md": "", 289 }, 290 expected: []string{"foo.yaml"}, 291 }, 292 { 293 description: "dir with subdirs and file path variants", 294 kpt: latest.KptDeploy{ 295 Dir: ".", 296 }, 297 createFiles: map[string]string{ 298 "food.yml": "", 299 "foo/bar.yaml": "", 300 "foo/bat//bad.yml": "", 301 "foo/bat\\README.md": "", 302 }, 303 expected: []string{"foo/bar.yaml", "foo/bat/bad.yml", "food.yml"}, 304 }, 305 { 306 description: "fnpath inside directory", 307 kpt: latest.KptDeploy{ 308 Dir: ".", 309 Fn: latest.KptFn{FnPath: "."}, 310 }, 311 createFiles: map[string]string{ 312 "./kpt-func.yaml": "", 313 }, 314 expected: []string{"kpt-func.yaml"}, 315 }, 316 { 317 description: "fnpath outside directory", 318 kpt: latest.KptDeploy{ 319 Dir: "./config", 320 Fn: latest.KptFn{FnPath: "./kpt-fn"}, 321 }, 322 createFiles: map[string]string{ 323 "./config/deployment.yaml": "", 324 "./kpt-fn/kpt-func.yaml": "", 325 }, 326 expected: []string{"config/deployment.yaml", "kpt-fn/kpt-func.yaml"}, 327 }, 328 329 { 330 description: "fnpath and dir and kustomization", 331 kpt: latest.KptDeploy{ 332 Dir: ".", 333 Fn: latest.KptFn{FnPath: "./kpt-fn"}, 334 }, 335 createFiles: map[string]string{ 336 "./kpt-fn/func.yaml": "", 337 }, 338 kustomizations: map[string]string{"kustomization.yaml": `configMapGenerator: 339 - files: [app1.properties]`}, 340 expected: []string{"app1.properties", "kpt-fn/func.yaml", "kustomization.yaml"}, 341 }, 342 { 343 description: "dependencies that can only be detected as a kustomization", 344 kpt: latest.KptDeploy{ 345 Dir: ".", 346 }, 347 kustomizations: map[string]string{"kustomization.yaml": `configMapGenerator: 348 - files: [app1.properties]`}, 349 expected: []string{"app1.properties", "kustomization.yaml"}, 350 }, 351 { 352 description: "kustomization.yml variant", 353 kpt: latest.KptDeploy{ 354 Dir: ".", 355 }, 356 kustomizations: map[string]string{"kustomization.yml": `configMapGenerator: 357 - files: [app1.properties]`}, 358 expected: []string{"app1.properties", "kustomization.yml"}, 359 }, 360 { 361 description: "Kustomization variant", 362 kpt: latest.KptDeploy{ 363 Dir: ".", 364 }, 365 kustomizations: map[string]string{"Kustomization": `configMapGenerator: 366 - files: [app1.properties]`}, 367 expected: []string{"Kustomization", "app1.properties"}, 368 }, 369 { 370 description: "incorrectly named kustomization", 371 kpt: latest.KptDeploy{ 372 Dir: ".", 373 }, 374 kustomizations: map[string]string{"customization": `configMapGenerator: 375 - files: [app1.properties]`}, 376 }, 377 } 378 for _, test := range tests { 379 testutil.Run(t, test.description, func(t *testutil.T) { 380 tmpDir := t.NewTempDir().Chdir() 381 382 tmpDir.WriteFiles(test.createFiles) 383 tmpDir.WriteFiles(test.kustomizations) 384 385 k, err := NewDeployer(&kptConfig{}, &label.DefaultLabeller{}, &test.kpt) 386 if err != nil { 387 t.Fatalf("unexpected error occurred in NewDeployer: %v", err) 388 } 389 390 res, err := k.Dependencies() 391 392 t.CheckErrorAndDeepEqual(test.shouldErr, err, tmpDir.Paths(test.expected...), tmpDir.Paths(res...)) 393 }) 394 } 395 } 396 397 func TestKpt_Cleanup(t *testing.T) { 398 tests := []struct { 399 description string 400 applyDir string 401 globalFlags []string 402 commands util.Command 403 shouldErr bool 404 }{ 405 { 406 description: "invalid user specified applyDir", 407 applyDir: "invalid_path", 408 shouldErr: true, 409 }, 410 { 411 description: "valid user specified applyDir w/o template resource", 412 applyDir: "valid_path", 413 commands: testutil.CmdRunErr("kpt live destroy valid_path --context kubecontext --namespace testNamespace", errors.New("BUG")), 414 shouldErr: true, 415 }, 416 { 417 description: "valid user specified applyDir w/ template resource (emulated)", 418 applyDir: "valid_path", 419 commands: testutil.CmdRun("kpt live destroy valid_path --context kubecontext --namespace testNamespace"), 420 }, 421 { 422 description: "unspecified applyDir", 423 commands: testutil. 424 CmdRunOut("kpt live init .kpt-hydrated --context kubecontext --namespace testNamespace", ""). 425 AndRun("kpt live destroy .kpt-hydrated --context kubecontext --namespace testNamespace"), 426 }, 427 } 428 for _, test := range tests { 429 testutil.Run(t, test.description, func(t *testutil.T) { 430 t.Override(&util.DefaultExecCommand, test.commands) 431 t.NewTempDir().Chdir() 432 433 if test.applyDir == "valid_path" { 434 // 0755 is a permission setting where the owner can read, write, and execute. 435 // Others can read and execute but not modify the directory. 436 t.CheckNoError(os.Mkdir(test.applyDir, 0755)) 437 } 438 439 k, err := NewDeployer(&kptConfig{ 440 workingDir: ".", 441 }, &label.DefaultLabeller{}, &latest.KptDeploy{ 442 Live: latest.KptLive{ 443 Apply: latest.KptApplyInventory{ 444 Dir: test.applyDir, 445 }, 446 }, 447 }) 448 if err != nil { 449 t.Fatalf("unexpected error occurred in NewDeployer: %v", err) 450 } 451 452 err = k.Cleanup(context.Background(), ioutil.Discard, false) 453 454 t.CheckError(test.shouldErr, err) 455 }) 456 } 457 } 458 459 func TestKpt_Render(t *testing.T) { 460 sanityCheck = func(ctx context.Context, dir string, buf io.Writer) error { return nil } 461 // The follow are outputs to `kpt fn run` commands. 462 output1 := `apiVersion: v1 463 kind: Pod 464 metadata: 465 namespace: default 466 spec: 467 containers: 468 - image: gcr.io/project/image1 469 name: image1 470 ` 471 472 output2 := `apiVersion: v1 473 kind: Pod 474 metadata: 475 namespace: default 476 spec: 477 containers: 478 - image: gcr.io/project/image1 479 name: image1 480 - image: gcr.io/project/image2 481 name: image2 482 ` 483 484 output3 := `apiVersion: v1 485 kind: Pod 486 metadata: 487 namespace: default 488 spec: 489 containers: 490 - image: gcr.io/project/image1 491 name: image1 492 --- 493 apiVersion: v1 494 kind: Pod 495 metadata: 496 namespace: default 497 spec: 498 containers: 499 - image: gcr.io/project/image2 500 name: image2 501 ` 502 503 tests := []struct { 504 description string 505 builds []graph.Artifact 506 labels []string 507 kpt latest.KptDeploy 508 commands util.Command 509 hasKustomization func(string) bool 510 expected string 511 shouldErr bool 512 }{ 513 { 514 description: "no fnPath or image specified", 515 builds: []graph.Artifact{ 516 { 517 ImageName: "gcr.io/project/image1", 518 Tag: "gcr.io/project/image1:tag1", 519 }, 520 }, 521 kpt: latest.KptDeploy{ 522 Dir: ".", 523 }, 524 commands: testutil. 525 CmdRunOut("kpt fn source .", ``). 526 AndRunOut("kpt fn run", output1). 527 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 528 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 529 expected: `apiVersion: v1 530 kind: Pod 531 metadata: 532 namespace: default 533 spec: 534 containers: 535 - image: gcr.io/project/image1:tag1 536 name: image1 537 `, 538 }, 539 { 540 description: "fnPath specified, multiple resources, and labels", 541 builds: []graph.Artifact{ 542 { 543 ImageName: "gcr.io/project/image1", 544 Tag: "gcr.io/project/image1:tag1", 545 }, 546 { 547 ImageName: "gcr.io/project/image2", 548 Tag: "gcr.io/project/image2:tag2", 549 }, 550 }, 551 labels: []string{"user/label=test"}, 552 kpt: latest.KptDeploy{ 553 Dir: "test", 554 Fn: latest.KptFn{FnPath: "kpt-func.yaml"}, 555 }, 556 commands: testutil. 557 CmdRunOut("kpt fn source test", ``). 558 AndRunOut("kpt fn run --fn-path kpt-func.yaml", output3). 559 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 560 AndRunOut("kpt fn sink .tmp-sink-dir/test", ``), 561 expected: `apiVersion: v1 562 kind: Pod 563 metadata: 564 labels: 565 user/label: test 566 namespace: default 567 spec: 568 containers: 569 - image: gcr.io/project/image1:tag1 570 name: image1 571 --- 572 apiVersion: v1 573 kind: Pod 574 metadata: 575 labels: 576 user/label: test 577 namespace: default 578 spec: 579 containers: 580 - image: gcr.io/project/image2:tag2 581 name: image2 582 `, 583 }, 584 { 585 description: "fn image specified, multiple images in resource", 586 builds: []graph.Artifact{ 587 { 588 ImageName: "gcr.io/project/image1", 589 Tag: "gcr.io/project/image1:tag1", 590 }, 591 { 592 ImageName: "gcr.io/project/image2", 593 Tag: "gcr.io/project/image2:tag2", 594 }, 595 }, 596 kpt: latest.KptDeploy{ 597 Dir: ".", 598 Fn: latest.KptFn{Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar"}, 599 }, 600 commands: testutil. 601 CmdRunOut("kpt fn source .", ``). 602 AndRunOut("kpt fn run --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", output2). 603 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 604 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 605 expected: `apiVersion: v1 606 kind: Pod 607 metadata: 608 namespace: default 609 spec: 610 containers: 611 - image: gcr.io/project/image1:tag1 612 name: image1 613 - image: gcr.io/project/image2:tag2 614 name: image2 615 `, 616 }, 617 { 618 description: "empty output from pipeline", 619 builds: []graph.Artifact{ 620 { 621 ImageName: "gcr.io/project/image1", 622 Tag: "gcr.io/project/image1:tag1", 623 }, 624 }, 625 labels: []string{"user/label=test"}, 626 kpt: latest.KptDeploy{ 627 Dir: ".", 628 }, 629 commands: testutil. 630 CmdRunOut("kpt fn source .", ``). 631 AndRunOut("kpt fn run", ``). 632 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 633 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 634 expected: "\n", 635 }, 636 { 637 description: "both fnPath and image specified", 638 kpt: latest.KptDeploy{ 639 Dir: ".", 640 Fn: latest.KptFn{ 641 FnPath: "kpt-func.yaml", 642 Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar"}, 643 }, 644 commands: testutil. 645 CmdRunOut("kpt fn source .", ``). 646 AndRunOut("kpt fn source kpt-func.yaml", ``). 647 AndRunOut("kpt fn run --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``). 648 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 649 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 650 shouldErr: true, 651 }, 652 { 653 description: "kustomization render", 654 builds: []graph.Artifact{ 655 { 656 ImageName: "gcr.io/project/image1", 657 Tag: "gcr.io/project/image1:tag1", 658 }, 659 }, 660 kpt: latest.KptDeploy{ 661 Dir: ".", 662 }, 663 commands: testutil. 664 CmdRunOut("kpt fn source .", ``). 665 AndRunOut("kpt fn run", ``). 666 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 667 AndRunOut(fmt.Sprintf("kustomize build %v", tmpKustomizeDir), output1), 668 hasKustomization: func(dir string) bool { return dir == tmpKustomizeDir }, 669 expected: `apiVersion: v1 670 kind: Pod 671 metadata: 672 namespace: default 673 spec: 674 containers: 675 - image: gcr.io/project/image1:tag1 676 name: image1 677 `, 678 }, 679 { 680 description: "reading configs from sourceDir fails", 681 kpt: latest.KptDeploy{ 682 Dir: ".", 683 }, 684 commands: testutil. 685 CmdRunOutErr("kpt fn source .", ``, errors.New("BUG")). 686 AndRunOut("kpt fn run", "invalid pipeline"). 687 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 688 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 689 shouldErr: true, 690 }, 691 { 692 description: "outputting configs to sinkDir fails", 693 kpt: latest.KptDeploy{ 694 Dir: ".", 695 }, 696 commands: testutil. 697 CmdRunOut("kpt fn source .", ``). 698 AndRunOut("kpt fn run", "invalid pipeline"). 699 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 700 AndRunOutErr("kpt fn sink .tmp-sink-dir", ``, errors.New("BUG")), 701 shouldErr: true, 702 }, 703 { 704 description: "kustomize build fails (invalid kustomization config)", 705 builds: []graph.Artifact{ 706 { 707 ImageName: "gcr.io/project/image1", 708 Tag: "gcr.io/project/image1:tag1", 709 }, 710 }, 711 kpt: latest.KptDeploy{ 712 Dir: ".", 713 }, 714 commands: testutil. 715 CmdRunOut("kpt fn source .", ``). 716 AndRunOut("kpt fn run", output1). 717 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 718 AndRunOutErr(fmt.Sprintf("kustomize build %v", tmpKustomizeDir), ``, errors.New("BUG")), 719 hasKustomization: func(dir string) bool { return dir == tmpKustomizeDir }, 720 shouldErr: true, 721 }, 722 { 723 description: "kpt fn run fails", 724 kpt: latest.KptDeploy{ 725 Dir: ".", 726 }, 727 commands: testutil. 728 CmdRunOut("kpt fn source .", ``). 729 AndRunOutErr("kpt fn run", "invalid pipeline", errors.New("BUG")). 730 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 731 shouldErr: true, 732 }, 733 { 734 description: "kpt fn run with --global-scope", 735 kpt: latest.KptDeploy{ 736 Dir: ".", 737 Fn: latest.KptFn{ 738 Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", 739 GlobalScope: true, 740 }, 741 }, 742 commands: testutil. 743 CmdRunOut("kpt fn source .", ``). 744 AndRunOut("kpt fn run --global-scope --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``). 745 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 746 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 747 expected: "\n", 748 }, 749 { 750 description: "kpt fn run with --mount arguments", 751 kpt: latest.KptDeploy{ 752 Dir: ".", 753 Fn: latest.KptFn{ 754 Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", 755 Mount: []string{"type=bind", "src=$(pwd)", "dst=/source"}, 756 }, 757 }, 758 commands: testutil. 759 CmdRunOut("kpt fn source .", ``). 760 AndRunOut("kpt fn run --mount type=bind,src=$(pwd),dst=/source --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``). 761 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 762 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 763 expected: "\n", 764 }, 765 { 766 description: "kpt fn run with invalid --mount arguments", 767 kpt: latest.KptDeploy{ 768 Dir: ".", 769 Fn: latest.KptFn{ 770 Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", 771 Mount: []string{"foo", "", "bar"}, 772 }, 773 }, 774 commands: testutil. 775 CmdRunOut("kpt fn source .", ``). 776 AndRunOut("kpt fn run --mount foo,,bar --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``). 777 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 778 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 779 expected: "\n", 780 }, 781 { 782 description: "kpt fn run flag with --network and --network-name arguments", 783 kpt: latest.KptDeploy{ 784 Dir: ".", 785 Fn: latest.KptFn{ 786 Image: "gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", 787 Network: true, 788 NetworkName: "foo", 789 }, 790 }, 791 commands: testutil. 792 CmdRunOut("kpt fn source .", ``). 793 AndRunOut("kpt fn run --network --network-name foo --image gcr.io/example.com/my-fn:v1.0.0 -- foo=bar", ``). 794 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 795 AndRunOut("kpt fn sink .tmp-sink-dir", ``), 796 expected: "\n", 797 }, 798 } 799 for _, test := range tests { 800 testutil.Run(t, test.description, func(t *testutil.T) { 801 t.Override(&util.DefaultExecCommand, test.commands) 802 t.NewTempDir().Chdir() 803 804 labeller := label.NewLabeller(false, test.labels, "") 805 806 k, err := NewDeployer(&kptConfig{workingDir: "."}, labeller, &test.kpt) 807 if err != nil { 808 t.Fatalf("unexpected error occurred in NewDeployer: %v", err) 809 } 810 811 if test.hasKustomization != nil { 812 k.hasKustomization = test.hasKustomization 813 } 814 815 var b bytes.Buffer 816 err = k.Render(context.Background(), &b, test.builds, true, "") 817 818 t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, b.String()) 819 }) 820 } 821 } 822 823 func TestKpt_GetApplyDir(t *testing.T) { 824 tests := []struct { 825 description string 826 live latest.KptLive 827 expected string 828 commands util.Command 829 shouldErr bool 830 }{ 831 { 832 description: "specified an invalid applyDir", 833 live: latest.KptLive{ 834 Apply: latest.KptApplyInventory{ 835 Dir: "invalid_path", 836 }, 837 }, 838 shouldErr: true, 839 }, 840 { 841 description: "specified a valid applyDir", 842 live: latest.KptLive{ 843 Apply: latest.KptApplyInventory{ 844 Dir: "valid_path", 845 }, 846 }, 847 expected: "valid_path", 848 }, 849 { 850 description: "unspecified applyDir", 851 expected: ".kpt-hydrated", 852 commands: testutil.CmdRunOut("kpt live init .kpt-hydrated --context kubecontext --namespace testNamespace", ""), 853 }, 854 { 855 description: "unspecified applyDir with specified inventory-id and namespace", 856 live: latest.KptLive{ 857 Apply: latest.KptApplyInventory{ 858 InventoryID: "1a23bcde-4f56-7891-a2bc-de34fabcde5f6", 859 InventoryNamespace: "foo", 860 }, 861 }, 862 expected: ".kpt-hydrated", 863 commands: testutil.CmdRunOut("kpt live init .kpt-hydrated --inventory-id 1a23bcde-4f56-7891-a2bc-de34fabcde5f6 --context kubecontext --namespace foo", ""), 864 }, 865 { 866 description: "existing template resource in .kpt-hydrated", 867 expected: ".kpt-hydrated", 868 }, 869 } 870 for _, test := range tests { 871 testutil.Run(t, test.description, func(t *testutil.T) { 872 t.Override(&util.DefaultExecCommand, test.commands) 873 tmpDir := t.NewTempDir().Chdir() 874 875 if test.live.Apply.Dir == test.expected { 876 // 0755 is a permission setting where the owner can read, write, and execute. 877 // Others can read and execute but not modify the directory. 878 t.CheckNoError(os.Mkdir(test.live.Apply.Dir, 0755)) 879 } 880 881 if test.description == "existing template resource in .kpt-hydrated" { 882 tmpDir.Touch(".kpt-hydrated/inventory-template.yaml") 883 } 884 885 k, err := NewDeployer(&kptConfig{ 886 workingDir: ".", 887 }, &label.DefaultLabeller{}, &latest.KptDeploy{ 888 Live: test.live, 889 }) 890 if err != nil { 891 t.Fatalf("unexpected error occurred in NewDeployer: %v", err) 892 } 893 894 applyDir, err := k.getApplyDir(context.Background()) 895 896 t.CheckErrorAndDeepEqual(test.shouldErr, err, test.expected, applyDir) 897 }) 898 } 899 } 900 901 func TestKpt_KptCommandArgs(t *testing.T) { 902 tests := []struct { 903 description string 904 dir string 905 commands []string 906 flags []string 907 globalFlags []string 908 expected []string 909 }{ 910 { 911 description: "empty", 912 }, 913 { 914 description: "all inputs have len >0", 915 dir: "test", 916 commands: []string{"live", "apply"}, 917 flags: []string{"--fn-path", "kpt-func.yaml"}, 918 globalFlags: []string{"-h"}, 919 expected: strings.Split("live apply test --fn-path kpt-func.yaml -h", " "), 920 }, 921 { 922 description: "empty dir", 923 commands: []string{"live", "apply"}, 924 flags: []string{"--fn-path", "kpt-func.yaml"}, 925 globalFlags: []string{"-v", "3"}, 926 expected: strings.Split("live apply --fn-path kpt-func.yaml -v 3", " "), 927 }, 928 { 929 description: "empty commands", 930 dir: "test", 931 flags: []string{"--fn-path", "kpt-func.yaml"}, 932 globalFlags: []string{"-h"}, 933 expected: strings.Split("test --fn-path kpt-func.yaml -h", " "), 934 }, 935 { 936 description: "empty flags", 937 dir: "test", 938 commands: []string{"live", "apply"}, 939 globalFlags: []string{"-h"}, 940 expected: strings.Split("live apply test -h", " "), 941 }, 942 { 943 description: "empty globalFlags", 944 dir: "test", 945 commands: []string{"live", "apply"}, 946 flags: []string{"--fn-path", "kpt-func.yaml"}, 947 expected: strings.Split("live apply test --fn-path kpt-func.yaml", " "), 948 }, 949 } 950 for _, test := range tests { 951 testutil.Run(t, test.description, func(t *testutil.T) { 952 res := kptCommandArgs(test.dir, test.commands, test.flags, test.globalFlags) 953 t.CheckDeepEqual(test.expected, res) 954 }) 955 } 956 } 957 958 // TestKpt_ExcludeKptFn checks the declarative kpt fn has expected annotations added. 959 func TestKpt_ExcludeKptFn(t *testing.T) { 960 // A declarative fn. 961 testFn1 := []byte(`apiVersion: v1 962 data: 963 annotation_name: k1 964 annotation_value: v1 965 kind: ConfigMap 966 metadata: 967 annotations: 968 config.kubernetes.io/function: fake`) 969 // A declarative fn which has `local-config` annotation specified. 970 testFn2 := []byte(`apiVersion: v1 971 kind: ConfigMap 972 metadata: 973 annotations: 974 config.kubernetes.io/function: fake 975 config.kubernetes.io/local-config: "false" 976 data: 977 annotation_name: k2 978 annotation_value: v2`) 979 testPod := []byte(`apiVersion: v1 980 kind: Pod 981 metadata: 982 namespace: default 983 spec: 984 containers: 985 - image: gcr.io/project/image1 986 name: image1`) 987 tests := []struct { 988 description string 989 manifests manifest.ManifestList 990 expected manifest.ManifestList 991 }{ 992 { 993 description: "Add `local-config` annotation to kpt fn", 994 manifests: manifest.ManifestList{testFn1}, 995 expected: manifest.ManifestList{[]byte(`apiVersion: v1 996 data: 997 annotation_name: k1 998 annotation_value: v1 999 kind: ConfigMap 1000 metadata: 1001 annotations: 1002 config.kubernetes.io/function: fake 1003 config.kubernetes.io/local-config: "true"`)}, 1004 }, 1005 { 1006 description: "Skip preset `local-config` annotation", 1007 manifests: manifest.ManifestList{testFn2}, 1008 expected: manifest.ManifestList{[]byte(`apiVersion: v1 1009 kind: ConfigMap 1010 metadata: 1011 annotations: 1012 config.kubernetes.io/function: fake 1013 config.kubernetes.io/local-config: "false" 1014 data: 1015 annotation_name: k2 1016 annotation_value: v2`)}, 1017 }, 1018 { 1019 description: "Valid in kpt fn pipeline.", 1020 manifests: manifest.ManifestList{testFn1, testFn2, testPod}, 1021 expected: manifest.ManifestList{[]byte(`apiVersion: v1 1022 data: 1023 annotation_name: k1 1024 annotation_value: v1 1025 kind: ConfigMap 1026 metadata: 1027 annotations: 1028 config.kubernetes.io/function: fake 1029 config.kubernetes.io/local-config: "true"`), []byte(`apiVersion: v1 1030 kind: ConfigMap 1031 metadata: 1032 annotations: 1033 config.kubernetes.io/function: fake 1034 config.kubernetes.io/local-config: "false" 1035 data: 1036 annotation_name: k2 1037 annotation_value: v2`), []byte(`apiVersion: v1 1038 kind: Pod 1039 metadata: 1040 namespace: default 1041 spec: 1042 containers: 1043 - image: gcr.io/project/image1 1044 name: image1`)}, 1045 }, 1046 } 1047 for _, test := range tests { 1048 testutil.Run(t, test.description, func(t *testutil.T) { 1049 k, err := NewDeployer(&kptConfig{}, &label.DefaultLabeller{}, nil) 1050 if err != nil { 1051 t.Fatalf("unexpected error occurred in NewDeployer: %v", err) 1052 } 1053 1054 actualManifest, err := k.excludeKptFn(test.manifests) 1055 t.CheckErrorAndDeepEqual(false, err, test.expected.String(), actualManifest.String()) 1056 }) 1057 } 1058 } 1059 1060 func TestVersionCheck(t *testing.T) { 1061 tests := []struct { 1062 description string 1063 commands util.Command 1064 kustomizations map[string]string 1065 shouldErr bool 1066 error error 1067 out string 1068 }{ 1069 { 1070 description: "Both kpt and kustomize versions are good", 1071 commands: testutil. 1072 CmdRunOut("kpt version", `0.38.1`). 1073 AndRunOut("kustomize version", `{Version:v3.6.1 GitCommit:a0072a2cf92bf5399565e84c621e1e7c5c1f1094 BuildDate:2020-06-15T20:19:07Z GoOs:darwin GoArch:amd64}`), 1074 kustomizations: map[string]string{"Kustomization": `resources: 1075 - foo.yaml`}, 1076 shouldErr: false, 1077 error: nil, 1078 }, 1079 { 1080 description: "kpt is not installed", 1081 commands: testutil.CmdRunOutErr("kpt version", "", errors.New("BUG")), 1082 shouldErr: true, 1083 error: fmt.Errorf("kpt is not installed yet\nSee kpt installation: %v", 1084 kptDownloadLink), 1085 }, 1086 { 1087 description: "kustomize is not used, kpt version is good", 1088 commands: testutil. 1089 CmdRunOut("kpt version", `0.38.1`), 1090 shouldErr: false, 1091 error: nil, 1092 }, 1093 { 1094 description: "kustomize is used but not installed", 1095 commands: testutil. 1096 CmdRunOut("kpt version", `0.38.1`). 1097 AndRunOutErr("kustomize version", "", errors.New("BUG")), 1098 kustomizations: map[string]string{"Kustomization": `resources: 1099 - foo.yaml`}, 1100 shouldErr: true, 1101 error: fmt.Errorf("kustomize is not installed yet\nSee kpt installation: %v", 1102 kustomizeDownloadLink), 1103 }, 1104 { 1105 description: "kpt version is too old (<0.38.1)", 1106 commands: testutil. 1107 CmdRunOut("kpt version", `0.37.0`), 1108 kustomizations: map[string]string{"Kustomization": `resources: 1109 - foo.yaml`}, 1110 shouldErr: true, 1111 error: fmt.Errorf("you are using kpt \"v0.37.0\"\nPlease install "+ 1112 "kpt %v <= version < %v\nSee kpt installation: %v", 1113 kptMinVersionInclusive, kptMaxVersionExclusive, kptDownloadLink), 1114 }, 1115 { 1116 description: "kpt version is too new (>=1.0.0-alpha)", 1117 commands: testutil. 1118 CmdRunOut("kpt version", `1.0.0-beta.4`), 1119 kustomizations: map[string]string{"Kustomization": `resources: 1120 - foo.yaml`}, 1121 shouldErr: true, 1122 error: fmt.Errorf("you are using kpt \"v1.0.0-beta.4\"\nPlease install "+ 1123 "kpt %v <= version < %v\nSee kpt installation: %v", 1124 kptMinVersionInclusive, kptMaxVersionExclusive, kptDownloadLink), 1125 }, 1126 { 1127 description: "kpt version is unknown", 1128 commands: testutil. 1129 CmdRunOut("kpt version", `unknown`), 1130 kustomizations: map[string]string{"Kustomization": `resources: 1131 - foo.yaml`}, 1132 shouldErr: true, 1133 error: fmt.Errorf("unknown kpt version unknown\nPlease install "+ 1134 "kpt %v <= version < %v\nSee kpt installation: %v", 1135 kptMinVersionInclusive, kptMaxVersionExclusive, kptDownloadLink), 1136 }, 1137 { 1138 description: "kustomize versions is too old (< v3.2.3)", 1139 commands: testutil. 1140 CmdRunOut("kpt version", `0.38.1`). 1141 AndRunOut("kustomize version", `{Version:v0.0.1 GitCommit:a0072a2cf92bf5399565e84c621e1e7c5c1f1094 BuildDate:2020-06-15T20:19:07Z GoOs:darwin GoArch:amd64}`), 1142 kustomizations: map[string]string{"Kustomization": `resources: 1143 - foo.yaml`}, 1144 shouldErr: false, 1145 out: fmt.Sprintf("you are using kustomize version \"v0.0.1\" "+ 1146 "(recommended >= %v). You can download the official kustomize from %v\n", 1147 kustomizeMinVersion, kustomizeDownloadLink), 1148 }, 1149 { 1150 description: "kustomize version is unknown", 1151 commands: testutil. 1152 CmdRunOut("kpt version", `0.38.1`). 1153 AndRunOut("kustomize version", `{Version:unknown GitCommit:a0072a2cf92bf5399565e84c621e1e7c5c1f1094 BuildDate:2020-06-15T20:19:07Z GoOs:darwin GoArch:amd64}`), 1154 kustomizations: map[string]string{"Kustomization": `resources: 1155 - foo.yaml`}, 1156 shouldErr: false, 1157 out: fmt.Sprintf("you are using kustomize version \"unknown\" "+ 1158 "(recommended >= %v). You can download the official kustomize from %v\n", 1159 kustomizeMinVersion, kustomizeDownloadLink), 1160 }, 1161 { 1162 description: "kustomize version is non-official", 1163 commands: testutil. 1164 CmdRunOut("kpt version", `0.38.1`). 1165 AndRunOut("kustomize version", `UNKNOWN`), 1166 kustomizations: map[string]string{"Kustomization": `resources: 1167 - foo.yaml`}, 1168 shouldErr: false, 1169 out: fmt.Sprintf("unable to determine kustomize version from \"UNKNOWN\"\n"+ 1170 "You can download the official kustomize (recommended >= %v) from %v\n", 1171 kustomizeMinVersion, kustomizeDownloadLink), 1172 }, 1173 } 1174 for _, test := range tests { 1175 var buf bytes.Buffer 1176 testutil.Run(t, test.description, func(t *testutil.T) { 1177 t.Override(&util.DefaultExecCommand, test.commands) 1178 tmpDir := t.NewTempDir().Chdir() 1179 tmpDir.WriteFiles(test.kustomizations) 1180 err := versionCheck(context.Background(), "", io.Writer(&buf)) 1181 t.CheckError(test.shouldErr, err) 1182 if test.shouldErr { 1183 testutil.CheckDeepEqual(t.T, test.error.Error(), err.Error()) 1184 } 1185 }) 1186 testutil.CheckDeepEqual(t, test.out, buf.String()) 1187 } 1188 } 1189 1190 func TestNonEmptyKubeconfig(t *testing.T) { 1191 commands := testutil.CmdRunOut("kpt fn source .", ``). 1192 AndRunOut("kpt fn run", testPod). 1193 AndRunOut(fmt.Sprintf("kpt fn sink %v", tmpKustomizeDir), ``). 1194 AndRunOut("kpt fn sink valid_path", ``). 1195 AndRun("kpt live apply valid_path --context kubecontext --kubeconfig testConfigPath --namespace testNamespace") 1196 1197 testutil.Run(t, "", func(t *testutil.T) { 1198 t.Override(&util.DefaultExecCommand, commands) 1199 t.Override(&client.Client, deployutil.MockK8sClient) 1200 k, err := NewDeployer(&kptConfig{config: "testConfigPath"}, &label.DefaultLabeller{}, &latest.KptDeploy{ 1201 Dir: ".", 1202 Live: latest.KptLive{ 1203 Apply: latest.KptApplyInventory{ 1204 Dir: "valid_path", 1205 }, 1206 }, 1207 }) 1208 if err != nil { 1209 t.Fatalf("unexpected error occurred in NewDeployer: %v", err) 1210 } 1211 1212 t.CheckNoError(os.Mkdir(k.Live.Apply.Dir, 0755)) 1213 defer os.RemoveAll(k.Live.Apply.Dir) 1214 err = k.Deploy(context.Background(), ioutil.Discard, []graph.Artifact{}) 1215 t.CheckNoError(err) 1216 }) 1217 } 1218 1219 type kptConfig struct { 1220 runcontext.RunContext // Embedded to provide the default values. 1221 workingDir string 1222 config string 1223 } 1224 1225 func (c *kptConfig) WorkingDir() string { return c.workingDir } 1226 func (c *kptConfig) GetKubeContext() string { return kubectl.TestKubeContext } 1227 func (c *kptConfig) GetKubeNamespace() string { return kubectl.TestNamespace } 1228 func (c *kptConfig) GetKubeConfig() string { return c.config } 1229 func (c *kptConfig) PortForwardResources() []*latest.PortForwardResource { return nil }