diff --git a/cli/exec/flags.go b/cli/exec/flags.go index ba29546ea..2d7c12925 100644 --- a/cli/exec/flags.go +++ b/cli/exec/flags.go @@ -150,6 +150,10 @@ var flags = []cli.Flag{ EnvVars: []string{"CI_REPO_CLONE_URL"}, Name: "repo-clone-url", }, + &cli.StringFlag{ + EnvVars: []string{"CI_REPO_CLONE_SSH_URL"}, + Name: "repo-clone-ssh-url", + }, &cli.StringFlag{ EnvVars: []string{"CI_REPO_PRIVATE"}, Name: "repo-private", diff --git a/cli/exec/metadata.go b/cli/exec/metadata.go index c1ef6e487..3abfe604c 100644 --- a/cli/exec/metadata.go +++ b/cli/exec/metadata.go @@ -42,13 +42,14 @@ func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata { return metadata.Metadata{ Repo: metadata.Repo{ - Name: repoName, - Owner: repoOwner, - RemoteID: c.String("repo-remote-id"), - Link: c.String("repo-link"), - CloneURL: c.String("repo-clone-url"), - Private: c.Bool("repo-private"), - Trusted: c.Bool("repo-trusted"), + Name: repoName, + Owner: repoOwner, + RemoteID: c.String("repo-remote-id"), + Link: c.String("repo-link"), + CloneURL: c.String("repo-clone-url"), + CloneSSHURL: c.String("repo-clone-ssh-url"), + Private: c.Bool("repo-private"), + Trusted: c.Bool("repo-trusted"), }, Curr: metadata.Pipeline{ Number: c.Int64("pipeline-number"), diff --git a/cmd/server/docs/docs.go b/cmd/server/docs/docs.go index dba6cad2c..3a85b9f8e 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/docs/docs.go @@ -3908,6 +3908,9 @@ const docTemplate = `{ "clone_url": { "type": "string" }, + "clone_url_ssh": { + "type": "string" + }, "config_file": { "type": "string" }, diff --git a/docs/docs/20-usage/50-environment.md b/docs/docs/20-usage/50-environment.md index a6066166f..f598399f7 100644 --- a/docs/docs/20-usage/50-environment.md +++ b/docs/docs/20-usage/50-environment.md @@ -57,7 +57,8 @@ This is the reference list of all environment variables available to your pipeli | `CI_REPO_SCM` | repository SCM (git) | | `CI_REPO_URL` | repository web URL | | `CI_REPO_CLONE_URL` | repository clone URL | -| `CI_REPO_DEFAULT_BRANCH` | repository default branch (main) | +| `CI_REPO_CLONE_SSH_URL` | repository SSH clone URL | +| `CI_REPO_DEFAULT_BRANCH` | repository default branch (main) | | `CI_REPO_PRIVATE` | repository is private | | `CI_REPO_TRUSTED` | repository is trusted | | | **Current Commit** | diff --git a/pipeline/frontend/metadata.go b/pipeline/frontend/metadata.go index b790bfddb..12bd10780 100644 --- a/pipeline/frontend/metadata.go +++ b/pipeline/frontend/metadata.go @@ -55,14 +55,15 @@ func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline, fRepo := metadata.Repo{} if repo != nil { fRepo = metadata.Repo{ - Name: repo.Name, - Owner: repo.Owner, - RemoteID: fmt.Sprint(repo.ForgeRemoteID), - Link: repo.Link, - CloneURL: repo.Clone, - Private: repo.IsSCMPrivate, - Branch: repo.Branch, - Trusted: repo.IsTrusted, + Name: repo.Name, + Owner: repo.Owner, + RemoteID: fmt.Sprint(repo.ForgeRemoteID), + Link: repo.Link, + CloneURL: repo.Clone, + CloneSSHURL: repo.CloneSSH, + Private: repo.IsSCMPrivate, + Branch: repo.Branch, + Trusted: repo.IsTrusted, } if idx := strings.LastIndex(repo.FullName, "/"); idx != -1 { diff --git a/pipeline/frontend/metadata/environment.go b/pipeline/frontend/metadata/environment.go index 6827b35a1..6055dcab9 100644 --- a/pipeline/frontend/metadata/environment.go +++ b/pipeline/frontend/metadata/environment.go @@ -45,6 +45,7 @@ func (m *Metadata) Environ() map[string]string { "CI_REPO_SCM": "git", "CI_REPO_URL": m.Repo.Link, "CI_REPO_CLONE_URL": m.Repo.CloneURL, + "CI_REPO_CLONE_SSH_URL": m.Repo.CloneSSHURL, "CI_REPO_DEFAULT_BRANCH": m.Repo.Branch, "CI_REPO_PRIVATE": strconv.FormatBool(m.Repo.Private), "CI_REPO_TRUSTED": strconv.FormatBool(m.Repo.Trusted), diff --git a/pipeline/frontend/metadata/types.go b/pipeline/frontend/metadata/types.go index 43f086917..ef17d57b2 100644 --- a/pipeline/frontend/metadata/types.go +++ b/pipeline/frontend/metadata/types.go @@ -29,15 +29,16 @@ type ( // Repo defines runtime metadata for a repository. Repo struct { - Name string `json:"name,omitempty"` - Owner string `json:"owner,omitempty"` - RemoteID string `json:"remote_id,omitempty"` - Link string `json:"link,omitempty"` - CloneURL string `json:"clone_url,omitempty"` - Private bool `json:"private,omitempty"` - Secrets []Secret `json:"secrets,omitempty"` - Branch string `json:"default_branch,omitempty"` - Trusted bool `json:"trusted,omitempty"` + Name string `json:"name,omitempty"` + Owner string `json:"owner,omitempty"` + RemoteID string `json:"remote_id,omitempty"` + Link string `json:"link,omitempty"` + CloneURL string `json:"clone_url,omitempty"` + CloneSSHURL string `json:"clone_url_ssh,omitempty"` + Private bool `json:"private,omitempty"` + Secrets []Secret `json:"secrets,omitempty"` + Branch string `json:"default_branch,omitempty"` + Trusted bool `json:"trusted,omitempty"` } // Pipeline defines runtime metadata for a pipeline. diff --git a/pipeline/frontend/metadata_test.go b/pipeline/frontend/metadata_test.go index f381c0cef..259b5b2b8 100644 --- a/pipeline/frontend/metadata_test.go +++ b/pipeline/frontend/metadata_test.go @@ -79,7 +79,7 @@ func TestMetadataFromStruct(t *testing.T) { "CI_PREV_COMMIT_AUTHOR": "", "CI_PREV_COMMIT_AUTHOR_AVATAR": "", "CI_PREV_COMMIT_AUTHOR_EMAIL": "", "CI_PREV_COMMIT_BRANCH": "", "CI_PREV_COMMIT_MESSAGE": "", "CI_PREV_COMMIT_REF": "", "CI_PREV_COMMIT_REFSPEC": "", "CI_PREV_COMMIT_SHA": "", "CI_PREV_COMMIT_URL": "", "CI_PREV_PIPELINE_CREATED": "0", "CI_PREV_PIPELINE_DEPLOY_TARGET": "", "CI_PREV_PIPELINE_EVENT": "", "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "0", "CI_PREV_PIPELINE_PARENT": "0", - "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "", "CI_REPO": "", "CI_REPO_CLONE_URL": "", "CI_REPO_DEFAULT_BRANCH": "", "CI_REPO_REMOTE_ID": "", + "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "", "CI_REPO": "", "CI_REPO_CLONE_URL": "", "CI_REPO_CLONE_SSH_URL": "", "CI_REPO_DEFAULT_BRANCH": "", "CI_REPO_REMOTE_ID": "", "CI_REPO_NAME": "", "CI_REPO_OWNER": "", "CI_REPO_PRIVATE": "false", "CI_REPO_SCM": "git", "CI_REPO_TRUSTED": "false", "CI_REPO_URL": "", "CI_STEP_FINISHED": "", "CI_STEP_NAME": "", "CI_STEP_NUMBER": "0", "CI_STEP_STARTED": "", "CI_STEP_STATUS": "", "CI_SYSTEM_HOST": "", "CI_SYSTEM_NAME": "woodpecker", "CI_SYSTEM_PLATFORM": "", "CI_SYSTEM_URL": "", "CI_SYSTEM_VERSION": "", "CI_WORKFLOW_NAME": "", "CI_WORKFLOW_NUMBER": "0", @@ -88,7 +88,7 @@ func TestMetadataFromStruct(t *testing.T) { { name: "Test with forge", forge: forge, - repo: &model.Repo{FullName: "testUser/testRepo", Link: "https://gitea.com/testUser/testRepo", Clone: "https://gitea.com/testUser/testRepo.git", Branch: "main", IsSCMPrivate: true}, + repo: &model.Repo{FullName: "testUser/testRepo", Link: "https://gitea.com/testUser/testRepo", Clone: "https://gitea.com/testUser/testRepo.git", CloneSSH: "git@gitea.com:testUser/testRepo.git", Branch: "main", IsSCMPrivate: true}, pipeline: &model.Pipeline{Number: 3}, last: &model.Pipeline{Number: 2}, workflow: &model.Workflow{Name: "hello"}, @@ -96,7 +96,7 @@ func TestMetadataFromStruct(t *testing.T) { expectedMetadata: metadata.Metadata{ Forge: metadata.Forge{Type: "gitea", URL: "https://gitea.com"}, Sys: metadata.System{Name: "woodpecker", Host: "example.com", Link: "https://example.com"}, - Repo: metadata.Repo{Owner: "testUser", Name: "testRepo", Link: "https://gitea.com/testUser/testRepo", CloneURL: "https://gitea.com/testUser/testRepo.git", Branch: "main", Private: true}, + Repo: metadata.Repo{Owner: "testUser", Name: "testRepo", Link: "https://gitea.com/testUser/testRepo", CloneURL: "https://gitea.com/testUser/testRepo.git", CloneSSHURL: "git@gitea.com:testUser/testRepo.git", Branch: "main", Private: true}, Curr: metadata.Pipeline{Number: 3}, Prev: metadata.Pipeline{Number: 2}, Workflow: metadata.Workflow{Name: "hello"}, @@ -111,7 +111,7 @@ func TestMetadataFromStruct(t *testing.T) { "CI_PREV_COMMIT_AUTHOR": "", "CI_PREV_COMMIT_AUTHOR_AVATAR": "", "CI_PREV_COMMIT_AUTHOR_EMAIL": "", "CI_PREV_COMMIT_BRANCH": "", "CI_PREV_COMMIT_MESSAGE": "", "CI_PREV_COMMIT_REF": "", "CI_PREV_COMMIT_REFSPEC": "", "CI_PREV_COMMIT_SHA": "", "CI_PREV_COMMIT_URL": "", "CI_PREV_PIPELINE_CREATED": "0", "CI_PREV_PIPELINE_DEPLOY_TARGET": "", "CI_PREV_PIPELINE_EVENT": "", "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "2", "CI_PREV_PIPELINE_PARENT": "0", - "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "", "CI_REPO": "testUser/testRepo", "CI_REPO_CLONE_URL": "https://gitea.com/testUser/testRepo.git", + "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "", "CI_REPO": "testUser/testRepo", "CI_REPO_CLONE_URL": "https://gitea.com/testUser/testRepo.git", "CI_REPO_CLONE_SSH_URL": "git@gitea.com:testUser/testRepo.git", "CI_REPO_DEFAULT_BRANCH": "main", "CI_REPO_NAME": "testRepo", "CI_REPO_OWNER": "testUser", "CI_REPO_PRIVATE": "true", "CI_REPO_REMOTE_ID": "", "CI_REPO_SCM": "git", "CI_REPO_TRUSTED": "false", "CI_REPO_URL": "https://gitea.com/testUser/testRepo", "CI_STEP_FINISHED": "", "CI_STEP_NAME": "", "CI_STEP_NUMBER": "0", "CI_STEP_STARTED": "", "CI_STEP_STATUS": "", "CI_SYSTEM_HOST": "example.com", diff --git a/server/forge/bitbucket/convert.go b/server/forge/bitbucket/convert.go index edd6d40a9..dc3ddeed0 100644 --- a/server/forge/bitbucket/convert.go +++ b/server/forge/bitbucket/convert.go @@ -52,6 +52,7 @@ func convertRepo(from *internal.Repo, perm *internal.RepoPerm) *model.Repo { repo := model.Repo{ ForgeRemoteID: model.ForgeRemoteID(from.UUID), Clone: cloneLink(from), + CloneSSH: sshCloneLink(from), Owner: strings.Split(from.FullName, "/")[0], Name: strings.Split(from.FullName, "/")[1], FullName: from.FullName, @@ -114,6 +115,18 @@ func cloneLink(repo *internal.Repo) string { return clone } +// cloneLink is a helper function that tries to extract the clone url from the +// repository object. +func sshCloneLink(repo *internal.Repo) string { + for _, link := range repo.Links.Clone { + if link.Name == "ssh" { + return link.Href + } + } + + return "" +} + // convertUser is a helper function used to convert a Bitbucket user account // structure to the Woodpecker User structure. func convertUser(from *internal.Account, token *oauth2.Token) *model.User { diff --git a/server/forge/gitea/helper.go b/server/forge/gitea/helper.go index 5fe697f7d..5df733b1b 100644 --- a/server/forge/gitea/helper.go +++ b/server/forge/gitea/helper.go @@ -46,6 +46,7 @@ func toRepo(from *gitea.Repository) *model.Repo { Link: from.HTMLURL, IsSCMPrivate: from.Private || from.Owner.Visibility != gitea.VisibleTypePublic, Clone: from.CloneURL, + CloneSSH: from.SSHURL, Branch: from.DefaultBranch, Perm: toPerm(from.Permissions), } diff --git a/server/forge/github/convert.go b/server/forge/github/convert.go index 8eda746b5..a421e8ac6 100644 --- a/server/forge/github/convert.go +++ b/server/forge/github/convert.go @@ -89,6 +89,7 @@ func convertRepo(from *github.Repository) *model.Repo { Link: from.GetHTMLURL(), IsSCMPrivate: from.GetPrivate(), Clone: from.GetCloneURL(), + CloneSSH: from.GetSSHURL(), Branch: from.GetDefaultBranch(), Owner: from.GetOwner().GetLogin(), Avatar: from.GetOwner().GetAvatarURL(), @@ -148,6 +149,7 @@ func convertRepoHook(eventRepo *github.PushEventRepository) *model.Repo { Link: eventRepo.GetHTMLURL(), IsSCMPrivate: eventRepo.GetPrivate(), Clone: eventRepo.GetCloneURL(), + CloneSSH: eventRepo.GetSSHURL(), Branch: eventRepo.GetDefaultBranch(), SCMKind: model.RepoGit, } diff --git a/server/forge/gitlab/convert.go b/server/forge/gitlab/convert.go index 097199ea7..6bee2c508 100644 --- a/server/forge/gitlab/convert.go +++ b/server/forge/gitlab/convert.go @@ -43,6 +43,7 @@ func (g *GitLab) convertGitLabRepo(_repo *gitlab.Project) (*model.Repo, error) { Avatar: _repo.AvatarURL, Link: _repo.WebURL, Clone: _repo.HTTPURLToRepo, + CloneSSH: _repo.SSHURLToRepo, Branch: _repo.DefaultBranch, Visibility: model.RepoVisibility(_repo.Visibility), IsSCMPrivate: !_repo.Public, @@ -96,6 +97,11 @@ func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, * } else { repo.Clone = target.HTTPURL } + if target.GitSSHURL != "" { + repo.CloneSSH = target.GitSSHURL + } else { + repo.CloneSSH = target.SSHURL + } repo.Branch = target.DefaultBranch @@ -143,6 +149,7 @@ func convertPushHook(hook *gitlab.PushEvent) (*model.Repo, *model.Pipeline, erro repo.Avatar = hook.Project.AvatarURL repo.Link = hook.Project.WebURL repo.Clone = hook.Project.GitHTTPURL + repo.CloneSSH = hook.Project.GitSSHURL repo.FullName = hook.Project.PathWithNamespace repo.Branch = hook.Project.DefaultBranch @@ -195,6 +202,7 @@ func convertTagHook(hook *gitlab.TagEvent) (*model.Repo, *model.Pipeline, error) repo.Avatar = hook.Project.AvatarURL repo.Link = hook.Project.WebURL repo.Clone = hook.Project.GitHTTPURL + repo.CloneSSH = hook.Project.GitSSHURL repo.FullName = hook.Project.PathWithNamespace repo.Branch = hook.Project.DefaultBranch diff --git a/server/model/repo.go b/server/model/repo.go index 836aae96f..46fd0fddb 100644 --- a/server/model/repo.go +++ b/server/model/repo.go @@ -33,6 +33,7 @@ type Repo struct { Avatar string `json:"avatar_url,omitempty" xorm:"varchar(500) 'repo_avatar'"` Link string `json:"link_url,omitempty" xorm:"varchar(1000) 'repo_link'"` Clone string `json:"clone_url,omitempty" xorm:"varchar(1000) 'repo_clone'"` + CloneSSH string `json:"clone_url_ssh" xorm:"varchar(1000) 'repo_clone_ssh'"` Branch string `json:"default_branch,omitempty" xorm:"varchar(500) 'repo_branch'"` SCMKind SCMKind `json:"scm,omitempty" xorm:"varchar(50) 'repo_scm'"` Timeout int64 `json:"timeout,omitempty" xorm:"repo_timeout"` @@ -87,6 +88,9 @@ func (r *Repo) Update(from *Repo) { if len(from.Clone) > 0 { r.Clone = from.Clone } + if len(from.CloneSSH) > 0 { + r.CloneSSH = from.CloneSSH + } r.Branch = from.Branch if from.IsSCMPrivate != r.IsSCMPrivate { if from.IsSCMPrivate {