diff --git a/cli/deploy/deploy.go b/cli/deploy/deploy.go index 11e478b6b..7b20199d0 100644 --- a/cli/deploy/deploy.go +++ b/cli/deploy/deploy.go @@ -112,9 +112,12 @@ func deploy(c *cli.Context) error { return fmt.Errorf("please specify the target environment (i.e. production)") } - params := internal.ParseKeyPair(c.StringSlice("param")) + opt := woodpecker.DeployOptions{ + DeployTo: env, + Params: internal.ParseKeyPair(c.StringSlice("param")), + } - deploy, err := client.Deploy(repoID, number, env, params) + deploy, err := client.Deploy(repoID, number, opt) if err != nil { return err } diff --git a/cli/pipeline/start.go b/cli/pipeline/start.go index c3d870982..40d992c48 100644 --- a/cli/pipeline/start.go +++ b/cli/pipeline/start.go @@ -22,6 +22,7 @@ import ( "github.com/urfave/cli/v2" "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" ) var pipelineStartCmd = &cli.Command{ @@ -68,9 +69,11 @@ func pipelineStart(c *cli.Context) (err error) { } } - params := internal.ParseKeyPair(c.StringSlice("param")) + opt := woodpecker.PipelineStartOptions{ + Params: internal.ParseKeyPair(c.StringSlice("param")), + } - pipeline, err := client.PipelineStart(repoID, number, params) + pipeline, err := client.PipelineStart(repoID, number, opt) if err != nil { return err } diff --git a/woodpecker-go/woodpecker/interface.go b/woodpecker-go/woodpecker/interface.go index 69aaeb772..f2893b3fe 100644 --- a/woodpecker-go/woodpecker/interface.go +++ b/woodpecker-go/woodpecker/interface.go @@ -90,7 +90,7 @@ type Client interface { PipelineCreate(repoID int64, opts *PipelineOptions) (*Pipeline, error) // PipelineStart re-starts a stopped pipeline. - PipelineStart(repoID, num int64, params map[string]string) (*Pipeline, error) + PipelineStart(repoID, num int64, opt PipelineStartOptions) (*Pipeline, error) // PipelineStop stops the given pipeline. PipelineStop(repoID, pipeline int64) error @@ -109,7 +109,7 @@ type Client interface { // Deploy triggers a deployment for an existing pipeline using the specified // target environment. - Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) + Deploy(repoID, pipeline int64, opt DeployOptions) (*Pipeline, error) // LogsPurge purges the pipeline logs for the specified pipeline. LogsPurge(repoID, pipeline int64) error diff --git a/woodpecker-go/woodpecker/repo.go b/woodpecker-go/woodpecker/repo.go index 48b6edefd..127723ea3 100644 --- a/woodpecker-go/woodpecker/repo.go +++ b/woodpecker-go/woodpecker/repo.go @@ -34,6 +34,15 @@ type PipelineListOptions struct { After time.Time } +type DeployOptions struct { + DeployTo string // override the target deploy value + Params map[string]string // custom parameters to be injected into the step environment. Format: KEY=value +} + +type PipelineStartOptions struct { + Params map[string]string // custom parameters to be injected into the step environment. Format: KEY=value +} + // QueryEncode returns the URL query parameters for the PipelineListOptions. func (opt *PipelineListOptions) QueryEncode() string { query := opt.getURLQuery() @@ -46,6 +55,22 @@ func (opt *PipelineListOptions) QueryEncode() string { return query.Encode() } +// QueryEncode returns the URL query parameters for the DeployOptions. +func (opt *DeployOptions) QueryEncode() string { + query := mapValues(opt.Params) + if opt.DeployTo != "" { + query.Add("deploy_to", opt.DeployTo) + } + query.Add("event", EventDeploy) + return query.Encode() +} + +// QueryEncode returns the URL query parameters for the PipelineStartOptions. +func (opt *PipelineStartOptions) QueryEncode() string { + query := mapValues(opt.Params) + return query.Encode() +} + // Repo returns a repository by id. func (c *client) Repo(repoID int64) (*Repo, error) { out := new(Repo) @@ -239,10 +264,8 @@ func (c *client) PipelineLast(repoID int64, branch string) (*Pipeline, error) { // the specified repository. func (c *client) PipelineList(repoID int64, opt PipelineListOptions) ([]*Pipeline, error) { var out []*Pipeline - uri, _ := url.Parse(fmt.Sprintf(pathPipelines, c.addr, repoID)) uri.RawQuery = opt.QueryEncode() - err := c.get(uri.String(), &out) return out, err } @@ -256,11 +279,11 @@ func (c *client) PipelineCreate(repoID int64, options *PipelineOptions) (*Pipeli } // PipelineStart re-starts a stopped pipeline. -func (c *client) PipelineStart(repoID, pipeline int64, params map[string]string) (*Pipeline, error) { +func (c *client) PipelineStart(repoID, pipeline int64, opt PipelineStartOptions) (*Pipeline, error) { out := new(Pipeline) - val := mapValues(params) - uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline) - err := c.post(uri+"?"+val.Encode(), nil, out) + uri, _ := url.Parse(fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)) + uri.RawQuery = opt.QueryEncode() + err := c.post(uri.String(), nil, out) return out, err } @@ -303,13 +326,11 @@ func (c *client) LogsPurge(repoID, pipeline int64) error { // Deploy triggers a deployment for an existing pipeline using the // specified target environment. -func (c *client) Deploy(repoID, pipeline int64, env string, params map[string]string) (*Pipeline, error) { +func (c *client) Deploy(repoID, pipeline int64, opt DeployOptions) (*Pipeline, error) { out := new(Pipeline) - val := mapValues(params) - val.Set("event", EventDeploy) - val.Set("deploy_to", env) - uri := fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline) - err := c.post(uri+"?"+val.Encode(), nil, out) + uri, _ := url.Parse(fmt.Sprintf(pathPipeline, c.addr, repoID, pipeline)) + uri.RawQuery = opt.QueryEncode() + err := c.post(uri.String(), nil, out) return out, err } diff --git a/woodpecker-go/woodpecker/repo_test.go b/woodpecker-go/woodpecker/repo_test.go index 0061a412b..d0df0f91e 100644 --- a/woodpecker-go/woodpecker/repo_test.go +++ b/woodpecker-go/woodpecker/repo_test.go @@ -13,7 +13,7 @@ import ( func TestPipelineList(t *testing.T) { tests := []struct { name string - fixtureHandler http.HandlerFunc + handler http.HandlerFunc opts PipelineListOptions wantErr bool expectedLength int @@ -21,7 +21,7 @@ func TestPipelineList(t *testing.T) { }{ { name: "success", - fixtureHandler: func(w http.ResponseWriter, r *http.Request) { + handler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) assert.Equal(t, "/api/repos/123/pipelines?after=2023-01-15T00%3A00%3A00Z&before=2023-01-16T00%3A00%3A00Z&page=2&perPage=10", r.URL.RequestURI()) @@ -42,7 +42,7 @@ func TestPipelineList(t *testing.T) { }, { name: "empty ListOptions", - fixtureHandler: func(w http.ResponseWriter, r *http.Request) { + handler: func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, http.MethodGet, r.Method) assert.Equal(t, "/api/repos/123/pipelines", r.URL.RequestURI()) @@ -55,8 +55,8 @@ func TestPipelineList(t *testing.T) { expectedIDs: []int64{1, 2}, }, { - name: "error", - fixtureHandler: func(w http.ResponseWriter, _ *http.Request) { + name: "server error", + handler: func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) }, opts: PipelineListOptions{}, @@ -66,7 +66,7 @@ func TestPipelineList(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - ts := httptest.NewServer(tt.fixtureHandler) + ts := httptest.NewServer(tt.handler) defer ts.Close() client := NewClient(ts.URL, http.DefaultClient) @@ -86,3 +86,157 @@ func TestPipelineList(t *testing.T) { }) } } + +func TestClientDeploy(t *testing.T) { + tests := []struct { + name string + handler http.HandlerFunc + repoID int64 + pipelineID int64 + opts DeployOptions + wantErr bool + expectedPipeline *Pipeline + }{ + { + name: "success", + handler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/api/repos/123/pipelines/456?event=deployment", r.URL.RequestURI()) + + w.WriteHeader(http.StatusOK) + _, err := fmt.Fprint(w, `{"id":789}`) + assert.NoError(t, err) + }, + repoID: 123, + pipelineID: 456, + opts: DeployOptions{}, + expectedPipeline: &Pipeline{ + ID: 789, + }, + }, + { + name: "error", + handler: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }, + repoID: 123, + pipelineID: 456, + opts: DeployOptions{}, + wantErr: true, + }, + { + name: "with options", + handler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/api/repos/123/pipelines/456?deploy_to=production&event=deployment", r.URL.RequestURI()) + + w.WriteHeader(http.StatusOK) + _, err := fmt.Fprint(w, `{"id":789}`) + assert.NoError(t, err) + }, + repoID: 123, + pipelineID: 456, + opts: DeployOptions{ + DeployTo: "production", + }, + expectedPipeline: &Pipeline{ + ID: 789, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := httptest.NewServer(tt.handler) + defer ts.Close() + + client := NewClient(ts.URL, http.DefaultClient) + + pipeline, err := client.Deploy(tt.repoID, tt.pipelineID, tt.opts) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.expectedPipeline, pipeline) + }) + } +} + +func TestClientPipelineStart(t *testing.T) { + tests := []struct { + name string + handler http.HandlerFunc + repoID int64 + pipelineID int64 + opts PipelineStartOptions + wantErr bool + expectedPipeline *Pipeline + }{ + { + name: "success", + handler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/api/repos/123/pipelines/456", r.URL.RequestURI()) + + w.WriteHeader(http.StatusOK) + _, err := fmt.Fprint(w, `{"id":789}`) + assert.NoError(t, err) + }, + repoID: 123, + pipelineID: 456, + opts: PipelineStartOptions{}, + expectedPipeline: &Pipeline{ + ID: 789, + }, + }, + { + name: "error", + handler: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }, + repoID: 123, + pipelineID: 456, + opts: PipelineStartOptions{}, + wantErr: true, + }, + { + name: "with options", + handler: func(w http.ResponseWriter, r *http.Request) { + assert.Equal(t, http.MethodPost, r.Method) + assert.Equal(t, "/api/repos/123/pipelines/456?foo=bar", r.URL.RequestURI()) + + w.WriteHeader(http.StatusOK) + _, err := fmt.Fprint(w, `{"id":789}`) + assert.NoError(t, err) + }, + repoID: 123, + pipelineID: 456, + opts: PipelineStartOptions{ + Params: map[string]string{"foo": "bar"}, + }, + expectedPipeline: &Pipeline{ + ID: 789, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ts := httptest.NewServer(tt.handler) + defer ts.Close() + + client := NewClient(ts.URL, http.DefaultClient) + + pipeline, err := client.PipelineStart(tt.repoID, tt.pipelineID, tt.opts) + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.expectedPipeline, pipeline) + }) + } +} diff --git a/woodpecker-go/woodpecker/user.go b/woodpecker-go/woodpecker/user.go index d35add653..448c28706 100644 --- a/woodpecker-go/woodpecker/user.go +++ b/woodpecker-go/woodpecker/user.go @@ -76,10 +76,8 @@ func (c *client) UserDel(login string) error { // the user has explicit access in the host system. func (c *client) RepoList(opt RepoListOptions) ([]*Repo, error) { var out []*Repo - uri, _ := url.Parse(fmt.Sprintf(pathRepos, c.addr)) uri.RawQuery = opt.QueryEncode() - err := c.get(uri.String(), &out) return out, err }