diff --git a/client/client.go b/client/client.go index c1f4834bd..c9d141677 100644 --- a/client/client.go +++ b/client/client.go @@ -76,21 +76,21 @@ type Client interface { BuildQueue() ([]*model.Feed, error) // BuildStart re-starts a stopped build. - BuildStart(string, string, int) (*model.Build, error) + BuildStart(string, string, int, map[string]string) (*model.Build, error) // BuildStop stops the specified running job for given build. BuildStop(string, string, int, int) error // BuildFork re-starts a stopped build with a new build number, preserving // the prior history. - BuildFork(string, string, int) (*model.Build, error) + BuildFork(string, string, int, map[string]string) (*model.Build, error) // BuildLogs returns the build logs for the specified job. BuildLogs(string, string, int, int) (io.ReadCloser, error) // Deploy triggers a deployment for an existing build using the specified // target environment. - Deploy(string, string, int, string) (*model.Build, error) + Deploy(string, string, int, string, map[string]string) (*model.Build, error) // AgentList returns a list of build agents. AgentList() ([]*model.Agent, error) diff --git a/client/client_impl.go b/client/client_impl.go index b421b2a44..2c7357ab7 100644 --- a/client/client_impl.go +++ b/client/client_impl.go @@ -217,9 +217,10 @@ func (c *client) BuildQueue() ([]*model.Feed, error) { } // BuildStart re-starts a stopped build. -func (c *client) BuildStart(owner, name string, num int) (*model.Build, error) { +func (c *client) BuildStart(owner, name string, num int, params map[string]string) (*model.Build, error) { out := new(model.Build) - uri := fmt.Sprintf(pathBuild, c.base, owner, name, num) + val := parseToQueryParams(params) + uri := fmt.Sprintf(pathBuild+"?"+val.Encode(), c.base, owner, name, num) err := c.post(uri, nil, out) return out, err } @@ -233,9 +234,11 @@ func (c *client) BuildStop(owner, name string, num, job int) error { // BuildFork re-starts a stopped build with a new build number, // preserving the prior history. -func (c *client) BuildFork(owner, name string, num int) (*model.Build, error) { +func (c *client) BuildFork(owner, name string, num int, params map[string]string) (*model.Build, error) { out := new(model.Build) - uri := fmt.Sprintf(pathBuild+"?fork=true", c.base, owner, name, num) + val := parseToQueryParams(params) + val.Set("fork", "true") + uri := fmt.Sprintf(pathBuild+"?"+val.Encode(), c.base, owner, name, num) err := c.post(uri, nil, out) return out, err } @@ -248,9 +251,9 @@ func (c *client) BuildLogs(owner, name string, num, job int) (io.ReadCloser, err // Deploy triggers a deployment for an existing build using the // specified target environment. -func (c *client) Deploy(owner, name string, num int, env string) (*model.Build, error) { +func (c *client) Deploy(owner, name string, num int, env string, params map[string]string) (*model.Build, error) { out := new(model.Build) - val := url.Values{} + val := parseToQueryParams(params) val.Set("fork", "true") val.Set("event", "deployment") val.Set("deploy_to", env) @@ -523,3 +526,12 @@ func (c *client) createRequest(rawurl, method string, in interface{}) (*http.Req } return req, nil } + +// parseToQueryParams parses a map of strings and returns url.Values +func parseToQueryParams(p map[string]string) url.Values { + values := url.Values{} + for k, v := range p { + values.Add(k, v) + } + return values +} diff --git a/drone/build_start.go b/drone/build_start.go index 00d18aa66..8aaf63998 100644 --- a/drone/build_start.go +++ b/drone/build_start.go @@ -22,6 +22,10 @@ var buildStartCmd = cli.Command{ Name: "fork", Usage: "fork the build", }, + cli.StringSliceFlag{ + Name: "param, p", + Usage: "custom parameters to be injected into the job environment. Format: KEY=value", + }, }, } @@ -41,11 +45,13 @@ func buildStart(c *cli.Context) (err error) { return err } + params := parseKVPairs(c.StringSlice("param")) + var build *model.Build if c.Bool("fork") { - build, err = client.BuildFork(owner, name, number) + build, err = client.BuildFork(owner, name, number, params) } else { - build, err = client.BuildStart(owner, name, number) + build, err = client.BuildStart(owner, name, number, params) } if err != nil { return err diff --git a/drone/deploy.go b/drone/deploy.go index 63590af01..ec0355ba5 100644 --- a/drone/deploy.go +++ b/drone/deploy.go @@ -25,6 +25,10 @@ var deployCmd = cli.Command{ Usage: "format output", Value: tmplDeployInfo, }, + cli.StringSliceFlag{ + Name: "param, p", + Usage: "custom parameters to be injected into the job environment. Format: KEY=value", + }, }, } @@ -56,7 +60,9 @@ func deploy(c *cli.Context) error { return fmt.Errorf("Please specify the target environment (ie production)") } - deploy, err := client.Deploy(owner, name, number, env) + params := parseKVPairs(c.StringSlice("param")) + + deploy, err := client.Deploy(owner, name, number, env, params) if err != nil { return err } diff --git a/drone/util.go b/drone/util.go index 69fbc3a3f..bdc81f0f6 100644 --- a/drone/util.go +++ b/drone/util.go @@ -61,3 +61,15 @@ func stringInSlice(a string, list []string) bool { return false } + +func parseKVPairs(p []string) map[string]string { + params := map[string]string{} + for _, i := range p { + parts := strings.Split(i, "=") + if len(parts) != 2 { + continue + } + params[parts[0]] = parts[1] + } + return params +} diff --git a/drone/util_test.go b/drone/util_test.go new file mode 100644 index 000000000..b7856f28b --- /dev/null +++ b/drone/util_test.go @@ -0,0 +1,17 @@ +package main + +import "testing" + +func Test_parseKVPairs(t *testing.T) { + s := []string{"FOO=bar", "BAR=", "INVALID"} + p := parseKVPairs(s) + if p["FOO"] != "bar" { + t.Errorf("Wanted %q, got %q.", "bar", p["FOO"]) + } + if _, exists := p["BAR"]; !exists { + t.Error("Missing a key with no value. Keys with empty values are also valid.") + } + if _, exists := p["INVALID"]; exists { + t.Error("Keys without an equal sign suffix are invalid.") + } +} diff --git a/server/build.go b/server/build.go index 285717822..d65cb1ae3 100644 --- a/server/build.go +++ b/server/build.go @@ -248,6 +248,18 @@ func PostBuild(c *gin.Context) { build.Deploy = c.DefaultQuery("deploy_to", build.Deploy) } + // Read query string parameters into buildParams, exclude reserved params + var buildParams = map[string]string{} + for key, val := range c.Request.URL.Query() { + switch key { + case "fork", "event", "deply_to": + default: + // We only accept string literals, because build parameters will be + // injected as environment variables + buildParams[key] = val[0] + } + } + // todo move this to database tier // and wrap inside a transaction build.Status = model.StatusPending @@ -255,6 +267,9 @@ func PostBuild(c *gin.Context) { build.Finished = 0 build.Enqueued = time.Now().UTC().Unix() for _, job := range jobs { + for k, v := range buildParams { + job.Environment[k] = v + } job.Error = "" job.Status = model.StatusPending job.Started = 0