diff --git a/agent/agent.go b/agent/agent.go index a69fef1f5..9a63fcf4c 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -157,7 +157,9 @@ func (a *Agent) prep(w *queue.Work) (*yaml.Config, error) { transform.ImageTag(conf) transform.ImageName(conf) transform.ImageNamespace(conf, a.Namespace) - transform.ImageEscalate(conf, a.Escalate) + if err := transform.ImageEscalate(conf, a.Escalate); err != nil { + return nil, err + } transform.PluginParams(conf) if a.Local != "" { 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/exec.go b/drone/exec.go index 86f7c4cea..2d2172e59 100644 --- a/drone/exec.go +++ b/drone/exec.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "io/ioutil" "log" "os" @@ -13,6 +14,7 @@ import ( "github.com/drone/drone/build/docker" "github.com/drone/drone/model" "github.com/drone/drone/queue" + "github.com/drone/drone/yaml" "github.com/codegangsta/cli" "github.com/joho/godotenv" @@ -338,7 +340,7 @@ func exec(c *cli.Context) error { Pull: c.Bool("pull"), } - payload := queue.Work{ + payload := &queue.Work{ Yaml: string(file), Verified: c.BoolT("yaml.verified"), Signed: c.BoolT("yaml.signed"), @@ -382,12 +384,45 @@ func exec(c *cli.Context) error { Status: c.String("prev.build.status"), Commit: c.String("prev.commit.sha"), }, - Job: &model.Job{ - Environment: getMatrix(c), - }, } - return a.Run(&payload, cancelc) + if len(c.StringSlice("matrix")) > 0 { + p := *payload + p.Job = &model.Job{ + Environment: getMatrix(c), + } + return a.Run(&p, cancelc) + } + + axes, err := yaml.ParseMatrix(file) + if err != nil { + return err + } + + if len(axes) == 0 { + axes = append(axes, yaml.Axis{}) + } + + var jobs []*model.Job + count := 0 + for _, axis := range axes { + jobs = append(jobs, &model.Job{ + Number: count, + Environment: axis, + }) + count++ + } + + for _, job := range jobs { + fmt.Printf("Running Matrix job #%d\n", job.Number) + p := *payload + p.Job = job + if err := a.Run(&p, cancelc); err != nil { + return err + } + } + + return nil } // helper function to retrieve matrix variables. 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