diff --git a/api/schedule.go b/api/schedule.go new file mode 100644 index 00000000..9275c882 --- /dev/null +++ b/api/schedule.go @@ -0,0 +1,18 @@ +package api + +import "github.com/xanzy/go-gitlab" + +var GetSchedules = func(client *gitlab.Client, l *gitlab.ListPipelineSchedulesOptions, repo string) ([]*gitlab.PipelineSchedule, error) { + if client == nil { + client = apiClient.Lab() + } + if l.PerPage == 0 { + l.PerPage = DefaultListLimit + } + + schedules, _, err := client.PipelineSchedules.ListPipelineSchedules(repo, l) + if err != nil { + return nil, err + } + return schedules, nil +} diff --git a/commands/ci/ciutils/utils.go b/commands/ci/ciutils/utils.go index 5052342e..f592e093 100644 --- a/commands/ci/ciutils/utils.go +++ b/commands/ci/ciutils/utils.go @@ -26,6 +26,21 @@ func makeHyperlink(s *iostreams.IOStreams, pipeline *gitlab.PipelineInfo) string return s.Hyperlink(fmt.Sprintf("%d", pipeline.ID), pipeline.WebURL) } +func DisplaySchedules(i *iostreams.IOStreams, s []*gitlab.PipelineSchedule, projectID string) string { + if len(s) > 0 { + table := tableprinter.NewTablePrinter() + table.AddRow("ID", "Description", "Cron", "Owner", "Active") + for _, schedule := range s { + table.AddRow(schedule.ID, schedule.Description, schedule.Cron, schedule.Owner.Username, schedule.Active) + } + + return table.Render() + } + + // return empty string, since when there is no schedule, the title will already display it accordingly + return "" +} + func DisplayMultiplePipelines(s *iostreams.IOStreams, p []*gitlab.PipelineInfo, projectID string) string { c := s.Color() diff --git a/commands/root.go b/commands/root.go index 0b20401d..51a4f616 100644 --- a/commands/root.go +++ b/commands/root.go @@ -20,6 +20,7 @@ import ( mrCmd "gitlab.com/gitlab-org/cli/commands/mr" projectCmd "gitlab.com/gitlab-org/cli/commands/project" releaseCmd "gitlab.com/gitlab-org/cli/commands/release" + scheduleCmd "gitlab.com/gitlab-org/cli/commands/schedule" snippetCmd "gitlab.com/gitlab-org/cli/commands/snippet" sshCmd "gitlab.com/gitlab-org/cli/commands/ssh-key" updateCmd "gitlab.com/gitlab-org/cli/commands/update" @@ -117,6 +118,7 @@ func NewCmdRoot(f *cmdutils.Factory, version, buildDate string) *cobra.Command { rootCmd.AddCommand(userCmd.NewCmdUser(f)) rootCmd.AddCommand(variableCmd.NewVariableCmd(f)) rootCmd.AddCommand(apiCmd.NewCmdApi(f, nil)) + rootCmd.AddCommand(scheduleCmd.NewCmdSchedule(f)) rootCmd.AddCommand(snippetCmd.NewCmdSnippet(f)) rootCmd.Flags().BoolP("version", "v", false, "show glab version information") diff --git a/commands/schedule/list/list.go b/commands/schedule/list/list.go new file mode 100644 index 00000000..31ef32f8 --- /dev/null +++ b/commands/schedule/list/list.go @@ -0,0 +1,60 @@ +package list + +import ( + "fmt" + + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/commands/ci/ciutils" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + "gitlab.com/gitlab-org/cli/pkg/utils" + + "github.com/MakeNowJust/heredoc" + "github.com/spf13/cobra" + "github.com/xanzy/go-gitlab" +) + +func NewCmdList(f *cmdutils.Factory) *cobra.Command { + scheduleListCmd := &cobra.Command{ + Use: "list [flags]", + Short: `Get the list of schedules`, + Example: heredoc.Doc(` + glab schedule list + `), + Long: ``, + Args: cobra.ExactArgs(0), + RunE: func(cmd *cobra.Command, args []string) error { + apiClient, err := f.HttpClient() + if err != nil { + return err + } + + repo, err := f.BaseRepo() + if err != nil { + return err + } + + l := &gitlab.ListPipelineSchedulesOptions{} + page, _ := cmd.Flags().GetInt("page") + l.Page = page + perPage, _ := cmd.Flags().GetInt("per-page") + l.PerPage = perPage + + schedules, err := api.GetSchedules(apiClient, l, repo.FullName()) + if err != nil { + return err + } + + title := utils.NewListTitle("schedule") + title.RepoName = repo.FullName() + title.Page = l.Page + title.CurrentPageTotal = len(schedules) + + fmt.Fprintf(f.IO.StdOut, "%s\n%s\n", title.Describe(), ciutils.DisplaySchedules(f.IO, schedules, repo.FullName())) + return nil + }, + } + scheduleListCmd.Flags().IntP("page", "p", 1, "Page number") + scheduleListCmd.Flags().IntP("per-page", "P", 30, "Number of items to list per page.") + + return scheduleListCmd +} diff --git a/commands/schedule/list/list_test.go b/commands/schedule/list/list_test.go new file mode 100644 index 00000000..0e097874 --- /dev/null +++ b/commands/schedule/list/list_test.go @@ -0,0 +1,103 @@ +package list + +import ( + "testing" + + "github.com/acarl005/stripansi" + "github.com/stretchr/testify/assert" + "github.com/xanzy/go-gitlab" + "gitlab.com/gitlab-org/cli/api" + "gitlab.com/gitlab-org/cli/commands/cmdtest" + "gitlab.com/gitlab-org/cli/commands/cmdutils" + "gitlab.com/gitlab-org/cli/internal/config" + "gitlab.com/gitlab-org/cli/pkg/iostreams" +) + +func Test_ScheduleList(t *testing.T) { + defer config.StubConfig(`--- +hosts: + gitlab.com: + username: monalisa + token: OTOKEN +`, "")() + + io, _, stdout, stderr := iostreams.Test() + stubFactory, _ := cmdtest.StubFactoryWithConfig("") + stubFactory.IO = io + stubFactory.IO.IsaTTY = true + stubFactory.IO.IsErrTTY = true + + api.GetSchedules = func(client *gitlab.Client, l *gitlab.ListPipelineSchedulesOptions, repo string) ([]*gitlab.PipelineSchedule, error) { + _, err := stubFactory.BaseRepo() + if err != nil { + return nil, err + } + + return []*gitlab.PipelineSchedule{ + { + ID: 1, + Description: "foo", + Cron: "* * * * *", + Owner: &gitlab.User{ + ID: 1, + Username: "bar", + }, + Active: true, + }, + }, nil + } + + cmd := NewCmdList(stubFactory) + cmdutils.EnableRepoOverride(cmd, stubFactory) + + t.Run("Schedule exists", func(t *testing.T) { + _, err := cmd.ExecuteC() + if err != nil { + t.Fatal(err) + } + + out := stripansi.Strip(stdout.String()) + + assert.Contains(t, out, "1\tfoo\t* * * * *\tbar\ttrue") + assert.Equal(t, "", stderr.String()) + }) +} + +func Test_NoScheduleList(t *testing.T) { + defer config.StubConfig(`--- +hosts: + gitlab.com: + username: monalisa + token: OTOKEN +`, "")() + + io, _, stdout, stderr := iostreams.Test() + stubFactory, _ := cmdtest.StubFactoryWithConfig("") + stubFactory.IO = io + stubFactory.IO.IsaTTY = true + stubFactory.IO.IsErrTTY = true + + api.GetSchedules = func(client *gitlab.Client, l *gitlab.ListPipelineSchedulesOptions, repo string) ([]*gitlab.PipelineSchedule, error) { + _, err := stubFactory.BaseRepo() + if err != nil { + return nil, err + } + + return nil, nil + } + + cmd := NewCmdList(stubFactory) + cmdutils.EnableRepoOverride(cmd, stubFactory) + + t.Run("No schedules exist", func(t *testing.T) { + _, err := cmd.ExecuteC() + if err != nil { + t.Fatal(err) + } + + out := stripansi.Strip(stdout.String()) + + assert.Contains(t, out, "No schedules available on") + assert.Equal(t, "", stderr.String()) + }) +} diff --git a/commands/schedule/schedule.go b/commands/schedule/schedule.go new file mode 100644 index 00000000..33963b18 --- /dev/null +++ b/commands/schedule/schedule.go @@ -0,0 +1,24 @@ +package schedule + +import ( + scheduleListCmd "gitlab.com/gitlab-org/cli/commands/schedule/list" + + "gitlab.com/gitlab-org/cli/commands/cmdutils" + + "github.com/spf13/cobra" +) + +func NewCmdSchedule(f *cmdutils.Factory) *cobra.Command { + scheduleCmd := &cobra.Command{ + Use: "schedule [flags]", + Short: `Work with GitLab CI schedules`, + Long: ``, + Aliases: []string{"sched", "skd"}, + } + + cmdutils.EnableRepoOverride(scheduleCmd, f) + + scheduleCmd.AddCommand(scheduleListCmd.NewCmdList(f)) + + return scheduleCmd +} diff --git a/docs/source/schedule/help.md b/docs/source/schedule/help.md new file mode 100644 index 00000000..6a11f142 --- /dev/null +++ b/docs/source/schedule/help.md @@ -0,0 +1,25 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab schedule help` + +Help about any command + +```plaintext +glab schedule help [command] [flags] +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` diff --git a/docs/source/schedule/index.md b/docs/source/schedule/index.md new file mode 100755 index 00000000..924e54c6 --- /dev/null +++ b/docs/source/schedule/index.md @@ -0,0 +1,30 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab schedule` + +Work with GitLab CI schedules + +## Options + +```plaintext + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` + +## Options inherited from parent commands + +```plaintext + --help Show help for command +``` + +## Subcommands + +- [list](list.md) diff --git a/docs/source/schedule/list.md b/docs/source/schedule/list.md new file mode 100644 index 00000000..2e322f88 --- /dev/null +++ b/docs/source/schedule/list.md @@ -0,0 +1,39 @@ +--- +stage: Create +group: Code Review +info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments +--- + + + +# `glab schedule list` + +Get the list of schedules + +```plaintext +glab schedule list [flags] +``` + +## Examples + +```plaintext +glab schedule list + +``` + +## Options + +```plaintext + -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 + +```plaintext + --help Show help for command + -R, --repo OWNER/REPO Select another repository using the OWNER/REPO or `GROUP/NAMESPACE/REPO` format or full URL or git URL +``` diff --git a/gl-code-quality-report.json b/gl-code-quality-report.json new file mode 100644 index 00000000..0637a088 --- /dev/null +++ b/gl-code-quality-report.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/go.mod b/go.mod index 62dfa0d2..9362043d 100644 --- a/go.mod +++ b/go.mod @@ -39,6 +39,7 @@ require ( github.com/xanzy/go-gitlab v0.73.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 + golang.org/x/text v0.3.7 gopkg.in/yaml.v3 v3.0.1 ) @@ -79,7 +80,6 @@ require ( golang.org/x/net v0.0.0-20220805013720-a33c5aa5df48 // indirect golang.org/x/oauth2 v0.0.0-20220722155238-128564f6959c // indirect golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 // indirect - golang.org/x/text v0.3.7 // indirect golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.28.1 // indirect diff --git a/go.sum b/go.sum index b58d4e44..051be336 100644 --- a/go.sum +++ b/go.sum @@ -184,6 +184,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= @@ -233,6 +234,7 @@ github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= @@ -331,7 +333,9 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/spf13/afero v1.6.0 h1:xoax2sJ2DT8S8xA2paPFjDCScCNeWsg75VG0DLRreiY= github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=