Merge branch 'json-output' into 'main'

feat: Json output

Closes #828

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

Merged-by: Jay McCure <jmccure@gitlab.com>
Approved-by: Amy Qualls <aqualls@gitlab.com>
Approved-by: Jay McCure <jmccure@gitlab.com>
Reviewed-by: Jay McCure <jmccure@gitlab.com>
Co-authored-by: Dmitry Makovey <dmakovey@gitlab.com>
This commit is contained in:
Jay McCure 2024-03-07 06:08:07 +00:00
commit 4983225503
37 changed files with 943 additions and 122 deletions

View File

@ -95,8 +95,9 @@ func NewCmdGet(f *cmdutils.Factory) *cobra.Command {
}
outputFormat, _ := cmd.Flags().GetString("output-format")
if outputFormat == "json" {
printJSON(*mergedPipelineObject)
output, _ := cmd.Flags().GetString("output")
if output == "json" || outputFormat == "json" {
printJSON(*mergedPipelineObject, f.IO.StdOut)
} else {
showJobDetails, _ := cmd.Flags().GetBool("with-job-details")
printTable(*mergedPipelineObject, f.IO.StdOut, showJobDetails)
@ -108,16 +109,19 @@ func NewCmdGet(f *cmdutils.Factory) *cobra.Command {
pipelineGetCmd.Flags().StringP("branch", "b", "", "Check pipeline status for a branch. (Default is current branch)")
pipelineGetCmd.Flags().IntP("pipeline-id", "p", 0, "Provide pipeline ID")
pipelineGetCmd.Flags().StringP("output-format", "o", "text", "Format output as: text, json")
pipelineGetCmd.Flags().StringP("output", "F", "text", "Format output as: text, json")
pipelineGetCmd.Flags().StringP("output-format", "o", "text", "Use output")
_ = pipelineGetCmd.Flags().MarkHidden("output-format")
_ = pipelineGetCmd.Flags().MarkDeprecated("output-format", "Deprecated use output")
pipelineGetCmd.Flags().BoolP("with-job-details", "d", false, "Show extended job information")
pipelineGetCmd.Flags().Bool("with-variables", false, "Show variables in pipeline (maintainer role required)")
return pipelineGetCmd
}
func printJSON(p PipelineMergedResponse) {
func printJSON(p PipelineMergedResponse, dest io.Writer) {
JSONStr, _ := json.Marshal(p)
fmt.Println(string(JSONStr))
fmt.Fprintln(dest, string(JSONStr))
}
func printTable(p PipelineMergedResponse, dest io.Writer, showJobDetails bool) {

View File

@ -2,6 +2,7 @@ package status
import (
"net/http"
"os"
"testing"
"gitlab.com/gitlab-org/cli/commands/cmdtest"
@ -24,19 +25,26 @@ func runCommand(rt http.RoundTripper, isTTY bool, args string) (*test.CmdOut, er
return cmdtest.ExecuteCommand(cmd, args, stdout, stderr)
}
const (
FileBody = 1
InlineBody = 2
)
func TestCIGet(t *testing.T) {
type httpMock struct {
method string
path string
status int
body string
method string
path string
status int
body string
bodyType int
}
tests := []struct {
name string
args string
httpMocks []httpMock
expectedOut string
name string
args string
httpMocks []httpMock
expectedOut string
expectedOutType int
}{
{
name: "when get is called on an existing pipeline",
@ -61,12 +69,14 @@ func TestCIGet(t *testing.T) {
"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,
},
},
expectedOut: `# Pipeline:
@ -109,12 +119,14 @@ updated: 2023-10-10 00:00:00 +0000 UTC
"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,
@ -125,6 +137,7 @@ updated: 2023-10-10 00:00:00 +0000 UTC
"id": 123
}
}`,
InlineBody,
},
},
expectedOut: `# Pipeline:
@ -167,6 +180,7 @@ updated: 2023-10-10 00:00:00 +0000 UTC
"started_at": "2023-10-10T00:00:00Z",
"updated_at": "2023-10-10T00:00:00Z"
}`,
InlineBody,
},
{
http.MethodGet,
@ -177,6 +191,7 @@ updated: 2023-10-10 00:00:00 +0000 UTC
"name": "publish",
"status": "failed"
}]`,
InlineBody,
},
},
expectedOut: `# Pipeline:
@ -220,6 +235,7 @@ publish: failed
"started_at": "2023-10-10T00:00:00Z",
"updated_at": "2023-10-10T00:00:00Z"
}`,
InlineBody,
},
{
http.MethodGet,
@ -231,6 +247,7 @@ publish: failed
"status": "failed",
"failure_reason": "bad timing"
}]`,
InlineBody,
},
},
expectedOut: `# Pipeline:
@ -276,12 +293,14 @@ ID Name Status Duration Failure reason
"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,
@ -292,6 +311,7 @@ ID Name Status Duration Failure reason
"variable_type": "env_var",
"value": "true"
}]`,
InlineBody,
},
},
expectedOut: `# Pipeline:
@ -338,18 +358,21 @@ RUN_NIGHTLY_BUILD: true
"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/5/pipelines/123/variables",
http.StatusOK,
"[]",
InlineBody,
},
},
expectedOut: `# Pipeline:
@ -371,6 +394,28 @@ updated: 2023-10-10 00:00:00 +0000 UTC
No variables found in pipeline.
`,
},
{
name: "when getting JSON for pipeline",
args: "-p 452959326 -F json -b main",
httpMocks: []httpMock{
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/pipelines/452959326",
http.StatusOK,
"testdata/ci_get-0.json",
FileBody,
},
{
http.MethodGet,
"/api/v4/projects/OWNER%2FREPO/pipelines/452959326/jobs?per_page=100",
http.StatusOK,
"testdata/ci_get-1.json",
FileBody,
},
},
expectedOut: "testdata/ci_get.result",
expectedOutType: FileBody,
},
}
for _, tc := range tests {
@ -381,13 +426,30 @@ No variables found in pipeline.
defer fakeHTTP.Verify(t)
for _, mock := range tc.httpMocks {
fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, mock.body))
var body string
if mock.bodyType == FileBody {
bodyBytes, _ := os.ReadFile(mock.body)
body = string(bodyBytes)
} else {
body = mock.body
}
fakeHTTP.RegisterResponder(mock.method, mock.path, httpmock.NewStringResponse(mock.status, body))
}
output, err := runCommand(fakeHTTP, false, tc.args)
require.Nil(t, err)
var expectedOut string
var expectedOutBytes []byte
assert.Equal(t, tc.expectedOut, output.String())
if tc.expectedOutType == FileBody {
expectedOutBytes, err = os.ReadFile(tc.expectedOut)
expectedOut = string(expectedOutBytes)
require.Nil(t, err)
} else {
expectedOut = tc.expectedOut
}
assert.Equal(t, expectedOut, output.String())
assert.Empty(t, output.Stderr())
})
}

42
commands/ci/get/testdata/ci_get-0.json vendored Normal file
View File

@ -0,0 +1,42 @@
{
"id": 452959326,
"iid": 14,
"project_id": 29316529,
"sha": "44eb489568f7cb1a5a730fce6b247cd3797172ca",
"ref": "1-fake-issue-3",
"status": "success",
"source": "push",
"created_at": "2022-01-20T21:47:16.276Z",
"updated_at": "2022-01-20T21:47:31.358Z",
"web_url": "https://gitlab.com/OWNER/REPO/-/pipelines/452959326",
"before_sha": "001eb421e586a3f07f90aea102c8b2d4068ab5b6",
"tag": false,
"yaml_errors": null,
"user": {
"id": 8814129,
"username": "OWNER",
"name": "Some User",
"state": "active",
"locked": false,
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/8814129/avatar.png",
"web_url": "https://gitlab.com/OWNER"
},
"started_at": "2022-01-20T21:47:17.448Z",
"finished_at": "2022-01-20T21:47:31.350Z",
"committed_at": null,
"duration": 14,
"queued_duration": 1,
"coverage": null,
"detailed_status": {
"icon": "status_success",
"text": "Passed",
"label": "passed",
"group": "success",
"tooltip": "passed",
"has_details": true,
"details_path": "/OWNER/REPO/-/pipelines/452959326",
"illustration": null,
"favicon": "/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"
},
"name": null
}

102
commands/ci/get/testdata/ci_get-1.json vendored Normal file
View File

@ -0,0 +1,102 @@
[
{
"id": 1999017704,
"status": "success",
"stage": "test",
"name": "test_vars",
"ref": "1-fake-issue-3",
"tag": false,
"coverage": null,
"allow_failure": false,
"created_at": "2022-01-20T21:47:16.291Z",
"started_at": "2022-01-20T21:47:16.693Z",
"finished_at": "2022-01-20T21:47:31.274Z",
"erased_at": null,
"duration": 14.580467,
"queued_duration": 0.211715,
"user": {
"id": 8814129,
"username": "OWNER",
"name": "Some User",
"state": "active",
"locked": false,
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/8814129/avatar.png",
"web_url": "https://gitlab.com/OWNER",
"created_at": "2021-05-03T14:58:50.059Z",
"bio": "",
"location": "Canada",
"public_email": "",
"skype": "",
"linkedin": "",
"twitter": "",
"discord": "",
"website_url": "",
"organization": "GitLab",
"job_title": "Sr Backend Engineer",
"pronouns": "",
"bot": false,
"work_information": "Sr Backend Engineer at GitLab",
"followers": 2,
"following": 0,
"local_time": "10:48 AM"
},
"commit": {
"id": "44eb489568f7cb1a5a730fce6b247cd3797172ca",
"short_id": "44eb4895",
"created_at": "2022-01-20T21:47:15.000+00:00",
"parent_ids": [
"001eb421e586a3f07f90aea102c8b2d4068ab5b6"
],
"title": "Add new file",
"message": "Add new file",
"author_name": "Some User",
"author_email": "OWNER@gitlab.com",
"authored_date": "2022-01-20T21:47:15.000+00:00",
"committer_name": "Some User",
"committer_email": "OWNER@gitlab.com",
"committed_date": "2022-01-20T21:47:15.000+00:00",
"trailers": {},
"extended_trailers": {},
"web_url": "https://gitlab.com/OWNER/REPO/-/commit/44eb489568f7cb1a5a730fce6b247cd3797172ca"
},
"pipeline": {
"id": 452959326,
"iid": 14,
"project_id": 29316529,
"sha": "44eb489568f7cb1a5a730fce6b247cd3797172ca",
"ref": "1-fake-issue-3",
"status": "success",
"source": "push",
"created_at": "2022-01-20T21:47:16.276Z",
"updated_at": "2022-01-20T21:47:31.358Z",
"web_url": "https://gitlab.com/OWNER/REPO/-/pipelines/452959326"
},
"web_url": "https://gitlab.com/OWNER/REPO/-/jobs/1999017704",
"project": {
"ci_job_token_scope_enabled": false
},
"artifacts": [
{
"file_type": "trace",
"size": 2770,
"filename": "job.log",
"file_format": null
}
],
"runner": {
"id": 12270859,
"description": "5-green.saas-linux-small-amd64.runners-manager.gitlab.com/default",
"ip_address": "10.1.5.249",
"active": true,
"paused": false,
"is_shared": true,
"runner_type": "instance_type",
"name": "gitlab-runner",
"online": false,
"status": "offline"
},
"artifacts_expire_at": null,
"archived": false,
"tag_list": []
}
]

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,7 @@
package list
import (
"encoding/json"
"fmt"
"gitlab.com/gitlab-org/cli/api"
@ -38,6 +39,10 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command {
}
l := &gitlab.ListProjectPipelinesOptions{}
format, _ := cmd.Flags().GetString("output")
jsonOut := format == "json"
l.Page = 1
l.PerPage = 30
@ -68,7 +73,12 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command {
title.Page = l.Page
title.CurrentPageTotal = len(pipes)
fmt.Fprintf(f.IO.StdOut, "%s\n%s\n", title.Describe(), ciutils.DisplayMultiplePipelines(f.IO, pipes, repo.FullName()))
if jsonOut {
pipeListJSON, _ := json.Marshal(pipes)
fmt.Fprintln(f.IO.StdOut, string(pipeListJSON))
} else {
fmt.Fprintf(f.IO.StdOut, "%s\n%s\n", title.Describe(), ciutils.DisplayMultiplePipelines(f.IO, pipes, repo.FullName()))
}
return nil
},
}
@ -77,6 +87,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command {
pipelineListCmd.Flags().StringP("sort", "", "desc", "Sort pipeline by {asc|desc}")
pipelineListCmd.Flags().IntP("page", "p", 1, "Page number")
pipelineListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page")
pipelineListCmd.Flags().StringP("output", "F", "text", "Format output as: text, json")
return pipelineListCmd
}

View File

@ -1,12 +1,12 @@
package list
import (
"fmt"
"net/http"
"os"
"regexp"
"testing"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"github.com/MakeNowJust/heredoc"
"github.com/stretchr/testify/assert"
@ -15,19 +15,16 @@ import (
"gitlab.com/gitlab-org/cli/test"
)
func runCommand(rt http.RoundTripper) (*test.CmdOut, error) {
ios, _, stdout, stderr := iostreams.Test()
func runCommand(rt http.RoundTripper, args string) (*test.CmdOut, error) {
ios, _, stdout, stderr := cmdtest.InitIOStreams(false, "")
factory := cmdtest.InitFactory(ios, rt)
_, _ = factory.HttpClient()
cmd := NewCmdList(factory)
_, err := cmd.ExecuteC()
return &test.CmdOut{
OutBuf: stdout,
ErrBuf: stderr,
}, err
return cmdtest.ExecuteCommand(cmd, args, stdout, stderr)
}
func TestCiList(t *testing.T) {
@ -64,7 +61,7 @@ func TestCiList(t *testing.T) {
]
`))
output, err := runCommand(fakeHTTP)
output, err := runCommand(fakeHTTP, "")
if err != nil {
t.Errorf("error running command `ci list`: %v", err)
}
@ -82,3 +79,26 @@ func TestCiList(t *testing.T) {
`), out)
assert.Empty(t, output.Stderr())
}
func TestCiListJSON(t *testing.T) {
fakeHTTP := httpmock.New()
defer fakeHTTP.Verify(t)
fakeHTTP.RegisterResponder(http.MethodGet, "/api/v4/projects/OWNER/REPO/pipelines",
httpmock.NewFileResponse(http.StatusOK, "testdata/ciList.json"))
output, err := runCommand(fakeHTTP, "-F json")
if err != nil {
t.Errorf("error running command `ci list -F json`: %v", err)
}
b, err := os.ReadFile("testdata/ciList.json")
if err != nil {
fmt.Print(err)
}
expectedOut := string(b)
assert.JSONEq(t, expectedOut, output.String())
assert.Empty(t, output.Stderr())
}

26
commands/ci/list/testdata/ciList.json vendored Normal file
View File

@ -0,0 +1,26 @@
[
{
"id": 1172622998,
"iid": 338,
"project_id": 37777023,
"status": "success",
"source": "schedule",
"ref": "#test#",
"sha": "3c890c11d784329052aa4ff63526dde2fa65b320",
"web_url": "https://gitlab.com/jay_mccure/test2target/-/pipelines/1172622998",
"updated_at": "2024-02-11T18:56:07.777Z",
"created_at": "2024-02-11T18:55:08.793Z"
},
{
"id": 1172086480,
"iid": 337,
"project_id": 37777023,
"status": "success",
"source": "schedule",
"ref": "#test#",
"sha": "3c890c11d784329052aa4ff63526dde2fa65b320",
"web_url": "https://gitlab.com/jay_mccure/test2target/-/pipelines/1172086480",
"updated_at": "2024-02-10T18:56:13.972Z",
"created_at": "2024-02-10T18:55:16.722Z"
}
]

View File

@ -1,6 +1,7 @@
package list
import (
"encoding/json"
"errors"
"fmt"
@ -56,6 +57,8 @@ type ListOptions struct {
IO *iostreams.IOStreams
BaseRepo func() (glrepo.Interface, error)
HTTPClient func() (*gitlab.Client, error)
JSONOutput bool
}
func NewCmdList(f *cmdutils.Factory, runE func(opts *ListOptions) error, issueType issuable.IssueType) *cobra.Command {
@ -135,7 +138,7 @@ func NewCmdList(f *cmdutils.Factory, runE func(opts *ListOptions) error, issueTy
issueListCmd.Flags().BoolVarP(&opts.All, "all", "A", false, fmt.Sprintf("Get all %ss", issueType))
issueListCmd.Flags().BoolVarP(&opts.Closed, "closed", "c", false, fmt.Sprintf("Get only closed %ss", issueType))
issueListCmd.Flags().BoolVarP(&opts.Confidential, "confidential", "C", false, fmt.Sprintf("Filter by confidential %ss", issueType))
issueListCmd.Flags().StringVarP(&opts.OutputFormat, "output-format", "F", "details", "One of 'details', 'ids', or 'urls'")
issueListCmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "details", "One of 'details', 'ids', 'urls' or 'json'")
issueListCmd.Flags().IntVarP(&opts.Page, "page", "p", 1, "Page number")
issueListCmd.Flags().IntVarP(&opts.PerPage, "per-page", "P", 30, "Number of items to list per page.")
issueListCmd.PersistentFlags().StringP("group", "g", "", "Select a group/subgroup. This option is ignored if a repo argument is set.")
@ -260,6 +263,12 @@ func listRun(opts *ListOptions) error {
title.ListActionType = opts.ListType
title.CurrentPageTotal = len(issues)
if opts.OutputFormat == "json" {
issueListJSON, _ := json.Marshal(issues)
fmt.Fprintln(opts.IO.StdOut, string(issueListJSON))
return nil
}
if opts.OutputFormat == "ids" {
for _, i := range issues {
fmt.Fprintf(opts.IO.StdOut, "%d\n", i.IID)

View File

@ -3,6 +3,7 @@ package list
import (
"fmt"
"net/http"
"os"
"regexp"
"strings"
"testing"
@ -350,3 +351,30 @@ func TestIssueList_hyperlinks(t *testing.T) {
})
}
}
func TestIssueListJSON(t *testing.T) {
fakeHTTP := httpmock.New()
defer fakeHTTP.Verify(t)
fakeHTTP.RegisterResponder(http.MethodGet, "/projects/OWNER/REPO/issues",
httpmock.NewFileResponse(http.StatusOK, "./testdata/issueListFull.json"))
output, err := runCommand("issue", fakeHTTP, true, "-F json", nil, "")
if err != nil {
t.Errorf("error running command `issue list -F json`: %v", err)
}
if err != nil {
panic(err)
}
b, err := os.ReadFile("./testdata/issueListFull.json")
if err != nil {
fmt.Print(err)
}
expectedOut := string(b)
assert.JSONEq(t, expectedOut, output.String())
assert.Empty(t, output.Stderr())
}

View File

@ -0,0 +1,72 @@
[
{
"id": 141525495,
"iid": 15,
"external_id": "",
"project_id": 37777023,
"title": "tem",
"description": "",
"state": "opened",
"created_at": "2024-01-31T05:37:57.883Z",
"updated_at": "2024-02-02T00:54:02.842Z",
"closed_at": null,
"epic": null,
"epic_issue_id": 0,
"iteration": null,
"label_details": null,
"subscribed": false,
"closed_by": null,
"labels":"",
"milestone": null,
"assignees":
[],
"author":
{
"id": 11809982,
"username": "jay_mccure",
"name": "Jay McCure",
"state": "active",
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/11809982/avatar.png",
"web_url": "https://gitlab.com/jay_mccure"
},
"assignee": null,
"user_notes_count": 0,
"issue_link_id": 0,
"merge_requests_count": 0,
"upvotes": 0,
"downvotes": 0,
"due_date": null,
"confidential": false,
"discussion_locked": false,
"issue_type": "issue",
"web_url": "https://gitlab.com/jay_mccure/test2target/-/issues/15",
"time_stats":
{
"time_estimate": 0,
"total_time_spent": 0,
"human_time_estimate": "",
"human_total_time_spent": ""
},
"task_completion_status":
{
"count": 0,
"completed_count": 0
},
"weight": 0,
"_links":
{
"self": "https://gitlab.com/api/v4/projects/37777023/issues/15",
"notes": "https://gitlab.com/api/v4/projects/37777023/issues/15/notes",
"award_emoji": "https://gitlab.com/api/v4/projects/37777023/issues/15/award_emoji",
"project": "https://gitlab.com/api/v4/projects/37777023"
},
"references":
{
"short": "#15",
"relative": "#15",
"full": "jay_mccure/test2target#15"
},
"moved_to_id": 0,
"health_status": ""
}
]

View File

@ -1,6 +1,7 @@
package view
import (
"encoding/json"
"fmt"
"strings"
@ -18,11 +19,17 @@ import (
"github.com/xanzy/go-gitlab"
)
type IssueWithNotes struct {
*gitlab.Issue
Notes []*gitlab.Note
}
type ViewOpts struct {
ShowComments bool
ShowSystemLogs bool
OpenInBrowser bool
Web bool
OutputFormat string
CommentPageNumber int
CommentLimit int
@ -109,6 +116,9 @@ func NewCmdView(f *cmdutils.Factory, issueType issuable.IssueType) *cobra.Comman
return err
}
defer f.IO.StopPager()
if opts.OutputFormat == "json" {
return printJSONIssue(opts)
}
if f.IO.IsErrTTY && f.IO.IsaTTY {
return printTTYIssuePreview(opts)
}
@ -121,6 +131,7 @@ func NewCmdView(f *cmdutils.Factory, issueType issuable.IssueType) *cobra.Comman
issueViewCmd.Flags().BoolVarP(&opts.Web, "web", "w", false, fmt.Sprintf("Open %s in a browser. Uses default browser or browser specified in BROWSER variable", issueType))
issueViewCmd.Flags().IntVarP(&opts.CommentPageNumber, "page", "p", 1, "Page number")
issueViewCmd.Flags().IntVarP(&opts.CommentLimit, "per-page", "P", 20, "Number of items to list per page")
issueViewCmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json")
return issueViewCmd
}
@ -275,3 +286,17 @@ func RawIssuableNotes(notes []*gitlab.Note, showComments bool, showSystemLogs bo
return out
}
func printJSONIssue(opts *ViewOpts) error {
// var notes []gitlab.Note
if opts.ShowComments {
extendedIssue := IssueWithNotes{opts.Issue, opts.Notes}
issueJSON, _ := json.Marshal(extendedIssue)
fmt.Fprintln(opts.IO.StdOut, string(issueJSON))
} else {
issueJSON, _ := json.Marshal(opts.Issue)
fmt.Fprintln(opts.IO.StdOut, string(issueJSON))
}
return nil
}

View File

@ -2,6 +2,7 @@ package view
import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"regexp"
@ -499,3 +500,15 @@ func Test_assigneesList(t *testing.T) {
})
}
}
func TestIssueViewJSON(t *testing.T) {
cmd := NewCmdView(stubFactory, issuable.TypeIssue)
output, err := cmdtest.ExecuteCommand(cmd, "1 -F json", stdout, stderr)
if err != nil {
t.Errorf("error running command `issue view 1 -F json`: %v", err)
}
assert.True(t, json.Valid([]byte(output.String())))
assert.Empty(t, output.Stderr())
}

View File

@ -1,6 +1,7 @@
package list
import (
"encoding/json"
"fmt"
"github.com/MakeNowJust/heredoc"
@ -13,6 +14,8 @@ import (
"github.com/xanzy/go-gitlab"
)
var OutputFormat string
func NewCmdList(f *cmdutils.Factory) *cobra.Command {
labelListCmd := &cobra.Command{
Use: "list [flags]",
@ -54,16 +57,22 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command {
if err != nil {
return err
}
fmt.Fprintf(f.IO.StdOut, "Showing label %d of %d on %s\n\n", len(labels), len(labels), repo.FullName())
var labelPrintInfo string
for _, label := range labels {
var description string
if label.Description != "" {
description = fmt.Sprintf(" -> %s", label.Description)
if OutputFormat == "json" {
labelListJSON, _ := json.Marshal(labels)
fmt.Fprintln(f.IO.StdOut, string(labelListJSON))
} else {
fmt.Fprintf(f.IO.StdOut, "Showing label %d of %d on %s\n\n", len(labels), len(labels), repo.FullName())
var labelPrintInfo string
for _, label := range labels {
var description string
if label.Description != "" {
description = fmt.Sprintf(" -> %s", label.Description)
}
labelPrintInfo += fmt.Sprintf("%s%s (%s)\n", label.Name, description, label.Color)
}
labelPrintInfo += fmt.Sprintf("%s%s (%s)\n", label.Name, description, label.Color)
fmt.Fprintln(f.IO.StdOut, utils.Indent(labelPrintInfo, " "))
}
fmt.Fprintln(f.IO.StdOut, utils.Indent(labelPrintInfo, " "))
// Cache labels for host
//labelNames := make([]string, 0, len(labels))
@ -81,6 +90,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command {
labelListCmd.Flags().IntP("page", "p", 1, "Page number")
labelListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page")
labelListCmd.Flags().StringVarP(&OutputFormat, "output", "F", "text", "Format output as: text, json")
return labelListCmd
}

View File

@ -4,8 +4,6 @@ import (
"net/http"
"testing"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
"github.com/MakeNowJust/heredoc"
"github.com/stretchr/testify/assert"
@ -14,8 +12,8 @@ import (
"gitlab.com/gitlab-org/cli/test"
)
func runCommand(rt http.RoundTripper) (*test.CmdOut, error) {
ios, _, stdout, stderr := iostreams.Test()
func runCommand(rt http.RoundTripper, cli string) (*test.CmdOut, error) {
ios, _, stdout, stderr := cmdtest.InitIOStreams(true, "")
factory := cmdtest.InitFactory(ios, rt)
// TODO: shouldn't be there but the stub doesn't work without it
@ -23,11 +21,7 @@ func runCommand(rt http.RoundTripper) (*test.CmdOut, error) {
cmd := NewCmdList(factory)
_, err := cmd.ExecuteC()
return &test.CmdOut{
OutBuf: stdout,
ErrBuf: stderr,
}, err
return cmdtest.ExecuteCommand(cmd, cli, stdout, stderr)
}
func TestLabelList(t *testing.T) {
@ -58,7 +52,7 @@ func TestLabelList(t *testing.T) {
]
`))
output, err := runCommand(fakeHTTP)
output, err := runCommand(fakeHTTP, "")
if err != nil {
t.Errorf("error running command `label list`: %v", err)
}
@ -74,3 +68,51 @@ func TestLabelList(t *testing.T) {
`), out)
assert.Empty(t, output.Stderr())
}
func TestLabelListJSON(t *testing.T) {
fakeHTTP := httpmock.New()
fakeHTTP.MatchURL = httpmock.PathAndQuerystring
defer fakeHTTP.Verify(t)
expectedBody := `[
{
"id": 29739671,
"name": "my label",
"color": "#00b140",
"text_color": "#FFFFFF",
"description": "Simple label",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
"subscribed": false,
"priority": 0,
"is_project_label": true
}
]`
fakeHTTP.RegisterResponder(http.MethodGet, "/api/v4/projects/OWNER%2FREPO/labels?page=1&per_page=30&with_counts=true",
httpmock.NewStringResponse(http.StatusOK, `[
{
"id": 29739671,
"name": "my label",
"description": "Simple label",
"description_html": "Simple label",
"text_color": "#FFFFFF",
"color": "#00b140",
"open_issues_count": 0,
"closed_issues_count": 0,
"open_merge_requests_count": 0,
"subscribed": false,
"priority": null,
"is_project_label": true
}
]`))
output, err := runCommand(fakeHTTP, "-F json")
if err != nil {
t.Errorf("error running command `label list -F json`: %v", err)
}
assert.JSONEq(t, expectedBody, output.String())
assert.Empty(t, output.Stderr())
}

View File

@ -1,6 +1,7 @@
package list
import (
"encoding/json"
"errors"
"fmt"
@ -41,8 +42,9 @@ type ListOptions struct {
Draft bool
// Pagination
Page int
PerPage int
Page int
PerPage int
OutputFormat string
// display opts
ListType string
@ -132,12 +134,13 @@ func NewCmdList(f *cmdutils.Factory, runE func(opts *ListOptions) error) *cobra.
mrListCmd.Flags().BoolVarP(&opts.Closed, "closed", "c", false, "Get only closed merge requests")
mrListCmd.Flags().BoolVarP(&opts.Merged, "merged", "M", false, "Get only merged merge requests")
mrListCmd.Flags().BoolVarP(&opts.Draft, "draft", "d", false, "Filter by draft merge requests")
mrListCmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json")
mrListCmd.Flags().IntVarP(&opts.Page, "page", "p", 1, "Page number")
mrListCmd.Flags().IntVarP(&opts.PerPage, "per-page", "P", 30, "Number of items to list per page")
mrListCmd.Flags().StringSliceVarP(&opts.Assignee, "assignee", "a", []string{}, "Get only merge requests assigned to users")
mrListCmd.Flags().StringSliceVarP(&opts.Reviewer, "reviewer", "r", []string{}, "Get only merge requests with users as reviewer")
mrListCmd.Flags().BoolP("opened", "o", false, "Get only open merge requests")
mrListCmd.Flags().BoolP("opened", "O", false, "Get only open merge requests")
_ = mrListCmd.Flags().MarkHidden("opened")
_ = mrListCmd.Flags().MarkDeprecated("opened", "default value if neither --closed, --locked or --merged is used")
@ -165,8 +168,14 @@ func listRun(opts *ListOptions) error {
l := &gitlab.ListProjectMergeRequestsOptions{
State: gitlab.String(opts.State),
}
l.Page = 1
l.PerPage = 30
jsonOutput := opts.OutputFormat == "json"
if jsonOutput {
l.Page = 0
l.PerPage = 0
} else {
l.Page = 1
l.PerPage = 30
}
if opts.Author != "" {
u, err := api.UserByName(apiClient, opts.Author)
@ -256,11 +265,15 @@ func listRun(opts *ListOptions) error {
title.ListActionType = opts.ListType
title.CurrentPageTotal = len(mergeRequests)
if err = opts.IO.StartPager(); err != nil {
return err
if jsonOutput {
mrListJSON, _ := json.Marshal(mergeRequests)
fmt.Fprintln(opts.IO.StdOut, string(mrListJSON))
} else {
if err = opts.IO.StartPager(); err != nil {
return err
}
defer opts.IO.StopPager()
fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), mrutils.DisplayAllMRs(opts.IO, mergeRequests))
}
defer opts.IO.StopPager()
fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), mrutils.DisplayAllMRs(opts.IO, mergeRequests))
return nil
}

View File

@ -3,6 +3,7 @@ package list
import (
"fmt"
"net/http"
"os"
"strings"
"testing"
@ -355,3 +356,31 @@ func TestMergeRequestList_labels(t *testing.T) {
})
}
}
func TestMrListJSON(t *testing.T) {
fakeHTTP := httpmock.New()
fakeHTTP.MatchURL = httpmock.PathAndQuerystring
defer fakeHTTP.Verify(t)
fakeHTTP.RegisterResponder(http.MethodGet, "/api/v4/projects/OWNER/REPO/merge_requests?page=1&per_page=30&state=opened",
httpmock.NewFileResponse(http.StatusOK, "./testdata/mrList.json"))
output, err := runCommand(fakeHTTP, true, "-F json", nil, "")
if err != nil {
t.Errorf("error running command `mr list -F json`: %v", err)
}
if err != nil {
panic(err)
}
b, err := os.ReadFile("./testdata/mrList.json")
if err != nil {
fmt.Print(err)
}
expectedOut := string(b)
assert.JSONEq(t, expectedOut, output.String())
assert.Empty(t, output.Stderr())
}

211
commands/mr/list/testdata/mrList.json vendored Normal file
View File

@ -0,0 +1,211 @@
[
{
"id": 136297744,
"iid": 4,
"target_branch": "main",
"source_branch": "1-fake-issue-3",
"project_id": 29316529,
"title": "Draft: Resolve \"fake issue\"",
"state": "opened",
"created_at": "2022-01-20T21:20:50.665Z",
"updated_at": "2022-01-20T21:47:54.11Z",
"upvotes": 0,
"downvotes": 0,
"author":
{
"id": 8814129,
"username": "OWNER",
"name": "Some User",
"state": "active",
"created_at": null,
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/8814129/avatar.png",
"web_url": "https://gitlab.com/OWNER"
},
"assignee":
{
"id": 8814129,
"username": "OWNER",
"name": "Some User",
"state": "active",
"created_at": null,
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/8814129/avatar.png",
"web_url": "https://gitlab.com/OWNER"
},
"assignees":
[
{
"id": 8814129,
"username": "OWNER",
"name": "Some User",
"state": "active",
"created_at": null,
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/8814129/avatar.png",
"web_url": "https://gitlab.com/OWNER"
}
],
"reviewers":
[],
"source_project_id": 29316529,
"target_project_id": 29316529,
"labels": "",
"label_details": null,
"description": "Closes #1",
"draft": true,
"work_in_progress": true,
"milestone": null,
"merge_when_pipeline_succeeds": false,
"detailed_merge_status": "draft_status",
"merge_error": "",
"merged_by": null,
"merged_at": null,
"closed_by": null,
"closed_at": null,
"subscribed": false,
"sha": "44eb489568f7cb1a5a730fce6b247cd3797172ca",
"merge_commit_sha": "",
"squash_commit_sha": "",
"user_notes_count": 0,
"changes_count": "",
"should_remove_source_branch": false,
"force_remove_source_branch": true,
"allow_collaboration": false,
"web_url": "https://gitlab.com/OWNER/REPO/-/merge_requests/4",
"references":
{
"short": "!4",
"relative": "!4",
"full": "OWNER/REPO!4"
},
"discussion_locked": false,
"changes": null,
"user":
{
"can_merge": false
},
"time_stats":
{
"human_time_estimate": "",
"human_total_time_spent": "",
"time_estimate": 0,
"total_time_spent": 0
},
"squash": false,
"pipeline": null,
"head_pipeline": null,
"diff_refs":
{
"base_sha": "",
"head_sha": "",
"start_sha": ""
},
"diverged_commits_count": 0,
"rebase_in_progress": false,
"approvals_before_merge": 0,
"reference": "!4",
"first_contribution": false,
"task_completion_status":
{
"count": 0,
"completed_count": 0
},
"has_conflicts": false,
"blocking_discussions_resolved": true,
"overflow": false,
"merge_status": "can_be_merged"
},
{
"id": 135750125,
"iid": 1,
"target_branch": "main",
"source_branch": "OWNER-main-patch-25608",
"project_id": 29316529,
"title": "Update .gitlab-ci.yml",
"state": "opened",
"created_at": "2022-01-18T17:02:23.27Z",
"updated_at": "2022-01-18T18:06:50.054Z",
"upvotes": 0,
"downvotes": 0,
"author":
{
"id": 8814129,
"username": "OWNER",
"name": "Some User",
"state": "active",
"created_at": null,
"avatar_url": "https://gitlab.com/uploads/-/system/user/avatar/8814129/avatar.png",
"web_url": "https://gitlab.com/OWNER"
},
"assignee": null,
"assignees":
[],
"reviewers":
[],
"source_project_id": 29316529,
"target_project_id": 29316529,
"labels": "",
"label_details": null,
"description": "",
"draft": false,
"work_in_progress": false,
"milestone": null,
"merge_when_pipeline_succeeds": false,
"detailed_merge_status": "mergeable",
"merge_error": "",
"merged_by": null,
"merged_at": null,
"closed_by": null,
"closed_at": null,
"subscribed": false,
"sha": "123f34ebfd5d97ef562974e55e01b83f06ae7b4a",
"merge_commit_sha": "",
"squash_commit_sha": "",
"user_notes_count": 0,
"changes_count": "",
"should_remove_source_branch": false,
"force_remove_source_branch": true,
"allow_collaboration": false,
"web_url": "https://gitlab.com/OWNER/REPO/-/merge_requests/1",
"references":
{
"short": "!1",
"relative": "!1",
"full": "OWNER/REPO!1"
},
"discussion_locked": false,
"changes": null,
"user":
{
"can_merge": false
},
"time_stats":
{
"human_time_estimate": "",
"human_total_time_spent": "",
"time_estimate": 0,
"total_time_spent": 0
},
"squash": false,
"pipeline": null,
"head_pipeline": null,
"diff_refs":
{
"base_sha": "",
"head_sha": "",
"start_sha": ""
},
"diverged_commits_count": 0,
"rebase_in_progress": false,
"approvals_before_merge": 0,
"reference": "!1",
"first_contribution": false,
"task_completion_status":
{
"count": 0,
"completed_count": 0
},
"has_conflicts": false,
"blocking_discussions_resolved": true,
"overflow": false,
"merge_status": "can_be_merged"
}
]

View File

@ -1,6 +1,7 @@
package view
import (
"encoding/json"
"fmt"
"strings"
@ -21,6 +22,7 @@ type ViewOpts struct {
ShowComments bool
ShowSystemLogs bool
OpenInBrowser bool
OutputFormat string
CommentPageNumber int
CommentLimit int
@ -28,6 +30,11 @@ type ViewOpts struct {
IO *iostreams.IOStreams
}
type MRWithNotes struct {
*gitlab.MergeRequest
Notes []*gitlab.Note
}
func NewCmdView(f *cmdutils.Factory) *cobra.Command {
opts := &ViewOpts{
IO: f.IO,
@ -96,6 +103,9 @@ func NewCmdView(f *cmdutils.Factory) *cobra.Command {
}
defer f.IO.StopPager()
if opts.OutputFormat == "json" {
return printJSONMR(opts, mr, notes)
}
if f.IO.IsOutputTTY() {
return printTTYMRPreview(opts, mr, mrApprovals, notes)
}
@ -105,6 +115,7 @@ func NewCmdView(f *cmdutils.Factory) *cobra.Command {
mrViewCmd.Flags().BoolVarP(&opts.ShowComments, "comments", "c", false, "Show mr comments and activities")
mrViewCmd.Flags().BoolVarP(&opts.ShowSystemLogs, "system-logs", "s", false, "Show system activities / logs")
mrViewCmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json")
mrViewCmd.Flags().BoolVarP(&opts.OpenInBrowser, "web", "w", false, "Open mr in a browser. Uses default browser or browser specified in BROWSER variable")
mrViewCmd.Flags().IntVarP(&opts.CommentPageNumber, "page", "p", 0, "Page number")
mrViewCmd.Flags().IntVarP(&opts.CommentLimit, "per-page", "P", 20, "Number of items to list per page")
@ -281,3 +292,15 @@ func rawMRPreview(opts *ViewOpts, mr *gitlab.MergeRequest, notes []*gitlab.Note)
return out
}
func printJSONMR(opts *ViewOpts, mr *gitlab.MergeRequest, notes []*gitlab.Note) error {
if opts.ShowComments {
extendedMR := MRWithNotes{mr, notes}
mrJSON, _ := json.Marshal(extendedMR)
fmt.Fprintln(opts.IO.StdOut, string(mrJSON))
} else {
mrJSON, _ := json.Marshal(mr)
fmt.Fprintln(opts.IO.StdOut, string(mrJSON))
}
return nil
}

View File

@ -2,6 +2,7 @@ package view
import (
"bytes"
"encoding/json"
"fmt"
"os/exec"
"regexp"
@ -465,3 +466,17 @@ func Test_reviewersList(t *testing.T) {
})
}
}
func TestMrViewJSON(t *testing.T) {
cmd := NewCmdView(stubFactory)
stdout.Reset()
stderr.Reset()
output, err := cmdtest.ExecuteCommand(cmd, "1 -F json", stdout, stderr)
if err != nil {
t.Errorf("error running command `mr view 1 -F json`: %v", err)
}
assert.True(t, json.Valid([]byte(output.String())))
assert.Empty(t, output.Stderr())
}

View File

@ -1,6 +1,7 @@
package list
import (
"encoding/json"
"fmt"
"gitlab.com/gitlab-org/cli/pkg/iostreams"
@ -18,6 +19,7 @@ type Options struct {
Group string
PerPage int
Page int
OutputFormat string
FilterAll bool
FilterOwned bool
FilterMember bool
@ -51,6 +53,7 @@ func NewCmdList(f *cmdutils.Factory) *cobra.Command {
repoListCmd.Flags().StringVarP(&opts.Group, "group", "g", "", "Return only repositories in the given group and its subgroups")
repoListCmd.Flags().IntVarP(&opts.Page, "page", "p", 1, "Page number")
repoListCmd.Flags().IntVarP(&opts.PerPage, "per-page", "P", 30, "Number of items to list per page")
repoListCmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json")
repoListCmd.Flags().BoolVarP(&opts.FilterAll, "all", "a", false, "List all projects on the instance")
repoListCmd.Flags().BoolVarP(&opts.FilterOwned, "mine", "m", false, "Only list projects you own (default if no filters are passed)")
repoListCmd.Flags().BoolVar(&opts.FilterMember, "member", false, "Only list projects which you are a member")
@ -79,17 +82,25 @@ func runE(opts *Options) error {
return err
}
title := fmt.Sprintf("Showing %d of %d projects (Page %d of %d)\n", len(projects), resp.TotalItems, resp.CurrentPage, resp.TotalPages)
if opts.OutputFormat == "json" {
projectListJSON, _ := json.Marshal(projects)
fmt.Fprintln(opts.IO.StdOut, string(projectListJSON))
} else {
// Title
title := fmt.Sprintf("Showing %d of %d projects (Page %d of %d)\n", len(projects), resp.TotalItems, resp.CurrentPage, resp.TotalPages)
table := tableprinter.NewTablePrinter()
for _, prj := range projects {
table.AddCell(c.Blue(prj.PathWithNamespace))
table.AddCell(prj.SSHURLToRepo)
table.AddCell(prj.Description)
table.EndRow()
// List
table := tableprinter.NewTablePrinter()
for _, prj := range projects {
table.AddCell(c.Blue(prj.PathWithNamespace))
table.AddCell(prj.SSHURLToRepo)
table.AddCell(prj.Description)
table.EndRow()
}
fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title, table.String())
}
fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title, table.String())
return err
}

View File

@ -2,6 +2,7 @@ package view
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/url"
"strings"
@ -20,6 +21,7 @@ type ViewOptions struct {
ProjectID string
APIClient *gitlab.Client
Web bool
OutputFormat string
Branch string
Browser string
GlamourStyle string
@ -120,6 +122,7 @@ func NewCmdView(f *cmdutils.Factory) *cobra.Command {
}
projectViewCmd.Flags().BoolVarP(&opts.Web, "web", "w", false, "Open a project in the browser")
projectViewCmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json")
projectViewCmd.Flags().StringVarP(&opts.Branch, "branch", "b", "", "View a specific branch of the repository")
return projectViewCmd
@ -146,22 +149,24 @@ func runViewProject(opts *ViewOptions) error {
generateProjectOpenURL(projectURL, project.DefaultBranch, opts.Branch),
opts.Browser,
)
}
readmeFile, err := getReadmeFile(opts, project)
if err != nil {
return err
}
if opts.IO.IsaTTY {
if err := opts.IO.StartPager(); err != nil {
} else if opts.OutputFormat == "json" {
printProjectContentJSON(opts, project)
} else {
readmeFile, err := getReadmeFile(opts, project)
if err != nil {
return err
}
defer opts.IO.StopPager()
printProjectContentTTY(opts, project, readmeFile)
} else {
printProjectContentRaw(opts, project, readmeFile)
if opts.IO.IsaTTY {
if err := opts.IO.StartPager(); err != nil {
return err
}
defer opts.IO.StopPager()
printProjectContentTTY(opts, project, readmeFile)
} else {
printProjectContentRaw(opts, project, readmeFile)
}
}
return nil
@ -257,3 +262,8 @@ func printProjectContentRaw(opts *ViewOptions, project *gitlab.Project, readme *
fmt.Fprintln(opts.IO.StdOut)
}
}
func printProjectContentJSON(opts *ViewOptions, project *gitlab.Project) {
projectJSON, _ := json.Marshal(project)
fmt.Fprintln(opts.IO.StdOut, string(projectJSON))
}

View File

@ -1,6 +1,7 @@
package get
import (
"encoding/json"
"errors"
"fmt"
@ -19,9 +20,11 @@ type GetOps struct {
IO *iostreams.IOStreams
BaseRepo func() (glrepo.Interface, error)
Key string
Group string
Scope string
Scope string
Key string
Group string
OutputFormat string
JSONOutput bool
}
func NewCmdSet(f *cmdutils.Factory, runE func(opts *GetOps) error) *cobra.Command {
@ -65,6 +68,7 @@ func NewCmdSet(f *cmdutils.Factory, runE func(opts *GetOps) error) *cobra.Comman
cmd.Flags().StringVarP(&opts.Scope, "scope", "s", "*", "The environment_scope of the variable. All (*), or specific environments.")
cmd.Flags().StringVarP(&opts.Group, "group", "g", "", "Get variable for a group")
cmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json")
return cmd
}
@ -81,6 +85,10 @@ func getRun(opts *GetOps) error {
if err != nil {
return err
}
if opts.OutputFormat == "json" {
varJSON, _ := json.Marshal(variable)
fmt.Println(string(varJSON))
}
variableValue = variable.Value
} else {
baseRepo, err := opts.BaseRepo()
@ -92,9 +100,15 @@ func getRun(opts *GetOps) error {
if err != nil {
return err
}
if opts.OutputFormat == "json" {
varJSON, _ := json.Marshal(variable)
fmt.Fprintln(opts.IO.StdOut, string(varJSON))
}
variableValue = variable.Value
}
fmt.Fprint(opts.IO.StdOut, variableValue)
if opts.OutputFormat != "json" {
fmt.Fprint(opts.IO.StdOut, variableValue)
}
return nil
}

View File

@ -1,6 +1,9 @@
package list
import (
"encoding/json"
"fmt"
"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"
"github.com/xanzy/go-gitlab"
@ -18,8 +21,9 @@ type ListOpts struct {
IO *iostreams.IOStreams
BaseRepo func() (glrepo.Interface, error)
ValueSet bool
Group string
ValueSet bool
Group string
OutputFormat string
}
func NewCmdSet(f *cmdutils.Factory, runE func(opts *ListOpts) error) *cobra.Command {
@ -64,6 +68,7 @@ func NewCmdSet(f *cmdutils.Factory, runE func(opts *ListOpts) error) *cobra.Comm
"",
"Select a group/subgroup. This option is ignored if a repo argument is set.",
)
cmd.Flags().StringVarP(&opts.OutputFormat, "output", "F", "text", "Format output as: text, json")
return cmd
}
@ -90,8 +95,14 @@ func listRun(opts *ListOpts) error {
if err != nil {
return err
}
for _, variable := range variables {
table.AddRow(variable.Key, variable.Protected, variable.Masked, !variable.Raw, variable.EnvironmentScope)
if opts.OutputFormat == "json" {
varListJSON, _ := json.Marshal(variables)
fmt.Fprintln(opts.IO.StdOut, string(varListJSON))
} else {
for _, variable := range variables {
table.AddRow(variable.Key, variable.Protected, variable.Masked, !variable.Raw, variable.EnvironmentScope)
}
}
} else {
opts.IO.Logf("Listing variables for the %s project:\n\n", color.Bold(repo.FullName()))
@ -100,11 +111,18 @@ func listRun(opts *ListOpts) error {
if err != nil {
return err
}
for _, variable := range variables {
table.AddRow(variable.Key, variable.Protected, variable.Masked, !variable.Raw, variable.EnvironmentScope)
if opts.OutputFormat == "json" {
varListJSON, _ := json.Marshal(variables)
fmt.Fprintln(opts.IO.StdOut, string(varListJSON))
} else {
for _, variable := range variables {
table.AddRow(variable.Key, variable.Protected, variable.Masked, !variable.Raw, variable.EnvironmentScope)
}
}
}
opts.IO.Log(table.String())
if opts.OutputFormat != "json" {
opts.IO.Log(table.String())
}
return nil
}

View File

@ -34,11 +34,11 @@ glab ci -R some/project -p 12345
## Options
```plaintext
-b, --branch string Check pipeline status for a branch. (Default is current branch)
-o, --output-format string Format output as: text, json (default "text")
-p, --pipeline-id int Provide pipeline ID
-d, --with-job-details Show extended job information
--with-variables Show variables in pipeline (maintainer role required)
-b, --branch string Check pipeline status for a branch. (Default is current branch)
-F, --output string Format output as: text, json (default "text")
-p, --pipeline-id int Provide pipeline ID
-d, --with-job-details Show extended job information
--with-variables Show variables in pipeline (maintainer role required)
```
## Options inherited from parent commands

View File

@ -29,6 +29,7 @@ glab ci list --status=failed
```plaintext
-o, --orderBy string Order pipeline by {id|status|ref|updated_at|user_id} (default "id")
-F, --output string Format output as: text, json (default "text")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 30)
--sort string Sort pipeline by {asc|desc} (default "desc")

View File

@ -48,7 +48,7 @@ glab incident list --milestone release-2.0.0 --opened
--not-assignee strings Filter incident by not being assigneed to <username>
--not-author strings Filter by not being by author(s) <username>
--not-label strings Filter incident by lack of label <name>
-F, --output-format string One of 'details', 'ids', or 'urls' (default "details")
-F, --output string One of 'details', 'ids', 'urls' or 'json' (default "details")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page. (default 30)
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL

View File

@ -37,11 +37,12 @@ glab incident view https://gitlab.com/NAMESPACE/REPO/-/issues/incident/123
## Options
```plaintext
-c, --comments Show incident comments and activities
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 20)
-s, --system-logs Show system activities / logs
-w, --web Open incident in a browser. Uses default browser or browser specified in BROWSER variable
-c, --comments Show incident comments and activities
-F, --output string Format output as: text, json (default "text")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 20)
-s, --system-logs Show system activities / logs
-w, --web Open incident in a browser. Uses default browser or browser specified in BROWSER variable
```
## Options inherited from parent commands

View File

@ -49,7 +49,7 @@ glab issue list --milestone release-2.0.0 --opened
--not-assignee strings Filter issue by not being assigneed to <username>
--not-author strings Filter by not being by author(s) <username>
--not-label strings Filter issue by lack of label <name>
-F, --output-format string One of 'details', 'ids', or 'urls' (default "details")
-F, --output string One of 'details', 'ids', 'urls' or 'json' (default "details")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page. (default 30)
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL

View File

@ -37,11 +37,12 @@ glab issue view https://gitlab.com/NAMESPACE/REPO/-/issues/123
## Options
```plaintext
-c, --comments Show issue comments and activities
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 20)
-s, --system-logs Show system activities / logs
-w, --web Open issue in a browser. Uses default browser or browser specified in BROWSER variable
-c, --comments Show issue comments and activities
-F, --output string Format output as: text, json (default "text")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 20)
-s, --system-logs Show system activities / logs
-w, --web Open issue in a browser. Uses default browser or browser specified in BROWSER variable
```
## Options inherited from parent commands

View File

@ -35,8 +35,9 @@ glab label list -R owner/repository
## Options
```plaintext
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 30)
-F, --output string Format output as: text, json (default "text")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 30)
```
## Options inherited from parent commands

View File

@ -52,6 +52,7 @@ glab mr list -M --per-page 10
-M, --merged Get only merged merge requests
-m, --milestone string Filter merge request by milestone <id>
--not-label strings Filter merge requests by not having label <name>
-F, --output string Format output as: text, json (default "text")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 30)
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL

View File

@ -26,11 +26,12 @@ show
## Options
```plaintext
-c, --comments Show mr comments and activities
-p, --page int Page number
-P, --per-page int Number of items to list per page (default 20)
-s, --system-logs Show system activities / logs
-w, --web Open mr in a browser. Uses default browser or browser specified in BROWSER variable
-c, --comments Show mr comments and activities
-F, --output string Format output as: text, json (default "text")
-p, --page int Page number
-P, --per-page int Number of items to list per page (default 20)
-s, --system-logs Show system activities / logs
-w, --web Open mr in a browser. Uses default browser or browser specified in BROWSER variable
```
## Options inherited from parent commands

View File

@ -33,15 +33,16 @@ glab repo list
## Options
```plaintext
-a, --all List all projects on the instance
-g, --group string Return only repositories in the given group and its subgroups
--member Only list projects which you are a member
-m, --mine Only list projects you own (default if no filters are passed)
-o, --order string Return repositories ordered by id, created_at, or other fields (default "last_activity_at")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 30)
-s, --sort string Return repositories sorted in asc or desc order
--starred Only list starred projects
-a, --all List all projects on the instance
-g, --group string Return only repositories in the given group and its subgroups
--member Only list projects which you are a member
-m, --mine Only list projects you own (default if no filters are passed)
-o, --order string Return repositories ordered by id, created_at, or other fields (default "last_activity_at")
-F, --output string Format output as: text, json (default "text")
-p, --page int Page number (default 1)
-P, --per-page int Number of items to list per page (default 30)
-s, --sort string Return repositories sorted in asc or desc order
--starred Only list starred projects
```
## Options inherited from parent commands

View File

@ -43,6 +43,7 @@ $ glab repo view https://gitlab.company.org/user/repo.git
```plaintext
-b, --branch string View a specific branch of the repository
-F, --output string Format output as: text, json (default "text")
-w, --web Open a project in the browser
```

View File

@ -29,8 +29,9 @@ glab variable get -s SCOPE VAR_KEY
## Options
```plaintext
-g, --group string Get variable for a group
-s, --scope string The environment_scope of the variable. All (*), or specific environments. (default "*")
-g, --group string Get variable for a group
-F, --output string Format output as: text, json (default "text")
-s, --scope string The environment_scope of the variable. All (*), or specific environments. (default "*")
```
## Options inherited from parent commands

View File

@ -34,6 +34,7 @@ glab variable list
```plaintext
-g, --group string Select a group/subgroup. This option is ignored if a repo argument is set.
-F, --output string Format output as: text, json (default "text")
-R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL
```