diff --git a/commands/ci/get/get.go b/commands/ci/get/get.go index a774c9eecd0f964619a685783c056243b6ba3d28..f8b2676fdd43f87663071b4ff9403a386440531b 100644 --- a/commands/ci/get/get.go +++ b/commands/ci/get/get.go @@ -94,9 +94,9 @@ func NewCmdGet(f *cmdutils.Factory) *cobra.Command { Variables: variables, } - outputFormat, _ := cmd.Flags().GetString("output-format") + outputFormat, _ := cmd.Flags().GetString("output") if outputFormat == "json" { - printJSON(*mergedPipelineObject) + printJSON(*mergedPipelineObject, f.IO.StdOut) } else { showJobDetails, _ := cmd.Flags().GetBool("with-job-details") printTable(*mergedPipelineObject, f.IO.StdOut, showJobDetails) @@ -108,16 +108,16 @@ 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().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) { diff --git a/commands/ci/get/get_test.go b/commands/ci/get/get_test.go index 4f894e931a192c8c7dd87fdf0184d4b706aad84b..af0ad524f1b5725d0f9f1d570281a272140aec35 100644 --- a/commands/ci/get/get_test.go +++ b/commands/ci/get/get_test.go @@ -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 + + if tc.expectedOutType == FileBody { + expectedOutBytes, err = os.ReadFile(tc.expectedOut) + expectedOut = string(expectedOutBytes) + require.Nil(t, err) + } else { + expectedOut = tc.expectedOut + } - assert.Equal(t, tc.expectedOut, output.String()) + assert.Equal(t, expectedOut, output.String()) assert.Empty(t, output.Stderr()) }) } diff --git a/commands/ci/get/testdata/ci_get-0.json b/commands/ci/get/testdata/ci_get-0.json new file mode 100644 index 0000000000000000000000000000000000000000..498f839725c0a7902f0c1ec9e8c715d28f4b8161 --- /dev/null +++ b/commands/ci/get/testdata/ci_get-0.json @@ -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 +} diff --git a/commands/ci/get/testdata/ci_get-1.json b/commands/ci/get/testdata/ci_get-1.json new file mode 100644 index 0000000000000000000000000000000000000000..d43e6eebe6a00735f3fd2a4ba0f01d39e8b525c2 --- /dev/null +++ b/commands/ci/get/testdata/ci_get-1.json @@ -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": [] + } +] diff --git a/commands/ci/get/testdata/ci_get.result b/commands/ci/get/testdata/ci_get.result new file mode 100644 index 0000000000000000000000000000000000000000..f997aec80e710061b563d79cdcc752bd9284b3ec --- /dev/null +++ b/commands/ci/get/testdata/ci_get.result @@ -0,0 +1 @@ +{"id":452959326,"iid":14,"project_id":29316529,"status":"success","source":"push","ref":"1-fake-issue-3","sha":"44eb489568f7cb1a5a730fce6b247cd3797172ca","before_sha":"001eb421e586a3f07f90aea102c8b2d4068ab5b6","tag":false,"yaml_errors":"","user":{"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"},"updated_at":"2022-01-20T21:47:31.358Z","created_at":"2022-01-20T21:47:16.276Z","started_at":"2022-01-20T21:47:17.448Z","finished_at":"2022-01-20T21:47:31.35Z","committed_at":null,"duration":14,"queued_duration":1,"coverage":"","web_url":"https://gitlab.com/OWNER/REPO/-/pipelines/452959326","detailed_status":{"icon":"status_success","text":"Passed","label":"passed","group":"success","tooltip":"passed","has_details":true,"details_path":"/OWNER/REPO/-/pipelines/452959326","illustration":{"image":""},"favicon":"/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2.png"},"jobs":[{"commit":{"id":"44eb489568f7cb1a5a730fce6b247cd3797172ca","short_id":"44eb4895","title":"Add new file","author_name":"Some User","author_email":"OWNER@gitlab.com","authored_date":"2022-01-20T21:47:15Z","committer_name":"Some User","committer_email":"OWNER@gitlab.com","committed_date":"2022-01-20T21:47:15Z","created_at":"2022-01-20T21:47:15Z","message":"Add new file","parent_ids":["001eb421e586a3f07f90aea102c8b2d4068ab5b6"],"stats":null,"status":null,"last_pipeline":null,"project_id":0,"trailers":{},"web_url":"https://gitlab.com/OWNER/REPO/-/commit/44eb489568f7cb1a5a730fce6b247cd3797172ca"},"coverage":0,"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,"artifacts_expire_at":null,"tag_list":[],"id":1999017704,"name":"test_vars","pipeline":{"id":452959326,"project_id":29316529,"ref":"1-fake-issue-3","sha":"44eb489568f7cb1a5a730fce6b247cd3797172ca","status":"success"},"ref":"1-fake-issue-3","artifacts":[{"file_type":"trace","filename":"job.log","size":2770,"file_format":""}],"artifacts_file":{"filename":"","size":0},"runner":{"id":12270859,"description":"5-green.saas-linux-small-amd64.runners-manager.gitlab.com/default","active":true,"is_shared":true,"name":"gitlab-runner"},"stage":"test","status":"success","failure_reason":"","tag":false,"web_url":"https://gitlab.com/OWNER/REPO/-/jobs/1999017704","project":{"id":0,"description":"","default_branch":"","public":false,"visibility":"","ssh_url_to_repo":"","http_url_to_repo":"","web_url":"","readme_url":"","tag_list":null,"topics":null,"owner":null,"name":"","name_with_namespace":"","path":"","path_with_namespace":"","issues_enabled":false,"open_issues_count":0,"merge_requests_enabled":false,"approvals_before_merge":0,"jobs_enabled":false,"wiki_enabled":false,"snippets_enabled":false,"resolve_outdated_diff_discussions":false,"container_registry_enabled":false,"container_registry_access_level":"","creator_id":0,"namespace":null,"permissions":null,"marked_for_deletion_at":null,"empty_repo":false,"archived":false,"avatar_url":"","license_url":"","license":null,"shared_runners_enabled":false,"group_runners_enabled":false,"runner_token_expiration_interval":0,"forks_count":0,"star_count":0,"runners_token":"","allow_merge_on_skipped_pipeline":false,"only_allow_merge_if_pipeline_succeeds":false,"only_allow_merge_if_all_discussions_are_resolved":false,"remove_source_branch_after_merge":false,"printing_merge_request_link_enabled":false,"lfs_enabled":false,"repository_storage":"","request_access_enabled":false,"merge_method":"","can_create_merge_request_in":false,"forked_from_project":null,"mirror":false,"mirror_user_id":0,"mirror_trigger_builds":false,"only_mirror_protected_branches":false,"mirror_overwrites_diverged_branches":false,"packages_enabled":false,"service_desk_enabled":false,"service_desk_address":"","issues_access_level":"","repository_access_level":"","merge_requests_access_level":"","forking_access_level":"","wiki_access_level":"","builds_access_level":"","snippets_access_level":"","pages_access_level":"","operations_access_level":"","analytics_access_level":"","environments_access_level":"","feature_flags_access_level":"","infrastructure_access_level":"","monitor_access_level":"","autoclose_referenced_issues":false,"suggestion_commit_message":"","squash_option":"","shared_with_groups":null,"statistics":null,"import_url":"","import_type":"","import_status":"","import_error":"","ci_default_git_depth":0,"ci_forward_deployment_enabled":false,"ci_separated_caches":false,"ci_job_token_scope_enabled":false,"ci_opt_in_jwt":false,"ci_allow_fork_pipelines_to_run_in_parent_project":false,"public_jobs":false,"build_timeout":0,"auto_cancel_pending_pipelines":"","ci_config_path":"","custom_attributes":null,"compliance_frameworks":null,"build_coverage_regex":"","issues_template":"","merge_requests_template":"","issue_branch_template":"","keep_latest_artifact":false,"merge_pipelines_enabled":false,"merge_trains_enabled":false,"restrict_user_defined_variables":false,"merge_commit_template":"","squash_commit_template":"","auto_devops_deploy_strategy":"","auto_devops_enabled":false,"build_git_strategy":"","emails_enabled":false,"external_authorization_classification_label":"","requirements_enabled":false,"requirements_access_level":"","security_and_compliance_enabled":false,"security_and_compliance_access_level":"","mr_default_target_self":false,"emails_disabled":false,"public_builds":false},"user":{"id":8814129,"username":"OWNER","email":"","name":"Some User","state":"active","web_url":"https://gitlab.com/OWNER","created_at":"2021-05-03T14:58:50.059Z","bio":"","bot":false,"location":"Canada","public_email":"","skype":"","linkedin":"","twitter":"","website_url":"","organization":"GitLab","job_title":"Sr Backend Engineer","extern_uid":"","provider":"","theme_id":0,"last_activity_on":null,"color_scheme_id":0,"is_admin":false,"avatar_url":"https://gitlab.com/uploads/-/system/user/avatar/8814129/avatar.png","can_create_group":false,"can_create_project":false,"projects_limit":0,"current_sign_in_at":null,"current_sign_in_ip":null,"last_sign_in_at":null,"last_sign_in_ip":null,"confirmed_at":null,"two_factor_enabled":false,"note":"","identities":null,"external":false,"private_profile":false,"shared_runners_minutes_limit":0,"extra_shared_runners_minutes_limit":0,"using_license_seat":false,"custom_attributes":null,"namespace_id":0}}],"variables":null} diff --git a/commands/ci/list/list.go b/commands/ci/list/list.go index f28aff70a70874bf7d5bac4f9f5a2c25cec82aa2..46b4772f927ba6881b6e0f49a606f3be802da05e 100644 --- a/commands/ci/list/list.go +++ b/commands/ci/list/list.go @@ -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 } diff --git a/commands/ci/list/list_test.go b/commands/ci/list/list_test.go index 841c736af40823ca9743b8004da79d3b23544538..c88870d23f8df26538a3a39a6af8ee861c0eb500 100644 --- a/commands/ci/list/list_test.go +++ b/commands/ci/list/list_test.go @@ -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()) +} diff --git a/commands/ci/list/testdata/ciList.json b/commands/ci/list/testdata/ciList.json new file mode 100644 index 0000000000000000000000000000000000000000..9297365261142bee8c6d57fb33835cb3da87f53f --- /dev/null +++ b/commands/ci/list/testdata/ciList.json @@ -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" + } +] \ No newline at end of file diff --git a/commands/issuable/list/issuable_list.go b/commands/issuable/list/issuable_list.go index 652b1748b6aec840840e6feb98d8d6004957f6b4..bd09d0706aa883ebea10e878dbd1faaf2c194528 100644 --- a/commands/issuable/list/issuable_list.go +++ b/commands/issuable/list/issuable_list.go @@ -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) diff --git a/commands/issuable/list/issuable_list_test.go b/commands/issuable/list/issuable_list_test.go index 6d23b0427efdf1e5171468cc7cfed890dec676bc..125855ec1e7ceb781b1f63ea7f0bdd1a12ddba3a 100644 --- a/commands/issuable/list/issuable_list_test.go +++ b/commands/issuable/list/issuable_list_test.go @@ -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()) +} diff --git a/commands/issuable/list/testdata/issueListFull.json b/commands/issuable/list/testdata/issueListFull.json new file mode 100644 index 0000000000000000000000000000000000000000..ec63ebd9825bd3736d27b960e462580231198f57 --- /dev/null +++ b/commands/issuable/list/testdata/issueListFull.json @@ -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": "" + } +] \ No newline at end of file diff --git a/commands/issuable/view/issuable_view.go b/commands/issuable/view/issuable_view.go index c5bfe9eb7ea9e06f9ba410858e00f01915a3b76f..99b2bb3067db0c1847fae14f6afce1f699ee1a17 100644 --- a/commands/issuable/view/issuable_view.go +++ b/commands/issuable/view/issuable_view.go @@ -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 +} diff --git a/commands/issuable/view/issuable_view_test.go b/commands/issuable/view/issuable_view_test.go index 0fd24f3ee92ad1f98dca6c8832f6245c986fc893..e5071c6901d4e7e2b6652e0ff7eed0d834e90379 100644 --- a/commands/issuable/view/issuable_view_test.go +++ b/commands/issuable/view/issuable_view_test.go @@ -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()) +} diff --git a/commands/label/list/label_list.go b/commands/label/list/label_list.go index 3ebb2e253624146e743ea7f29f474bb3e291f9eb..c209596f08c4782757a7af90804a01ff16b11dc5 100644 --- a/commands/label/list/label_list.go +++ b/commands/label/list/label_list.go @@ -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 } diff --git a/commands/label/list/label_list_test.go b/commands/label/list/label_list_test.go index 54baf6d82028daa7eb060a965f07383fecc7128a..eec106516e0a563224a4da71bb48d15c2ef5ee86 100644 --- a/commands/label/list/label_list_test.go +++ b/commands/label/list/label_list_test.go @@ -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()) +} diff --git a/commands/mr/list/mr_list.go b/commands/mr/list/mr_list.go index 5233c42d0b6b53e3fc90eb979f687e9f5b9b43e2..741d011147be50a1a5bbaa934b8a2f0f01d9c101 100644 --- a/commands/mr/list/mr_list.go +++ b/commands/mr/list/mr_list.go @@ -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) @@ -260,7 +269,12 @@ func listRun(opts *ListOptions) error { return err } defer opts.IO.StopPager() - fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), mrutils.DisplayAllMRs(opts.IO, mergeRequests)) + if jsonOutput { + mrListJSON, _ := json.Marshal(mergeRequests) + fmt.Fprintln(opts.IO.StdOut, string(mrListJSON)) + } else { + fmt.Fprintf(opts.IO.StdOut, "%s\n%s\n", title.Describe(), mrutils.DisplayAllMRs(opts.IO, mergeRequests)) + } return nil } diff --git a/commands/mr/list/mr_list_test.go b/commands/mr/list/mr_list_test.go index 8354461f3bba665de58aa1fd3948db55b632cda2..8f06720db528b1dddb9cfc85e0c49c7ae61af566 100644 --- a/commands/mr/list/mr_list_test.go +++ b/commands/mr/list/mr_list_test.go @@ -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()) +} diff --git a/commands/mr/list/testdata/mrList.json b/commands/mr/list/testdata/mrList.json new file mode 100644 index 0000000000000000000000000000000000000000..29ea8797745fb8cecefeb706a4b41083e3ba69a2 --- /dev/null +++ b/commands/mr/list/testdata/mrList.json @@ -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" + } +] \ No newline at end of file diff --git a/commands/mr/view/mr_view.go b/commands/mr/view/mr_view.go index 8f9c31f86cd0ff70feaf77d39409b82888ca332b..715c107c2783355b905b17ff5d752fbfb6016fa2 100644 --- a/commands/mr/view/mr_view.go +++ b/commands/mr/view/mr_view.go @@ -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 +} diff --git a/commands/mr/view/mr_view_test.go b/commands/mr/view/mr_view_test.go index c39be3961ec2bde294902bb375bd307491dd73d5..b40c635d5ca1ca6c54b9f118db142ed97382965e 100644 --- a/commands/mr/view/mr_view_test.go +++ b/commands/mr/view/mr_view_test.go @@ -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()) +} diff --git a/commands/project/list/list.go b/commands/project/list/list.go index b5507aa742871029de5ae3bf775ee4155d570fdb..e59c718a91eb6d26953726a0e4329980edc3dbb1 100644 --- a/commands/project/list/list.go +++ b/commands/project/list/list.go @@ -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) + + // List + table := tableprinter.NewTablePrinter() + for _, prj := range projects { + table.AddCell(c.Blue(prj.PathWithNamespace)) + table.AddCell(prj.SSHURLToRepo) + table.AddCell(prj.Description) + table.EndRow() + } - 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 } diff --git a/commands/project/view/project_view.go b/commands/project/view/project_view.go index 69bd12a47147cf3c0f98630b2956475fc7204e46..480dc920499e668b0720572134c918f04253d549 100644 --- a/commands/project/view/project_view.go +++ b/commands/project/view/project_view.go @@ -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)) +} diff --git a/commands/variable/get/get.go b/commands/variable/get/get.go index 47d6e56352940ac6c17d8daf1e71c0dd5b8468e2..5532e3abd9fc6ce48fc4cd53ea889444c9648c9a 100644 --- a/commands/variable/get/get.go +++ b/commands/variable/get/get.go @@ -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 } diff --git a/commands/variable/list/list.go b/commands/variable/list/list.go index 4ae0a34813352dda342a090e702dd0eabbe55458..dbcc1cb91105aefa536697fc75a541cf44533962 100644 --- a/commands/variable/list/list.go +++ b/commands/variable/list/list.go @@ -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 } diff --git a/docs/source/ci/get.md b/docs/source/ci/get.md index b024b0707666401ffed3ee268b31bb9e2bf0aaaf..898441adbc4eedc238c7304ba0cbf0be446c9217 100644 --- a/docs/source/ci/get.md +++ b/docs/source/ci/get.md @@ -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 diff --git a/docs/source/ci/list.md b/docs/source/ci/list.md index e72c5adc6ba571f3e110f9f8696627f1bd80bfaf..dcbf24fb3ef7e7e14665dec7276d44afb04058fc 100644 --- a/docs/source/ci/list.md +++ b/docs/source/ci/list.md @@ -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") diff --git a/docs/source/incident/list.md b/docs/source/incident/list.md index c1bcfa045a57795eedfd69679834b998b38494af..01b352608567d2a60e818befc84f908ae444e683 100644 --- a/docs/source/incident/list.md +++ b/docs/source/incident/list.md @@ -48,7 +48,7 @@ glab incident list --milestone release-2.0.0 --opened --not-assignee strings Filter incident by not being assigneed to --not-author strings Filter by not being by author(s) --not-label strings Filter incident by lack of label - -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 diff --git a/docs/source/incident/view.md b/docs/source/incident/view.md index dac54bfbe9cca0b346423581d98e15bc93bdca8f..3e1d0e6de09411b538ea62f15435732b7cd77ba8 100644 --- a/docs/source/incident/view.md +++ b/docs/source/incident/view.md @@ -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 diff --git a/docs/source/issue/list.md b/docs/source/issue/list.md index 1cc1abead390625e59cbabc9f085c48acc07c103..2682e55d0905537a032d761b3cf526acadc194bc 100644 --- a/docs/source/issue/list.md +++ b/docs/source/issue/list.md @@ -49,7 +49,7 @@ glab issue list --milestone release-2.0.0 --opened --not-assignee strings Filter issue by not being assigneed to --not-author strings Filter by not being by author(s) --not-label strings Filter issue by lack of label - -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 diff --git a/docs/source/issue/view.md b/docs/source/issue/view.md index 203013fae7b4bcac53c66bb695ae48d9886499e4..051a8906f09ad16d951a48e44c54a1eaa3496d6e 100644 --- a/docs/source/issue/view.md +++ b/docs/source/issue/view.md @@ -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 diff --git a/docs/source/label/list.md b/docs/source/label/list.md index 8034f6d064f02888ef176c069acec2c48740b1d8..d5bacda8c90cec86d0140790d0f3eaa392cd83e1 100644 --- a/docs/source/label/list.md +++ b/docs/source/label/list.md @@ -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 diff --git a/docs/source/mr/list.md b/docs/source/mr/list.md index d96787ff0638db7a060fee378c8991c625c993ce..9481889dac9cd180d7def22f5962d35c0cada3e5 100644 --- a/docs/source/mr/list.md +++ b/docs/source/mr/list.md @@ -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 --not-label strings Filter merge requests by not having label + -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 diff --git a/docs/source/mr/view.md b/docs/source/mr/view.md index 78752b3f25169b455f2e0238d3fdc7bc66b7bb12..bc4b2e5df701db146775f07e5b7052d642b09970 100644 --- a/docs/source/mr/view.md +++ b/docs/source/mr/view.md @@ -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 diff --git a/docs/source/repo/list.md b/docs/source/repo/list.md index d39efc6e86cdf3721414b237284681fbf8114abc..2fbfdc0ff60d66f3b754c4f3a3acfe7cd9335861 100644 --- a/docs/source/repo/list.md +++ b/docs/source/repo/list.md @@ -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 diff --git a/docs/source/repo/view.md b/docs/source/repo/view.md index a030fccf54e238a8d65c74a146cc6f87fc1532e1..cb24b58dd25d17dff3e0930367dd2db367a26e9b 100644 --- a/docs/source/repo/view.md +++ b/docs/source/repo/view.md @@ -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 ``` diff --git a/docs/source/variable/get.md b/docs/source/variable/get.md index d3ff10be784a887c89fc65d3226cab937bbe7a21..71eee592e79d3a6632e93380045ab57053787118 100644 --- a/docs/source/variable/get.md +++ b/docs/source/variable/get.md @@ -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 diff --git a/docs/source/variable/list.md b/docs/source/variable/list.md index bb36400192e1b5c13e66ed2b2e130284dbde226d..69b0f165b2606954d0285d9a255cbea297fb1bce 100644 --- a/docs/source/variable/list.md +++ b/docs/source/variable/list.md @@ -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 ```