diff --git a/api/client/pipelines_v1_alpha.go b/api/client/pipelines_v1_alpha.go index 634a254..a8a8f0e 100644 --- a/api/client/pipelines_v1_alpha.go +++ b/api/client/pipelines_v1_alpha.go @@ -3,6 +3,7 @@ package client import ( "errors" "fmt" + "net/url" models "github.com/semaphoreci/cli/api/models" "github.com/semaphoreci/cli/api/uuid" @@ -92,6 +93,11 @@ func (c *PipelinesApiV1AlphaApi) ListPplByWfID(projectID, wfID string) ([]byte, return body, nil } +type ListOptions struct { + CreatedAfter int64 + CreatedBefore int64 +} + func (c *PipelinesApiV1AlphaApi) ListPpl(projectID string) ([]byte, error) { detailed := fmt.Sprintf("%s?project_id=%s", c.ResourceNamePlural, projectID) body, status, err := c.BaseClient.List(detailed) @@ -106,3 +112,28 @@ func (c *PipelinesApiV1AlphaApi) ListPpl(projectID string) ([]byte, error) { return body, nil } + +func (c *PipelinesApiV1AlphaApi) ListPplWithOptions(projectID string, options ListOptions) ([]byte, error) { + query := url.Values{} + query.Add("project_id", projectID) + + if options.CreatedAfter > 0 { + query.Add("created_after", fmt.Sprintf("%d", options.CreatedAfter)) + } + + if options.CreatedBefore > 0 { + query.Add("created_before", fmt.Sprintf("%d", options.CreatedBefore)) + } + + body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query) + + if err != nil { + return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) + } + + if status != 200 { + return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) + } + + return body, nil +} diff --git a/api/client/workflows_v1_alpha.go b/api/client/workflows_v1_alpha.go index 723de2d..f2a738a 100644 --- a/api/client/workflows_v1_alpha.go +++ b/api/client/workflows_v1_alpha.go @@ -3,6 +3,7 @@ package client import ( "errors" "fmt" + "net/url" models "github.com/semaphoreci/cli/api/models" "github.com/semaphoreci/cli/api/uuid" @@ -40,6 +41,30 @@ func (c *WorkflowApiV1AlphaApi) ListWorkflows(project_id string) (*models.Workfl return models.NewWorkflowListV1AlphaFromJson(body) } +func (c *WorkflowApiV1AlphaApi) ListWorkflowsWithOptions(projectID string, options ListOptions) (*models.WorkflowListV1Alpha, error) { + query := url.Values{} + query.Add("project_id", projectID) + + if options.CreatedAfter > 0 { + query.Add("created_after", fmt.Sprintf("%d", options.CreatedAfter)) + } + + if options.CreatedBefore > 0 { + query.Add("created_before", fmt.Sprintf("%d", options.CreatedBefore)) + } + + body, status, err := c.BaseClient.ListWithParams(c.ResourceNamePlural, query) + if err != nil { + return nil, errors.New(fmt.Sprintf("connecting to Semaphore failed '%s'", err)) + } + + if status != 200 { + return nil, errors.New(fmt.Sprintf("http status %d with message \"%s\" received from upstream", status, body)) + } + + return models.NewWorkflowListV1AlphaFromJson(body) +} + func (c *WorkflowApiV1AlphaApi) CreateSnapshotWf(project_id, label, archivePath string) ([]byte, error) { requestToken, err := uuid.NewUUID() diff --git a/cmd/get.go b/cmd/get.go index 9abb0b1..ba56deb 100644 --- a/cmd/get.go +++ b/cmd/get.go @@ -5,6 +5,7 @@ import ( "log" "os" "text/tabwriter" + "time" client "github.com/semaphoreci/cli/api/client" models "github.com/semaphoreci/cli/api/models" @@ -16,6 +17,10 @@ import ( "github.com/spf13/cobra" ) +const ( + DefaultListingAge = time.Hour * 24 * 90 +) + var getCmd = &cobra.Command{ Use: "get [KIND]", Short: "List resources.", @@ -398,7 +403,7 @@ var GetPplCmd = &cobra.Command{ if len(args) == 0 { projectID := getPrj(cmd) - pipelines.List(projectID) + pipelines.List(projectID, listOptions(cmd)) } else { id := args[0] pipelines.Describe(id, GetPplFollow) @@ -417,7 +422,7 @@ var GetWfCmd = &cobra.Command{ projectID := getPrj(cmd) if len(args) == 0 { - workflows.List(projectID) + workflows.List(projectID, listOptions(cmd)) } else { wfID := args[0] workflows.Describe(projectID, wfID) @@ -504,6 +509,16 @@ func getPrj(cmd *cobra.Command) string { return projectID } +func listOptions(cmd *cobra.Command) client.ListOptions { + age, err := cmd.Flags().GetDuration("age") + utils.Check(err) + + return client.ListOptions{ + CreatedBefore: time.Now().Unix(), + CreatedAfter: time.Now().Add(-1 * age).Unix(), + } +} + func init() { RootCmd.AddCommand(getCmd) @@ -533,6 +548,8 @@ func init() { "project name; if not specified will be inferred from git origin") GetPplCmd.Flags().StringP("project-id", "i", "", "project id; if not specified will be inferred from git origin") + GetPplCmd.Flags().DurationP("age", "", DefaultListingAge, + "list only pipelines created in the given duration; it accepts a Go duration. e.g. 24h, 30m, 60s") getCmd.AddCommand(GetPplCmd) getCmd.AddCommand(GetWfCmd) @@ -540,6 +557,8 @@ func init() { "project name; if not specified will be inferred from git origin") GetWfCmd.Flags().StringP("project-id", "i", "", "project id; if not specified will be inferred from git origin") + GetWfCmd.Flags().DurationP("age", "", DefaultListingAge, + "list only workflows created in the given duration; it accepts a Go duration. e.g. 24h, 30m, 60s") getCmd.AddCommand(GetDTCmd) GetDTCmd.Flags().StringP("project-name", "p", "", diff --git a/cmd/get_test.go b/cmd/get_test.go index 5f3cdf7..8796231 100644 --- a/cmd/get_test.go +++ b/cmd/get_test.go @@ -5,6 +5,7 @@ import ( "net/http" "net/url" "testing" + "time" httpmock "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" @@ -360,6 +361,112 @@ func Test__GetAgent__Response200(t *testing.T) { } } +func Test__GetPipelines__Response200(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + received := false + + httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/projects/foo", + func(req *http.Request) (*http.Response, error) { + received = true + + p := `{ + "metadata": { + "id": "758cb945-7495-4e40-a9a1-4b3991c6a8fe" + } + }` + + return httpmock.NewStringResponse(200, p), nil + }, + ) + + httpmock.RegisterResponder("GET", `=~^https:\/\/org\.semaphoretext\.xyz\/api\/v1alpha\/pipelines\?created_after=\d+&created_before=\d+&project_id=758cb945-7495-4e40-a9a1-4b3991c6a8fe`, + func(req *http.Request) (*http.Response, error) { + received = true + + p := `[{ + "pipeline": { + "ppl_id": "494b76aa-f3f0-4ecf-b5ef-c389591a01be", + "name": "snapshot test", + "state": "done", + "result": "passed", + "result_reason": "test", + "error_description": "" + } + }]` + + return httpmock.NewStringResponse(200, p), nil + }, + ) + + RootCmd.SetArgs([]string{"get", "pipelines", "--project-name", "foo"}) + RootCmd.Execute() + + if received == false { + t.Error("Expected the API to receive GET /pipelines") + } +} + +func Test__GetPipelines__WithCreationTimestampFilters(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + received := false + + httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/projects/foo", + func(req *http.Request) (*http.Response, error) { + received = true + + p := `{ + "metadata": { + "id": "758cb945-7495-4e40-a9a1-4b3991c6a8fe" + } + }` + + return httpmock.NewStringResponse(200, p), nil + }, + ) + + age := 720 * time.Hour + createdBefore := fmt.Sprintf("%d", time.Now().Unix()) + createdAfter := fmt.Sprintf("%d", time.Now().Add(-1*age).Unix()) + url := fmt.Sprintf("https://org.semaphoretext.xyz/api/v1alpha/pipelines?created_after=%s&created_before=%s&project_id=758cb945-7495-4e40-a9a1-4b3991c6a8fe", createdAfter, createdBefore) + httpmock.RegisterResponder("GET", url, + func(req *http.Request) (*http.Response, error) { + received = true + + p := `[{ + "pipeline": { + "ppl_id": "494b76aa-f3f0-4ecf-b5ef-c389591a01be", + "name": "snapshot test", + "state": "done", + "result": "passed", + "result_reason": "test", + "error_description": "" + } + }]` + + return httpmock.NewStringResponse(200, p), nil + }, + ) + + RootCmd.SetArgs([]string{ + "get", + "pipelines", + "--project-name", + "foo", + "--age", + age.String(), + }) + + RootCmd.Execute() + + if received == false { + t.Error("Expected the API to receive GET /pipelines") + } +} + func Test__GetPipeline__Response200(t *testing.T) { httpmock.Activate() defer httpmock.DeactivateAndReset() @@ -537,7 +644,7 @@ func Test__GetWorkflows__Response200(t *testing.T) { }, ) - httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/plumber-workflows?project_id=758cb945-7495-4e40-a9a1-4b3991c6a8fe", + httpmock.RegisterResponder("GET", `=~^https:\/\/org\.semaphoretext\.xyz\/api\/v1alpha\/plumber-workflows\?created_after=\d+&created_before=\d+&project_id=758cb945-7495-4e40-a9a1-4b3991c6a8fe`, func(req *http.Request) (*http.Response, error) { received = true @@ -560,6 +667,65 @@ func Test__GetWorkflows__Response200(t *testing.T) { RootCmd.Execute() if received == false { - t.Error("Expected the API to receive GET secrets/aaaaaaa") + t.Error("Expected the API to receive GET /plumber-workflows") + } +} + +func Test__GetWorkflows__WithCreationTimestampFilters(t *testing.T) { + httpmock.Activate() + defer httpmock.DeactivateAndReset() + + received := false + + httpmock.RegisterResponder("GET", "https://org.semaphoretext.xyz/api/v1alpha/projects/foo", + func(req *http.Request) (*http.Response, error) { + received = true + + p := `{ + "metadata": { + "id": "758cb945-7495-4e40-a9a1-4b3991c6a8fe" + } + }` + + return httpmock.NewStringResponse(200, p), nil + }, + ) + + age := 720 * time.Hour + createdBefore := fmt.Sprintf("%d", time.Now().Unix()) + createdAfter := fmt.Sprintf("%d", time.Now().Add(-1*age).Unix()) + url := fmt.Sprintf("https://org.semaphoretext.xyz/api/v1alpha/plumber-workflows?created_after=%s&created_before=%s&project_id=758cb945-7495-4e40-a9a1-4b3991c6a8fe", createdAfter, createdBefore) + httpmock.RegisterResponder("GET", url, + func(req *http.Request) (*http.Response, error) { + received = true + + p := `[{ + "wf_id": "b129e277-4aa5-4308-8e31-ec825815e335", + "requester_id": "92f81b82-3584-4852-ab28-4866624bed1e", + "project_id": "758cb945-7495-4e40-a9a1-4b3991c6a8fe", + "initial_ppl_id": "92f81b82-3584-4852-ab28-4866624bed1e", + "created_at": { + "seconds": 1533833523, + "nanos": 537460000 + } + }]` + + return httpmock.NewStringResponse(200, p), nil + }, + ) + + RootCmd.SetArgs([]string{ + "get", + "workflows", + "--project-name", + "foo", + "--age", + age.String(), + }) + + RootCmd.Execute() + + if received == false { + t.Error("Expected the API to receive GET /plumber-workflows") } } diff --git a/cmd/pipelines/get.go b/cmd/pipelines/get.go index 24fc6c1..e610caf 100644 --- a/cmd/pipelines/get.go +++ b/cmd/pipelines/get.go @@ -37,10 +37,10 @@ func describe(c client.PipelinesApiV1AlphaApi, id string) ([]byte, bool) { return pplY, pplJ.IsDone() } -func List(projectID string) { +func List(projectID string, options client.ListOptions) { fmt.Printf("%s\n", projectID) c := client.NewPipelinesV1AlphaApi() - body, err := c.ListPpl(projectID) + body, err := c.ListPplWithOptions(projectID, options) utils.Check(err) prettyPrintPipelineList(body) diff --git a/cmd/workflows/get.go b/cmd/workflows/get.go index 8cf6c01..2a4ed0d 100644 --- a/cmd/workflows/get.go +++ b/cmd/workflows/get.go @@ -11,9 +11,9 @@ import ( "github.com/semaphoreci/cli/cmd/utils" ) -func List(projectID string) { +func List(projectID string, options client.ListOptions) { wfClient := client.NewWorkflowV1AlphaApi() - workflows, err := wfClient.ListWorkflows(projectID) + workflows, err := wfClient.ListWorkflowsWithOptions(projectID, options) utils.Check(err) prettyPrint(workflows)