github.com/SamarSidharth/kpt@v0.0.0-20231122062228-c7d747ae3ace/commands/pkg/get/cmdget_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 get_test
    16  
    17  import (
    18  	"fmt"
    19  	"os"
    20  	"path/filepath"
    21  	"runtime"
    22  	"testing"
    23  
    24  	"github.com/GoogleContainerTools/kpt/commands/pkg/get"
    25  	"github.com/GoogleContainerTools/kpt/internal/testutil"
    26  	kptfilev1 "github.com/GoogleContainerTools/kpt/pkg/api/kptfile/v1"
    27  	"github.com/GoogleContainerTools/kpt/pkg/printer/fake"
    28  	"github.com/spf13/cobra"
    29  	"github.com/stretchr/testify/assert"
    30  	"sigs.k8s.io/kustomize/kyaml/yaml"
    31  )
    32  
    33  func TestMain(m *testing.M) {
    34  	os.Exit(testutil.ConfigureTestKptCache(m))
    35  }
    36  
    37  // TestCmd_execute tests that get is correctly invoked.
    38  func TestCmd_execute(t *testing.T) {
    39  	g, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{
    40  		Data:   testutil.Dataset1,
    41  		Branch: "master",
    42  	})
    43  	defer clean()
    44  
    45  	defer testutil.Chdir(t, w.WorkspaceDirectory)()
    46  
    47  	dest := filepath.Join(w.WorkspaceDirectory, g.RepoName)
    48  
    49  	r := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt")
    50  	// defaults LOCAL_DEST_DIR to current working directory
    51  	r.Command.SetArgs([]string{"file://" + g.RepoDirectory + ".git/"})
    52  	err := r.Command.Execute()
    53  
    54  	assert.NoError(t, err)
    55  
    56  	// verify the cloned contents matches the repository with merge comment added
    57  	g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset1), dest, true)
    58  
    59  	commit, err := g.GetCommit()
    60  	assert.NoError(t, err)
    61  	g.AssertKptfile(t, dest, kptfilev1.KptFile{
    62  		ResourceMeta: yaml.ResourceMeta{
    63  			ObjectMeta: yaml.ObjectMeta{
    64  				NameMeta: yaml.NameMeta{
    65  					Name: g.RepoName,
    66  				},
    67  			},
    68  			TypeMeta: yaml.TypeMeta{
    69  				APIVersion: kptfilev1.KptFileGVK().GroupVersion().String(),
    70  				Kind:       kptfilev1.KptFileGVK().Kind,
    71  			},
    72  		},
    73  		Upstream: &kptfilev1.Upstream{
    74  			Type: kptfilev1.GitOrigin,
    75  			Git: &kptfilev1.Git{
    76  				Directory: "/",
    77  				Repo:      "file://" + g.RepoDirectory,
    78  				Ref:       "master",
    79  			},
    80  			UpdateStrategy: kptfilev1.ResourceMerge,
    81  		},
    82  		UpstreamLock: &kptfilev1.UpstreamLock{
    83  			Type: kptfilev1.GitOrigin,
    84  			Git: &kptfilev1.GitLock{
    85  				Directory: "/",
    86  				Repo:      "file://" + g.RepoDirectory,
    87  				Ref:       "master",
    88  				Commit:    commit, // verify the commit matches the repo
    89  			},
    90  		},
    91  	})
    92  }
    93  
    94  // TestCmdMainBranch_execute tests that get is correctly invoked if default branch
    95  // is main and master branch doesn't exist
    96  func TestCmdMainBranch_execute(t *testing.T) {
    97  	// set up git repository with master and main branches
    98  	g, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{
    99  		Data: testutil.Dataset1,
   100  	})
   101  	defer clean()
   102  
   103  	defer testutil.Chdir(t, w.WorkspaceDirectory)()
   104  
   105  	dest := filepath.Join(w.WorkspaceDirectory, g.RepoName)
   106  	err := g.CheckoutBranch("main", false)
   107  	if !assert.NoError(t, err) {
   108  		t.FailNow()
   109  	}
   110  
   111  	r := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt")
   112  	r.Command.SetArgs([]string{"file://" + g.RepoDirectory + ".git/", "./"})
   113  	err = r.Command.Execute()
   114  
   115  	assert.NoError(t, err)
   116  
   117  	// verify the cloned contents matches the repository with merge comment added
   118  	g.AssertEqual(t, filepath.Join(g.DatasetDirectory, testutil.Dataset1), dest, true)
   119  
   120  	commit, err := g.GetCommit()
   121  	assert.NoError(t, err)
   122  	g.AssertKptfile(t, dest, kptfilev1.KptFile{
   123  		ResourceMeta: yaml.ResourceMeta{
   124  			ObjectMeta: yaml.ObjectMeta{
   125  				NameMeta: yaml.NameMeta{
   126  					Name: g.RepoName,
   127  				},
   128  			},
   129  			TypeMeta: yaml.TypeMeta{
   130  				APIVersion: kptfilev1.KptFileGVK().GroupVersion().String(),
   131  				Kind:       kptfilev1.KptFileGVK().Kind,
   132  			},
   133  		},
   134  		Upstream: &kptfilev1.Upstream{
   135  			Type: kptfilev1.GitOrigin,
   136  			Git: &kptfilev1.Git{
   137  				Directory: "/",
   138  				Repo:      "file://" + g.RepoDirectory,
   139  				Ref:       "main",
   140  			},
   141  			UpdateStrategy: kptfilev1.ResourceMerge,
   142  		},
   143  		UpstreamLock: &kptfilev1.UpstreamLock{
   144  			Type: kptfilev1.GitOrigin,
   145  			Git: &kptfilev1.GitLock{
   146  				Directory: "/",
   147  				Repo:      "file://" + g.RepoDirectory,
   148  				Ref:       "main",
   149  				Commit:    commit, // verify the commit matches the repo
   150  			},
   151  		},
   152  	})
   153  }
   154  
   155  // TestCmd_fail verifies that that command returns an error rather than exiting the process
   156  func TestCmd_fail(t *testing.T) {
   157  	r := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt")
   158  	r.Command.SilenceErrors = true
   159  	r.Command.SilenceUsage = true
   160  	r.Command.SetArgs([]string{"file://" + filepath.Join("not", "real", "dir") + ".git/@master", "./"})
   161  
   162  	defer os.RemoveAll("dir")
   163  
   164  	err := r.Command.Execute()
   165  	if !assert.Error(t, err) {
   166  		return
   167  	}
   168  	assert.Contains(t, err.Error(), "'/real/dir' does not appear to be a git repository")
   169  }
   170  
   171  // NoOpRunE is a noop function to replace the run function of a command.  Useful for testing argument parsing.
   172  var NoOpRunE = func(cmd *cobra.Command, args []string) error { return nil }
   173  
   174  // NoOpFailRunE causes the test to fail if run is called.  Useful for validating run isn't called for
   175  // errors.
   176  type NoOpFailRunE struct {
   177  	t *testing.T
   178  }
   179  
   180  func (t NoOpFailRunE) runE(_ *cobra.Command, _ []string) error {
   181  	assert.Fail(t.t, "run should not be called")
   182  	return nil
   183  }
   184  
   185  // TestCmd_Execute_flagAndArgParsing verifies that the flags and args are parsed into the correct Command fields
   186  func TestCmd_Execute_flagAndArgParsing(t *testing.T) {
   187  	var pathPrefix string
   188  	if runtime.GOOS == "darwin" {
   189  		pathPrefix = "/private"
   190  	}
   191  
   192  	_, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{
   193  		Data:   testutil.Dataset1,
   194  		Branch: "master",
   195  	})
   196  	defer clean()
   197  
   198  	defer testutil.Chdir(t, w.WorkspaceDirectory)()
   199  
   200  	failRun := NoOpFailRunE{t: t}.runE
   201  
   202  	testCases := map[string]struct {
   203  		argsFunc    func(repo, dir string) []string
   204  		runE        func(*cobra.Command, []string) error
   205  		validations func(repo, dir string, r *get.Runner, err error)
   206  	}{
   207  		"must have at least 1 arg": {
   208  			argsFunc: func(repo, _ string) []string {
   209  				return []string{}
   210  			},
   211  			runE: failRun,
   212  			validations: func(_, _ string, r *get.Runner, err error) {
   213  				assert.EqualError(t, err, "requires at least 1 arg(s), only received 0")
   214  			},
   215  		},
   216  		"must provide unambiguous repo, dir and version": {
   217  			argsFunc: func(repo, _ string) []string {
   218  				return []string{"foo", "bar", "baz"}
   219  			},
   220  			runE: failRun,
   221  			validations: func(_, _ string, r *get.Runner, err error) {
   222  				assert.Error(t, err)
   223  				assert.Contains(t, err.Error(), "ambiguous repo/dir@version specify '.git' in argument")
   224  			},
   225  		},
   226  		"repo arg is split up correctly into ref and repo": {
   227  			argsFunc: func(repo, _ string) []string {
   228  				return []string{"something://foo.git/@master", "./"}
   229  			},
   230  			runE: NoOpRunE,
   231  			validations: func(_, _ string, r *get.Runner, err error) {
   232  				assert.NoError(t, err)
   233  				assert.Equal(t, "master", r.Get.Git.Ref)
   234  				assert.Equal(t, "something://foo", r.Get.Git.Repo)
   235  				assert.Equal(t, filepath.Join(pathPrefix, w.WorkspaceDirectory, "foo"), r.Get.Destination)
   236  			},
   237  		},
   238  		"repo arg is split up correctly into ref, directory and repo": {
   239  			argsFunc: func(repo, _ string) []string {
   240  				return []string{fmt.Sprintf("file://%s.git/blueprints/java", repo), "."}
   241  			},
   242  			runE: NoOpRunE,
   243  			validations: func(repo, _ string, r *get.Runner, err error) {
   244  				assert.NoError(t, err)
   245  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   246  				assert.Equal(t, "master", r.Get.Git.Ref)
   247  				assert.Equal(t, "/blueprints/java", r.Get.Git.Directory)
   248  				assert.Equal(t, filepath.Join(pathPrefix, w.WorkspaceDirectory, "java"), r.Get.Destination)
   249  			},
   250  		},
   251  		"current working dir -- should use package name": {
   252  			argsFunc: func(repo, _ string) []string {
   253  				return []string{fmt.Sprintf("file://%s.git/blueprints/java", repo), "foo/../bar/../"}
   254  			},
   255  			runE: NoOpRunE,
   256  			validations: func(repo, _ string, r *get.Runner, err error) {
   257  				assert.NoError(t, err)
   258  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   259  				assert.Equal(t, "master", r.Get.Git.Ref)
   260  				assert.Equal(t, "/blueprints/java", r.Get.Git.Directory)
   261  				assert.Equal(t, filepath.Join(pathPrefix, w.WorkspaceDirectory, "java"), r.Get.Destination)
   262  			},
   263  		},
   264  		"clean relative path": {
   265  			argsFunc: func(repo, _ string) []string {
   266  				return []string{fmt.Sprintf("file://%s.git/blueprints/java", repo), "./foo/../bar/../baz"}
   267  			},
   268  			runE: NoOpRunE,
   269  			validations: func(repo, _ string, r *get.Runner, err error) {
   270  				assert.NoError(t, err)
   271  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   272  				assert.Equal(t, "master", r.Get.Git.Ref)
   273  				assert.Equal(t, "/blueprints/java", r.Get.Git.Directory)
   274  				assert.Equal(t, filepath.Join(pathPrefix, w.WorkspaceDirectory, "baz"), r.Get.Destination)
   275  			},
   276  		},
   277  		"clean absolute path": {
   278  			argsFunc: func(repo, _ string) []string {
   279  				return []string{fmt.Sprintf("file://%s.git/blueprints/java", repo), "/foo/../bar/../baz"}
   280  			},
   281  			runE: NoOpRunE,
   282  			validations: func(repo, _ string, r *get.Runner, err error) {
   283  				assert.NoError(t, err)
   284  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   285  				assert.Equal(t, "master", r.Get.Git.Ref)
   286  				assert.Equal(t, "/blueprints/java", r.Get.Git.Directory)
   287  				assert.Equal(t, "/baz", r.Get.Destination)
   288  			},
   289  		},
   290  		"provide an absolute destination directory": {
   291  			argsFunc: func(repo, dir string) []string {
   292  				return []string{fmt.Sprintf("file://%s.git", repo), filepath.Join(dir, "my-app")}
   293  			},
   294  			runE: NoOpRunE,
   295  			validations: func(repo, dir string, r *get.Runner, err error) {
   296  				assert.NoError(t, err)
   297  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   298  				assert.Equal(t, "master", r.Get.Git.Ref)
   299  				assert.Equal(t, filepath.Join(dir, "my-app"), r.Get.Destination)
   300  			},
   301  		},
   302  		"package in a subdirectory": {
   303  			argsFunc: func(repo, dir string) []string {
   304  				return []string{fmt.Sprintf("file://%s.git/baz", repo), filepath.Join(dir, "my-app")}
   305  			},
   306  			runE: NoOpRunE,
   307  			validations: func(repo, dir string, r *get.Runner, err error) {
   308  				assert.NoError(t, err)
   309  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   310  				assert.Equal(t, "/baz", r.Get.Git.Directory)
   311  				assert.Equal(t, "master", r.Get.Git.Ref)
   312  				assert.Equal(t, filepath.Join(dir, "my-app"), r.Get.Destination)
   313  			},
   314  		},
   315  		"package in a subdirectory at a specific ref": {
   316  			argsFunc: func(repo, dir string) []string {
   317  				return []string{fmt.Sprintf("file://%s.git/baz@v1", repo), filepath.Join(dir, "my-app")}
   318  			},
   319  			runE: NoOpRunE,
   320  			validations: func(repo, dir string, r *get.Runner, err error) {
   321  				assert.NoError(t, err)
   322  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   323  				assert.Equal(t, "/baz", r.Get.Git.Directory)
   324  				assert.Equal(t, "v1", r.Get.Git.Ref)
   325  				assert.Equal(t, filepath.Join(dir, "my-app"), r.Get.Destination)
   326  			},
   327  		},
   328  		"provided directory already exists": {
   329  			argsFunc: func(repo, dir string) []string {
   330  				return []string{fmt.Sprintf("file://%s.git", repo), filepath.Join(dir, "package")}
   331  			},
   332  			runE: NoOpRunE,
   333  			validations: func(repo, dir string, r *get.Runner, err error) {
   334  				assert.NoError(t, err)
   335  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   336  				assert.Equal(t, "master", r.Get.Git.Ref)
   337  				assert.Equal(t, filepath.Join(dir, "package"), r.Get.Destination)
   338  			},
   339  		},
   340  		"invalid repo": {
   341  			argsFunc: func(repo, dir string) []string {
   342  				return []string{"/", filepath.Join(dir, "package", "my-app")}
   343  			},
   344  			runE: failRun,
   345  			validations: func(repo, dir string, r *get.Runner, err error) {
   346  				assert.Error(t, err)
   347  				assert.Contains(t, err.Error(), "specify '.git'")
   348  			},
   349  		},
   350  		"valid strategy provided": {
   351  			argsFunc: func(repo, dir string) []string {
   352  				return []string{fmt.Sprintf("file://%s.git", repo), filepath.Join(dir, "package"), "--strategy=fast-forward"}
   353  			},
   354  			runE: NoOpRunE,
   355  			validations: func(repo, dir string, r *get.Runner, err error) {
   356  				assert.NoError(t, err)
   357  				assert.Equal(t, fmt.Sprintf("file://%s", repo), r.Get.Git.Repo)
   358  				assert.Equal(t, "master", r.Get.Git.Ref)
   359  				assert.Equal(t, filepath.Join(dir, "package"), r.Get.Destination)
   360  				assert.Equal(t, kptfilev1.FastForward, r.Get.UpdateStrategy)
   361  			},
   362  		},
   363  		"invalid strategy provided": {
   364  			argsFunc: func(repo, dir string) []string {
   365  				return []string{fmt.Sprintf("file://%s.git", repo), filepath.Join(dir, "package"), "--strategy=does-not-exist"}
   366  			},
   367  			runE: failRun,
   368  			validations: func(repo, dir string, r *get.Runner, err error) {
   369  				assert.Error(t, err)
   370  				assert.Contains(t, err.Error(), "unknown update strategy \"does-not-exist\"")
   371  			},
   372  		},
   373  	}
   374  
   375  	for tn, tc := range testCases {
   376  		t.Run(tn, func(t *testing.T) {
   377  			g, w, clean := testutil.SetupRepoAndWorkspace(t, testutil.Content{
   378  				Data:   testutil.Dataset1,
   379  				Branch: "master",
   380  			})
   381  			defer clean()
   382  
   383  			r := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt")
   384  			r.Command.SilenceErrors = true
   385  			r.Command.SilenceUsage = true
   386  			r.Command.RunE = tc.runE
   387  			r.Command.SetArgs(tc.argsFunc(g.RepoDirectory, w.WorkspaceDirectory))
   388  			err := r.Command.Execute()
   389  			tc.validations(g.RepoDirectory, w.WorkspaceDirectory, r, err)
   390  		})
   391  	}
   392  }
   393  
   394  func TestCmd_flagAndArgParsing_Symlink(t *testing.T) {
   395  	dir := t.TempDir()
   396  	defer testutil.Chdir(t, dir)()
   397  
   398  	err := os.MkdirAll(filepath.Join(dir, "path", "to", "pkg", "dir"), 0700)
   399  	assert.NoError(t, err)
   400  	err = os.Symlink(filepath.Join("path", "to", "pkg", "dir"), "link")
   401  	assert.NoError(t, err)
   402  
   403  	r := get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt")
   404  	r.Command.RunE = NoOpRunE
   405  	r.Command.SetArgs([]string{"file://foo.git" + "@refs/heads/foo", "link"})
   406  	err = r.Command.Execute()
   407  	assert.NoError(t, err)
   408  	cwd, err := os.Getwd()
   409  	assert.NoError(t, err)
   410  	assert.Equal(t, filepath.Join(cwd, "path", "to", "pkg", "dir", "foo"), r.Get.Destination)
   411  
   412  	// make the link broken by deleting the dir
   413  	err = os.RemoveAll(filepath.Join("path", "to", "pkg", "dir"))
   414  	assert.NoError(t, err)
   415  	r = get.NewRunner(fake.CtxWithDefaultPrinter(), "kpt")
   416  	r.Command.RunE = NoOpRunE
   417  	r.Command.SetArgs([]string{"file://foo.git" + "@refs/heads/foo", "link"})
   418  	err = r.Command.Execute()
   419  	assert.Error(t, err)
   420  	assert.Contains(t, err.Error(), "no such file or directory")
   421  }