diff --git a/docs/content/workflows/pipelines/actions/builtin/checkout-application.md b/docs/content/workflows/pipelines/actions/builtin/checkout-application.md index 90bce7b7d5..cfba47dd45 100644 --- a/docs/content/workflows/pipelines/actions/builtin/checkout-application.md +++ b/docs/content/workflows/pipelines/actions/builtin/checkout-application.md @@ -6,7 +6,7 @@ chapter = true **CheckoutApplication** is a builtin action, you can't modify it. -This action clones a repository into a directory. +This action clones a repository into a directory. If you want to clone a tag from your repository in this way, in your workflow payload you can add a key in your JSON like `"git.tag": "0.2"`. This will run git clone with the following options: diff --git a/docs/content/workflows/pipelines/actions/builtin/gitclone.md b/docs/content/workflows/pipelines/actions/builtin/gitclone.md index e176d2116a..c427c5a8b0 100644 --- a/docs/content/workflows/pipelines/actions/builtin/gitclone.md +++ b/docs/content/workflows/pipelines/actions/builtin/gitclone.md @@ -22,10 +22,11 @@ Advanced parameters: * depth - optional - 50 by default. You can remove --depth with the value 'false' * submodules - true by default, you can set false to avoid this. +* tag - optional - empty by default, you can set to `{{.git.tag}}` to clone a tag from your repository. In this way, in your workflow payload you can add a key in your JSON like `"git.tag": "0.2"`. Notes: -By defaut, depth is 50 and git clone with `--single-branch` automatically. +By default, depth is 50 and git clone with `--single-branch` automatically. So, if you want to do in a step script `git diff anotherBranch`, you have to set depth to 'false'. If there is no user && password && sshkey setted in action GitClone, CDS checks on Application VCS Strategy if some auth parameters can be used. diff --git a/docs/content/workflows/pipelines/variables.md b/docs/content/workflows/pipelines/variables.md index e5833c8961..50daf5f58a 100644 --- a/docs/content/workflows/pipelines/variables.md +++ b/docs/content/workflows/pipelines/variables.md @@ -42,7 +42,7 @@ Here is the list of builtin variables, generated for every build: - `{{.cds.application}}` The name of the current application - `{{.cds.job}}` The name of the current job - `{{.cds.manual}}` true if current pipeline is manually run, false otherwise -- `{{.cds.pipeline}}` The name of the current pipeline +- `{{.cds.pipeline}}` The name of the current pipeline - `{{.cds.project}}` The name of the current project - `{{.cds.run}}` Run Number of current workflow, example: 3.0 - `{{.cds.run.number}}` Number of current workflow, example: 3 if `{{.cds.run}} = 3.0` @@ -97,6 +97,7 @@ Here is the list of git variables: - `{{.git.url}}` - `{{.git.http_url}}` - `{{.git.branch}}` +- `{{.git.tag}}` - `{{.git.author}}` - `{{.git.message}}` - `{{.git.server}}` @@ -159,4 +160,4 @@ Helpers available and some examples: - b64dec - escape : replace '_', '/', '.' by '-' -You're a go developper? See all helpers on https://github.com/ovh/cds/blob/master/sdk/interpolate/interpolate_helper.go#L23 \ No newline at end of file +You're a go developper? See all helpers on https://github.com/ovh/cds/blob/master/sdk/interpolate/interpolate_helper.go#L23 diff --git a/engine/api/action/builtin.go b/engine/api/action/builtin.go index 12ddbcb0c5..433c0ce9ad 100644 --- a/engine/api/action/builtin.go +++ b/engine/api/action/builtin.go @@ -123,6 +123,13 @@ The public key have to be granted on your repository`, Type: sdk.BooleanParameter, Advanced: true, }) + gitclone.Parameter(sdk.Parameter{ + Name: "tag", + Description: "Useful when you want to git clone a specific tag", + Value: "", + Type: sdk.StringParameter, + Advanced: true, + }) gitclone.Requirement("git", sdk.BinaryRequirement, "git") if err := checkBuiltinAction(db, gitclone); err != nil { diff --git a/engine/api/repositoriesmanager/repositories_manager.go b/engine/api/repositoriesmanager/repositories_manager.go index 8818b7d183..068ce58b19 100644 --- a/engine/api/repositoriesmanager/repositories_manager.go +++ b/engine/api/repositoriesmanager/repositories_manager.go @@ -297,6 +297,17 @@ func (c *vcsClient) Commits(ctx context.Context, fullname, branch, since, until return commits, nil } +func (c *vcsClient) CommitsBetweenRefs(ctx context.Context, fullname, base, head string) ([]sdk.VCSCommit, error) { + var commits []sdk.VCSCommit + path := fmt.Sprintf("/vcs/%s/repos/%s/commits?base=%s&head=%s", c.name, fullname, url.QueryEscape(base), url.QueryEscape(head)) + if code, err := c.doJSONRequest(ctx, "GET", path, nil, &commits); err != nil { + if code != http.StatusNotFound { + return nil, err + } + } + return commits, nil +} + func (c *vcsClient) Commit(ctx context.Context, fullname, hash string) (sdk.VCSCommit, error) { commit := sdk.VCSCommit{} path := fmt.Sprintf("/vcs/%s/repos/%s/commits/%s", c.name, fullname, hash) diff --git a/engine/api/suggest.go b/engine/api/suggest.go index 5d5ac53d63..4376cd926d 100644 --- a/engine/api/suggest.go +++ b/engine/api/suggest.go @@ -138,6 +138,7 @@ func (api *API) getVariablesHandler() service.Handler { gitVar := []string{ "{{.git.hash}}", "{{.git.branch}}", + "{{.git.tag}}", "{{.git.author}}", "{{.git.project}}", "{{.git.repository}}", diff --git a/engine/api/workflow/dao_node_run.go b/engine/api/workflow/dao_node_run.go index 3aa394fbaf..34041e790f 100644 --- a/engine/api/workflow/dao_node_run.go +++ b/engine/api/workflow/dao_node_run.go @@ -42,6 +42,7 @@ workflow_node_run.triggers_run, workflow_node_run.vcs_repository, workflow_node_run.vcs_hash, workflow_node_run.vcs_branch, +workflow_node_run.vcs_tag, workflow_node_run.vcs_server, workflow_node_run.workflow_node_name, workflow_node_run.header @@ -247,6 +248,9 @@ func fromDBNodeRun(rr NodeRun, opts LoadRunOptions) (*sdk.WorkflowNodeRun, error if rr.VCSBranch.Valid { r.VCSBranch = rr.VCSBranch.String } + if rr.VCSTag.Valid { + r.VCSTag = rr.VCSTag.String + } if rr.VCSServer.Valid { r.VCSServer = rr.VCSServer.String } @@ -339,6 +343,8 @@ func makeDBNodeRun(n sdk.WorkflowNodeRun) (*NodeRun, error) { nodeRunDB.VCSHash.String = n.VCSHash nodeRunDB.VCSBranch.Valid = true nodeRunDB.VCSBranch.String = n.VCSBranch + nodeRunDB.VCSTag.Valid = true + nodeRunDB.VCSTag.String = n.VCSTag nodeRunDB.VCSRepository.Valid = true nodeRunDB.VCSRepository.String = n.VCSRepository @@ -476,16 +482,16 @@ func GetNodeRunBuildCommits(ctx context.Context, db gorp.SqlExecutor, store cach if errclient != nil { return nil, cur, sdk.WrapError(errclient, "GetNodeRunBuildCommits> Cannot get client") } - cur.Remote = nodeRun.VCSRepository cur.Branch = nodeRun.VCSBranch cur.Hash = nodeRun.VCSHash + cur.Tag = nodeRun.VCSTag if cur.Remote == "" { cur.Remote = app.RepositoryFullname } - if cur.Branch == "" { + if cur.Branch == "" && cur.Tag == "" { branches, errBr := client.Branches(ctx, cur.Remote) if errBr != nil { return nil, cur, sdk.WrapError(errBr, "GetNodeRunBuildCommits> Cannot load branches from vcs api remote %s", cur.Remote) @@ -514,7 +520,7 @@ func GetNodeRunBuildCommits(ctx context.Context, db gorp.SqlExecutor, store cach } var lastCommit sdk.VCSCommit - if cur.Hash == "" { + if cur.Hash == "" && cur.Tag == "" && cur.Branch != "" { //If we only have the current branch, search for the branch br, err := client.Branch(ctx, repo, cur.Branch) if err != nil { @@ -548,24 +554,46 @@ func GetNodeRunBuildCommits(ctx context.Context, db gorp.SqlExecutor, store cach if prev.Hash != "" && cur.Hash == prev.Hash { log.Debug("GetNodeRunBuildCommits> there is not difference between the previous build and the current build for node %s", nodeRun.WorkflowNodeName) } else if prev.Hash != "" { - if cur.Hash == "" { + switch { + case cur.Hash == "" && cur.Tag == "": br, err := client.Branch(ctx, repo, cur.Branch) if err != nil { return nil, cur, sdk.WrapError(err, "GetNodeRunBuildCommits> Cannot get branch %s", cur.Branch) } cur.Hash = br.LatestCommit - } - //If we are lucky, return a true diff - commits, err := client.Commits(ctx, repo, cur.Branch, prev.Hash, cur.Hash) - if err != nil { - return nil, cur, sdk.WrapError(err, "GetNodeRunBuildCommits> Cannot get commits") - } - if commits != nil { - res = commits + //If we are lucky, return a true diff + commits, err := client.Commits(ctx, repo, cur.Branch, prev.Hash, cur.Hash) + if err != nil { + return nil, cur, sdk.WrapError(err, "GetNodeRunBuildCommits> Cannot get commits") + } + if commits != nil { + res = commits + } + + case cur.Hash == "" && cur.Tag != "": + c, err := client.CommitsBetweenRefs(ctx, repo, prev.Hash, cur.Tag) + if err != nil { + return nil, cur, sdk.WrapError(err, "GetNodeRunBuildCommits> Cannot get commits") + } + if c != nil { + res = c + } } } else if prev.Hash == "" { if lastCommit.Hash != "" { res = []sdk.VCSCommit{lastCommit} + } else if cur.Tag != "" { + base := prev.Tag + if base == "" { + base = prev.Hash + } + c, err := client.CommitsBetweenRefs(ctx, repo, base, cur.Tag) + if err != nil { + return nil, cur, sdk.WrapError(err, "GetNodeRunBuildCommits> Cannot get commits") + } + if c != nil { + res = c + } } } else { //If we only get current node run hash @@ -593,7 +621,7 @@ func PreviousNodeRun(db gorp.SqlExecutor, nr sdk.WorkflowNodeRun, n sdk.Workflow `, nodeRunFields) var nodeRun sdk.WorkflowNodeRun var rr = NodeRun{} - if err := db.SelectOne(&rr, query, n.Name, workflowID, nr.VCSBranch, nr.Number, nr.WorkflowNodeID, nr.ID); err != nil { + if err := db.SelectOne(&rr, query, n.Name, workflowID, nr.VCSBranch, nr.VCSTag, nr.Number, nr.WorkflowNodeID, nr.ID); err != nil { return nodeRun, sdk.WrapError(err, "PreviousNodeRun> Cannot load previous run on workflow %d node %s", workflowID, n.Name) } pNodeRun, errF := fromDBNodeRun(rr, LoadRunOptions{}) @@ -610,11 +638,11 @@ func PreviousNodeRun(db gorp.SqlExecutor, nr sdk.WorkflowNodeRun, n sdk.Workflow //If you don't have environment linked set envID to 0 or -1 func PreviousNodeRunVCSInfos(db gorp.SqlExecutor, projectKey string, wf *sdk.Workflow, nodeName string, current sdk.BuildNumberAndHash, appID int64, envID int64) (sdk.BuildNumberAndHash, error) { var previous sdk.BuildNumberAndHash - var prevHash, prevBranch, prevRepository sql.NullString + var prevHash, prevBranch, prevTag, prevRepository sql.NullString var previousBuildNumber sql.NullInt64 queryPrevious := ` - SELECT workflow_node_run.vcs_branch, workflow_node_run.vcs_hash, workflow_node_run.vcs_repository, workflow_node_run.num + SELECT workflow_node_run.vcs_branch, workflow_node_run.vcs_tag, workflow_node_run.vcs_hash, workflow_node_run.vcs_repository, workflow_node_run.num FROM workflow_node_run JOIN workflow_node ON workflow_node.name = workflow_node_run.workflow_node_name AND workflow_node.name = $1 AND workflow_node.workflow_id = $2 JOIN workflow_node_context ON workflow_node_context.workflow_node_id = workflow_node.id @@ -630,7 +658,7 @@ func PreviousNodeRunVCSInfos(db gorp.SqlExecutor, projectKey string, wf *sdk.Wor } queryPrevious += fmt.Sprintf(" ORDER BY workflow_node_run.num DESC LIMIT 1") - errPrev := db.QueryRow(queryPrevious, argPrevious...).Scan(&prevBranch, &prevHash, &prevRepository, &previousBuildNumber) + errPrev := db.QueryRow(queryPrevious, argPrevious...).Scan(&prevBranch, &prevTag, &prevHash, &prevRepository, &previousBuildNumber) if errPrev == sql.ErrNoRows { log.Warning("PreviousNodeRunVCSInfos> no result with previous %d %s , arguments %v", current.BuildNumber, nodeName, argPrevious) return previous, nil @@ -642,6 +670,9 @@ func PreviousNodeRunVCSInfos(db gorp.SqlExecutor, projectKey string, wf *sdk.Wor if prevBranch.Valid { previous.Branch = prevBranch.String } + if prevTag.Valid { + previous.Tag = prevTag.String + } if prevHash.Valid { previous.Hash = prevHash.String } diff --git a/engine/api/workflow/execute_node_run.go b/engine/api/workflow/execute_node_run.go index ebd5cee613..c191d5efba 100644 --- a/engine/api/workflow/execute_node_run.go +++ b/engine/api/workflow/execute_node_run.go @@ -766,6 +766,7 @@ func SyncNodeRunRunJob(ctx context.Context, db gorp.SqlExecutor, nodeRun *sdk.Wo type vcsInfos struct { Repository string + Tag string Branch string Hash string Author string @@ -783,6 +784,7 @@ func getVCSInfos(ctx context.Context, db gorp.SqlExecutor, store cache.Store, vc var vcsInfos vcsInfos vcsInfos.Repository = gitValues[tagGitRepository] vcsInfos.Branch = gitValues[tagGitBranch] + vcsInfos.Tag = gitValues[tagGitTag] vcsInfos.Hash = gitValues[tagGitHash] vcsInfos.Author = gitValues[tagGitAuthor] vcsInfos.Message = gitValues[tagGitMessage] @@ -848,6 +850,7 @@ func getVCSInfos(ctx context.Context, db gorp.SqlExecutor, store cache.Store, vc return vcsInfos, sdk.NewError(sdk.ErrNotFound, fmt.Errorf("repository %s not found", vcsInfos.Repository)) } vcsInfos.Repository = applicationRepositoryFullname + vcsInfos.Tag = "" } } @@ -868,6 +871,9 @@ func getVCSInfos(ctx context.Context, db gorp.SqlExecutor, store cache.Store, vc vcsInfos.HTTPUrl = repo.HTTPCloneURL if vcsInfos.Branch == "" && !isChildNode { + if vcsInfos.Tag != "" { + return vcsInfos, nil + } return vcsInfos, sdk.WrapError(sdk.ErrBranchNameNotProvided, "computeVCSInfos> should not have an empty branch") } diff --git a/engine/api/workflow/gorp_model.go b/engine/api/workflow/gorp_model.go index 718773e3f8..d7f343c85b 100644 --- a/engine/api/workflow/gorp_model.go +++ b/engine/api/workflow/gorp_model.go @@ -66,6 +66,7 @@ type NodeRun struct { TriggersRun sql.NullString `db:"triggers_run"` VCSRepository sql.NullString `db:"vcs_repository"` VCSBranch sql.NullString `db:"vcs_branch"` + VCSTag sql.NullString `db:"vcs_tag"` VCSHash sql.NullString `db:"vcs_hash"` VCSServer sql.NullString `db:"vcs_server"` Header sql.NullString `db:"header"` diff --git a/engine/api/workflow/process.go b/engine/api/workflow/process.go index 0eae016637..72c9087d15 100644 --- a/engine/api/workflow/process.go +++ b/engine/api/workflow/process.go @@ -620,7 +620,12 @@ func processWorkflowNodeRun(ctx context.Context, db gorp.SqlExecutor, store cach // Tag VCS infos : add in tag only if it does not exist if !w.TagExists(tagGitRepository) { w.Tag(tagGitRepository, run.VCSRepository) - w.Tag(tagGitBranch, run.VCSBranch) + if run.VCSBranch != "" { + w.Tag(tagGitBranch, run.VCSBranch) + } + if run.VCSTag != "" { + w.Tag(tagGitTag, run.VCSTag) + } if len(run.VCSHash) >= 7 { w.Tag(tagGitHash, run.VCSHash[:7]) } else { @@ -720,11 +725,13 @@ func processWorkflowNodeRun(ctx context.Context, db gorp.SqlExecutor, store cach func setValuesGitInBuildParameters(run *sdk.WorkflowNodeRun, vcsInfos vcsInfos) { run.VCSRepository = vcsInfos.Repository run.VCSBranch = vcsInfos.Branch + run.VCSTag = vcsInfos.Tag run.VCSHash = vcsInfos.Hash run.VCSServer = vcsInfos.Server sdk.ParameterAddOrSetValue(&run.BuildParameters, tagGitRepository, sdk.StringParameter, run.VCSRepository) sdk.ParameterAddOrSetValue(&run.BuildParameters, tagGitBranch, sdk.StringParameter, run.VCSBranch) + sdk.ParameterAddOrSetValue(&run.BuildParameters, tagGitTag, sdk.StringParameter, run.VCSTag) sdk.ParameterAddOrSetValue(&run.BuildParameters, tagGitHash, sdk.StringParameter, run.VCSHash) sdk.ParameterAddOrSetValue(&run.BuildParameters, tagGitAuthor, sdk.StringParameter, vcsInfos.Author) sdk.ParameterAddOrSetValue(&run.BuildParameters, tagGitMessage, sdk.StringParameter, vcsInfos.Message) diff --git a/engine/api/workflow/resync_workflow.go b/engine/api/workflow/resync_workflow.go index e33a28da93..ffea510ed7 100644 --- a/engine/api/workflow/resync_workflow.go +++ b/engine/api/workflow/resync_workflow.go @@ -120,7 +120,8 @@ func ResyncNodeRunsWithCommits(ctx context.Context, db gorp.SqlExecutor, store c return } - commits, curVCSInfos, err := GetNodeRunBuildCommits(ctx, db, store, proj, &wr.Workflow, n.Name, wr.Number, &nr, n.Context.Application, n.Context.Environment) + //New context because we are in goroutine + commits, curVCSInfos, err := GetNodeRunBuildCommits(context.Background(), db, store, proj, &wr.Workflow, n.Name, wr.Number, &nr, n.Context.Application, n.Context.Environment) if err != nil { log.Error("ResyncNodeRuns> cannot get build commits on a node run %v", err) } else if commits != nil { @@ -143,6 +144,9 @@ func ResyncNodeRunsWithCommits(ctx context.Context, db gorp.SqlExecutor, store c if curVCSInfos.Remote != "" { tagsUpdated = wr.Tag(tagGitRepository, curVCSInfos.Remote) } + if curVCSInfos.Tag != "" { + tagsUpdated = wr.Tag(tagGitTag, curVCSInfos.Tag) + } if tagsUpdated { if err := UpdateWorkflowRunTags(db, wr); err != nil { diff --git a/engine/api/workflow/workflow_run_event.go b/engine/api/workflow/workflow_run_event.go index ea196c7962..c98553dbef 100644 --- a/engine/api/workflow/workflow_run_event.go +++ b/engine/api/workflow/workflow_run_event.go @@ -91,7 +91,11 @@ func ResyncCommitStatus(ctx context.Context, db gorp.SqlExecutor, store cache.St return sdk.WrapError(errClient, "resyncCommitStatus> Cannot get client") } - statuses, errStatuses := client.ListStatuses(ctx, node.Context.Application.RepositoryFullname, nodeRun.VCSHash) + ref := nodeRun.VCSHash + if nodeRun.VCSTag != "" { + ref = nodeRun.VCSTag + } + statuses, errStatuses := client.ListStatuses(ctx, node.Context.Application.RepositoryFullname, ref) if errStatuses != nil { return sdk.WrapError(errStatuses, "resyncCommitStatus> Cannot get statuses") } @@ -191,6 +195,7 @@ func sendVCSEventStatus(ctx context.Context, db gorp.SqlExecutor, store cache.St Payload: nodeRun.Payload, SourceNodeRuns: nodeRun.SourceNodeRuns, Hash: nodeRun.VCSHash, + Tag: nodeRun.VCSTag, BranchName: nodeRun.VCSBranch, NodeID: nodeRun.WorkflowNodeID, RunID: nodeRun.WorkflowRunID, diff --git a/engine/hooks/tasks.go b/engine/hooks/tasks.go index 73907d5eaf..9e6f595571 100644 --- a/engine/hooks/tasks.go +++ b/engine/hooks/tasks.go @@ -526,7 +526,12 @@ func executeRepositoryWebHook(t *sdk.TaskExecution) (*sdk.WorkflowNodeRunHookEve } payload["git.author"] = pushEvent.HeadCommit.Author.Username payload["git.author.email"] = pushEvent.HeadCommit.Author.Email - payload["git.branch"] = strings.TrimPrefix(strings.TrimPrefix(pushEvent.Ref, "refs/heads/"), "refs/tags/") + + if !strings.HasPrefix(pushEvent.Ref, "refs/tags/") { + payload["git.branch"] = strings.TrimPrefix(pushEvent.Ref, "refs/heads/") + } else { + payload["git.tag"] = strings.TrimPrefix(pushEvent.Ref, "refs/tags/") + } payload["git.hash.before"] = pushEvent.Before payload["git.hash"] = pushEvent.After payload["git.repository"] = pushEvent.Repository.FullName @@ -534,9 +539,6 @@ func executeRepositoryWebHook(t *sdk.TaskExecution) (*sdk.WorkflowNodeRunHookEve payload["cds.triggered_by.fullname"] = pushEvent.HeadCommit.Author.Name payload["cds.triggered_by.email"] = pushEvent.HeadCommit.Author.Email - if strings.HasPrefix(pushEvent.Ref, "refs/tags/") { - payload["git.tag"] = strings.TrimPrefix(pushEvent.Ref, "refs/tags/") - } if len(pushEvent.Commits) > 0 { payload["git.message"] = pushEvent.Commits[0].Message } @@ -551,7 +553,11 @@ func executeRepositoryWebHook(t *sdk.TaskExecution) (*sdk.WorkflowNodeRunHookEve } payload["git.author"] = pushEvent.UserUsername payload["git.author.email"] = pushEvent.UserEmail - payload["git.branch"] = strings.TrimPrefix(strings.TrimPrefix(pushEvent.Ref, "refs/heads/"), "refs/tags/") + if !strings.HasPrefix(pushEvent.Ref, "refs/tags/") { + payload["git.branch"] = strings.TrimPrefix(pushEvent.Ref, "refs/heads/") + } else { + payload["git.tag"] = strings.TrimPrefix(pushEvent.Ref, "refs/tags/") + } payload["git.hash.before"] = pushEvent.Before payload["git.hash"] = pushEvent.After payload["git.repository"] = pushEvent.Project.PathWithNamespace @@ -560,9 +566,6 @@ func executeRepositoryWebHook(t *sdk.TaskExecution) (*sdk.WorkflowNodeRunHookEve payload["cds.triggered_by.fullname"] = pushEvent.UserName payload["cds.triggered_by.email"] = pushEvent.UserEmail - if strings.HasPrefix(pushEvent.Ref, "refs/tags/") { - payload["git.tag"] = strings.TrimPrefix(pushEvent.Ref, "refs/tags/") - } if len(pushEvent.Commits) > 0 { payload["git.message"] = pushEvent.Commits[0].Message } @@ -578,7 +581,11 @@ func executeRepositoryWebHook(t *sdk.TaskExecution) (*sdk.WorkflowNodeRunHookEve return nil, nil } - payload["git.branch"] = strings.TrimPrefix(strings.TrimPrefix(pushEvent.Changes[0].RefID, "refs/heads/"), "refs/tags/") + if !strings.HasPrefix(pushEvent.Changes[0].RefID, "refs/tags/") { + payload["git.branch"] = strings.TrimPrefix(pushEvent.Changes[0].RefID, "refs/heads/") + } else { + payload["git.tag"] = strings.TrimPrefix(pushEvent.Changes[0].RefID, "refs/tags/") + } payload["git.hash.before"] = pushEvent.Changes[0].FromHash payload["git.hash"] = pushEvent.Changes[0].ToHash payload["git.repository"] = fmt.Sprintf("%s/%s", pushEvent.Repository.Project.Key, pushEvent.Repository.Slug) @@ -586,10 +593,6 @@ func executeRepositoryWebHook(t *sdk.TaskExecution) (*sdk.WorkflowNodeRunHookEve payload["cds.triggered_by.username"] = pushEvent.Actor.Name payload["cds.triggered_by.fullname"] = pushEvent.Actor.DisplayName payload["cds.triggered_by.email"] = pushEvent.Actor.EmailAddress - - if strings.HasPrefix(pushEvent.Changes[0].RefID, "refs/tags/") { - payload["git.tag"] = strings.TrimPrefix(pushEvent.Changes[0].RefID, "refs/tags/") - } default: log.Warning("executeRepositoryWebHook> Repository manager not found. Cannot read %s", string(t.WebHook.RequestBody)) return nil, fmt.Errorf("Repository manager not found. Cannot read request body") diff --git a/engine/hooks/tasks_test.go b/engine/hooks/tasks_test.go index 7a1a80d5de..19356f536c 100644 --- a/engine/hooks/tasks_test.go +++ b/engine/hooks/tasks_test.go @@ -73,7 +73,8 @@ func Test_doWebHookExecutionTagGithub(t *testing.T) { h, err := s.doWebHookExecution(task) test.NoError(t, err) - assert.Equal(t, "my-branch", h.Payload["git.branch"]) + assert.Equal(t, "", h.Payload["git.branch"]) + assert.Equal(t, "my-branch", h.Payload["git.tag"]) assert.Equal(t, "baxterthehacker", h.Payload["git.author"]) assert.Equal(t, "Update README.md", h.Payload["git.message"]) assert.Equal(t, "0d1a26e67d8f5eaf1f6ba5c57fc3c7d91ac0fd1c", h.Payload["git.hash"]) diff --git a/engine/sql/121_run_vcs_tag.sql b/engine/sql/121_run_vcs_tag.sql new file mode 100644 index 0000000000..8147c1c906 --- /dev/null +++ b/engine/sql/121_run_vcs_tag.sql @@ -0,0 +1,7 @@ +-- +migrate Up +ALTER TABLE workflow_node_run ADD COLUMN vcs_tag TEXT; +INSERT into action_parameter (action_id, name, description, type, value, advanced) (SELECT id, 'tag' AS name, 'Useful when you want to git clone a specific tag' AS description, 'string' AS type, '' AS value, true AS advanced from action where name = 'GitClone' and type = 'Builtin'); + +-- +migrate Down +DELETE from action_parameter where name = 'tag' and action_id = (select id from action where name = 'GitClone' and type = 'Builtin'); +ALTER TABLE workflow_node_run DROP COLUMN vcs_tag; diff --git a/engine/vcs/bitbucket/client_commit.go b/engine/vcs/bitbucket/client_commit.go index 602d827152..4f34adfd8f 100644 --- a/engine/vcs/bitbucket/client_commit.go +++ b/engine/vcs/bitbucket/client_commit.go @@ -147,3 +147,74 @@ func newUnknownStashUser(author Author) *User { Slug: "unknownSlug", } } + +func (b *bitbucketClient) CommitsBetweenRefs(ctx context.Context, repo, base, head string) ([]sdk.VCSCommit, error) { + var commits []sdk.VCSCommit + project, slug, err := getRepo(repo) + if err != nil { + return nil, sdk.WrapError(err, "vcs> bitbucket> CommitsBetweenRefs>") + } + + var stashCommits []Commit + + var stashCommitsKey = cache.Key("vcs", "bitbucket", b.consumer.URL, repo, "compare/commits", "from@"+base, "to@"+head) + + if !b.consumer.cache.Get(stashCommitsKey, &stashCommits) { + response := CommitsResponse{} + path := fmt.Sprintf("/projects/%s/repos/%s/compare/commits", project, slug) + params := url.Values{} + if base != "" { + params.Add("from", base) + } + if head != "" { + params.Add("to", head) + } + + for { + if response.NextPageStart != 0 { + params.Set("start", fmt.Sprintf("%d", response.NextPageStart)) + } + + if err := b.do(ctx, "GET", "core", path, params, nil, &response, nil); err != nil { + if err == sdk.ErrNotFound { + return nil, nil + } + return nil, sdk.WrapError(err, "vcs> bitbucket> CommitsBetweenRefs> Unable to get commits %s", path) + } + + stashCommits = append(stashCommits, response.Values...) + if response.IsLastPage { + break + } + } + b.consumer.cache.SetWithTTL(stashCommitsKey, stashCommits, 3*60*60) //3 hours + } + + urlCommit := b.consumer.URL + "/projects/" + project + "/repos/" + slug + "/compare/commits" + for _, sc := range stashCommits { + c := sdk.VCSCommit{ + Hash: sc.Hash, + Timestamp: sc.Timestamp, + Message: sc.Message, + Author: sdk.VCSAuthor{ + Name: sc.Author.Name, + Email: sc.Author.Email, + }, + URL: urlCommit + sc.Hash, + } + stashUser := b.findUser(ctx, sc.Author.Email) + if stashUser == nil { + newStashUserUnknown := newUnknownStashUser(*sc.Author) + var stashUserKey = cache.Key("vcs", "bitbucket", b.consumer.URL, sc.Author.Email) + b.consumer.cache.SetWithTTL(stashUserKey, newStashUserUnknown, 86400) // 1 day + stashUser = newUnknownStashUser(*sc.Author) + } + + c.Author.DisplayName = stashUser.DisplayName + if stashUser.Slug != "" && stashUser.Slug != "unknownSlug" { + c.Author.Avatar = fmt.Sprintf("%s/users/%s/avatar.png", b.consumer.URL, stashUser.Slug) + } + commits = append(commits, c) + } + return commits, nil +} diff --git a/engine/vcs/github/client_commit.go b/engine/vcs/github/client_commit.go index bbd7787948..5fc94cda35 100644 --- a/engine/vcs/github/client_commit.go +++ b/engine/vcs/github/client_commit.go @@ -257,3 +257,48 @@ func (g *githubClient) Commit(ctx context.Context, repo, hash string) (sdk.VCSCo return commit, nil } + +func (g *githubClient) CommitsBetweenRefs(ctx context.Context, repo, base, head string) ([]sdk.VCSCommit, error) { + var commits []sdk.VCSCommit + url := fmt.Sprintf("/repos/%s/compare/%s...%s", repo, base, head) + status, body, _, err := g.get(url) + if err != nil { + log.Warning("githubClient.CommitsBetweenRefs> Error %s", err) + return commits, err + } + if status >= 400 { + return commits, sdk.NewError(sdk.ErrRepoNotFound, errorAPI(body)) + } + + var diff DiffCommits + //Github may return 304 status because we are using conditional request with ETag based headers + if status == http.StatusNotModified { + //If repo isn't updated, lets get them from cache + g.Cache.Get(cache.Key("vcs", "github", "commitdiff", g.OAuthToken, url), &commits) + } else { + if err := json.Unmarshal(body, &diff); err != nil { + log.Warning("githubClient.CommitsBetweenRefs> Unable to parse github commit: %s", err) + return commits, err + } + + commits = make([]sdk.VCSCommit, len(diff.Commits)) + for i, commit := range diff.Commits { + commits[i] = sdk.VCSCommit{ + Timestamp: commit.Commit.Author.Date.Unix() * 1000, + Message: commit.Commit.Message, + Hash: commit.Sha, + Author: sdk.VCSAuthor{ + DisplayName: commit.Commit.Author.Name, + Email: commit.Commit.Author.Email, + Name: commit.Author.Login, + Avatar: commit.Author.AvatarURL, + }, + URL: commit.HTMLURL, + } + } + //Put the body on cache for one hour and one minute + g.Cache.SetWithTTL(cache.Key("vcs", "github", "commitdiff", g.OAuthToken, url), &commits, 61*60) + } + + return commits, nil +} diff --git a/engine/vcs/github/types.go b/engine/vcs/github/types.go index 90f80fd2b2..98fbc46424 100644 --- a/engine/vcs/github/types.go +++ b/engine/vcs/github/types.go @@ -654,3 +654,57 @@ type RepositoryInvitation struct { URL string `json:"url"` HTMLURL string `json:"html_url"` } + +// DiffCommits represent response from github api for a diff between commits +type DiffCommits struct { + URL string `json:"url"` + HTMLURL string `json:"html_url"` + PermalinkURL string `json:"permalink_url"` + DiffURL string `json:"diff_url"` + PatchURL string `json:"patch_url"` + BaseCommit struct { + URL string `json:"url"` + Sha string `json:"sha"` + NodeID string `json:"node_id"` + HTMLURL string `json:"html_url"` + CommentsURL string `json:"comments_url"` + Commit Commit `json:"commit"` + Author User `json:"author"` + Committer User `json:"committer"` + Parents []struct { + URL string `json:"url"` + Sha string `json:"sha"` + } `json:"parents"` + } `json:"base_commit"` + MergeBaseCommit struct { + URL string `json:"url"` + Sha string `json:"sha"` + NodeID string `json:"node_id"` + HTMLURL string `json:"html_url"` + CommentsURL string `json:"comments_url"` + Commit Commit `json:"commit"` + Author User `json:"author"` + Committer User `json:"committer"` + Parents []struct { + URL string `json:"url"` + Sha string `json:"sha"` + } `json:"parents"` + } `json:"merge_base_commit"` + Status string `json:"status"` + AheadBy int `json:"ahead_by"` + BehindBy int `json:"behind_by"` + TotalCommits int `json:"total_commits"` + Commits []Commit `json:"commits"` + Files []struct { + Sha string `json:"sha"` + Filename string `json:"filename"` + Status string `json:"status"` + Additions int `json:"additions"` + Deletions int `json:"deletions"` + Changes int `json:"changes"` + BlobURL string `json:"blob_url"` + RawURL string `json:"raw_url"` + ContentsURL string `json:"contents_url"` + Patch string `json:"patch"` + } `json:"files"` +} diff --git a/engine/vcs/gitlab/client_branch.go b/engine/vcs/gitlab/client_branch.go index b47b9f396d..28df1d7035 100644 --- a/engine/vcs/gitlab/client_branch.go +++ b/engine/vcs/gitlab/client_branch.go @@ -14,16 +14,15 @@ func (c *gitlabClient) Branches(ctx context.Context, fullname string) ([]sdk.VCS return nil, err } - var brs []sdk.VCSBranch - for _, b := range branches { - br := sdk.VCSBranch{ + brs := make([]sdk.VCSBranch, len(branches)) + for i, b := range branches { + brs[i] = sdk.VCSBranch{ ID: b.Name, DisplayID: b.Name, LatestCommit: b.Commit.ID, Default: false, Parents: nil, } - brs = append(brs, br) } return brs, nil diff --git a/engine/vcs/gitlab/client_commit.go b/engine/vcs/gitlab/client_commit.go index f7a9445ed5..fe9f10076e 100644 --- a/engine/vcs/gitlab/client_commit.go +++ b/engine/vcs/gitlab/client_commit.go @@ -34,9 +34,9 @@ func (c *gitlabClient) Commits(ctx context.Context, repo, branch, since, until s return nil, err } - var vcscommits []sdk.VCSCommit - for _, c := range commits { - vcsc := sdk.VCSCommit{ + vcscommits := make([]sdk.VCSCommit, len(commits)) + for i, c := range commits { + vcscommits[i] = sdk.VCSCommit{ Hash: c.ID, Author: sdk.VCSAuthor{ Name: c.AuthorName, @@ -46,8 +46,6 @@ func (c *gitlabClient) Commits(ctx context.Context, repo, branch, since, until s Timestamp: c.AuthoredDate.Unix(), Message: c.Message, } - - vcscommits = append(vcscommits, vcsc) } return vcscommits, nil @@ -73,3 +71,35 @@ func (c *gitlabClient) Commit(ctx context.Context, repo, hash string) (sdk.VCSCo return commit, nil } + +func (c *gitlabClient) CommitsBetweenRefs(ctx context.Context, repo, base, head string) ([]sdk.VCSCommit, error) { + opt := &gitlab.CompareOptions{ + From: &base, + To: &head, + } + + compare, _, err := c.client.Repositories.Compare(repo, opt) + if err != nil { + return nil, err + } + + if compare == nil || compare.Commits == nil { + return nil, nil + } + + vcscommits := make([]sdk.VCSCommit, len(compare.Commits)) + for i, c := range compare.Commits { + vcscommits[i] = sdk.VCSCommit{ + Hash: c.ID, + Author: sdk.VCSAuthor{ + Name: c.AuthorName, + DisplayName: c.AuthorName, + Email: c.AuthorEmail, + }, + Timestamp: c.AuthoredDate.Unix(), + Message: c.Message, + } + } + + return vcscommits, nil +} diff --git a/engine/vcs/vcs_handlers.go b/engine/vcs/vcs_handlers.go index b94b9a4bf6..58f3bd3f1d 100644 --- a/engine/vcs/vcs_handlers.go +++ b/engine/vcs/vcs_handlers.go @@ -295,6 +295,39 @@ func (s *Service) getCommitsHandler() service.Handler { } } +func (s *Service) getCommitsBetweenRefsHandler() service.Handler { + return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { + name := muxVar(r, "name") + owner := muxVar(r, "owner") + repo := muxVar(r, "repo") + base := r.URL.Query().Get("base") + head := r.URL.Query().Get("head") + + log.Debug("getCommitsBetweenRefsHandler>") + + accessToken, accessTokenSecret, ok := getAccessTokens(ctx) + if !ok { + return sdk.WrapError(sdk.ErrUnauthorized, "VCS> getCommitsBetweenRefsHandler> Unable to get access token headers") + } + + consumer, err := s.getConsumer(name) + if err != nil { + return sdk.WrapError(err, "VCS> getCommitsBetweenRefsHandler> VCS server unavailable") + } + + client, err := consumer.GetAuthorizedClient(ctx, accessToken, accessTokenSecret) + if err != nil { + return sdk.WrapError(err, "VCS> getCommitsBetweenRefsHandler> Unable to get authorized client") + } + + commits, err := client.CommitsBetweenRefs(ctx, fmt.Sprintf("%s/%s", owner, repo), base, head) + if err != nil { + return sdk.WrapError(err, "VCS> getCommitsBetweenRefsHandler> Unable to get commits of %s/%s commits diff between %s and %s", owner, repo, base, head) + } + return service.WriteJSON(w, commits, http.StatusOK) + } +} + func (s *Service) getCommitHandler() service.Handler { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { name := muxVar(r, "name") diff --git a/engine/vcs/vcs_router.go b/engine/vcs/vcs_router.go index 844463f629..4c8c914a2e 100644 --- a/engine/vcs/vcs_router.go +++ b/engine/vcs/vcs_router.go @@ -31,6 +31,7 @@ func (s *Service) initRouter(ctx context.Context) { r.Handle("/vcs/{name}/repos/{owner}/{repo}/branches", r.GET(s.getBranchesHandler, api.EnableTracing())) r.Handle("/vcs/{name}/repos/{owner}/{repo}/branches/", r.GET(s.getBranchHandler, api.EnableTracing())) r.Handle("/vcs/{name}/repos/{owner}/{repo}/branches/commits", r.GET(s.getCommitsHandler, api.EnableTracing())) + r.Handle("/vcs/{name}/repos/{owner}/{repo}/commits", r.GET(s.getCommitsBetweenRefsHandler, api.EnableTracing())) r.Handle("/vcs/{name}/repos/{owner}/{repo}/commits/{commit}", r.GET(s.getCommitHandler, api.EnableTracing())) r.Handle("/vcs/{name}/repos/{owner}/{repo}/commits/{commit}/statuses", r.GET(s.getCommitStatusHandler, api.EnableTracing())) r.Handle("/vcs/{name}/repos/{owner}/{repo}/grant", r.POST(s.postRepoGrantHandler, api.EnableTracing())) diff --git a/engine/worker/builtin_checkout_application.go b/engine/worker/builtin_checkout_application.go index a1fa1d920e..f52c2a88db 100644 --- a/engine/worker/builtin_checkout_application.go +++ b/engine/worker/builtin_checkout_application.go @@ -17,6 +17,7 @@ func runCheckoutApplication(w *currentWorker) BuiltInAction { // Load build param branch := sdk.ParameterFind(params, "git.branch") defaultBranch := sdk.ParameterValue(*params, "git.default_branch") + tag := sdk.ParameterValue(*params, "git.tag") commit := sdk.ParameterFind(params, "git.hash") gitURL, auth, err := extractVCSInformations(*params, secrets) @@ -34,6 +35,7 @@ func runCheckoutApplication(w *currentWorker) BuiltInAction { Recursive: true, NoStrictHostKeyChecking: true, Depth: 50, + Tag: tag, } if branch != nil { opts.Branch = branch.Value @@ -42,13 +44,13 @@ func runCheckoutApplication(w *currentWorker) BuiltInAction { } // if there is no branch, check if there a defaultBranch - if (opts.Branch == "" || opts.Branch == "{{.git.branch}}") && defaultBranch != "" { + if (opts.Branch == "" || opts.Branch == "{{.git.branch}}") && defaultBranch != "" && tag == "" { opts.Branch = defaultBranch opts.SingleBranch = false sendLog(fmt.Sprintf("branch is empty, using the default branch %s", defaultBranch)) } - r, _ := regexp.Compile("{{.*}}") + r := regexp.MustCompile("{{.*}}") if commit != nil && commit.Value != "" && !r.MatchString(commit.Value) { opts.CheckoutCommit = commit.Value } diff --git a/engine/worker/builtin_gitclone.go b/engine/worker/builtin_gitclone.go index 98c0f425c9..3e6572dd9b 100644 --- a/engine/worker/builtin_gitclone.go +++ b/engine/worker/builtin_gitclone.go @@ -24,6 +24,7 @@ func runGitClone(w *currentWorker) BuiltInAction { password := sdk.ParameterFind(&a.Parameters, "password") branch := sdk.ParameterFind(&a.Parameters, "branch") defaultBranch := sdk.ParameterValue(*params, "git.default_branch") + tag := sdk.ParameterValue(a.Parameters, "tag") commit := sdk.ParameterFind(&a.Parameters, "commit") directory := sdk.ParameterFind(&a.Parameters, "directory") depth := sdk.ParameterFind(&a.Parameters, "depth") @@ -153,6 +154,7 @@ func runGitClone(w *currentWorker) BuiltInAction { Recursive: true, NoStrictHostKeyChecking: true, Depth: 50, + Tag: tag, } if branch != nil { opts.Branch = branch.Value @@ -176,7 +178,7 @@ func runGitClone(w *currentWorker) BuiltInAction { } // if there is no branch, check if there a defaultBranch - if (opts.Branch == "" || opts.Branch == "{{.git.branch}}") && defaultBranch != "" { + if (opts.Branch == "" || opts.Branch == "{{.git.branch}}") && defaultBranch != "" && tag == "" { opts.Branch = defaultBranch opts.SingleBranch = false sendLog(fmt.Sprintf("branch is empty, using the default branch %s", defaultBranch)) @@ -206,7 +208,6 @@ func gitClone(w *currentWorker, params *[]sdk.Parameter, url string, dir string, } git.LogFunc = log.Info - //Perform the git clone userLogCommand, err := git.Clone(url, dir, auth, clone, output) @@ -233,7 +234,7 @@ func gitClone(w *currentWorker, params *[]sdk.Parameter, url string, dir string, gitURLSSH := sdk.ParameterValue(*params, "git.url") gitURLHTTP := sdk.ParameterValue(*params, "git.http_url") if gitURLSSH == url || gitURLHTTP == url { - extractInfo(w, dir, params, clone.Branch, clone.CheckoutCommit, sendLog) + _ = extractInfo(w, dir, params, clone.Tag, clone.Branch, clone.CheckoutCommit, sendLog) } stdTaglistErr := new(bytes.Buffer) @@ -261,7 +262,7 @@ func gitClone(w *currentWorker, params *[]sdk.Parameter, url string, dir string, return sdk.Result{Status: sdk.StatusSuccess.String()} } -func extractInfo(w *currentWorker, dir string, params *[]sdk.Parameter, branch, commit string, sendLog LoggerFunc) error { +func extractInfo(w *currentWorker, dir string, params *[]sdk.Parameter, tag, branch, commit string, sendLog LoggerFunc) error { author := sdk.ParameterValue(*params, "git.author") authorEmail := sdk.ParameterValue(*params, "git.author.email") message := sdk.ParameterValue(*params, "git.message") @@ -339,7 +340,7 @@ func extractInfo(w *currentWorker, dir string, params *[]sdk.Parameter, branch, sendLog(fmt.Sprintf("cds.semver: %s", cdsSemver)) } - if branch == "" || branch == "{{.git.branch}}" { + if (branch == "" || branch == "{{.git.branch}}") && tag == "" { if info.Branch != "" { gitBranch := sdk.Variable{ Name: "git.branch", @@ -354,10 +355,14 @@ func extractInfo(w *currentWorker, dir string, params *[]sdk.Parameter, branch, } else { sendLog("git.branch: [empty]") } - } else { + } else if branch != "" && branch != "{{.git.branch}}" { sendLog(fmt.Sprintf("git.branch: %s", branch)) } + if tag != "" { + sendLog(fmt.Sprintf("git.tag: %s", tag)) + } + if commit == "" || commit == "{{.git.hash}}" { if info.Hash != "" { gitHash := sdk.Variable{ diff --git a/sdk/error.go b/sdk/error.go index b9c30187bd..6ca9538223 100644 --- a/sdk/error.go +++ b/sdk/error.go @@ -227,7 +227,7 @@ var errorsAmericanEnglish = map[int]string{ ErrConflict.ID: "object conflict", ErrPipelineAlreadyAttached.ID: "pipeline already attached to this application", ErrApplicationExist.ID: "application already exists", - ErrBranchNameNotProvided.ID: "branchName parameter must be provided", + ErrBranchNameNotProvided.ID: "git.branch or git.tag parameter must be provided", ErrInfiniteTriggerLoop.ID: "infinite trigger loop are forbidden", ErrInvalidResetUser.ID: "invalid user or email", ErrUserConflict.ID: "this user already exists", @@ -371,7 +371,7 @@ var errorsFrench = map[int]string{ ErrConflict.ID: "l'objet est en conflit", ErrPipelineAlreadyAttached.ID: "le pipeline est déjà attaché à cette application", ErrApplicationExist.ID: "une application du même nom existe déjà", - ErrBranchNameNotProvided.ID: "le paramètre branchName doit être envoyé", + ErrBranchNameNotProvided.ID: "le paramètre git.branch ou git.tag est obligatoire", ErrInfiniteTriggerLoop.ID: "création d'une boucle de trigger infinie interdite", ErrInvalidResetUser.ID: "mauvaise combinaison compte/mail utilisateur", ErrUserConflict.ID: "cet utilisateur existe deja", diff --git a/sdk/event.go b/sdk/event.go index ccea8398b7..e3108f0d9a 100644 --- a/sdk/event.go +++ b/sdk/event.go @@ -72,6 +72,7 @@ type EventRunWorkflowNode struct { RepositoryManagerName string `json:"repository_manager_name"` RepositoryFullName string `json:"repository_full_name"` Hash string `json:"hash"` + Tag string `json:"tag"` BranchName string `json:"branch_name"` NodeName string `json:"node_name"` StagesSummary []StageSummary `json:"stages_summary"` diff --git a/sdk/exportentities/action.go b/sdk/exportentities/action.go index 57a88a5661..12b99ea26f 100644 --- a/sdk/exportentities/action.go +++ b/sdk/exportentities/action.go @@ -159,6 +159,10 @@ func newSteps(a sdk.Action) []Step { if submodules != nil && submodules.Value == "false" { gitCloneArgs["submodules"] = submodules.Value } + tag := sdk.ParameterFind(&act.Parameters, "tag") + if tag != nil && tag.Value != "" { + gitCloneArgs["tag"] = tag.Value + } s["gitClone"] = gitCloneArgs case sdk.JUnitAction: path := sdk.ParameterFind(&act.Parameters, "path") diff --git a/sdk/vcs.go b/sdk/vcs.go index c5d0968a10..5528ccae15 100644 --- a/sdk/vcs.go +++ b/sdk/vcs.go @@ -12,6 +12,7 @@ type BuildNumberAndHash struct { BuildNumber int64 Hash string Branch string + Tag string Remote string RemoteURL string } @@ -36,6 +37,7 @@ type VCSAuthorizedClient interface { //Commits Commits(ctx context.Context, repo, branch, since, until string) ([]VCSCommit, error) Commit(ctx context.Context, repo, hash string) (VCSCommit, error) + CommitsBetweenRefs(ctx context.Context, repo, base, head string) ([]VCSCommit, error) // PullRequests PullRequests(context.Context, string) ([]VCSPullRequest, error) diff --git a/sdk/vcs/git/git_clone.go b/sdk/vcs/git/git_clone.go index c217410242..2ace7c0258 100644 --- a/sdk/vcs/git/git_clone.go +++ b/sdk/vcs/git/git_clone.go @@ -11,6 +11,7 @@ type CloneOpts struct { Depth int SingleBranch bool Branch string + Tag string Recursive bool Verbose bool Quiet bool @@ -57,8 +58,12 @@ func prepareGitCloneCommands(repo string, path string, opts *CloneOpts) (string, gitcmd.args = append(gitcmd.args, "--depth", fmt.Sprintf("%d", opts.Depth)) } - if opts.Branch != "" { - gitcmd.args = append(gitcmd.args, "--branch", opts.Branch) + if opts.Branch != "" || opts.Tag != "" { + if opts.Branch != "" { + gitcmd.args = append(gitcmd.args, "--branch", opts.Branch) + } else { + gitcmd.args = append(gitcmd.args, "--branch", opts.Tag) + } } else if opts.SingleBranch { gitcmd.args = append(gitcmd.args, "--single-branch") } diff --git a/sdk/workflow.go b/sdk/workflow.go index 76902ccb61..fb9b17da9b 100644 --- a/sdk/workflow.go +++ b/sdk/workflow.go @@ -930,6 +930,7 @@ func (c *WorkflowNodeContext) DefaultPayloadToMap() (map[string]string, error) { //WorkflowNodeContextDefaultPayloadVCS represents a default payload when a workflow is attached to a repository Webhook type WorkflowNodeContextDefaultPayloadVCS struct { GitBranch string `json:"git.branch" db:"-"` + GitTag string `json:"git.tag" db:"-"` GitHash string `json:"git.hash" db:"-"` GitAuthor string `json:"git.author" db:"-"` GitHashBefore string `json:"git.hash.before" db:"-"` diff --git a/sdk/workflow_run.go b/sdk/workflow_run.go index afb95b0745..3344e31106 100644 --- a/sdk/workflow_run.go +++ b/sdk/workflow_run.go @@ -169,6 +169,7 @@ type WorkflowNodeRun struct { Commits []VCSCommit `json:"commits,omitempty"` TriggersRun map[int64]WorkflowNodeTriggerRun `json:"triggers_run,omitempty"` VCSRepository string `json:"vcs_repository"` + VCSTag string `json:"vcs_tag"` VCSBranch string `json:"vcs_branch"` VCSHash string `json:"vcs_hash"` VCSServer string `json:"vcs_server"` @@ -176,7 +177,7 @@ type WorkflowNodeRun struct { Header WorkflowRunHeaders `json:"header,omitempty"` } -// WorkflowNodeRunVulnerability represents vulnerabilities report for the current node run +// WorkflowNodeRunVulnerabilityReport represents vulnerabilities report for the current node run type WorkflowNodeRunVulnerabilityReport struct { ID int64 `json:"id" db:"id"` ApplicationID int64 `json:"application_id" db:"application_id"` @@ -367,7 +368,7 @@ func (w *WorkflowNodeRunArtifact) GetPath() string { return container } -const workflowNodeRunReport = `{{- if .Stages }} +const workflowNodeRunReport = `{{- if .Stages }} CDS Report {{.WorkflowNodeName}}#{{.Number}}.{{.SubNumber}} {{ if eq .Status "Success" -}} ✔ {{ else }}{{ if eq .Status "Fail" -}} ✘ {{ else }}- {{ end }} {{ end }} {{- range $s := .Stages}} {{- if $s.RunJobs }} @@ -379,7 +380,7 @@ CDS Report {{.WorkflowNodeName}}#{{.Number}}.{{.SubNumber}} {{ if eq .Status "Su {{- end}} {{- end}} -{{- if .Tests }} +{{- if .Tests }} {{- if gt .Tests.TotalKO 0}} Unit Tests Report diff --git a/tests/fixtures/ITSCWRKFLW6/ITSCWRKFLW6.yml b/tests/fixtures/ITSCWRKFLW6/ITSCWRKFLW6.yml new file mode 100644 index 0000000000..612c74701d --- /dev/null +++ b/tests/fixtures/ITSCWRKFLW6/ITSCWRKFLW6.yml @@ -0,0 +1,9 @@ +name: "ITSCWRKFLW6-WORKFLOW" +version: v1.0 +pipeline: clonetagbis +payload: + git.author: "" + git.tag: "v0.4.0" + git.hash: "" + git.message: "" + git.repository: ovh/cds diff --git a/tests/fixtures/ITSCWRKFLW6/clone.pip.yml b/tests/fixtures/ITSCWRKFLW6/clone.pip.yml new file mode 100644 index 0000000000..08435f727e --- /dev/null +++ b/tests/fixtures/ITSCWRKFLW6/clone.pip.yml @@ -0,0 +1,16 @@ +version: v1.0 +name: clonetagbis +jobs: +- job: New Job + steps: + - gitClone: + branch: '{{.git.branch}}' + commit: '{{.git.hash}}' + directory: '{{.cds.workspace}}' + tag: '{{.git.tag}}' + url: https://github.com/ovh/cds.git + - script: + - git log + - cat README.md + requirements: + - binary: git diff --git a/tests/sc_workflow_git_clone_tag.yml b/tests/sc_workflow_git_clone_tag.yml new file mode 100644 index 0000000000..1192332d9f --- /dev/null +++ b/tests/sc_workflow_git_clone_tag.yml @@ -0,0 +1,40 @@ +name: Create a simple workflow (ITSCWRKFLW6) and run it to test gitClone action +testcases: +- name: assert filepath, your current directory must be at the root of this project + steps: + - script: '[ -f ./tests/fixtures/ITSCWRKFLW6/clone.pip.yml ]' + - script: '[ -f ./tests/fixtures/ITSCWRKFLW6/ITSCWRKFLW6.yml ]' + +- name: prepare test + steps: + - script: "{{.cds.build.cdsctl}} project remove --force ITSCWRKFLW6" + - script: "{{.cds.build.cdsctl}} group remove --force ITSCWRKFLW6" + - script: "{{.cds.build.cdsctl}} project add ITSCWRKFLW6 ITSCWRKFLW6" + +- name: import pipeline + steps: + - script: {{.cds.build.cdsctl}} pipeline import --force ITSCWRKFLW6 ./tests/fixtures/ITSCWRKFLW6/clone.pip.yml + assertions: + - result.code ShouldEqual 0 + +- name: import workflow + steps: + - script: {{.cds.build.cdsctl}} workflow import --force ITSCWRKFLW6 ./tests/fixtures/ITSCWRKFLW6/ITSCWRKFLW6.yml + assertions: + - result.code ShouldEqual 0 + +- name: run workflow + steps: + - script: "{{.cds.build.cdsctl}} workflow run ITSCWRKFLW6 ITSCWRKFLW6-WORKFLOW -d '{\"git.tag\": \"v0.4.0\"}'" + assertions: + - result.code ShouldEqual 0 + - "result.systemout ShouldContainSubstring Workflow ITSCWRKFLW6-WORKFLOW #1 has been launched" + +- name: check workflow + steps: + - script: {{.cds.build.cdsctl}} workflow status ITSCWRKFLW6 ITSCWRKFLW6-WORKFLOW 1 --format json + assertions: + - result.code ShouldEqual 0 + - result.systemoutjson.status ShouldEqual Success + retry: 30 + delay: 10