fix: update "glab ci run-trig" to use new go-gitlab functions

- Resolve merge conflicts
- Update "glab ci run-trig" to use new go-gitlab pointer functions

Fixes #7504

Signed-off-by: Justen Stall <justenstall@gmail.com>
This commit is contained in:
Justen Stall 2024-04-11 14:21:27 -04:00
commit 7c7896a4fb
No known key found for this signature in database
12 changed files with 423 additions and 27 deletions

View File

@ -1,2 +1,2 @@
golang 1.21.5
golang 1.21.9
vale 3.0.7

View File

@ -438,6 +438,14 @@ var CreatePipeline = func(client *gitlab.Client, projectID interface{}, opts *gi
return pipe, err
}
var RunPipelineTrigger = func(client *gitlab.Client, projectID interface{}, opts *gitlab.RunPipelineTriggerOptions) (*gitlab.Pipeline, error) {
if client == nil {
client = apiClient.Lab()
}
pipe, _, err := client.PipelineTriggers.RunPipelineTrigger(projectID, opts)
return pipe, err
}
var DownloadArtifactJob = func(client *gitlab.Client, repo string, ref string, opts *gitlab.DownloadArtifactsFileOptions) (*bytes.Reader, error) {
if client == nil {
client = apiClient.Lab()

View File

@ -10,9 +10,10 @@ import (
pipeListCmd "gitlab.com/gitlab-org/cli/commands/ci/list"
pipeRetryCmd "gitlab.com/gitlab-org/cli/commands/ci/retry"
pipeRunCmd "gitlab.com/gitlab-org/cli/commands/ci/run"
pipeRunTrigCmd "gitlab.com/gitlab-org/cli/commands/ci/run_trig"
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"
jobPlayCmd "gitlab.com/gitlab-org/cli/commands/ci/trigger"
ciViewCmd "gitlab.com/gitlab-org/cli/commands/ci/view"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
@ -38,7 +39,8 @@ 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(jobPlayCmd.NewCmdTrigger(f))
ciCmd.AddCommand(pipeRunTrigCmd.NewCmdRunTrig(f))
ciCmd.AddCommand(jobArtifactCmd.NewCmdRun(f))
ciCmd.AddCommand(pipeGetCmd.NewCmdGet(f))
ciCmd.AddCommand(ciConfigCmd.NewCmdConfig(f))

View File

@ -9,6 +9,7 @@ import (
"sync"
"time"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/internal/glrepo"
"gitlab.com/gitlab-org/cli/pkg/git"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
@ -179,6 +180,27 @@ func getPipelineId(inputs *JobInputs, opts *JobOptions) (int, error) {
return pipeline.ID, err
}
func GetDefaultBranch(f *cmdutils.Factory) string {
repo, err := f.BaseRepo()
if err != nil {
return "master"
}
remotes, err := f.Remotes()
if err != nil {
return "master"
}
repoRemote, err := remotes.FindByRepo(repo.RepoOwner(), repo.RepoName())
if err != nil {
return "master"
}
branch, _ := git.GetDefaultBranch(repoRemote.Name)
return branch
}
func getBranch(branch string, opts *JobOptions) (string, error) {
if branch != "" {
return branch, nil

View File

@ -9,6 +9,7 @@ import (
"github.com/xanzy/go-gitlab"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/commands/mr/mrutils"
"gitlab.com/gitlab-org/cli/pkg/git"
"gitlab.com/gitlab-org/cli/pkg/tableprinter"
@ -63,7 +64,24 @@ func NewCmdGet(f *cmdutils.Factory) *cobra.Command {
if err != nil {
return err
}
pipelineId = commit.LastPipeline.ID
// The latest commit on the branch won't work with a merged
// result pipeline
if commit.LastPipeline == nil {
mr, _, err := mrutils.MRFromArgs(f, args, "any")
if err != nil {
return err
}
if mr.HeadPipeline == nil {
return fmt.Errorf("no pipeline found. It might not exist yet. If this problem continues, check your pipeline configuration.")
} else {
pipelineId = mr.HeadPipeline.ID
}
} else {
pipelineId = commit.LastPipeline.ID
}
}
pipeline, err := api.GetPipeline(apiClient, pipelineId, nil, repo.FullName())

View File

@ -392,6 +392,88 @@ updated: 2023-10-10 00:00:00 +0000 UTC
# Variables:
No variables found in pipeline.
`,
},
{
name: "when there is a merged result pipeline and no commit pipeline",
args: "-b=main",
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/pipelines/123",
http.StatusOK,
`{
"id": 123,
"iid": 123,
"project_id": 5,
"status": "pending",
"source": "push",
"ref": "main",
"sha": "0ff3ae198f8601a285adcf5c0fff204ee6fba5fd",
"user": {
"username": "test"
},
"yaml_errors": "-",
"created_at": "2023-10-10T00:00:00Z",
"started_at": "2023-10-10T00:00:00Z",
"updated_at": "2023-10-10T00:00:00Z"
}`,
InlineBody,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/pipelines/123/jobs?per_page=100",
http.StatusOK,
`[]`,
InlineBody,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/merge_requests/1",
http.StatusOK,
`{
"head_pipeline": {
"id": 123
}
}`,
InlineBody,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/merge_requests?per_page=30&source_branch=main",
http.StatusOK,
`[
{
"iid": 1
}
]`,
InlineBody,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/repository/commits/main",
http.StatusOK,
`{
"last_pipeline": null
}`,
InlineBody,
},
},
expectedOut: `# Pipeline:
id: 123
status: pending
source: push
ref: main
sha: 0ff3ae198f8601a285adcf5c0fff204ee6fba5fd
tag: false
yaml Errors: -
user: test
created: 2023-10-10 00:00:00 +0000 UTC
started: 2023-10-10 00:00:00 +0000 UTC
updated: 2023-10-10 00:00:00 +0000 UTC
# Jobs:
`,
},
{

View File

@ -7,8 +7,8 @@ import (
"strings"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/ci/ciutils"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"gitlab.com/gitlab-org/cli/pkg/git"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
@ -22,27 +22,6 @@ var (
var envVariables = []string{}
func getDefaultBranch(f *cmdutils.Factory) string {
repo, err := f.BaseRepo()
if err != nil {
return "master"
}
remotes, err := f.Remotes()
if err != nil {
return "master"
}
repoRemote, err := remotes.FindByRepo(repo.RepoOwner(), repo.RepoName())
if err != nil {
return "master"
}
branch, _ := git.GetDefaultBranch(repoRemote.Name)
return branch
}
func parseVarArg(s string) (*gitlab.PipelineVariableOptions, error) {
// From https://pkg.go.dev/strings#Split:
//
@ -167,7 +146,7 @@ func NewCmdRun(f *cmdutils.Factory) *cobra.Command {
} else {
// `ci run` is running out of a git repo
fmt.Fprintln(f.IO.StdOut, "not in a git repository, using repository argument")
c.Ref = gitlab.Ptr(getDefaultBranch(f))
c.Ref = gitlab.Ptr(ciutils.GetDefaultBranch(f))
}
pipe, err := api.CreatePipeline(apiClient, repo.FullName(), c)

View File

@ -0,0 +1,115 @@
package run_trig
import (
"errors"
"fmt"
"os"
"strings"
"gitlab.com/gitlab-org/cli/api"
"gitlab.com/gitlab-org/cli/commands/ci/ciutils"
"gitlab.com/gitlab-org/cli/commands/cmdutils"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
)
var envVariables = []string{}
func parseVarArg(s string) (key string, val string, err error) {
// From https://pkg.go.dev/strings#Split:
//
// > If s does not contain sep and sep is not empty,
// > Split returns a slice of length 1 whose only element is s.
//
// Therefore, the function will always return a slice of min length 1.
v := strings.SplitN(s, ":", 2)
if len(v) == 1 {
return "", "", fmt.Errorf("invalid argument structure")
}
return v[0], v[1], nil
}
func NewCmdRunTrig(f *cmdutils.Factory) *cobra.Command {
pipelineRunCmd := &cobra.Command{
Use: "run-trig [flags]",
Short: `Run a CI/CD pipeline trigger`,
Aliases: []string{"run-trig"},
Example: heredoc.Doc(`
glab ci run-trig -t xxxx
glab ci run-trig -t xxxx -b main
glab ci run-trig -t xxxx -b main --variables key1:val1
glab ci run-trig -t xxxx -b main --variables key1:val1,key2:val2
glab ci run-trig -t xxxx -b main --variables key1:val1 --variables key2:val2
`),
Long: ``,
Args: cobra.ExactArgs(0),
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
}
c := &gitlab.RunPipelineTriggerOptions{
Variables: make(map[string]string),
}
if customPipelineVars, _ := cmd.Flags().GetStringSlice("variables"); len(customPipelineVars) > 0 {
for _, v := range customPipelineVars {
key, val, err := parseVarArg(v)
if err != nil {
return fmt.Errorf("parsing pipeline variable expected format KEY:VALUE: %w", err)
}
c.Variables[key] = val
}
}
branch, err := cmd.Flags().GetString("branch")
if err != nil {
return err
}
if branch != "" {
c.Ref = gitlab.Ptr(branch)
} else if currentBranch, err := f.Branch(); err == nil {
c.Ref = gitlab.Ptr(currentBranch)
} else {
// `ci run-trig` is running out of a git repo
fmt.Fprintln(f.IO.StdOut, "not in a git repository, using repository argument")
c.Ref = gitlab.Ptr(ciutils.GetDefaultBranch(f))
}
token, err := cmd.Flags().GetString("token")
if err != nil {
return err
}
if token == "" {
token = os.Getenv("CI_JOB_TOKEN")
}
if token == "" {
return errors.New("--token parameter can be omitted only if CI_JOB_TOKEN environment variable is set")
}
c.Token = &token
pipe, err := api.RunPipelineTrigger(apiClient, repo.FullName(), c)
if err != nil {
return err
}
fmt.Fprintln(f.IO.StdOut, "Created pipeline (id:", pipe.ID, "), status:", pipe.Status, ", ref:", pipe.Ref, ", weburl: ", pipe.WebURL, ")")
return nil
},
}
pipelineRunCmd.Flags().StringP("token", "t", "", "Pipeline trigger token (can be omitted only if CI_JOB_TOKEN environment variable is set)")
pipelineRunCmd.Flags().StringP("branch", "b", "", "Create pipeline on branch/ref <string>")
pipelineRunCmd.Flags().StringSliceVarP(&envVariables, "variables", "", []string{}, "Pass variables to pipeline in format <key>:<value>")
return pipelineRunCmd
}

View File

@ -0,0 +1,116 @@
package run_trig
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"testing"
"gitlab.com/gitlab-org/cli/commands/cmdtest"
"github.com/stretchr/testify/assert"
"gitlab.com/gitlab-org/cli/pkg/httpmock"
"gitlab.com/gitlab-org/cli/test"
)
type ResponseJSON struct {
Token string `json:"token"`
Ref string `json:"ref"`
}
func runCommand(rt http.RoundTripper, isTTY bool, cli string) (*test.CmdOut, error) {
ios, _, stdout, stderr := cmdtest.InitIOStreams(isTTY, "")
factory := cmdtest.InitFactory(ios, rt)
factory.Branch = func() (string, error) {
return "custom-branch-123", nil
}
_, _ = factory.HttpClient()
cmd := NewCmdRunTrig(factory)
return cmdtest.ExecuteCommand(cmd, cli, stdout, stderr)
}
func TestCIRun(t *testing.T) {
tests := []struct {
name string
cli string
ciJobToken string
expectedPOSTBody string
expectedOut string
}{
{
name: "when running `ci run-trig` without branch parameter, defaults to current branch",
cli: "-t foobar",
ciJobToken: "",
expectedPOSTBody: fmt.Sprintf(`"ref":"%s"`, "custom-branch-123"),
expectedOut: fmt.Sprintf("Created pipeline (id: 123 ), status: created , ref: %s , weburl: https://gitlab.com/OWNER/REPO/-/pipelines/123 )\n", "custom-branch-123"),
},
{
name: "when running `ci run-trig` with branch parameter, run CI at branch",
cli: "-t foobar -b ci-cd-improvement-399",
ciJobToken: "",
expectedPOSTBody: `"ref":"ci-cd-improvement-399"`,
expectedOut: "Created pipeline (id: 123 ), status: created , ref: ci-cd-improvement-399 , weburl: https://gitlab.com/OWNER/REPO/-/pipelines/123 )\n",
},
{
name: "when running `ci run-trig` without any parameter, takes trigger token from env variable",
cli: "",
ciJobToken: "foobar",
expectedPOSTBody: fmt.Sprintf(`"ref":"%s"`, "custom-branch-123"),
expectedOut: fmt.Sprintf("Created pipeline (id: 123 ), status: created , ref: %s , weburl: https://gitlab.com/OWNER/REPO/-/pipelines/123 )\n", "custom-branch-123"),
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
fakeHTTP := &httpmock.Mocker{
MatchURL: httpmock.PathAndQuerystring,
}
defer fakeHTTP.Verify(t)
initialEnvValue := os.Getenv("CI_JOB_TOKEN")
os.Setenv("CI_JOB_TOKEN", tc.ciJobToken)
defer os.Setenv("CI_JOB_TOKEN", initialEnvValue)
fakeHTTP.RegisterResponder(http.MethodPost, "/api/v4/projects/OWNER/REPO/trigger/pipeline",
func(req *http.Request) (*http.Response, error) {
rb, _ := io.ReadAll(req.Body)
var response ResponseJSON
err := json.Unmarshal(rb, &response)
if err != nil {
fmt.Printf("Error when parsing response body %s\n", rb)
}
if response.Token != "foobar" {
fmt.Printf("Invalid token %s\n", rb)
}
// ensure CLI runs CI on correct branch
assert.Contains(t, string(rb), tc.expectedPOSTBody)
resp, _ := httpmock.NewStringResponse(http.StatusOK, fmt.Sprintf(`{
"id": 123,
"iid": 123,
"project_id": 3,
"status": "created",
"ref": "%s",
"web_url": "https://gitlab.com/OWNER/REPO/-/pipelines/123"}`, response.Ref))(req)
return resp, nil
},
)
output, _ := runCommand(fakeHTTP, false, tc.cli)
out := output.String()
assert.Equal(t, tc.expectedOut, out)
assert.Empty(t, output.Stderr())
})
}
}

View File

@ -162,6 +162,9 @@ func InitFactory(ios *iostreams.IOStreams, rt http.RoundTripper) *cmdutils.Facto
BaseRepo: func() (glrepo.Interface, error) {
return glrepo.New("OWNER", "REPO"), nil
},
Branch: func() (string, error) {
return "main", nil
},
}
}

View File

@ -43,6 +43,7 @@ pipeline
- [`list`](list.md)
- [`retry`](retry.md)
- [`run`](run.md)
- [`run-trig`](run-trig.md)
- [`status`](status.md)
- [`trace`](trace.md)
- [`trigger`](trigger.md)

View File

@ -0,0 +1,50 @@
---
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 run-trig`
Run a CI/CD pipeline trigger
```plaintext
glab ci run-trig [flags]
```
## Aliases
```plaintext
run-trig
```
## Examples
```plaintext
glab ci run-trig -t xxxx
glab ci run-trig -t xxxx -b main
glab ci run-trig -t xxxx -b main --variables key1:val1
glab ci run-trig -t xxxx -b main --variables key1:val1,key2:val2
glab ci run-trig -t xxxx -b main --variables key1:val1 --variables key2:val2
```
## Options
```plaintext
-b, --branch string Create pipeline on branch/ref <string>
-t, --token string Pipeline trigger token (can be omitted only if CI_JOB_TOKEN environment variable is set)
--variables strings Pass variables to pipeline in format <key>:<value>
```
## 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
```