diff --git a/Dockerfile b/Dockerfile index e40104b..9c73bf7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -28,7 +28,7 @@ USER root ENV ARGOCD_USER_ID=999 RUN adduser -S -H -u $ARGOCD_USER_ID argocd \ - && apk --no-cache add git git-lfs + && apk --no-cache add git openssh-client git-lfs COPY --from=build \ /usr/local/bin/kustomize \ diff --git a/git-ssh.go b/git-ssh.go index d00e838..e4933c7 100644 --- a/git-ssh.go +++ b/git-ssh.go @@ -28,8 +28,8 @@ const ( ) var ( - reKeyName = regexp.MustCompile(`#.*?argocd-voodoobox-plugin:\s*?(?P\w+)`) - reRepoAddressWithSSH = regexp.MustCompile(`(?P^\s*-\s*ssh:\/\/)(?P\w.+?)(?P\/.*$)`) + reKeyName = regexp.MustCompile(`#.*?argocd-voodoobox-plugin:\s*?(?P\w+)`) + reRepoURLWithSSH = regexp.MustCompile(`(?P^\s*-\s*(?:ssh:\/\/)?)(?P\w.+?@)?(?P\w.+?)(?P[\/:].*$)`) ) func setupGitSSH(ctx context.Context, cwd string, app applicationInfo) (string, error) { @@ -154,25 +154,22 @@ func updateRepoBaseAddresses(in io.Reader) (map[string]string, []byte, error) { keyName = s[reKeyName.SubexpIndex("keyName")] } - case keyName != "" && !reRepoAddressWithSSH.MatchString(l): - return nil, nil, fmt.Errorf("found key reference in comment but next remote base url doesn't contain ssh://") + case keyName != "" && !reRepoURLWithSSH.MatchString(l): + return nil, nil, fmt.Errorf("found key reference in comment but next remote base url is not a valid SSH URL") // referencing key is not mandatory since only 1 key can be used for all private base // case keyName == "" && reRepoAddressWithSSH.MatchString(l): // return nil, nil, fmt.Errorf("found remote base url with ssh protocol without referenced key comment above") - case keyName != "" && reRepoAddressWithSSH.MatchString(l): + case keyName != "" && reRepoURLWithSSH.MatchString(l): // If Key if found replace domain - sections := reRepoAddressWithSSH.FindStringSubmatch(l) - if len(sections) != 4 { + new, domain, err := replaceDomainWithConfigHostName(l, keyName) + if err != nil { return nil, nil, fmt.Errorf("error parsing remote base url") } - domain := sections[reRepoAddressWithSSH.SubexpIndex("domain")] - keyedDomains[keyName] = domain - l = sections[reRepoAddressWithSSH.SubexpIndex("beginning")] + - keyName + "_" + strings.ReplaceAll(domain, ".", "_") + - sections[reRepoAddressWithSSH.SubexpIndex("repoDetails")] + l = new + keyedDomains[keyName] = domain keyName = "" } @@ -184,6 +181,28 @@ func updateRepoBaseAddresses(in io.Reader) (map[string]string, []byte, error) { return keyedDomains, out, nil } +func replaceDomainWithConfigHostName(original string, keyName string) (string, string, error) { + sections := reRepoURLWithSSH.FindStringSubmatch(original) + if len(sections) != 4 && len(sections) != 5 { + return "", "", fmt.Errorf("error parsing remote base url") + } + + // URL should be either ssh:// or git@domain.com + // need to do check because in our regex both are optional + if !strings.Contains(sections[reRepoURLWithSSH.SubexpIndex("beginning")], "ssh://") && + sections[reRepoURLWithSSH.SubexpIndex("user")] == "" { + return "", "", fmt.Errorf("private remote URL should either contain ssh:// or user@ i.e. git@domain") + } + + domain := sections[reRepoURLWithSSH.SubexpIndex("domain")] + newURL := sections[reRepoURLWithSSH.SubexpIndex("beginning")] + + sections[reRepoURLWithSSH.SubexpIndex("user")] + + keyName + "_" + strings.ReplaceAll(domain, ".", "_") + + sections[reRepoURLWithSSH.SubexpIndex("repoDetails")] + + return newURL, domain, nil +} + func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]string) ([]byte, error) { if len(keyFilePaths) == 1 { for _, keyFilePath := range keyFilePaths { @@ -198,8 +217,8 @@ func constructSSHConfig(keyFilePaths map[string]string, keyedDomain map[string]s return nil, fmt.Errorf("unable to find path for key:%s, please make sure all referenced keys are added to git ssh secret", keyName) } - keyedDomain := keyName + "_" + strings.ReplaceAll(domain, ".", "_") - hostFragments = append(hostFragments, fmt.Sprintf(hostFragment, keyedDomain, domain, keyFilePath)) + host := keyName + "_" + strings.ReplaceAll(domain, ".", "_") + hostFragments = append(hostFragments, fmt.Sprintf(hostFragment, host, domain, keyFilePath)) } if len(hostFragments) == 0 { return nil, fmt.Errorf("keys are not referenced, please reference keys on remote base url in kustomize file") diff --git a/git-ssh_test.go b/git-ssh_test.go index 260b74d..a864fea 100644 --- a/git-ssh_test.go +++ b/git-ssh_test.go @@ -36,11 +36,16 @@ resources: # argocd-voodoobox-plugin: key_a - ssh://github.com/org/repo1//manifests/lab-foo?ref=master # argocd-voodoobox-plugin:keyD - - ssh://github.com/org/repo3//manifests/lab-zoo?ref=dev + - ssh://git@github.com/org/repo3//manifests/lab-zoo?ref=dev # argocd-voodoobox-plugin: sshKeyB - ssh://gitlab.io/org/repo2//manifests/lab-bar?ref=main # argocd-voodoobox-plugin: key_c - ssh://bitbucket.org/org/repo3//manifests/lab-zoo?ref=dev + # scp github url with git suffix + # argocd-voodoobox-plugin: key_e + - ssh://git@github.com:someorg/somerepo.git/somedir + # argocd-voodoobox-plugin: key_f + - git@github.com:owner/repo `)}, wantOut: []byte(`apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization @@ -51,17 +56,24 @@ resources: # argocd-voodoobox-plugin: key_a - ssh://key_a_github_com/org/repo1//manifests/lab-foo?ref=master # argocd-voodoobox-plugin:keyD - - ssh://keyD_github_com/org/repo3//manifests/lab-zoo?ref=dev + - ssh://git@keyD_github_com/org/repo3//manifests/lab-zoo?ref=dev # argocd-voodoobox-plugin: sshKeyB - ssh://sshKeyB_gitlab_io/org/repo2//manifests/lab-bar?ref=main # argocd-voodoobox-plugin: key_c - ssh://key_c_bitbucket_org/org/repo3//manifests/lab-zoo?ref=dev + # scp github url with git suffix + # argocd-voodoobox-plugin: key_e + - ssh://git@key_e_github_com:someorg/somerepo.git/somedir + # argocd-voodoobox-plugin: key_f + - git@key_f_github_com:owner/repo `), wantKeyMap: map[string]string{ "key_a": "github.com", "sshKeyB": "gitlab.io", "key_c": "bitbucket.org", "keyD": "github.com", + "key_e": "github.com", + "key_f": "github.com", }, }, { name: "valid-with-empty-line", @@ -197,6 +209,77 @@ resources: } } +func Test_replaceDomainWithConfigHostName(t *testing.T) { + type args struct { + original string + keyName string + } + tests := []struct { + name string + args args + url string + domain string + wantErr bool + }{ + { + "empty", + args{"", ""}, + "", "", true, + }, { + "non-ssh", + args{" - github.com/org/open1//manifests/lab-foo?ref=master", "keyA"}, + "", "", true, + }, { + "valid", + args{" - ssh://github.com/org/repo1//manifests/lab-foo?ref=master", "keyB"}, + " - ssh://keyB_github_com/org/repo1//manifests/lab-foo?ref=master", "github.com", false, + }, { + "valid with git user", + args{" - ssh://git@github.com/org/repo3//manifests/lab-zoo?ref=dev", "keyC"}, + " - ssh://git@keyC_github_com/org/repo3//manifests/lab-zoo?ref=dev", "github.com", false, + }, { + "valid with diff user", + args{" - ssh://user1@github.com/org/repo3//manifests/lab-zoo?ref=dev", "keyC"}, + " - ssh://user1@keyC_github_com/org/repo3//manifests/lab-zoo?ref=dev", "github.com", false, + }, { + "valid diff domain", + args{" - ssh://gitlab.io/org/repo2//manifests/lab-bar?ref=main", "keyD"}, + " - ssh://keyD_gitlab_io/org/repo2//manifests/lab-bar?ref=main", "gitlab.io", false, + }, { + "valid diff domain2", + args{" - ssh://bitbucket.org/org/repo3//manifests/lab-zoo?ref=dev", "keyE"}, + " - ssh://keyE_bitbucket_org/org/repo3//manifests/lab-zoo?ref=dev", "bitbucket.org", false, + }, { + "valid with :", + args{" - ssh://git@github.com:someorg/somerepo.git/somedir", "keyF"}, + " - ssh://git@keyF_github_com:someorg/somerepo.git/somedir", "github.com", false, + }, { + "valid without ssh", + args{" - git@github.com:someorg/somerepo.git/somedir", "keyG"}, + " - git@keyG_github_com:someorg/somerepo.git/somedir", "github.com", false, + }, { + "valid without ssh and diff user", + args{" - bob@github.com:someorg/somerepo.git/somedir", "keyG"}, + " - bob@keyG_github_com:someorg/somerepo.git/somedir", "github.com", false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, got1, err := replaceDomainWithConfigHostName(tt.args.original, tt.args.keyName) + if (err != nil) != tt.wantErr { + t.Errorf("replaceDomainWithConfigHostName() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.url { + t.Errorf("replaceDomainWithConfigHostName() got = %v, want %v", got, tt.url) + } + if got1 != tt.domain { + t.Errorf("replaceDomainWithConfigHostName() got1 = %v, want %v", got1, tt.domain) + } + }) + } +} + func Test_constructSSHConfig(t *testing.T) { type args struct { keyFilePaths map[string]string