From 2cc05eff82673827f30d49d8b839de9adde3d7c4 Mon Sep 17 00:00:00 2001 From: Soule BA Date: Wed, 10 Nov 2021 11:53:29 +0100 Subject: [PATCH] Add method to generate a clone url for a specific provider. Each provider might have its own uri/path An interface is added for each provider to implement. Signed-off-by: Soule BA --- gitprovider/repositoryref.go | 28 +++++++++---- gitprovider/resources.go | 5 +++ stash/client_repositories_user.go | 6 +-- stash/integration_repositories_org_test.go | 16 ++++++-- stash/integration_repositories_user_test.go | 11 +++++ stash/resource_repository.go | 45 ++++++++++++++++++++- 6 files changed, 95 insertions(+), 16 deletions(-) diff --git a/gitprovider/repositoryref.go b/gitprovider/repositoryref.go index 0a20706a..aa75eb70 100644 --- a/gitprovider/repositoryref.go +++ b/gitprovider/repositoryref.go @@ -279,8 +279,8 @@ func (r UserRepositoryRef) GetRepository() string { return r.RepositoryName } -// GetSlug returns the unique slug for this object. -func (r UserRepositoryRef) GetSlug() string { +// Slug returns the unique slug for this object. +func (r UserRepositoryRef) Slug() string { return r.slug } @@ -309,18 +309,30 @@ func (r UserRepositoryRef) GetCloneURL(transport TransportType) string { func GetCloneURL(rs RepositoryRef, transport TransportType) string { switch transport { case TransportTypeHTTPS: - return fmt.Sprintf("%s.git", rs.String()) + return ParseTypeHTTPS(rs.String()) case TransportTypeGit: - return fmt.Sprintf("git@%s:%s/%s.git", rs.GetDomain(), rs.GetIdentity(), rs.GetRepository()) + return ParseTypeGit(rs.GetDomain(), rs.GetIdentity(), rs.GetRepository()) case TransportTypeSSH: - trimmedDomain := rs.GetDomain() - trimmedDomain = strings.Replace(trimmedDomain, "https://", "", -1) - trimmedDomain = strings.Replace(trimmedDomain, "http://", "", -1) - return fmt.Sprintf("ssh://git@%s/%s/%s", trimmedDomain, rs.GetIdentity(), rs.GetRepository()) + return ParseTypeSSH(rs.GetDomain(), rs.GetIdentity(), rs.GetRepository()) } return "" } +func ParseTypeHTTPS(url string) string { + return fmt.Sprintf("%s.git", url) +} + +func ParseTypeGit(domain, identity, repository string) string { + return fmt.Sprintf("git@%s:%s/%s.git", domain, identity, repository) +} + +func ParseTypeSSH(domain, identity, repository string) string { + trimmedDomain := domain + trimmedDomain = strings.Replace(trimmedDomain, "https://", "", -1) + trimmedDomain = strings.Replace(trimmedDomain, "http://", "", -1) + return fmt.Sprintf("ssh://git@%s/%s/%s", trimmedDomain, identity, repository) +} + // ParseOrganizationURL parses an URL to an organization into a OrganizationRef object. func ParseOrganizationURL(o string) (*OrganizationRef, error) { u, parts, err := parseURL(o) diff --git a/gitprovider/resources.go b/gitprovider/resources.go index 08a0495c..076e0242 100644 --- a/gitprovider/resources.go +++ b/gitprovider/resources.go @@ -87,6 +87,11 @@ type OrgRepository interface { TeamAccess() TeamAccessClient } +// CloneableURL returns the HTTPS URL to clone the repository. +type CloneableURL interface { + GetCloneURL(prefix string, transport TransportType) string +} + // DeployKey represents a short-lived credential (e.g. an SSH public key) used to access a repository. type DeployKey interface { // DeployKey implements the Object interface, diff --git a/stash/client_repositories_user.go b/stash/client_repositories_user.go index 6f54f883..3a1b241e 100644 --- a/stash/client_repositories_user.go +++ b/stash/client_repositories_user.go @@ -47,7 +47,7 @@ func (c *UserRepositoriesClient) Get(ctx context.Context, ref gitprovider.UserRe return nil, err } - slug := ref.GetSlug() + slug := ref.Slug() if slug == "" { // try with name slug = ref.GetRepository() @@ -182,9 +182,9 @@ func (c *UserRepositoriesClient) reconcileRepository(ctx context.Context, actual ref := actual.Repository().(gitprovider.UserRepositoryRef) // Apply the desired state by running Update if *req.DefaultBranch != "" && repo.DefaultBranch != *req.DefaultBranch { - _, err = update(ctx, c.client, addTilde(ref.UserLogin), ref.GetSlug(), repo, *req.DefaultBranch) + _, err = update(ctx, c.client, addTilde(ref.UserLogin), ref.Slug(), repo, *req.DefaultBranch) } else { - _, err = update(ctx, c.client, addTilde(ref.UserLogin), ref.GetSlug(), repo, "") + _, err = update(ctx, c.client, addTilde(ref.UserLogin), ref.Slug(), repo, "") } if err != nil { diff --git a/stash/integration_repositories_org_test.go b/stash/integration_repositories_org_test.go index 8023f1de..6cd0827d 100644 --- a/stash/integration_repositories_org_test.go +++ b/stash/integration_repositories_org_test.go @@ -67,6 +67,7 @@ var _ = Describe("Stash Provider", func() { testOrgRepoName = fmt.Sprintf("test-org-repo-%03d", rand.Intn(1000)) } + fmt.Print("Creating repository ", testOrgRepoName, "...") // We know that a repo with this name doesn't exist in the organization, let's verify we get an // ErrNotFound repoRef := newOrgRepoRef(testOrg.Organization(), testOrgRepoName) @@ -92,10 +93,19 @@ var _ = Describe("Stash Provider", func() { validateOrgRepo(repo, getRepoRef.Repository()) - getRepo, err := client.OrgRepositories().Get(ctx, repoRef) - Expect(err).ToNot(HaveOccurred()) + // Verify that we can get clone url for the repo + if cloner, ok := getRepoRef.(gitprovider.CloneableURL); ok { + url := cloner.GetCloneURL("scm", gitprovider.TransportTypeHTTPS) + Expect(url).ToNot(BeEmpty()) + fmt.Println("Clone URL: ", url) + + sshURL := cloner.GetCloneURL("scm", gitprovider.TransportTypeSSH) + Expect(url).ToNot(BeEmpty()) + fmt.Println("Clone ssh URL: ", sshURL) + } + // Expect the two responses (one from POST and one from GET to have equal "spec") - getSpec := repositoryFromAPI(getRepo.APIObject().(*Repository)) + getSpec := repositoryFromAPI(getRepoRef.APIObject().(*Repository)) postSpec := repositoryFromAPI(repo.APIObject().(*Repository)) Expect(getSpec.Equals(postSpec)).To(BeTrue()) }) diff --git a/stash/integration_repositories_user_test.go b/stash/integration_repositories_user_test.go index a1482c7c..34a47df7 100644 --- a/stash/integration_repositories_user_test.go +++ b/stash/integration_repositories_user_test.go @@ -83,6 +83,17 @@ var _ = Describe("Stash Provider", func() { getRepoRef, err := client.UserRepositories().Get(ctx, repoRef) Expect(err).ToNot(HaveOccurred()) + // Verify that we can get clone url for the repo + if cloner, ok := getRepoRef.(gitprovider.CloneableURL); ok { + url := cloner.GetCloneURL("scm", gitprovider.TransportTypeHTTPS) + Expect(url).ToNot(BeEmpty()) + fmt.Println("Clone URL: ", url) + + sshURL := cloner.GetCloneURL("scm", gitprovider.TransportTypeSSH) + Expect(url).ToNot(BeEmpty()) + fmt.Println("Clone ssh URL: ", sshURL) + } + validateUserRepo(repo, getRepoRef.Repository()) getRepo, err := client.UserRepositories().Get(ctx, repoRef) diff --git a/stash/resource_repository.go b/stash/resource_repository.go index 9b5b63cb..58eab6c5 100644 --- a/stash/resource_repository.go +++ b/stash/resource_repository.go @@ -18,10 +18,13 @@ package stash import ( "context" + "fmt" "github.com/fluxcd/go-git-providers/gitprovider" ) +const defaultClonePrefix = "scm" + func newUserRepository(ctx *clientContext, apiObj *Repository, ref gitprovider.RepositoryRef) *userRepository { return &userRepository{ c: &UserRepositoriesClient{ @@ -100,7 +103,7 @@ func (r *userRepository) DeployKeys() gitprovider.DeployKeyClient { func (r *userRepository) Update(ctx context.Context) error { // update by calling client ref := r.ref.(gitprovider.UserRepositoryRef) - apiObj, err := update(ctx, r.c.client, addTilde(ref.UserLogin), ref.GetSlug(), &r.repository, "") + apiObj, err := update(ctx, r.c.client, addTilde(ref.UserLogin), ref.Slug(), &r.repository, "") if err != nil { // Log the error and return it r.c.log.V(1).Error(err, "Error updating repository", @@ -141,7 +144,26 @@ func (r *userRepository) Reconcile(ctx context.Context) (bool, error) { // ErrNotFound is returned if the resource doesn't exist anymore. func (r *userRepository) Delete(ctx context.Context) error { ref := r.ref.(gitprovider.UserRepositoryRef) - return delete(ctx, r.c.client, addTilde(ref.UserLogin), ref.GetSlug()) + return delete(ctx, r.c.client, addTilde(ref.UserLogin), ref.Slug()) +} + +// GetCloneURL returns a formatted string that can be used for cloning +// from a remote Git provider. +func (r *userRepository) GetCloneURL(prefix string, transport gitprovider.TransportType) string { + if prefix == "" { + prefix = defaultClonePrefix + } + ref := r.ref.(gitprovider.UserRepositoryRef) + switch transport { + case gitprovider.TransportTypeHTTPS: + return gitprovider.ParseTypeHTTPS(fmt.Sprintf("%s/%s/%s/%s", gitprovider.GetDomainURL(ref.GetDomain()), prefix, addTilde(ref.UserLogin), ref.Slug())) + case gitprovider.TransportTypeGit: + return gitprovider.ParseTypeGit(ref.GetDomain(), addTilde(ref.UserLogin), ref.Slug()) + case gitprovider.TransportTypeSSH: + return gitprovider.ParseTypeSSH(ref.GetDomain(), addTilde(ref.UserLogin), ref.Slug()) + default: + return "" + } } func newOrgRepository(ctx *clientContext, apiObj *Repository, ref gitprovider.RepositoryRef) *orgRepository { @@ -252,3 +274,22 @@ func repositoryInfoToAPIObj(repo *gitprovider.RepositoryInfo, apiObj *Repository apiObj.DefaultBranch = *gitprovider.StringVar(*repo.DefaultBranch) } } + +// GetCloneURL returns a formatted string that can be used for cloning +// from a remote Git provider. +func (r *orgRepository) GetCloneURL(prefix string, transport gitprovider.TransportType) string { + if prefix == "" { + prefix = defaultClonePrefix + } + ref := r.ref.(gitprovider.OrgRepositoryRef) + switch transport { + case gitprovider.TransportTypeHTTPS: + return gitprovider.ParseTypeHTTPS(fmt.Sprintf("%s/%s/%s/%s", gitprovider.GetDomainURL(ref.GetDomain()), prefix, ref.Key(), ref.Slug())) + case gitprovider.TransportTypeGit: + return gitprovider.ParseTypeGit(ref.GetDomain(), ref.Key(), ref.Slug()) + case gitprovider.TransportTypeSSH: + return gitprovider.ParseTypeSSH(ref.GetDomain(), ref.Key(), ref.Slug()) + default: + return "" + } +}