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 }