Merge branch '682' into 'main'

feat(ci): add ci trigger (#682)

Closes #682

See merge request https://gitlab.com/gitlab-org/cli/-/merge_requests/1374

Merged-by: Shekhar Patnaik <spatnaik@gitlab.com>
Approved-by: Amy Qualls <aqualls@gitlab.com>
Approved-by: Gary Holtz <gholtz@gitlab.com>
Approved-by: Shekhar Patnaik <spatnaik@gitlab.com>
Co-authored-by: Andreas Weber <weber.andreas@gmail.com>
Co-authored-by: Amy Qualls <aqualls@gitlab.com>
This commit is contained in:
Shekhar Patnaik 2023-12-06 12:43:58 +00:00
commit dd611918d7
5 changed files with 322 additions and 0 deletions

View File

@ -11,6 +11,7 @@ import (
pipeRunCmd "gitlab.com/gitlab-org/cli/commands/ci/run"
pipeStatusCmd "gitlab.com/gitlab-org/cli/commands/ci/status"
ciTraceCmd "gitlab.com/gitlab-org/cli/commands/ci/trace"
pipeTriggerCmd "gitlab.com/gitlab-org/cli/commands/ci/trigger"
ciViewCmd "gitlab.com/gitlab-org/cli/commands/ci/view"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
@ -36,6 +37,7 @@ func NewCmdCI(f *cmdutils.Factory) *cobra.Command {
ciCmd.AddCommand(pipeStatusCmd.NewCmdStatus(f))
ciCmd.AddCommand(pipeRetryCmd.NewCmdRetry(f))
ciCmd.AddCommand(pipeRunCmd.NewCmdRun(f))
ciCmd.AddCommand(pipeTriggerCmd.NewCmdTrigger(f))
ciCmd.AddCommand(jobArtifactCmd.NewCmdRun(f))
ciCmd.AddCommand(pipeGetCmd.NewCmdGet(f))
return ciCmd

View File

@ -0,0 +1,105 @@
package trigger
import (
"fmt"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/pkg/git"
"gitlab.com/gitlab-org/cli/pkg/utils"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
)
func NewCmdTrigger(f *cmdutils.Factory) *cobra.Command {
pipelineTriggerCmd := &cobra.Command{
Use: "trigger <job-id>",
Short: `Trigger a manual CI/CD job.`,
Aliases: []string{},
Example: heredoc.Doc(`
$ glab ci trigger 224356863
#=> trigger manual job with id 224356863
$ glab ci trigger lint
#=> trigger manual job with name lint
`),
Long: ``,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var err error
apiClient, err := f.HttpClient()
if err != nil {
return err
}
repo, err := f.BaseRepo()
if err != nil {
return err
}
jobID := utils.StringToInt(args[0])
if jobID < 1 {
jobName := args[0]
pipelineId, err := cmd.Flags().GetInt("pipeline-id")
if err != nil || pipelineId == 0 {
branch, _ := cmd.Flags().GetString("branch")
if branch == "" {
branch, err = git.CurrentBranch()
if err != nil {
return err
}
}
commit, err := api.GetCommit(apiClient, repo.FullName(), branch)
if err != nil {
return err
}
pipelineId = commit.LastPipeline.ID
}
jobs, _, err := apiClient.Jobs.ListPipelineJobs(repo.FullName(), pipelineId, nil)
if err != nil {
return err
}
for _, job := range jobs {
if job.Name == jobName {
jobID = job.ID
break
}
}
if jobID < 1 {
fmt.Fprintln(f.IO.StdErr, "invalid job id:", args[0])
return cmdutils.SilentError
}
}
job, err := api.PlayPipelineJob(apiClient, jobID, repo.FullName())
if err != nil {
return cmdutils.WrapError(err, fmt.Sprintf("Could not trigger job with ID: %d", jobID))
}
fmt.Fprintln(f.IO.StdOut, "Triggered job (ID:", job.ID, "), status:", job.Status, ", ref:", job.Ref, ", weburl: ", job.WebURL, ")")
return nil
},
}
pipelineTriggerCmd.Flags().StringP("branch", "b", "", "The branch to search for the job. (Default: current branch)")
pipelineTriggerCmd.Flags().IntP("pipeline-id", "p", 0, "The pipeline ID to search for the job.")
return pipelineTriggerCmd
}
type Jobs []*gitlab.Job
// FindByName returns the first Remote whose name matches the list
func (jobs Jobs) FindByName(name string) (*gitlab.Job, error) {
for _, job := range jobs {
if job.Name == name {
return job, nil
}
}
return nil, cmdutils.SilentError
}

View File

@ -0,0 +1,171 @@
package trigger
import (
"net/http"
"testing"
"gitlab.com/gitlab-org/cli/commands/cmdtest"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gitlab.com/gitlab-org/cli/pkg/httpmock"
"gitlab.com/gitlab-org/cli/test"
)
func runCommand(rt http.RoundTripper, isTTY bool, args string) (*test.CmdOut, error) {
ios, _, stdout, stderr := cmdtest.InitIOStreams(isTTY, "")
factory := cmdtest.InitFactory(ios, rt)
_, _ = factory.HttpClient()
cmd := NewCmdTrigger(factory)
return cmdtest.ExecuteCommand(cmd, args, stdout, stderr)
}
func TestCiTrigger(t *testing.T) {
type httpMock struct {
method string
path string
status int
body string
}
tests := []struct {
name string
args string
httpMocks []httpMock
expectedOut string
}{
{
name: "when trigger with job-id is created",
args: "1122",
httpMocks: []httpMock{
{
http.MethodPost,
"/api/v4/projects/OWNER/REPO/jobs/1122/play",
http.StatusCreated,
`{
"id": 1123,
"status": "pending",
"stage": "build",
"name": "build-job",
"ref": "branch-name",
"tag": false,
"coverage": null,
"allow_failure": false,
"created_at": "2022-12-01T05:13:13.703Z",
"web_url": "https://gitlab.com/OWNER/REPO/-/jobs/1123"
}`,
},
},
expectedOut: "Triggered job (ID: 1123 ), status: pending , ref: branch-name , weburl: https://gitlab.com/OWNER/REPO/-/jobs/1123 )\n",
},
{
name: "when trigger with job-name is created",
args: "lint -b main -p 123",
httpMocks: []httpMock{
{
http.MethodPost,
"/api/v4/projects/OWNER/REPO/jobs/1122/play",
http.StatusCreated,
`{
"id": 1123,
"status": "pending",
"stage": "build",
"name": "build-job",
"ref": "branch-name",
"tag": false,
"coverage": null,
"allow_failure": false,
"created_at": "2022-12-01T05:13:13.703Z",
"web_url": "https://gitlab.com/OWNER/REPO/-/jobs/1123"
}`,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/pipelines/123/jobs",
http.StatusOK,
`[{
"id": 1122,
"name": "lint",
"status": "failed"
}, {
"id": 1124,
"name": "publish",
"status": "failed"
}]`,
},
},
expectedOut: "Triggered job (ID: 1123 ), status: pending , ref: branch-name , weburl: https://gitlab.com/OWNER/REPO/-/jobs/1123 )\n",
},
{
name: "when trigger with job-name and last pipeline is created",
args: "lint -b main",
httpMocks: []httpMock{
{
http.MethodPost,
"/api/v4/projects/OWNER/REPO/jobs/1122/play",
http.StatusCreated,
`{
"id": 1123,
"status": "pending",
"stage": "build",
"name": "build-job",
"ref": "branch-name",
"tag": false,
"coverage": null,
"allow_failure": false,
"created_at": "2022-12-01T05:13:13.703Z",
"web_url": "https://gitlab.com/OWNER/REPO/-/jobs/1123"
}`,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/repository/commits/main",
http.StatusOK,
`{
"last_pipeline" : {
"id": 123
}
}`,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/pipelines/123/jobs",
http.StatusOK,
`[{
"id": 1122,
"name": "lint",
"status": "failed"
}, {
"id": 1124,
"name": "publish",
"status": "failed"
}]`,
},
},
expectedOut: "Triggered job (ID: 1123 ), status: pending , ref: branch-name , weburl: https://gitlab.com/OWNER/REPO/-/jobs/1123 )\n",
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
fakeHTTP := &httpmock.Mocker{
MatchURL: httpmock.PathAndQuerystring,
}
defer fakeHTTP.Verify(t)
for _, mock := range tc.httpMocks {
fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, mock.body))
}
output, err := runCommand(fakeHTTP, false, tc.args)
require.Nil(t, err)
assert.Equal(t, tc.expectedOut, output.String())
assert.Empty(t, output.Stderr())
})
}
}

View File

@ -44,4 +44,5 @@ pipeline
- [`run`](run.md)
- [`status`](status.md)
- [`trace`](trace.md)
- [`trigger`](trigger.md)
- [`view`](view.md)

43
docs/source/ci/trigger.md Normal file
View File

@ -0,0 +1,43 @@
---
stage: Create
group: Code Review
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
<!--
This documentation is auto generated by a script.
Please do not edit this file directly. Run `make gen-docs` instead.
-->
# `glab ci trigger`
Trigger a manual CI/CD job.
```plaintext
glab ci trigger <job-id> [flags]
```
## Examples
```plaintext
$ glab ci trigger 224356863
#=> trigger manual job with id 224356863
$ glab ci trigger lint
#=> trigger manual job with name lint
```
## Options
```plaintext
-b, --branch string The branch to search for the job. (Default: current branch)
-p, --pipeline-id int The pipeline ID to search for the job.
```
## Options inherited from parent commands
```plaintext
--help Show help for command
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL
```