...

Source file src/sigs.k8s.io/kustomize/api/internal/git/repospec_test.go

Documentation: sigs.k8s.io/kustomize/api/internal/git

     1  // Copyright 2019 The Kubernetes Authors.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package git
     5  
     6  import (
     7  	"fmt"
     8  	"path/filepath"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestNewRepoSpecFromUrl_Permute(t *testing.T) {
    17  	// Generate all many permutations of host, RepoPath, pathName, and ref.
    18  	// Not all combinations make sense, but the parsing is very permissive and
    19  	// we probably stil don't want to break backwards compatibility for things
    20  	// that are unintentionally supported.
    21  	var schemeAuthority = []struct{ raw, normalized string }{
    22  		{"gitHub.com/", "https://github.com/"},
    23  		{"github.com:", "https://github.com/"},
    24  		{"http://github.com/", "https://github.com/"},
    25  		{"https://github.com/", "https://github.com/"},
    26  		{"hTTps://github.com/", "https://github.com/"},
    27  		{"https://git-codecommit.us-east-2.amazonaws.com/", "https://git-codecommit.us-east-2.amazonaws.com/"},
    28  		{"https://fabrikops2.visualstudio.com/", "https://fabrikops2.visualstudio.com/"},
    29  		{"ssh://git.example.com:7999/", "ssh://git.example.com:7999/"},
    30  		{"git::https://gitlab.com/", "https://gitlab.com/"},
    31  		{"git::http://git.example.com/", "http://git.example.com/"},
    32  		{"git::https://git.example.com/", "https://git.example.com/"},
    33  		{"git@github.com:", "git@github.com:"},
    34  		{"git@github.com/", "git@github.com:"},
    35  		{"git::git@github.com:", "git@github.com:"},
    36  		{"org-12345@github.com:", "org-12345@github.com:"},
    37  		{"org-12345@github.com/", "org-12345@github.com:"},
    38  	}
    39  	var repoPaths = []string{"someOrg/someRepo", "kubernetes/website"}
    40  	var pathNames = []string{"README.md", "foo/krusty.txt", ""}
    41  	var refArgs = []string{"group/version", "someBranch", "master", "v0.1.0", ""}
    42  
    43  	makeURL := func(hostFmt, repoPath, path, ref string) string {
    44  		if len(path) > 0 {
    45  			repoPath = filepath.Join(repoPath, path)
    46  		}
    47  		url := hostFmt + repoPath
    48  		if ref != "" {
    49  			url += refQuery + ref
    50  		}
    51  		return url
    52  	}
    53  
    54  	var i int
    55  	for _, v := range schemeAuthority {
    56  		hostRaw := v.raw
    57  		hostSpec := v.normalized
    58  		for _, repoPath := range repoPaths {
    59  			for _, pathName := range pathNames {
    60  				for _, hrefArg := range refArgs {
    61  					t.Run(fmt.Sprintf("t%d", i), func(t *testing.T) {
    62  						uri := makeURL(hostRaw, repoPath, pathName, hrefArg)
    63  						rs, err := NewRepoSpecFromURL(uri)
    64  						require.NoErrorf(t, err, "unexpected error creating RepoSpec for uri %s", uri)
    65  						assert.Equal(t, hostSpec, rs.Host, "unexpected host for uri %s", uri)
    66  						assert.Equal(t, repoPath, rs.RepoPath, "unexpected RepoPath for uri %s", uri)
    67  						assert.Equal(t, pathName, rs.KustRootPath, "unexpected KustRootPath for uri %s", uri)
    68  						assert.Equal(t, hrefArg, rs.Ref, "unexpected ref for uri %s", uri)
    69  					})
    70  					i++
    71  				}
    72  			}
    73  		}
    74  	}
    75  }
    76  
    77  func TestNewRepoSpecFromUrlErrors(t *testing.T) {
    78  	badData := map[string]struct {
    79  		url, error string
    80  	}{
    81  		"absolute_path": {
    82  			"/tmp",
    83  			"uri looks like abs path",
    84  		},
    85  		"relative path": {
    86  			"../../tmp",
    87  			"failed to parse scheme",
    88  		},
    89  		"local path that looks somewhat like a github url": {
    90  			"src/github.com/org/repo/path",
    91  			"failed to parse scheme",
    92  		},
    93  		"no_slashes": {
    94  			"iauhsdiuashduas",
    95  			"failed to parse scheme",
    96  		},
    97  		"bad_scheme": {
    98  			"htxxxtp://github.com/",
    99  			"failed to parse scheme",
   100  		},
   101  		"no_org_repo": {
   102  			"ssh://git.example.com",
   103  			"failed to parse repo path segment",
   104  		},
   105  		"hashicorp_git_only": {
   106  			"git::___",
   107  			"failed to parse scheme",
   108  		},
   109  		"query_after_host": {
   110  			"https://host?ref=group/version/minor_version",
   111  			"failed to parse repo path segment",
   112  		},
   113  		"path_exits_repo": {
   114  			"https://github.com/org/repo.git//path/../../exits/repo",
   115  			"url path exits repo",
   116  		},
   117  		"bad github separator": {
   118  			"github.com!org/repo.git//path",
   119  			"failed to parse scheme",
   120  		},
   121  		"mysterious gh: prefix previously supported is no longer handled": {
   122  			"gh:org/repo",
   123  			"failed to parse scheme",
   124  		},
   125  		"invalid Github url missing orgrepo": {
   126  			"https://github.com/thisisa404.yaml",
   127  			"failed to parse repo path segment",
   128  		},
   129  		"file protocol with excessive slashes": { // max valid is three: two for the scheme and one for the root
   130  			"file:////tmp//path/to/whatever",
   131  			"failed to parse repo path segment",
   132  		},
   133  		"unsupported protocol after username (invalid)": {
   134  			"git@scp://github.com/org/repo.git//path",
   135  			"failed to parse repo path segment",
   136  		},
   137  		"supported protocol after username (invalid)": {
   138  			"git@ssh://github.com/org/repo.git//path",
   139  			"failed to parse repo path segment",
   140  		},
   141  	}
   142  
   143  	for name, testCase := range badData {
   144  		t.Run(name, func(t *testing.T) {
   145  			_, err := NewRepoSpecFromURL(testCase.url)
   146  			require.Error(t, err)
   147  			require.Contains(t, err.Error(), testCase.error)
   148  		})
   149  	}
   150  }
   151  
   152  func TestNewRepoSpecFromUrl_Smoke(t *testing.T) {
   153  	// A set of end to end parsing tests that smoke out obvious issues
   154  	// No tests for submodules and timeout as the expectations are hard-coded
   155  	// to the defaults for compactness.
   156  	testcases := []struct {
   157  		name      string
   158  		input     string
   159  		repoSpec  RepoSpec
   160  		cloneSpec string
   161  		absPath   string
   162  	}{
   163  		{
   164  			name:      "https aws code commit url",
   165  			input:     "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo/somedir",
   166  			cloneSpec: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo",
   167  			absPath:   notCloned.Join("somedir"),
   168  			repoSpec: RepoSpec{
   169  				Host:         "https://git-codecommit.us-east-2.amazonaws.com/",
   170  				RepoPath:     "someorg/somerepo",
   171  				KustRootPath: "somedir",
   172  			},
   173  		},
   174  		{
   175  			name:      "https aws code commit url with params",
   176  			input:     "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo/somedir?ref=testbranch",
   177  			cloneSpec: "https://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo",
   178  			absPath:   notCloned.Join("somedir"),
   179  			repoSpec: RepoSpec{
   180  				Host:         "https://git-codecommit.us-east-2.amazonaws.com/",
   181  				RepoPath:     "someorg/somerepo",
   182  				KustRootPath: "somedir",
   183  				Ref:          "testbranch",
   184  			},
   185  		},
   186  		{
   187  			name:      "legacy azure https url with params",
   188  			input:     "https://fabrikops2.visualstudio.com/someorg/somerepo?ref=master",
   189  			cloneSpec: "https://fabrikops2.visualstudio.com/someorg/somerepo",
   190  			absPath:   notCloned.String(),
   191  			repoSpec: RepoSpec{
   192  				Host:     "https://fabrikops2.visualstudio.com/",
   193  				RepoPath: "someorg/somerepo",
   194  				Ref:      "master",
   195  			},
   196  		},
   197  		{
   198  			name:      "http github url without git suffix",
   199  			input:     "http://github.com/someorg/somerepo/somedir",
   200  			cloneSpec: "https://github.com/someorg/somerepo",
   201  			absPath:   notCloned.Join("somedir"),
   202  			repoSpec: RepoSpec{
   203  				Host:         "https://github.com/",
   204  				RepoPath:     "someorg/somerepo",
   205  				KustRootPath: "somedir",
   206  			},
   207  		},
   208  		{
   209  			name:      "scp github url without git suffix",
   210  			input:     "git@github.com:someorg/somerepo/somedir",
   211  			cloneSpec: "git@github.com:someorg/somerepo",
   212  			absPath:   notCloned.Join("somedir"),
   213  			repoSpec: RepoSpec{
   214  				Host:         "git@github.com:",
   215  				RepoPath:     "someorg/somerepo",
   216  				KustRootPath: "somedir",
   217  			},
   218  		},
   219  		{
   220  			name:      "http github url with git suffix",
   221  			input:     "http://github.com/someorg/somerepo.git/somedir",
   222  			cloneSpec: "https://github.com/someorg/somerepo.git",
   223  			absPath:   notCloned.Join("somedir"),
   224  			repoSpec: RepoSpec{
   225  				Host:         "https://github.com/",
   226  				RepoPath:     "someorg/somerepo.git",
   227  				KustRootPath: "somedir",
   228  			},
   229  		},
   230  		{
   231  			name:      "scp github url with git suffix",
   232  			input:     "git@github.com:someorg/somerepo.git/somedir",
   233  			cloneSpec: "git@github.com:someorg/somerepo.git",
   234  			absPath:   notCloned.Join("somedir"),
   235  			repoSpec: RepoSpec{
   236  				Host:         "git@github.com:",
   237  				RepoPath:     "someorg/somerepo.git",
   238  				KustRootPath: "somedir",
   239  			},
   240  		},
   241  		{
   242  			name:      "non-github_scp",
   243  			input:     "git@gitlab2.sqtools.ru:infra/kubernetes/thanos-base.git?ref=v0.1.0",
   244  			cloneSpec: "git@gitlab2.sqtools.ru:infra/kubernetes/thanos-base.git",
   245  			absPath:   notCloned.String(),
   246  			repoSpec: RepoSpec{
   247  				Host:     "git@gitlab2.sqtools.ru:",
   248  				RepoPath: "infra/kubernetes/thanos-base.git",
   249  				Ref:      "v0.1.0",
   250  			},
   251  		},
   252  		{
   253  			name:      "non-github_scp with path delimiter",
   254  			input:     "git@bitbucket.org:company/project.git//path?ref=branch",
   255  			cloneSpec: "git@bitbucket.org:company/project.git",
   256  			absPath:   notCloned.Join("path"),
   257  			repoSpec: RepoSpec{
   258  				Host:         "git@bitbucket.org:",
   259  				RepoPath:     "company/project.git",
   260  				KustRootPath: "path",
   261  				Ref:          "branch",
   262  			},
   263  		},
   264  		{
   265  			name:      "non-github_scp incorrectly using slash (invalid but currently passed through to git)",
   266  			input:     "git@bitbucket.org/company/project.git//path?ref=branch",
   267  			cloneSpec: "git@bitbucket.org/company/project.git",
   268  			absPath:   notCloned.Join("path"),
   269  			repoSpec: RepoSpec{
   270  				Host:         "git@bitbucket.org/",
   271  				RepoPath:     "company/project.git",
   272  				KustRootPath: "path",
   273  				Ref:          "branch",
   274  			},
   275  		},
   276  		{
   277  			name:      "non-github_git-user_ssh",
   278  			input:     "ssh://git@bitbucket.org/company/project.git//path?ref=branch",
   279  			cloneSpec: "ssh://git@bitbucket.org/company/project.git",
   280  			absPath:   notCloned.Join("path"),
   281  			repoSpec: RepoSpec{
   282  				Host:         "ssh://git@bitbucket.org/",
   283  				RepoPath:     "company/project.git",
   284  				KustRootPath: "path",
   285  				Ref:          "branch",
   286  			},
   287  		},
   288  		{
   289  			name:      "_git host delimiter in non-github url",
   290  			input:     "https://itfs.mycompany.com/collection/project/_git/somerepos",
   291  			cloneSpec: "https://itfs.mycompany.com/collection/project/_git/somerepos",
   292  			absPath:   notCloned.String(),
   293  			repoSpec: RepoSpec{
   294  				Host:     "https://itfs.mycompany.com/",
   295  				RepoPath: "collection/project/_git/somerepos",
   296  			},
   297  		},
   298  		{
   299  			name:      "_git host delimiter in non-github url with params",
   300  			input:     "https://itfs.mycompany.com/collection/project/_git/somerepos?version=v1.0.0",
   301  			cloneSpec: "https://itfs.mycompany.com/collection/project/_git/somerepos",
   302  			absPath:   notCloned.String(),
   303  			repoSpec: RepoSpec{
   304  				Host:     "https://itfs.mycompany.com/",
   305  				RepoPath: "collection/project/_git/somerepos",
   306  				Ref:      "v1.0.0",
   307  			},
   308  		},
   309  		{
   310  			name:      "_git host delimiter in non-github url with kust root path and params",
   311  			input:     "https://itfs.mycompany.com/collection/project/_git/somerepos/somedir?version=v1.0.0",
   312  			cloneSpec: "https://itfs.mycompany.com/collection/project/_git/somerepos",
   313  			absPath:   notCloned.Join("somedir"),
   314  			repoSpec: RepoSpec{
   315  				Host:         "https://itfs.mycompany.com/",
   316  				RepoPath:     "collection/project/_git/somerepos",
   317  				KustRootPath: "somedir",
   318  				Ref:          "v1.0.0",
   319  			},
   320  		},
   321  		{
   322  			name:      "_git host delimiter in non-github url with no kust root path",
   323  			input:     "git::https://itfs.mycompany.com/collection/project/_git/somerepos",
   324  			cloneSpec: "https://itfs.mycompany.com/collection/project/_git/somerepos",
   325  			absPath:   notCloned.String(),
   326  			repoSpec: RepoSpec{
   327  				Host:     "https://itfs.mycompany.com/",
   328  				RepoPath: "collection/project/_git/somerepos",
   329  			},
   330  		},
   331  		{
   332  			name:      "https bitbucket url with git suffix",
   333  			input:     "https://bitbucket.example.com/scm/project/repository.git",
   334  			cloneSpec: "https://bitbucket.example.com/scm/project/repository.git",
   335  			absPath:   notCloned.String(),
   336  			repoSpec: RepoSpec{
   337  				Host:     "https://bitbucket.example.com/",
   338  				RepoPath: "scm/project/repository.git",
   339  			},
   340  		},
   341  		{
   342  			name:      "ssh aws code commit url",
   343  			input:     "ssh://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo/somepath",
   344  			cloneSpec: "ssh://git-codecommit.us-east-2.amazonaws.com/someorg/somerepo",
   345  			absPath:   notCloned.Join("somepath"),
   346  			repoSpec: RepoSpec{
   347  				Host:         "ssh://git-codecommit.us-east-2.amazonaws.com/",
   348  				RepoPath:     "someorg/somerepo",
   349  				KustRootPath: "somepath",
   350  			},
   351  		},
   352  		{
   353  			name:      "scp Github with slash fixed to colon",
   354  			input:     "git@github.com/someorg/somerepo/somepath",
   355  			cloneSpec: "git@github.com:someorg/somerepo",
   356  			absPath:   notCloned.Join("somepath"),
   357  			repoSpec: RepoSpec{
   358  				Host:         "git@github.com:",
   359  				RepoPath:     "someorg/somerepo",
   360  				KustRootPath: "somepath",
   361  			},
   362  		},
   363  		{
   364  			name:      "https Github with double slash path delimiter and params",
   365  			input:     "https://github.com/kubernetes-sigs/kustomize//examples/multibases/dev/?ref=v1.0.6",
   366  			cloneSpec: "https://github.com/kubernetes-sigs/kustomize",
   367  			absPath:   notCloned.Join("/examples/multibases/dev"),
   368  			repoSpec: RepoSpec{
   369  				Host:         "https://github.com/",
   370  				RepoPath:     "kubernetes-sigs/kustomize",
   371  				KustRootPath: "examples/multibases/dev/",
   372  				Ref:          "v1.0.6",
   373  			},
   374  		},
   375  		{
   376  			name:      "file protocol with git-suffixed repo path and params",
   377  			input:     "file://a/b/c/someRepo.git/somepath?ref=someBranch",
   378  			cloneSpec: "file://a/b/c/someRepo.git",
   379  			absPath:   notCloned.Join("somepath"),
   380  			repoSpec: RepoSpec{
   381  				Host:         "file://",
   382  				RepoPath:     "a/b/c/someRepo.git",
   383  				KustRootPath: "somepath",
   384  				Ref:          "someBranch",
   385  			},
   386  		},
   387  		{
   388  			name:      "file protocol with two slashes, with kust root path and params",
   389  			input:     "file://a/b/c/someRepo//somepath?ref=someBranch",
   390  			cloneSpec: "file://a/b/c/someRepo",
   391  			absPath:   notCloned.Join("somepath"),
   392  			repoSpec: RepoSpec{
   393  				Host:         "file://",
   394  				RepoPath:     "a/b/c/someRepo",
   395  				KustRootPath: "somepath",
   396  				Ref:          "someBranch",
   397  			},
   398  		},
   399  		{
   400  			name:      "file protocol with two slashes, with ref and no kust root path",
   401  			input:     "file://a/b/c/someRepo?ref=someBranch",
   402  			cloneSpec: "file://a/b/c/someRepo",
   403  			absPath:   notCloned.String(),
   404  			repoSpec: RepoSpec{
   405  				Host:     "file://",
   406  				RepoPath: "a/b/c/someRepo",
   407  				Ref:      "someBranch",
   408  			},
   409  		},
   410  		{
   411  			name:      "file protocol with three slashes, with ref and no kust root path",
   412  			input:     "file:///a/b/c/someRepo?ref=someBranch",
   413  			cloneSpec: "file:///a/b/c/someRepo",
   414  			absPath:   notCloned.String(),
   415  			repoSpec: RepoSpec{
   416  				Host:     "file://",
   417  				RepoPath: "/a/b/c/someRepo",
   418  				Ref:      "someBranch",
   419  			},
   420  		},
   421  		{
   422  			name:      "ssh Github with double-slashed path delimiter and params",
   423  			input:     "ssh://git@github.com/kubernetes-sigs/kustomize//examples/multibases/dev?ref=v1.0.6",
   424  			cloneSpec: "git@github.com:kubernetes-sigs/kustomize",
   425  			absPath:   notCloned.Join("examples/multibases/dev"),
   426  			repoSpec: RepoSpec{
   427  				Host:         "git@github.com:",
   428  				RepoPath:     "kubernetes-sigs/kustomize",
   429  				KustRootPath: "examples/multibases/dev",
   430  				Ref:          "v1.0.6",
   431  			},
   432  		},
   433  		{
   434  			name:      "file protocol with three slashes, no kust root path or params",
   435  			input:     "file:///a/b/c/someRepo",
   436  			cloneSpec: "file:///a/b/c/someRepo",
   437  			absPath:   notCloned.String(),
   438  			repoSpec: RepoSpec{
   439  				Host:     "file://",
   440  				RepoPath: "/a/b/c/someRepo",
   441  			},
   442  		},
   443  		{
   444  			name:      "file protocol with three slashes, no repo or kust root path or params",
   445  			input:     "file:///",
   446  			cloneSpec: "file:///",
   447  			absPath:   notCloned.String(),
   448  			repoSpec: RepoSpec{
   449  				Host:     "file://",
   450  				RepoPath: "/",
   451  			},
   452  		},
   453  		{
   454  			name:      "arbitrary https host with double-slash path delimiter",
   455  			input:     "https://example.org/path/to/repo//examples/multibases/dev",
   456  			cloneSpec: "https://example.org/path/to/repo",
   457  			absPath:   notCloned.Join("/examples/multibases/dev"),
   458  			repoSpec: RepoSpec{
   459  				Host:         "https://example.org/",
   460  				RepoPath:     "path/to/repo",
   461  				KustRootPath: "examples/multibases/dev",
   462  			},
   463  		},
   464  		{
   465  			name:      "arbitrary https host with .git repo suffix",
   466  			input:     "https://example.org/path/to/repo.git/examples/multibases/dev",
   467  			cloneSpec: "https://example.org/path/to/repo.git",
   468  			absPath:   notCloned.Join("/examples/multibases/dev"),
   469  			repoSpec: RepoSpec{
   470  				Host:         "https://example.org/",
   471  				RepoPath:     "path/to/repo.git",
   472  				KustRootPath: "examples/multibases/dev",
   473  			},
   474  		},
   475  		{
   476  			name:      "arbitrary ssh host with double-slash path delimiter",
   477  			input:     "ssh://alice@example.com/path/to/repo//examples/multibases/dev",
   478  			cloneSpec: "ssh://alice@example.com/path/to/repo",
   479  			absPath:   notCloned.Join("/examples/multibases/dev"),
   480  			repoSpec: RepoSpec{
   481  				Host:         "ssh://alice@example.com/",
   482  				RepoPath:     "path/to/repo",
   483  				KustRootPath: "examples/multibases/dev",
   484  			},
   485  		},
   486  		{
   487  			name:      "query_slash",
   488  			input:     "https://authority/org/repo?ref=group/version",
   489  			cloneSpec: "https://authority/org/repo",
   490  			absPath:   notCloned.String(),
   491  			repoSpec: RepoSpec{
   492  				Host:     "https://authority/",
   493  				RepoPath: "org/repo",
   494  				Ref:      "group/version",
   495  			},
   496  		},
   497  		{
   498  			name:      "query_git_delimiter",
   499  			input:     "https://authority/org/repo/?ref=includes_git/for_some_reason",
   500  			cloneSpec: "https://authority/org/repo",
   501  			absPath:   notCloned.String(),
   502  			repoSpec: RepoSpec{
   503  				Host:     "https://authority/",
   504  				RepoPath: "org/repo",
   505  				Ref:      "includes_git/for_some_reason",
   506  			},
   507  		},
   508  		{
   509  			name:      "query_git_suffix",
   510  			input:     "https://authority/org/repo/?ref=includes.git/for_some_reason",
   511  			cloneSpec: "https://authority/org/repo",
   512  			absPath:   notCloned.String(),
   513  			repoSpec: RepoSpec{
   514  				Host:     "https://authority/",
   515  				RepoPath: "org/repo",
   516  				Ref:      "includes.git/for_some_reason",
   517  			},
   518  		},
   519  		{
   520  			name:      "non_parsable_path",
   521  			input:     "https://authority/org/repo/%-invalid-uri-so-not-parsable-by-net/url.Parse",
   522  			cloneSpec: "https://authority/org/repo",
   523  			absPath:   notCloned.Join("%-invalid-uri-so-not-parsable-by-net/url.Parse"),
   524  			repoSpec: RepoSpec{
   525  				Host:         "https://authority/",
   526  				RepoPath:     "org/repo",
   527  				KustRootPath: "%-invalid-uri-so-not-parsable-by-net/url.Parse",
   528  			},
   529  		},
   530  		{
   531  			name:      "non-git username with non-github host",
   532  			input:     "ssh://myusername@bitbucket.org/ourteamname/ourrepositoryname.git//path?ref=branch",
   533  			cloneSpec: "ssh://myusername@bitbucket.org/ourteamname/ourrepositoryname.git",
   534  			absPath:   notCloned.Join("path"),
   535  			repoSpec: RepoSpec{
   536  				Host:         "ssh://myusername@bitbucket.org/",
   537  				RepoPath:     "ourteamname/ourrepositoryname.git",
   538  				KustRootPath: "path",
   539  				Ref:          "branch",
   540  			},
   541  		},
   542  		{
   543  			name:      "username-like filepath with file protocol",
   544  			input:     "file://git@home/path/to/repository.git//path?ref=branch",
   545  			cloneSpec: "file://git@home/path/to/repository.git",
   546  			absPath:   notCloned.Join("path"),
   547  			repoSpec: RepoSpec{
   548  				Host:         "file://",
   549  				RepoPath:     "git@home/path/to/repository.git",
   550  				KustRootPath: "path",
   551  				Ref:          "branch",
   552  			},
   553  		},
   554  		{
   555  			name:      "username with http protocol (invalid but currently passed through to git)",
   556  			input:     "http://git@home.com/path/to/repository.git//path?ref=branch",
   557  			cloneSpec: "http://git@home.com/path/to/repository.git",
   558  			absPath:   notCloned.Join("path"),
   559  			repoSpec: RepoSpec{
   560  				Host:         "http://git@home.com/",
   561  				RepoPath:     "path/to/repository.git",
   562  				KustRootPath: "path",
   563  				Ref:          "branch",
   564  			},
   565  		},
   566  		{
   567  			name:      "username with https protocol (invalid but currently passed through to git)",
   568  			input:     "https://git@home.com/path/to/repository.git//path?ref=branch",
   569  			cloneSpec: "https://git@home.com/path/to/repository.git",
   570  			absPath:   notCloned.Join("path"),
   571  			repoSpec: RepoSpec{
   572  				Host:         "https://git@home.com/",
   573  				RepoPath:     "path/to/repository.git",
   574  				KustRootPath: "path",
   575  				Ref:          "branch",
   576  			},
   577  		},
   578  		{
   579  			name:      "complex github ssh url from docs",
   580  			input:     "ssh://git@ssh.github.com:443/YOUR-USERNAME/YOUR-REPOSITORY.git",
   581  			cloneSpec: "ssh://git@ssh.github.com:443/YOUR-USERNAME/YOUR-REPOSITORY.git",
   582  			absPath:   notCloned.String(),
   583  			repoSpec: RepoSpec{
   584  				Host:         "ssh://git@ssh.github.com:443/",
   585  				RepoPath:     "YOUR-USERNAME/YOUR-REPOSITORY.git",
   586  				KustRootPath: "",
   587  			},
   588  		},
   589  		{
   590  			name:      "colon behind slash not scp delimiter",
   591  			input:     "git@gitlab.com/user:name/YOUR-REPOSITORY.git/path",
   592  			cloneSpec: "git@gitlab.com/user:name/YOUR-REPOSITORY.git",
   593  			absPath:   notCloned.Join("path"),
   594  			repoSpec: RepoSpec{
   595  				Host:         "git@gitlab.com/",
   596  				RepoPath:     "user:name/YOUR-REPOSITORY.git",
   597  				KustRootPath: "path",
   598  			},
   599  		},
   600  		{
   601  			name:      "gitlab URLs with explicit git suffix",
   602  			input:     "git@gitlab.com:gitlab-tests/sample-project.git",
   603  			cloneSpec: "git@gitlab.com:gitlab-tests/sample-project.git",
   604  			absPath:   notCloned.String(),
   605  			repoSpec: RepoSpec{
   606  				Host:     "git@gitlab.com:",
   607  				RepoPath: "gitlab-tests/sample-project.git",
   608  			},
   609  		},
   610  		{
   611  			name:      "gitlab URLs without explicit git suffix",
   612  			input:     "git@gitlab.com:gitlab-tests/sample-project",
   613  			cloneSpec: "git@gitlab.com:gitlab-tests/sample-project",
   614  			absPath:   notCloned.String(),
   615  			repoSpec: RepoSpec{
   616  				Host:     "git@gitlab.com:",
   617  				RepoPath: "gitlab-tests/sample-project",
   618  			},
   619  		},
   620  		{
   621  			name:      "azure host with _git and // path separator",
   622  			input:     "https://username@dev.azure.com/org/project/_git/repo//path/to/kustomization/root",
   623  			cloneSpec: "https://username@dev.azure.com/org/project/_git/repo",
   624  			absPath:   notCloned.Join("path/to/kustomization/root"),
   625  			repoSpec: RepoSpec{
   626  				Host:         "https://username@dev.azure.com/",
   627  				RepoPath:     "org/project/_git/repo",
   628  				KustRootPath: "path/to/kustomization/root",
   629  			},
   630  		},
   631  		{
   632  			name:      "legacy format azure host with _git",
   633  			input:     "https://org.visualstudio.com/project/_git/repo/path/to/kustomization/root",
   634  			cloneSpec: "https://org.visualstudio.com/project/_git/repo",
   635  			absPath:   notCloned.Join("path/to/kustomization/root"),
   636  			repoSpec: RepoSpec{
   637  				Host:         "https://org.visualstudio.com/",
   638  				RepoPath:     "project/_git/repo",
   639  				KustRootPath: "path/to/kustomization/root",
   640  			},
   641  		},
   642  		{
   643  			name:      "ssh on github with custom username for custom ssh certificate authority",
   644  			input:     "ssh://org-12345@github.com/kubernetes-sigs/kustomize",
   645  			cloneSpec: "org-12345@github.com:kubernetes-sigs/kustomize",
   646  			absPath:   notCloned.String(),
   647  			repoSpec: RepoSpec{
   648  				Host:     "org-12345@github.com:",
   649  				RepoPath: "kubernetes-sigs/kustomize",
   650  			},
   651  		},
   652  		{
   653  			name:      "scp on github with custom username for custom ssh certificate authority",
   654  			input:     "org-12345@github.com/kubernetes-sigs/kustomize",
   655  			cloneSpec: "org-12345@github.com:kubernetes-sigs/kustomize",
   656  			absPath:   notCloned.String(),
   657  			repoSpec: RepoSpec{
   658  				Host:     "org-12345@github.com:",
   659  				RepoPath: "kubernetes-sigs/kustomize",
   660  			},
   661  		},
   662  		{
   663  			name:      "scp format gist url",
   664  			input:     "git@gist.github.com:bc7947cb727d7f9217e7862d961a1ffd.git",
   665  			cloneSpec: "git@gist.github.com:bc7947cb727d7f9217e7862d961a1ffd.git",
   666  			absPath:   notCloned.String(),
   667  			repoSpec: RepoSpec{
   668  				Host:     "git@gist.github.com:",
   669  				RepoPath: "bc7947cb727d7f9217e7862d961a1ffd.git",
   670  			},
   671  		},
   672  		{
   673  			name:      "https gist url",
   674  			input:     "https://gist.github.com/bc7947cb727d7f9217e7862d961a1ffd.git",
   675  			cloneSpec: "https://gist.github.com/bc7947cb727d7f9217e7862d961a1ffd.git",
   676  			absPath:   notCloned.String(),
   677  			repoSpec: RepoSpec{
   678  				Host:     "https://gist.github.com/",
   679  				RepoPath: "bc7947cb727d7f9217e7862d961a1ffd.git",
   680  			},
   681  		},
   682  	}
   683  	for _, tc := range testcases {
   684  		t.Run(tc.name, func(t *testing.T) {
   685  			rs, err := NewRepoSpecFromURL(tc.input)
   686  			require.NoError(t, err)
   687  			assert.Equal(t, tc.cloneSpec, rs.CloneSpec(), "cloneSpec mismatch")
   688  			assert.Equal(t, tc.absPath, rs.AbsPath(), "absPath mismatch")
   689  			// some values have defaults. Clear them here so test cases remain compact.
   690  			// This means submodules and timeout cannot be tested here. That's fine since
   691  			// they are tested in TestParseQuery.
   692  			rs.raw = ""
   693  			rs.Dir = ""
   694  			rs.Submodules = false
   695  			rs.Timeout = 0
   696  			assert.Equal(t, &tc.repoSpec, rs)
   697  		})
   698  	}
   699  }
   700  
   701  func TestNewRepoSpecFromURL_DefaultQueryParams(t *testing.T) {
   702  	repoSpec, err := NewRepoSpecFromURL("https://github.com/org/repo")
   703  	require.NoError(t, err)
   704  	require.Equal(t, defaultSubmodules, repoSpec.Submodules)
   705  	require.Equal(t, defaultTimeout, repoSpec.Timeout)
   706  }
   707  
   708  func TestParseQuery(t *testing.T) {
   709  	testcases := []struct {
   710  		name       string
   711  		input      string
   712  		ref        string
   713  		submodules bool
   714  		timeout    time.Duration
   715  	}{
   716  		{
   717  			name:       "empty",
   718  			input:      "",
   719  			ref:        "",
   720  			submodules: defaultSubmodules,
   721  			timeout:    defaultTimeout,
   722  		},
   723  		{
   724  			name:       "ref",
   725  			input:      "ref=v1.0.0",
   726  			ref:        "v1.0.0",
   727  			submodules: defaultSubmodules,
   728  			timeout:    defaultTimeout,
   729  		},
   730  		{
   731  			name:       "ref_slash",
   732  			input:      "ref=kustomize/v4.5.7",
   733  			ref:        "kustomize/v4.5.7",
   734  			submodules: defaultSubmodules,
   735  			timeout:    defaultTimeout,
   736  		},
   737  		{
   738  			name:       "version",
   739  			input:      "version=master",
   740  			ref:        "master",
   741  			submodules: defaultSubmodules,
   742  			timeout:    defaultTimeout,
   743  		},
   744  		{
   745  			name: "ref_and_version",
   746  			// A ref value takes precedence over a version value.
   747  			input:      "version=master&ref=v1.0.0",
   748  			ref:        "v1.0.0",
   749  			submodules: defaultSubmodules,
   750  			timeout:    defaultTimeout,
   751  		},
   752  		{
   753  			name: "empty_submodules",
   754  			// Empty submodules value uses default.
   755  			input:      "version=master&submodules=",
   756  			ref:        "master",
   757  			submodules: defaultSubmodules,
   758  			timeout:    defaultTimeout,
   759  		},
   760  		{
   761  			name: "bad_submodules",
   762  			// Malformed submodules value uses default.
   763  			input:      "version=master&submodules=maybe",
   764  			ref:        "master",
   765  			submodules: defaultSubmodules,
   766  			timeout:    defaultTimeout,
   767  		},
   768  		{
   769  			name:       "submodules_true",
   770  			input:      "version=master&submodules=true",
   771  			ref:        "master",
   772  			submodules: true,
   773  			timeout:    defaultTimeout,
   774  		},
   775  		{
   776  			name:       "submodules_false",
   777  			input:      "version=master&submodules=false",
   778  			ref:        "master",
   779  			submodules: false,
   780  			timeout:    defaultTimeout,
   781  		},
   782  		{
   783  			name: "empty_timeout",
   784  			// Empty timeout value uses default.
   785  			input:      "version=master&timeout=",
   786  			ref:        "master",
   787  			submodules: defaultSubmodules,
   788  			timeout:    defaultTimeout,
   789  		},
   790  		{
   791  			name: "bad_timeout",
   792  			// Malformed timeout value uses default.
   793  			input:      "version=master&timeout=jiffy",
   794  			ref:        "master",
   795  			submodules: defaultSubmodules,
   796  			timeout:    defaultTimeout,
   797  		},
   798  		{
   799  			name: "zero_timeout",
   800  			// Zero timeout value uses default.
   801  			input:      "version=master&timeout=0",
   802  			ref:        "master",
   803  			submodules: defaultSubmodules,
   804  			timeout:    defaultTimeout,
   805  		},
   806  		{
   807  			name:       "zero_unit_timeout",
   808  			input:      "version=master&timeout=0s",
   809  			ref:        "master",
   810  			submodules: defaultSubmodules,
   811  			timeout:    defaultTimeout,
   812  		},
   813  		{
   814  			name:       "timeout",
   815  			input:      "version=master&timeout=61",
   816  			ref:        "master",
   817  			submodules: defaultSubmodules,
   818  			timeout:    61 * time.Second,
   819  		},
   820  		{
   821  			name:       "timeout_unit",
   822  			input:      "version=master&timeout=1m1s",
   823  			ref:        "master",
   824  			submodules: defaultSubmodules,
   825  			timeout:    61 * time.Second,
   826  		},
   827  		{
   828  			name:       "all",
   829  			input:      "version=master&submodules=false&timeout=1m1s",
   830  			ref:        "master",
   831  			submodules: false,
   832  			timeout:    61 * time.Second,
   833  		},
   834  	}
   835  	for _, tc := range testcases {
   836  		t.Run(tc.name, func(t *testing.T) {
   837  			ref, timeout, submodules := parseQuery(tc.input)
   838  			assert.Equal(t, tc.ref, ref, "ref mismatch")
   839  			assert.Equal(t, tc.timeout, timeout, "timeout mismatch")
   840  			assert.Equal(t, tc.submodules, submodules, "submodules mismatch")
   841  		})
   842  	}
   843  }
   844  

View as plain text