github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/commands/pkg/update/cmdupdate_test.go (about) 1 // Copyright 2019 The kpt Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package update_test 16 17 import ( 18 "bytes" 19 "os" 20 "path/filepath" 21 "regexp" 22 "runtime" 23 "strings" 24 "testing" 25 "text/template" 26 27 "github.com/GoogleContainerTools/kpt/commands/pkg/get" 28 "github.com/GoogleContainerTools/kpt/commands/pkg/update" 29 "github.com/GoogleContainerTools/kpt/internal/gitutil" 30 "github.com/GoogleContainerTools/kpt/internal/testutil" 31 "github.com/GoogleContainerTools/kpt/internal/testutil/pkgbuilder" 32 kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1" 33 "github.com/GoogleContainerTools/kpt/pkg/printer/fake" 34 "github.com/spf13/cobra" 35 "github.com/stretchr/testify/assert" 36 "sigs.k8s.io/kustomize/kyaml/yaml" 37 ) 38 39 func TestMain(m *testing.M) { 40 os.Exit(testutil.ConfigureTestKptCache(m)) 41 } 42 43 // TestCmd_execute verifies that update is correctly invoked. 44 func TestCmd_execute(t *testing.T) { 45 g, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{ 46 Data: testutil.Dataset1, 47 Branch: "master", 48 }) 49 defer clean() 50 51 defer testutil.Chdir(t, w.WorkspaceDirectory)() 52 53 dest := filepath.Join(w.WorkspaceDirectory, g.RepoName) 54 55 // clone the repo 56 getCmd := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 57 getCmd.Command.SetArgs([]string{"file://" + g.RepoDirectory + ".git", w.WorkspaceDirectory}) 58 err := getCmd.Command.Execute() 59 if !assert.NoError(t, err) { 60 return 61 } 62 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset1), dest, true) { 63 return 64 } 65 gitRunner, err := gitutil.NewLocalGitRunner(w.WorkspaceDirectory) 66 if !assert.NoError(t, err) { 67 t.FailNow() 68 } 69 _, err = gitRunner.Run(fake.CtxWithDefaultPrinter(), "add", ".") 70 if !assert.NoError(t, err) { 71 return 72 } 73 _, err = gitRunner.Run(fake.CtxWithDefaultPrinter(), "commit", "-m", "commit local package -- ds1") 74 if !assert.NoError(t, err) { 75 return 76 } 77 78 // update the master branch 79 if !assert.NoError(t, g.ReplaceData(testutil.Dataset2)) { 80 return 81 } 82 _, err = g.Commit("modify upstream package -- ds2") 83 if !assert.NoError(t, err) { 84 return 85 } 86 87 // update the cloned package 88 updateCmd := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 89 updateCmd.Command.SetArgs([]string{g.RepoName, "--strategy", "fast-forward"}) 90 if !assert.NoError(t, updateCmd.Command.Execute()) { 91 return 92 } 93 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset2), dest, true) { 94 return 95 } 96 97 commit, err := g.GetCommit() 98 if !assert.NoError(t, err) { 99 return 100 } 101 if !g.AssertKptfile(t, dest, kptfilev1.KptFile{ 102 ResourceMeta: yaml.ResourceMeta{ 103 ObjectMeta: yaml.ObjectMeta{ 104 NameMeta: yaml.NameMeta{ 105 Name: g.RepoName, 106 }, 107 }, 108 TypeMeta: yaml.TypeMeta{ 109 APIVersion: kptfilev1.TypeMeta.APIVersion, 110 Kind: kptfilev1.TypeMeta.Kind}, 111 }, 112 Upstream: &kptfilev1.Upstream{ 113 Type: kptfilev1.GitOrigin, 114 Git: &kptfilev1.Git{ 115 Repo: "file://" + g.RepoDirectory, 116 Ref: "master", 117 Directory: "/", 118 }, 119 UpdateStrategy: kptfilev1.FastForward, 120 }, 121 UpstreamLock: &kptfilev1.UpstreamLock{ 122 Type: kptfilev1.GitOrigin, 123 Git: &kptfilev1.GitLock{ 124 Repo: "file://" + g.RepoDirectory, 125 Ref: "master", 126 Directory: "/", 127 Commit: commit, 128 }, 129 }, 130 }) { 131 return 132 } 133 } 134 135 func TestCmd_successUnCommitted(t *testing.T) { 136 g, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{ 137 Data: testutil.Dataset1, 138 Branch: "master", 139 }) 140 defer clean() 141 142 defer testutil.Chdir(t, w.WorkspaceDirectory)() 143 144 dest := filepath.Join(w.WorkspaceDirectory, g.RepoName) 145 146 // clone the repo 147 getCmd := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 148 getCmd.Command.SetArgs([]string{"file://" + g.RepoDirectory + ".git", w.WorkspaceDirectory}) 149 err := getCmd.Command.Execute() 150 if !assert.NoError(t, err) { 151 return 152 } 153 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset1), dest, true) { 154 return 155 } 156 157 // update the master branch 158 if !assert.NoError(t, g.ReplaceData(testutil.Dataset2)) { 159 return 160 } 161 162 // commit the upstream but not the local 163 _, err = g.Commit("new dataset") 164 if !assert.NoError(t, err) { 165 return 166 } 167 168 // update the cloned package 169 updateCmd := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 170 updateCmd.Command.SetArgs([]string{g.RepoName}) 171 err = updateCmd.Command.Execute() 172 if !assert.NoError(t, err) { 173 t.FailNow() 174 } 175 176 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset2), dest, true) { 177 return 178 } 179 } 180 181 func TestCmd_successNoGit(t *testing.T) { 182 g, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{ 183 Data: testutil.Dataset1, 184 Branch: "master", 185 }) 186 defer clean() 187 188 defer testutil.Chdir(t, w.WorkspaceDirectory)() 189 190 err := os.RemoveAll(".git") 191 if !assert.NoError(t, err) { 192 t.FailNow() 193 } 194 dest := filepath.Join(w.WorkspaceDirectory, g.RepoName) 195 196 // clone the repo 197 getCmd := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 198 getCmd.Command.SetArgs([]string{"file://" + g.RepoDirectory + ".git", w.WorkspaceDirectory}) 199 err = getCmd.Command.Execute() 200 if !assert.NoError(t, err) { 201 return 202 } 203 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset1), dest, true) { 204 return 205 } 206 207 // update the master branch 208 if !assert.NoError(t, g.ReplaceData(testutil.Dataset2)) { 209 return 210 } 211 212 // commit the upstream but not the local 213 _, err = g.Commit("new dataset") 214 if !assert.NoError(t, err) { 215 return 216 } 217 218 // update the cloned package 219 updateCmd := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 220 updateCmd.Command.SetArgs([]string{g.RepoName}) 221 err = updateCmd.Command.Execute() 222 if !assert.NoError(t, err) { 223 t.FailNow() 224 } 225 226 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset2), dest, true) { 227 return 228 } 229 } 230 231 func TestCmd_onlyVersionAsInput(t *testing.T) { 232 g, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{ 233 Data: testutil.Dataset1, 234 Branch: "master", 235 }) 236 defer clean() 237 238 err := os.RemoveAll(".git") 239 if !assert.NoError(t, err) { 240 t.FailNow() 241 } 242 dest := filepath.Join(w.WorkspaceDirectory, g.RepoName) 243 244 // clone the repo 245 getCmd := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 246 getCmd.Command.SetArgs([]string{"file://" + g.RepoDirectory + ".git", w.WorkspaceDirectory}) 247 err = getCmd.Command.Execute() 248 if !assert.NoError(t, err) { 249 return 250 } 251 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset1), dest, true) { 252 return 253 } 254 255 // update the master branch 256 if !assert.NoError(t, g.ReplaceData(testutil.Dataset2)) { 257 return 258 } 259 260 // commit the upstream but not the local 261 _, err = g.Commit("new dataset") 262 if !assert.NoError(t, err) { 263 return 264 } 265 266 // update the cloned package 267 updateCmd := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 268 defer testutil.Chdir(t, dest)() 269 updateCmd.Command.SetArgs([]string{"@master"}) 270 err = updateCmd.Command.Execute() 271 if !assert.NoError(t, err) { 272 t.FailNow() 273 } 274 275 if !g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset2), dest, true) { 276 return 277 } 278 } 279 280 // NoOpRunE is a noop function to replace the run function of a command. Useful for testing argument parsing. 281 var NoOpRunE = func(cmd *cobra.Command, args []string) error { return nil } 282 283 // NoOpFailRunE causes the test to fail if run is called. Useful for validating run isn't called for 284 // errors. 285 type NoOpFailRunE struct { 286 t *testing.T 287 } 288 289 func (t NoOpFailRunE) runE(_ *cobra.Command, _ []string) error { 290 assert.Fail(t.t, "run should not be called") 291 return nil 292 } 293 294 // TestCmd_Execute_flagAndArgParsing verifies that the flags and args are parsed into the correct Command fields 295 func TestCmd_Execute_flagAndArgParsing(t *testing.T) { 296 failRun := NoOpFailRunE{t: t}.runE 297 298 dir := t.TempDir() 299 defer testutil.Chdir(t, filepath.Dir(dir))() 300 301 // verify the current working directory is used if no path is specified 302 r := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 303 r.Command.RunE = NoOpRunE 304 r.Command.SetArgs([]string{}) 305 err := r.Command.Execute() 306 assert.NoError(t, err) 307 assert.Equal(t, "", r.Update.Ref) 308 assert.Equal(t, kptfilev1.ResourceMerge, r.Update.Strategy) 309 310 // verify an error is thrown if multiple paths are specified 311 r = update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 312 r.Command.SilenceErrors = true 313 r.Command.RunE = failRun 314 r.Command.SetArgs([]string{"foo", "bar"}) 315 err = r.Command.Execute() 316 assert.EqualError(t, err, "accepts at most 1 arg(s), received 2") 317 assert.Equal(t, "", r.Update.Ref) 318 assert.Equal(t, kptfilev1.UpdateStrategyType(""), r.Update.Strategy) 319 320 // verify the branch ref is set to the correct value 321 r = update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 322 r.Command.RunE = NoOpRunE 323 r.Command.SetArgs([]string{dir + "@refs/heads/foo"}) 324 err = r.Command.Execute() 325 assert.NoError(t, err) 326 assert.Equal(t, "refs/heads/foo", r.Update.Ref) 327 assert.Equal(t, kptfilev1.ResourceMerge, r.Update.Strategy) 328 329 // verify the branch ref is set to the correct value 330 r = update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 331 r.Command.RunE = NoOpRunE 332 r.Command.SetArgs([]string{dir, "--strategy", "force-delete-replace"}) 333 err = r.Command.Execute() 334 assert.NoError(t, err) 335 assert.Equal(t, kptfilev1.ForceDeleteReplace, r.Update.Strategy) 336 assert.Equal(t, "", r.Update.Ref) 337 338 r = update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 339 r.Command.RunE = NoOpRunE 340 r.Command.SetArgs([]string{dir, "--strategy", "resource-merge"}) 341 err = r.Command.Execute() 342 assert.NoError(t, err) 343 assert.Equal(t, kptfilev1.ResourceMerge, r.Update.Strategy) 344 assert.Equal(t, "", r.Update.Ref) 345 } 346 347 func TestCmd_flagAndArgParsing_Symlink(t *testing.T) { 348 dir := t.TempDir() 349 defer testutil.Chdir(t, dir)() 350 351 err := os.MkdirAll(filepath.Join(dir, "path", "to", "pkg", "dir"), 0700) 352 assert.NoError(t, err) 353 err = os.Symlink(filepath.Join("path", "to", "pkg", "dir"), "foo") 354 assert.NoError(t, err) 355 356 // verify the branch ref is set to the correct value 357 r := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 358 r.Command.RunE = NoOpRunE 359 r.Command.SetArgs([]string{"foo" + "@refs/heads/foo"}) 360 err = r.Command.Execute() 361 assert.NoError(t, err) 362 assert.Equal(t, "refs/heads/foo", r.Update.Ref) 363 assert.Equal(t, kptfilev1.ResourceMerge, r.Update.Strategy) 364 cwd, err := os.Getwd() 365 assert.NoError(t, err) 366 assert.Equal(t, filepath.Join(cwd, "path", "to", "pkg", "dir"), r.Update.Pkg.UniquePath.String()) 367 } 368 369 // TestCmd_fail verifies that that command returns an error when it fails rather than exiting the process 370 func TestCmd_fail(t *testing.T) { 371 r := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 372 r.Command.SilenceErrors = true 373 r.Command.SilenceUsage = true 374 r.Command.SetArgs([]string{filepath.Join("not", "real", "dir")}) 375 err := r.Command.Execute() 376 if assert.Error(t, err) { 377 assert.Contains(t, err.Error(), "no such file or directory") 378 } 379 } 380 381 func TestCmd_path(t *testing.T) { 382 var pathPrefix string 383 if runtime.GOOS == "darwin" { 384 pathPrefix = "/private" 385 } 386 387 dir := t.TempDir() 388 389 testCases := []struct { 390 name string 391 currentWD string 392 path string 393 expectedPath string 394 expectedFullPackagePath string 395 expectedErrMsg string 396 }{ 397 { 398 name: "update package in current directory", 399 currentWD: dir, 400 path: ".", 401 expectedPath: ".", 402 expectedFullPackagePath: filepath.Join(pathPrefix, dir), 403 }, 404 { 405 name: "update package in subfolder of current directory", 406 currentWD: filepath.Dir(dir), 407 path: filepath.Base(dir), 408 expectedPath: filepath.Base(dir), 409 expectedFullPackagePath: filepath.Join(pathPrefix, dir), 410 }, 411 { 412 name: "update package with full absolute path", 413 currentWD: filepath.Dir(dir), 414 path: filepath.Join(pathPrefix, dir), 415 expectedPath: filepath.Base(dir), 416 expectedFullPackagePath: filepath.Join(pathPrefix, dir), 417 }, 418 { 419 name: "package must exist as a subdirectory of cwd", 420 currentWD: filepath.Dir(dir), 421 path: filepath.Dir(filepath.Dir(dir)), 422 expectedErrMsg: "package path must be under current working directory", 423 }, 424 } 425 426 for i := range testCases { 427 test := testCases[i] 428 t.Run(test.name, func(t *testing.T) { 429 defer testutil.Chdir(t, test.currentWD)() 430 431 r := update.NewRunner(fake.CtxWithDefaultPrinter(), "kpt") 432 r.Command.RunE = func(cmd *cobra.Command, args []string) error { 433 if !assert.Equal(t, test.expectedFullPackagePath, r.Update.Pkg.UniquePath.String()) { 434 t.FailNow() 435 } 436 return nil 437 } 438 439 r.Command.SetArgs([]string{test.path}) 440 err := r.Command.Execute() 441 442 if test.expectedErrMsg != "" { 443 if !assert.Error(t, err) { 444 t.FailNow() 445 } 446 assert.Contains(t, err.Error(), test.expectedErrMsg) 447 return 448 } 449 450 if !assert.NoError(t, err) { 451 t.FailNow() 452 } 453 }) 454 } 455 } 456 457 func TestCmd_output(t *testing.T) { 458 testCases := map[string]struct { 459 reposChanges map[string][]testutil.Content 460 updatedLocal testutil.Content 461 expectedLocal *pkgbuilder.RootPkg 462 expectedOutput string 463 }{ 464 "basic package": { 465 reposChanges: map[string][]testutil.Content{ 466 testutil.Upstream: { 467 { 468 Pkg: pkgbuilder.NewRootPkg(). 469 WithKptfile(). 470 WithResource(pkgbuilder.DeploymentResource), 471 Branch: "master", 472 }, 473 { 474 Pkg: pkgbuilder.NewRootPkg(). 475 WithKptfile(). 476 WithResource(pkgbuilder.SecretResource), 477 }, 478 }, 479 }, 480 expectedLocal: pkgbuilder.NewRootPkg(). 481 WithKptfile( 482 pkgbuilder.NewKptfile(). 483 WithUpstreamRef(testutil.Upstream, "/", "master", "resource-merge"). 484 WithUpstreamLockRef(testutil.Upstream, "/", "master", 1), 485 ). 486 WithResource(pkgbuilder.SecretResource), 487 expectedOutput: ` 488 Package "{{ .PKG_NAME }}": 489 Fetching upstream from {{ (index .REPOS "upstream").RepoDirectory }}@master 490 <git_output> 491 Fetching origin from {{ (index .REPOS "upstream").RepoDirectory }}@master 492 <git_output> 493 Updating package "{{ .PKG_NAME }}" with strategy "resource-merge". 494 495 Updated 1 package(s). 496 `, 497 }, 498 "nested packages": { 499 reposChanges: map[string][]testutil.Content{ 500 testutil.Upstream: { 501 { 502 Pkg: pkgbuilder.NewRootPkg(). 503 WithKptfile(). 504 WithResource(pkgbuilder.DeploymentResource). 505 WithSubPackages( 506 pkgbuilder.NewSubPkg("subpkg"). 507 WithKptfile( 508 pkgbuilder.NewKptfile(). 509 WithUpstreamRef("foo", "/", "master", "fast-forward"). 510 WithUpstreamLockRef("foo", "/", "master", 0), 511 ). 512 WithResource(pkgbuilder.DeploymentResource), 513 ), 514 Branch: "master", 515 }, 516 { 517 Pkg: pkgbuilder.NewRootPkg(). 518 WithKptfile(). 519 WithResource(pkgbuilder.SecretResource). 520 WithSubPackages( 521 pkgbuilder.NewSubPkg("subpkg"). 522 WithKptfile( 523 pkgbuilder.NewKptfile(). 524 WithUpstreamRef("foo", "/", "master", "fast-forward"). 525 WithUpstreamLockRef("foo", "/", "master", 0), 526 ). 527 WithResource(pkgbuilder.DeploymentResource), 528 ), 529 }, 530 }, 531 "foo": { 532 { 533 Pkg: pkgbuilder.NewRootPkg(). 534 WithKptfile(). 535 WithResource(pkgbuilder.DeploymentResource), 536 Branch: "master", 537 }, 538 { 539 Pkg: pkgbuilder.NewRootPkg(). 540 WithKptfile(). 541 WithResource(pkgbuilder.ConfigMapResource), 542 }, 543 }, 544 }, 545 expectedLocal: pkgbuilder.NewRootPkg(). 546 WithKptfile( 547 pkgbuilder.NewKptfile(). 548 WithUpstreamRef(testutil.Upstream, "/", "master", "resource-merge"). 549 WithUpstreamLockRef(testutil.Upstream, "/", "master", 1), 550 ). 551 WithResource(pkgbuilder.SecretResource). 552 WithSubPackages( 553 pkgbuilder.NewSubPkg("subpkg"). 554 WithKptfile( 555 pkgbuilder.NewKptfile(). 556 WithUpstreamRef("foo", "/", "master", "fast-forward"). 557 WithUpstreamLockRef("foo", "/", "master", 1), 558 ). 559 WithResource(pkgbuilder.ConfigMapResource), 560 ), 561 expectedOutput: ` 562 Package "{{ .PKG_NAME }}": 563 Fetching upstream from {{ (index .REPOS "upstream").RepoDirectory }}@master 564 <git_output> 565 Fetching origin from {{ (index .REPOS "upstream").RepoDirectory }}@master 566 <git_output> 567 Updating package "{{ .PKG_NAME }}" with strategy "resource-merge". 568 Updating package "subpkg" with strategy "fast-forward". 569 570 Package "{{ .PKG_NAME }}/subpkg": 571 Fetching upstream from {{ (index .REPOS "foo").RepoDirectory }}@master 572 <git_output> 573 Fetching origin from {{ (index .REPOS "foo").RepoDirectory }}@master 574 <git_output> 575 Updating package "subpkg" with strategy "fast-forward". 576 577 Updated 2 package(s). 578 `, 579 }, 580 "subpackage deleted from upstream": { 581 reposChanges: map[string][]testutil.Content{ 582 testutil.Upstream: { 583 { 584 Pkg: pkgbuilder.NewRootPkg(). 585 WithKptfile(). 586 WithResource(pkgbuilder.DeploymentResource). 587 WithSubPackages( 588 pkgbuilder.NewSubPkg("subpkg1"). 589 WithKptfile( 590 pkgbuilder.NewKptfile(). 591 WithUpstreamRef("foo", "/", "master", "resource-merge"). 592 WithUpstreamLockRef("foo", "/", "master", 0), 593 ). 594 WithResource(pkgbuilder.DeploymentResource), 595 pkgbuilder.NewSubPkg("subpkg2"). 596 WithKptfile( 597 pkgbuilder.NewKptfile(). 598 WithUpstreamRef("foo", "/", "master", "resource-merge"). 599 WithUpstreamLockRef("foo", "/", "master", 0), 600 ). 601 WithResource(pkgbuilder.DeploymentResource), 602 ), 603 Branch: "master", 604 }, 605 { 606 Pkg: pkgbuilder.NewRootPkg(). 607 WithKptfile(). 608 WithResource(pkgbuilder.SecretResource), 609 }, 610 }, 611 "foo": { 612 { 613 Pkg: pkgbuilder.NewRootPkg(). 614 WithKptfile(). 615 WithResource(pkgbuilder.DeploymentResource), 616 Branch: "master", 617 }, 618 619 { 620 Pkg: pkgbuilder.NewRootPkg(). 621 WithKptfile(). 622 WithResource(pkgbuilder.DeploymentResource). 623 WithResource(pkgbuilder.ConfigMapResource), 624 }, 625 }, 626 }, 627 updatedLocal: testutil.Content{ 628 Pkg: pkgbuilder.NewRootPkg(). 629 WithKptfile( 630 pkgbuilder.NewKptfile(). 631 WithUpstreamRef(testutil.Upstream, "/", "master", "resource-merge"). 632 WithUpstreamLockRef(testutil.Upstream, "/", "master", 0), 633 ). 634 WithResource(pkgbuilder.DeploymentResource). 635 WithSubPackages( 636 pkgbuilder.NewSubPkg("subpkg1"). 637 WithKptfile( 638 pkgbuilder.NewKptfile(). 639 WithUpstreamRef("foo", "/", "master", "resource-merge"). 640 WithUpstreamLockRef("foo", "/", "master", 0), 641 ). 642 WithResource(pkgbuilder.DeploymentResource, pkgbuilder.SetFieldPath("5", "spec", "replicas")), 643 pkgbuilder.NewSubPkg("subpkg2"). 644 WithKptfile( 645 pkgbuilder.NewKptfile(). 646 WithUpstreamRef("foo", "/", "master", "resource-merge"). 647 WithUpstreamLockRef("foo", "/", "master", 0), 648 ). 649 WithResource(pkgbuilder.DeploymentResource), 650 ), 651 }, 652 expectedLocal: pkgbuilder.NewRootPkg(). 653 WithKptfile( 654 pkgbuilder.NewKptfile(). 655 WithUpstreamRef(testutil.Upstream, "/", "master", "resource-merge"). 656 WithUpstreamLockRef(testutil.Upstream, "/", "master", 1), 657 ). 658 WithResource(pkgbuilder.SecretResource). 659 WithSubPackages( 660 pkgbuilder.NewSubPkg("subpkg1"). 661 WithKptfile( 662 pkgbuilder.NewKptfile(). 663 WithUpstreamRef("foo", "/", "master", "resource-merge"). 664 WithUpstreamLockRef("foo", "/", "master", 1), 665 ). 666 WithResource(pkgbuilder.ConfigMapResource). 667 WithResource(pkgbuilder.DeploymentResource, pkgbuilder.SetFieldPath("5", "spec", "replicas")), 668 ), 669 expectedOutput: ` 670 Package "{{ .PKG_NAME }}": 671 Fetching upstream from {{ (index .REPOS "upstream").RepoDirectory }}@master 672 <git_output> 673 Fetching origin from {{ (index .REPOS "upstream").RepoDirectory }}@master 674 <git_output> 675 Updating package "{{ .PKG_NAME }}" with strategy "resource-merge". 676 Deleting package "subpkg2" from local since it is removed in upstream. 677 Package "subpkg1" deleted from upstream, but keeping local since it has changes. 678 679 Package "{{ .PKG_NAME }}/subpkg1": 680 Fetching upstream from {{ (index .REPOS "foo").RepoDirectory }}@master 681 <git_output> 682 Fetching origin from {{ (index .REPOS "foo").RepoDirectory }}@master 683 <git_output> 684 Updating package "subpkg1" with strategy "resource-merge". 685 686 Updated 2 package(s). 687 `, 688 }, 689 "Adding package in upstream": { 690 reposChanges: map[string][]testutil.Content{ 691 testutil.Upstream: { 692 { 693 Pkg: pkgbuilder.NewRootPkg(). 694 WithKptfile(). 695 WithResource(pkgbuilder.DeploymentResource), 696 Branch: "master", 697 }, 698 { 699 Pkg: pkgbuilder.NewRootPkg(). 700 WithKptfile(). 701 WithResource(pkgbuilder.SecretResource). 702 WithSubPackages( 703 pkgbuilder.NewSubPkg("subpkg"). 704 WithKptfile( 705 pkgbuilder.NewKptfile(). 706 WithUpstreamRef("foo", "/", "v1", "force-delete-replace"), 707 ), 708 ), 709 }, 710 }, 711 "foo": { 712 { 713 Pkg: pkgbuilder.NewRootPkg(). 714 WithKptfile(). 715 WithResource(pkgbuilder.DeploymentResource), 716 Branch: "master", 717 Tag: "v1", 718 }, 719 }, 720 }, 721 expectedLocal: pkgbuilder.NewRootPkg(). 722 WithKptfile( 723 pkgbuilder.NewKptfile(). 724 WithUpstreamRef(testutil.Upstream, "/", "master", "resource-merge"). 725 WithUpstreamLockRef(testutil.Upstream, "/", "master", 1), 726 ). 727 WithResource(pkgbuilder.SecretResource). 728 WithSubPackages( 729 pkgbuilder.NewSubPkg("subpkg"). 730 WithKptfile( 731 pkgbuilder.NewKptfile(). 732 WithUpstreamRef("foo", "/", "v1", "force-delete-replace"). 733 WithUpstreamLockRef("foo", "/", "v1", 0), 734 ). 735 WithResource(pkgbuilder.DeploymentResource), 736 ), 737 expectedOutput: ` 738 Package "{{ .PKG_NAME }}": 739 Fetching upstream from {{ (index .REPOS "upstream").RepoDirectory }}@master 740 <git_output> 741 Fetching origin from {{ (index .REPOS "upstream").RepoDirectory }}@master 742 <git_output> 743 Updating package "{{ .PKG_NAME }}" with strategy "resource-merge". 744 Adding package "subpkg" from upstream. 745 746 Package "{{ .PKG_NAME }}/subpkg": 747 Fetching upstream from {{ (index .REPOS "foo").RepoDirectory }}@v1 748 <git_output> 749 Updating package "subpkg" with strategy "force-delete-replace". 750 751 Updated 2 package(s). 752 `, 753 }, 754 } 755 756 for tn, tc := range testCases { 757 t.Run(tn, func(t *testing.T) { 758 g := &testutil.TestSetupManager{ 759 T: t, 760 ReposChanges: tc.reposChanges, 761 } 762 defer g.Clean() 763 if tc.updatedLocal.Pkg != nil { 764 g.LocalChanges = []testutil.Content{ 765 tc.updatedLocal, 766 } 767 } 768 if !g.Init() { 769 return 770 } 771 772 clean := testutil.Chdir(t, g.LocalWorkspace.FullPackagePath()) 773 defer clean() 774 775 var outBuf bytes.Buffer 776 var errBuf bytes.Buffer 777 778 ctx := fake.CtxWithPrinter(&outBuf, &errBuf) 779 r := update.NewRunner(ctx, "kpt") 780 r.Command.SetArgs([]string{}) 781 err := r.Command.Execute() 782 if !assert.NoError(t, err) { 783 t.FailNow() 784 } 785 786 assert.Empty(t, outBuf.String()) 787 788 tmpl := template.Must(template.New("test").Parse(tc.expectedOutput)) 789 var expected bytes.Buffer 790 err = tmpl.Execute(&expected, map[string]interface{}{ 791 "PKG_PATH": g.LocalWorkspace.FullPackagePath(), 792 "PKG_NAME": g.LocalWorkspace.PackageDir, 793 "REPOS": g.Repos, 794 }) 795 if !assert.NoError(t, err) { 796 t.FailNow() 797 } 798 actual := scrubGitOutput(errBuf.String()) 799 800 assert.Equal(t, strings.TrimSpace(expected.String()), strings.TrimSpace(actual)) 801 802 expectedPath := tc.expectedLocal.ExpandPkgWithName(t, g.LocalWorkspace.PackageDir, testutil.ToReposInfo(g.Repos)) 803 testutil.KptfileAwarePkgEqual(t, expectedPath, g.LocalWorkspace.FullPackagePath(), true) 804 }) 805 } 806 } 807 808 const ( 809 gitOutputPattern = `From \/.*(\r\n|\r|\n)( * .*(\r\n|\r|\n))+` 810 ) 811 812 func scrubGitOutput(output string) string { 813 re := regexp.MustCompile(gitOutputPattern) 814 return re.ReplaceAllString(output, "<git_output>\n") 815 }