From 08c928b18e87048e140f2570100419f63e34d11b Mon Sep 17 00:00:00 2001 From: Noam Gal Date: Tue, 15 Jun 2021 11:40:36 +0300 Subject: [PATCH 1/2] split query param to ref|tag|sha --- cmd/commands/app.go | 4 +- cmd/commands/repo.go | 3 +- cmd/commands/repo_test.go | 9 +- pkg/application/application.go | 15 +-- pkg/application/application_test.go | 2 +- pkg/git/repository.go | 9 +- pkg/git/repository_test.go | 8 +- pkg/util/repospec.go | 66 ++++------- pkg/util/repospec_test.go | 173 ++++++++-------------------- 9 files changed, 98 insertions(+), 191 deletions(-) diff --git a/cmd/commands/app.go b/cmd/commands/app.go index 4106c0e7..88c7d91b 100644 --- a/cmd/commands/app.go +++ b/cmd/commands/app.go @@ -170,9 +170,7 @@ func setAppOptsDefaults(ctx context.Context, repofs fs.FS, opts *AppCreateOption // local directory fsys = fs.Create(osfs.New(opts.AppOpts.AppSpecifier)) } else { - host, orgRepo, p, _, _, _, _ := util.ParseGitUrl(opts.AppOpts.AppSpecifier) - url := host + orgRepo - log.G().Infof("cloning repo: '%s', to infer app type from path '%s'", url, p) + log.G().Infof("trying to infer application type from '%s'", opts.AppOpts.AppSpecifier) cloneOpts := &git.CloneOptions{ Repo: opts.AppOpts.AppSpecifier, Auth: opts.CloneOpts.Auth, diff --git a/cmd/commands/repo.go b/cmd/commands/repo.go index 682a6207..d74d5d89 100644 --- a/cmd/commands/repo.go +++ b/cmd/commands/repo.go @@ -165,7 +165,7 @@ func NewRepoBootstrapCommand() *cobra.Command { repo bootstrap --repo https://github.com/example/repo/path/to/installation_root `), - PreRun: func(cmd *cobra.Command, args []string) { cloneOpts.Parse() }, + PreRun: func(_ *cobra.Command, _ []string) { cloneOpts.Parse() }, RunE: func(cmd *cobra.Command, args []string) error { return RunRepoBootstrap(cmd.Context(), &RepoBootstrapOptions{ AppSpecifier: appSpecifier, @@ -264,6 +264,7 @@ func RunRepoCreate(ctx context.Context, opts *RepoCreateOptions) (*git.CloneOpti Password: opts.Token, }, } + co.Parse() return co, nil } diff --git a/cmd/commands/repo_test.go b/cmd/commands/repo_test.go index 11d97e5a..cce3c224 100644 --- a/cmd/commands/repo_test.go +++ b/cmd/commands/repo_test.go @@ -56,7 +56,7 @@ func TestRunRepoCreate(t *testing.T) { Repo: "https://github.com/owner/name/path?ref=revision", } expected.Parse() - assert.Equal(t, "https://github.com/owner/name", cloneOpts.URL()) + assert.Equal(t, "https://github.com/owner/name.git", cloneOpts.URL()) assert.Equal(t, "revision", cloneOpts.Revision()) assert.Equal(t, "path", cloneOpts.Path()) mp.AssertCalled(t, "CreateRepository", mock.Anything, mock.Anything) @@ -247,7 +247,7 @@ func Test_buildBootstrapManifests(t *testing.T) { argocdApp := &argocdv1alpha1.Application{} assert.NoError(t, yaml.Unmarshal(b.argocdApp, argocdApp)) - assert.Equal(t, "https://github.com/foo/bar", argocdApp.Spec.Source.RepoURL) + assert.Equal(t, "https://github.com/foo/bar.git", argocdApp.Spec.Source.RepoURL) assert.Equal(t, filepath.Join("installation1", store.Default.BootsrtrapDir, store.Default.ArgoCDName), argocdApp.Spec.Source.Path) assert.Equal(t, "main", argocdApp.Spec.Source.TargetRevision) assert.Equal(t, 0, len(argocdApp.ObjectMeta.Finalizers)) @@ -256,7 +256,7 @@ func Test_buildBootstrapManifests(t *testing.T) { bootstrapApp := &argocdv1alpha1.Application{} assert.NoError(t, yaml.Unmarshal(b.bootstrapApp, bootstrapApp)) - assert.Equal(t, "https://github.com/foo/bar", bootstrapApp.Spec.Source.RepoURL) + assert.Equal(t, "https://github.com/foo/bar.git", bootstrapApp.Spec.Source.RepoURL) assert.Equal(t, filepath.Join("installation1", store.Default.BootsrtrapDir), bootstrapApp.Spec.Source.Path) assert.Equal(t, "main", bootstrapApp.Spec.Source.TargetRevision) assert.NotEqual(t, 0, len(bootstrapApp.ObjectMeta.Finalizers)) @@ -265,7 +265,7 @@ func Test_buildBootstrapManifests(t *testing.T) { rootApp := &argocdv1alpha1.Application{} assert.NoError(t, yaml.Unmarshal(b.rootApp, rootApp)) - assert.Equal(t, "https://github.com/foo/bar", rootApp.Spec.Source.RepoURL) + assert.Equal(t, "https://github.com/foo/bar.git", rootApp.Spec.Source.RepoURL) assert.Equal(t, filepath.Join("installation1", store.Default.ProjectsDir), rootApp.Spec.Source.Path) assert.Equal(t, "main", rootApp.Spec.Source.TargetRevision) assert.NotEqual(t, 0, len(rootApp.ObjectMeta.Finalizers)) @@ -296,7 +296,6 @@ func Test_buildBootstrapManifests(t *testing.T) { for tname, tt := range tests { t.Run(tname, func(t *testing.T) { tt.args.cloneOpts.Parse() - b, ret := buildBootstrapManifests( tt.args.namespace, tt.args.appSpecifier, diff --git a/pkg/application/application.go b/pkg/application/application.go index 55de787c..10a35e9a 100644 --- a/pkg/application/application.go +++ b/pkg/application/application.go @@ -10,10 +10,10 @@ import ( "reflect" "github.com/argoproj-labs/argocd-autopilot/pkg/fs" + "github.com/argoproj-labs/argocd-autopilot/pkg/git" "github.com/argoproj-labs/argocd-autopilot/pkg/kube" "github.com/argoproj-labs/argocd-autopilot/pkg/log" "github.com/argoproj-labs/argocd-autopilot/pkg/store" - "github.com/argoproj-labs/argocd-autopilot/pkg/util" "github.com/ghodss/yaml" billyUtils "github.com/go-git/go-billy/v5/util" @@ -388,17 +388,18 @@ func newDirApp(opts *CreateOptions) *dirApp { app := &dirApp{ baseApp: baseApp{opts}, } - - host, orgRepo, path, gitRef, _, _, _ := util.ParseGitUrl(opts.AppSpecifier) - url := host + orgRepo + cloneOpts := &git.CloneOptions{ + Repo: opts.AppSpecifier, + } + cloneOpts.Parse() app.config = &Config{ AppName: opts.AppName, UserGivenName: opts.AppName, DestNamespace: opts.DestNamespace, DestServer: opts.DestServer, - SrcRepoURL: url, - SrcPath: path, - SrcTargetRevision: gitRef, + SrcRepoURL: cloneOpts.URL(), + SrcPath: cloneOpts.Path(), + SrcTargetRevision: cloneOpts.Revision(), } return app diff --git a/pkg/application/application_test.go b/pkg/application/application_test.go index 28ac0dad..d00718e7 100644 --- a/pkg/application/application_test.go +++ b/pkg/application/application_test.go @@ -605,7 +605,7 @@ func Test_newDirApp(t *testing.T) { UserGivenName: "fooapp", DestNamespace: "fizz", DestServer: "buzz", - SrcRepoURL: "https://github.com/foo/bar", + SrcRepoURL: "https://github.com/foo/bar.git", SrcTargetRevision: "v0.1.2", SrcPath: "somepath/in/repo", }, diff --git a/pkg/git/repository.go b/pkg/git/repository.go index 9fe35219..b8247b84 100644 --- a/pkg/git/repository.go +++ b/pkg/git/repository.go @@ -113,10 +113,11 @@ func (o *CloneOptions) Parse() { var ( host string orgRepo string + suffix string ) - host, orgRepo, o.path, o.revision, _, _, _ = util.ParseGitUrl(o.Repo) - o.url = host + orgRepo + host, orgRepo, o.path, o.revision, suffix = util.ParseGitUrl(o.Repo) + o.url = host + orgRepo + suffix } func (o *CloneOptions) Clone(ctx context.Context) (Repository, fs.FS, error) { @@ -153,7 +154,7 @@ func (o *CloneOptions) URL() string { } func (o *CloneOptions) Revision() string { - return o.revision + return plumbing.ReferenceName(o.revision).Short() } func (o *CloneOptions) Path() string { @@ -208,7 +209,7 @@ var clone = func(ctx context.Context, opts *CloneOptions) (*repo, error) { } if opts.revision != "" { - cloneOpts.ReferenceName = plumbing.NewBranchReferenceName(opts.revision) + cloneOpts.ReferenceName = plumbing.ReferenceName(opts.revision) } log.G(ctx).WithFields(log.Fields{ diff --git a/pkg/git/repository_test.go b/pkg/git/repository_test.go index 6e0d734d..c7a718c6 100644 --- a/pkg/git/repository_test.go +++ b/pkg/git/repository_test.go @@ -247,7 +247,7 @@ func Test_clone(t *testing.T) { Repo: "https://github.com/owner/name", }, expectedOpts: &gg.CloneOptions{ - URL: "https://github.com/owner/name", + URL: "https://github.com/owner/name.git", Auth: nil, Depth: 1, Progress: os.Stderr, @@ -266,7 +266,7 @@ func Test_clone(t *testing.T) { }, }, expectedOpts: &gg.CloneOptions{ - URL: "https://github.com/owner/name", + URL: "https://github.com/owner/name.git", Auth: &http.BasicAuth{ Username: "asd", Password: "123", @@ -284,7 +284,7 @@ func Test_clone(t *testing.T) { Repo: "https://github.com/owner/name", }, expectedOpts: &gg.CloneOptions{ - URL: "https://github.com/owner/name", + URL: "https://github.com/owner/name.git", Depth: 1, Progress: os.Stderr, Tags: gg.NoTags, @@ -300,7 +300,7 @@ func Test_clone(t *testing.T) { Repo: "https://github.com/owner/name?ref=test", }, expectedOpts: &gg.CloneOptions{ - URL: "https://github.com/owner/name", + URL: "https://github.com/owner/name.git", Depth: 1, Progress: os.Stderr, Tags: gg.NoTags, diff --git a/pkg/util/repospec.go b/pkg/util/repospec.go index b17f87c5..757bf324 100644 --- a/pkg/util/repospec.go +++ b/pkg/util/repospec.go @@ -8,7 +8,6 @@ package util import ( "net/url" - "strconv" "strings" "time" ) @@ -21,42 +20,43 @@ const ( // From strings like git@github.com:someOrg/someRepo.git or // https://github.com/someOrg/someRepo?ref=someHash, extract // the parts. -func ParseGitUrl(n string) ( - host string, orgRepo string, path string, gitRef string, gitSubmodules bool, gitSuff string, gitTimeout time.Duration) { - +func ParseGitUrl(n string) (host, orgRepo, path, ref, gitSuff string) { if strings.Contains(n, gitDelimiter) { index := strings.Index(n, gitDelimiter) // Adding _git/ to host host = normalizeGitHostSpec(n[:index+len(gitDelimiter)]) orgRepo = strings.Split(strings.Split(n[index+len(gitDelimiter):], "/")[0], "?")[0] - path, gitRef, gitTimeout, gitSubmodules = peelQuery(n[index+len(gitDelimiter)+len(orgRepo):]) + path, ref = peelQuery(n[index+len(gitDelimiter)+len(orgRepo):]) return } + host, n = parseHostSpec(n) gitSuff = gitSuffix if strings.Contains(n, gitSuffix) { index := strings.Index(n, gitSuffix) orgRepo = n[0:index] n = n[index+len(gitSuffix):] - path, gitRef, gitTimeout, gitSubmodules = peelQuery(n) + path, ref = peelQuery(n) return } i := strings.Index(n, "/") if i < 1 { - path, gitRef, gitTimeout, gitSubmodules = peelQuery(n) + path, ref = peelQuery(n) return } + j := strings.Index(n[i+1:], "/") if j >= 0 { j += i + 1 orgRepo = n[:j] - path, gitRef, gitTimeout, gitSubmodules = peelQuery(n[j+1:]) + path, ref = peelQuery(n[j+1:]) return } + path = "" - orgRepo, gitRef, gitTimeout, gitSubmodules = peelQuery(n) - return host, orgRepo, path, gitRef, gitSubmodules, gitSuff, gitTimeout + orgRepo, ref = peelQuery(n) + return } // Clone git submodules by default. @@ -65,45 +65,25 @@ const defaultSubmodules = true // Arbitrary, but non-infinite, timeout for running commands. const defaultTimeout = 27 * time.Second -func peelQuery(arg string) (string, string, time.Duration, bool) { - // Parse the given arg into a URL. In the event of a parse failure, return - // our defaults. +func peelQuery(arg string) (path, ref string) { parsed, err := url.Parse(arg) if err != nil { - return arg, "", defaultTimeout, defaultSubmodules - } - values := parsed.Query() - - // ref is the desired git ref to target. Can be specified by in a git URL - // with ?ref= or ?version=, although ref takes precedence. - ref := values.Get("version") - if queryValue := values.Get("ref"); queryValue != "" { - ref = queryValue - } - - // depth is the desired git exec timeout. Can be specified by in a git URL - // with ?timeout=. - duration := defaultTimeout - if queryValue := values.Get("timeout"); queryValue != "" { - // Attempt to first parse as a number of integer seconds (like "61"), - // and then attempt to parse as a suffixed duration (like "61s"). - if intValue, err := strconv.Atoi(queryValue); err == nil && intValue > 0 { - duration = time.Duration(intValue) * time.Second - } else if durationValue, err := time.ParseDuration(queryValue); err == nil && durationValue > 0 { - duration = durationValue - } + return path, "" } - // submodules indicates if git submodule cloning is desired. Can be - // specified by in a git URL with ?submodules=. - submodules := defaultSubmodules - if queryValue := values.Get("submodules"); queryValue != "" { - if boolValue, err := strconv.ParseBool(queryValue); err == nil { - submodules = boolValue - } + values := parsed.Query() + branch := values.Get("ref") + tag := values.Get("tag") + sha := values.Get("sha") + if sha != "" { + ref = sha + } else if tag != "" { + ref = "refs/tags/" + tag + } else if branch != "" { + ref = "refs/heads/" + branch } - return parsed.Path, ref, duration, submodules + return parsed.Path, ref } func parseHostSpec(n string) (string, string) { diff --git a/pkg/util/repospec_test.go b/pkg/util/repospec_test.go index a6735a7f..cd6b2100 100644 --- a/pkg/util/repospec_test.go +++ b/pkg/util/repospec_test.go @@ -10,7 +10,6 @@ import ( "fmt" "path/filepath" "testing" - "time" ) const refQuery = "?ref=" @@ -58,7 +57,7 @@ func TestNewRepoSpecFromUrl(t *testing.T) { for _, pathName := range pathNames { for _, hrefArg := range hrefArgs { uri := makeUrl(hostRaw, orgRepo, pathName, hrefArg) - host, org, path, ref, _, _, _ := ParseGitUrl(uri) + host, org, path, ref, _ := ParseGitUrl(uri) if host != hostSpec { bad = append(bad, []string{"host", uri, host, hostSpec}) } @@ -68,7 +67,7 @@ func TestNewRepoSpecFromUrl(t *testing.T) { if path != pathName { bad = append(bad, []string{"path", uri, path, pathName}) } - if ref != hrefArg { + if hrefArg != "" && ref != "refs/heads/"+hrefArg { bad = append(bad, []string{"ref", uri, ref, hrefArg}) } } @@ -93,6 +92,8 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) { cloneSpec string absPath string ref string + tag string + sha string }{ { input: "http://github.com/someorg/somerepo/somedir", @@ -107,22 +108,28 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) { ref: "", }, { - input: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git?ref=v0.1.0", + input: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git?ref=branch", cloneSpec: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git", absPath: "", - ref: "v0.1.0", + ref: "refs/heads/branch", }, { - input: "https://itfs.mycompany.com/collection/project/_git/somerepos", - cloneSpec: "https://itfs.mycompany.com/collection/project/_git/somerepos", + input: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git?tag=v0.1.0", + cloneSpec: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git", absPath: "", - ref: "", + ref: "refs/tags/v0.1.0", + }, + { + input: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git?sha=some_sha", + cloneSpec: "git@gitlab2.sqtools.ru:10022/infra/kubernetes/thanos-base.git", + absPath: "", + ref: "some_sha", }, { - input: "https://itfs.mycompany.com/collection/project/_git/somerepos?version=v1.0.0", + input: "https://itfs.mycompany.com/collection/project/_git/somerepos", cloneSpec: "https://itfs.mycompany.com/collection/project/_git/somerepos", absPath: "", - ref: "v1.0.0", + ref: "", }, { input: "git::https://itfs.mycompany.com/collection/project/_git/somerepos", @@ -132,19 +139,18 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) { }, } for _, testcase := range testcases { - host, orgRepo, path, ref, _, suffix, _ := ParseGitUrl(testcase.input) + host, orgRepo, path, ref, suffix := ParseGitUrl(testcase.input) cloneSpec := host + orgRepo + suffix if cloneSpec != testcase.cloneSpec { - t.Errorf("CloneSpec expected to be %v, but got %v on %s", - testcase.cloneSpec, cloneSpec, testcase.input) + t.Errorf("CloneSpec expected to be %v, but got %v on %s", testcase.cloneSpec, cloneSpec, testcase.input) } + if path != testcase.absPath { - t.Errorf("AbsPath expected to be %v, but got %v on %s", - testcase.absPath, path, testcase.input) + t.Errorf("AbsPath expected to be %v, but got %v on %s", testcase.absPath, path, testcase.input) } + if ref != testcase.ref { - t.Errorf("ref expected to be %v, but got %v on %s", - testcase.ref, ref, testcase.input) + t.Errorf("ref expected to be %v, but got %v on %s", testcase.ref, ref, testcase.input) } } } @@ -153,131 +159,52 @@ func TestPeelQuery(t *testing.T) { testcases := []struct { input string - path string - ref string - submodules bool - timeout time.Duration + path string + ref string }{ { // All empty. - input: "somerepos", - path: "somerepos", - ref: "", - submodules: defaultSubmodules, - timeout: defaultTimeout, - }, - { - input: "somerepos?ref=v1.0.0", - path: "somerepos", - ref: "v1.0.0", - submodules: defaultSubmodules, - timeout: defaultTimeout, - }, - { - input: "somerepos?version=master", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: defaultTimeout, - }, - { - // A ref value takes precedence over a version value. - input: "somerepos?version=master&ref=v1.0.0", - path: "somerepos", - ref: "v1.0.0", - submodules: defaultSubmodules, - timeout: defaultTimeout, - }, - { - // Empty submodules value uses default. - input: "somerepos?version=master&submodules=", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: defaultTimeout, - }, - { - // Malformed submodules value uses default. - input: "somerepos?version=master&submodules=maybe", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: defaultTimeout, - }, - { - input: "somerepos?version=master&submodules=true", - path: "somerepos", - ref: "master", - submodules: true, - timeout: defaultTimeout, - }, - { - input: "somerepos?version=master&submodules=false", - path: "somerepos", - ref: "master", - submodules: false, - timeout: defaultTimeout, - }, - { - // Empty timeout value uses default. - input: "somerepos?version=master&timeout=", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: defaultTimeout, + input: "somerepos", + path: "somerepos", }, { - // Malformed timeout value uses default. - input: "somerepos?version=master&timeout=jiffy", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: defaultTimeout, + input: "somerepos?ref=branch", + path: "somerepos", + ref: "refs/heads/branch", }, { - // Zero timeout value uses default. - input: "somerepos?version=master&timeout=0", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: defaultTimeout, + input: "somerepos?tag=v1.0.0", + path: "somerepos", + ref: "refs/tags/v1.0.0", }, { - input: "somerepos?version=master&timeout=0s", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: defaultTimeout, + input: "somerepos?sha=some_sha", + path: "somerepos", + ref: "some_sha", }, { - input: "somerepos?version=master&timeout=61", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: 61 * time.Second, + input: "somerepos?ref=branch&tag=v1.0.0", + path: "somerepos", + ref: "refs/tags/v1.0.0", }, { - input: "somerepos?version=master&timeout=1m1s", - path: "somerepos", - ref: "master", - submodules: defaultSubmodules, - timeout: 61 * time.Second, + input: "somerepos?ref=branch&sha=some_sha", + path: "somerepos", + ref: "some_sha", }, { - input: "somerepos?version=master&submodules=false&timeout=1m1s", - path: "somerepos", - ref: "master", - submodules: false, - timeout: 61 * time.Second, + input: "somerepos?sha=some_sha&tag=v1.0.0", + path: "somerepos", + ref: "some_sha", }, } for _, testcase := range testcases { - path, ref, timeout, submodules := peelQuery(testcase.input) - if path != testcase.path || ref != testcase.ref || timeout != testcase.timeout || submodules != testcase.submodules { - t.Errorf("peelQuery: expected (%s, %s, %v, %v) got (%s, %s, %v, %v) on %s", - testcase.path, testcase.ref, testcase.timeout, testcase.submodules, - path, ref, timeout, submodules, + path, ref := peelQuery(testcase.input) + if path != testcase.path || ref != testcase.ref { + t.Errorf("peelQuery: expected (%s, %s) got (%s, %s) on %s", + testcase.path, testcase.ref, + path, ref, testcase.input) } } From 1e18ea44e53bcce5440db24a7e988648bab68de9 Mon Sep 17 00:00:00 2001 From: Noam Gal Date: Tue, 15 Jun 2021 14:47:47 +0300 Subject: [PATCH 2/2] fixed PR comments --- pkg/git/repository.go | 137 ++++++++++++++++++++++++++- pkg/{util => git}/repospec_test.go | 10 +- pkg/util/repospec.go | 145 ----------------------------- 3 files changed, 143 insertions(+), 149 deletions(-) rename pkg/{util => git}/repospec_test.go (97%) delete mode 100644 pkg/util/repospec.go diff --git a/pkg/git/repository.go b/pkg/git/repository.go index b8247b84..7d3dc6f7 100644 --- a/pkg/git/repository.go +++ b/pkg/git/repository.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "io" + "net/url" "os" "strings" @@ -57,6 +58,11 @@ type ( } ) +const ( + gitSuffix = ".git" + gitDelimiter = "_git/" +) + // Errors var ( ErrNilOpts = errors.New("options cannot be nil") @@ -116,7 +122,7 @@ func (o *CloneOptions) Parse() { suffix string ) - host, orgRepo, o.path, o.revision, suffix = util.ParseGitUrl(o.Repo) + host, orgRepo, o.path, o.revision, suffix = parseGitUrl(o.Repo) o.url = host + orgRepo + suffix } @@ -281,3 +287,132 @@ func getAuth(auth Auth) transport.AuthMethod { Password: auth.Password, } } + +// From strings like git@github.com:someOrg/someRepo.git or +// https://github.com/someOrg/someRepo?ref=someHash, extract +// the parts. +func parseGitUrl(n string) (host, orgRepo, path, ref, gitSuff string) { + if strings.Contains(n, gitDelimiter) { + index := strings.Index(n, gitDelimiter) + // Adding _git/ to host + host = normalizeGitHostSpec(n[:index+len(gitDelimiter)]) + orgRepo = strings.Split(strings.Split(n[index+len(gitDelimiter):], "/")[0], "?")[0] + path, ref = peelQuery(n[index+len(gitDelimiter)+len(orgRepo):]) + return + } + + host, n = parseHostSpec(n) + gitSuff = gitSuffix + if strings.Contains(n, gitSuffix) { + index := strings.Index(n, gitSuffix) + orgRepo = n[0:index] + n = n[index+len(gitSuffix):] + path, ref = peelQuery(n) + return + } + + i := strings.Index(n, "/") + if i < 1 { + path, ref = peelQuery(n) + return + } + + j := strings.Index(n[i+1:], "/") + if j >= 0 { + j += i + 1 + orgRepo = n[:j] + path, ref = peelQuery(n[j+1:]) + return + } + + path = "" + orgRepo, ref = peelQuery(n) + return +} + +func peelQuery(arg string) (path, ref string) { + parsed, err := url.Parse(arg) + if err != nil { + return path, "" + } + + path = parsed.Path + values := parsed.Query() + branch := values.Get("ref") + tag := values.Get("tag") + sha := values.Get("sha") + if sha != "" { + ref = sha + return + } + + if tag != "" { + ref = "refs/tags/" + tag + return + } + + if branch != "" { + ref = "refs/heads/" + branch + return + } + + return +} + +func parseHostSpec(n string) (string, string) { + var host string + // Start accumulating the host part. + for _, p := range [...]string{ + // Order matters here. + "git::", "gh:", "ssh://", "https://", "http://", + "git@", "github.com:", "github.com/"} { + if len(p) < len(n) && strings.ToLower(n[:len(p)]) == p { + n = n[len(p):] + host += p + } + } + if host == "git@" { + i := strings.Index(n, "/") + if i > -1 { + host += n[:i+1] + n = n[i+1:] + } else { + i = strings.Index(n, ":") + if i > -1 { + host += n[:i+1] + n = n[i+1:] + } + } + return host, n + } + + // If host is a http(s) or ssh URL, grab the domain part. + for _, p := range [...]string{ + "ssh://", "https://", "http://"} { + if strings.HasSuffix(host, p) { + i := strings.Index(n, "/") + if i > -1 { + host = host + n[0:i+1] + n = n[i+1:] + } + break + } + } + + return normalizeGitHostSpec(host), n +} + +func normalizeGitHostSpec(host string) string { + s := strings.ToLower(host) + if strings.Contains(s, "github.com") { + if strings.Contains(s, "git@") || strings.Contains(s, "ssh:") { + host = "git@github.com:" + } else { + host = "https://github.com/" + } + } + if strings.HasPrefix(s, "git::") { + host = strings.TrimPrefix(s, "git::") + } + return host +} diff --git a/pkg/util/repospec_test.go b/pkg/git/repospec_test.go similarity index 97% rename from pkg/util/repospec_test.go rename to pkg/git/repospec_test.go index cd6b2100..53f8678c 100644 --- a/pkg/util/repospec_test.go +++ b/pkg/git/repospec_test.go @@ -4,7 +4,7 @@ // Copyright 2019 The Kubernetes Authors. // SPDX-License-Identifier: Apache-2.0 -package util +package git import ( "fmt" @@ -57,16 +57,19 @@ func TestNewRepoSpecFromUrl(t *testing.T) { for _, pathName := range pathNames { for _, hrefArg := range hrefArgs { uri := makeUrl(hostRaw, orgRepo, pathName, hrefArg) - host, org, path, ref, _ := ParseGitUrl(uri) + host, org, path, ref, _ := parseGitUrl(uri) if host != hostSpec { bad = append(bad, []string{"host", uri, host, hostSpec}) } + if org != orgRepo { bad = append(bad, []string{"orgRepo", uri, orgRepo, orgRepo}) } + if path != pathName { bad = append(bad, []string{"path", uri, path, pathName}) } + if hrefArg != "" && ref != "refs/heads/"+hrefArg { bad = append(bad, []string{"ref", uri, ref, hrefArg}) } @@ -74,6 +77,7 @@ func TestNewRepoSpecFromUrl(t *testing.T) { } } } + if len(bad) > 0 { for _, tuple := range bad { fmt.Printf("\n"+ @@ -139,7 +143,7 @@ func TestNewRepoSpecFromUrl_CloneSpecs(t *testing.T) { }, } for _, testcase := range testcases { - host, orgRepo, path, ref, suffix := ParseGitUrl(testcase.input) + host, orgRepo, path, ref, suffix := parseGitUrl(testcase.input) cloneSpec := host + orgRepo + suffix if cloneSpec != testcase.cloneSpec { t.Errorf("CloneSpec expected to be %v, but got %v on %s", testcase.cloneSpec, cloneSpec, testcase.input) diff --git a/pkg/util/repospec.go b/pkg/util/repospec.go deleted file mode 100644 index 757bf324..00000000 --- a/pkg/util/repospec.go +++ /dev/null @@ -1,145 +0,0 @@ -// The following file was copied from https://github.com/kubernetes-sigs/kustomize/blob/master/api/internal/git/repospec.go -// and modified to expose the ParseGitUrl function - -// Copyright 2019 The Kubernetes Authors. -// SPDX-License-Identifier: Apache-2.0 - -package util - -import ( - "net/url" - "strings" - "time" -) - -const ( - gitSuffix = ".git" - gitDelimiter = "_git/" -) - -// From strings like git@github.com:someOrg/someRepo.git or -// https://github.com/someOrg/someRepo?ref=someHash, extract -// the parts. -func ParseGitUrl(n string) (host, orgRepo, path, ref, gitSuff string) { - if strings.Contains(n, gitDelimiter) { - index := strings.Index(n, gitDelimiter) - // Adding _git/ to host - host = normalizeGitHostSpec(n[:index+len(gitDelimiter)]) - orgRepo = strings.Split(strings.Split(n[index+len(gitDelimiter):], "/")[0], "?")[0] - path, ref = peelQuery(n[index+len(gitDelimiter)+len(orgRepo):]) - return - } - - host, n = parseHostSpec(n) - gitSuff = gitSuffix - if strings.Contains(n, gitSuffix) { - index := strings.Index(n, gitSuffix) - orgRepo = n[0:index] - n = n[index+len(gitSuffix):] - path, ref = peelQuery(n) - return - } - - i := strings.Index(n, "/") - if i < 1 { - path, ref = peelQuery(n) - return - } - - j := strings.Index(n[i+1:], "/") - if j >= 0 { - j += i + 1 - orgRepo = n[:j] - path, ref = peelQuery(n[j+1:]) - return - } - - path = "" - orgRepo, ref = peelQuery(n) - return -} - -// Clone git submodules by default. -const defaultSubmodules = true - -// Arbitrary, but non-infinite, timeout for running commands. -const defaultTimeout = 27 * time.Second - -func peelQuery(arg string) (path, ref string) { - parsed, err := url.Parse(arg) - if err != nil { - return path, "" - } - - values := parsed.Query() - branch := values.Get("ref") - tag := values.Get("tag") - sha := values.Get("sha") - if sha != "" { - ref = sha - } else if tag != "" { - ref = "refs/tags/" + tag - } else if branch != "" { - ref = "refs/heads/" + branch - } - - return parsed.Path, ref -} - -func parseHostSpec(n string) (string, string) { - var host string - // Start accumulating the host part. - for _, p := range [...]string{ - // Order matters here. - "git::", "gh:", "ssh://", "https://", "http://", - "git@", "github.com:", "github.com/"} { - if len(p) < len(n) && strings.ToLower(n[:len(p)]) == p { - n = n[len(p):] - host += p - } - } - if host == "git@" { - i := strings.Index(n, "/") - if i > -1 { - host += n[:i+1] - n = n[i+1:] - } else { - i = strings.Index(n, ":") - if i > -1 { - host += n[:i+1] - n = n[i+1:] - } - } - return host, n - } - - // If host is a http(s) or ssh URL, grab the domain part. - for _, p := range [...]string{ - "ssh://", "https://", "http://"} { - if strings.HasSuffix(host, p) { - i := strings.Index(n, "/") - if i > -1 { - host = host + n[0:i+1] - n = n[i+1:] - } - break - } - } - - return normalizeGitHostSpec(host), n -} - -func normalizeGitHostSpec(host string) string { - s := strings.ToLower(host) - if strings.Contains(s, "github.com") { - if strings.Contains(s, "git@") || strings.Contains(s, "ssh:") { - host = "git@github.com:" - } else { - host = "https://github.com/" - } - } - if strings.HasPrefix(s, "git::") { - host = strings.TrimPrefix(s, "git::") - } - return host -}