diff --git a/cli/exec/exec.go b/cli/exec/exec.go index 7ce62445d..061d8d212 100644 --- a/cli/exec/exec.go +++ b/cli/exec/exec.go @@ -110,6 +110,11 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error for _, env := range c.StringSlice("env") { envs := strings.SplitN(env, "=", 2) droneEnv[envs[0]] = envs[1] + if _, exists := environ[envs[0]]; exists { + // don't override existing values + continue + } + environ[envs[0]] = envs[1] } tmpl, err := envsubst.ParseFile(file) diff --git a/docs/docs/20-usage/50-environment.md b/docs/docs/20-usage/50-environment.md index fc7e1a5e6..d3bf49c10 100644 --- a/docs/docs/20-usage/50-environment.md +++ b/docs/docs/20-usage/50-environment.md @@ -125,9 +125,9 @@ This is the reference list of all environment variables available to your pipeli ## Global environment variables -If you want specific environment variables to be available in all of your builds use the `WOODPECKER_ENVIRONMENT` setting on the Woodpecker server. +If you want specific environment variables to be available in all of your builds use the `WOODPECKER_ENVIRONMENT` setting on the Woodpecker server. Note that these can't overwrite any existing, built-in variables. -```.diff +```diff services: woodpecker-server: [...] @@ -136,6 +136,19 @@ services: + - WOODPECKER_ENVIRONMENT=first_var:value1,second_var:value2 ``` +These can be used, for example, to manage the image tag used by multiple projects. + +```diff +pipeline: + build: +- image: golang:1.18 ++ image: golang:${GOLANG_VERSION} + commands: + - [...] + environment: + - [...] ++ - WOODPECKER_ENVIRONMENT=GOLANG_VERSION:1.18 + ## String Substitution Woodpecker provides the ability to substitute environment variables at runtime. This gives us the ability to use dynamic build or commit details in our pipeline configuration. diff --git a/server/shared/procBuilder.go b/server/shared/procBuilder.go index 1eb5f2f64..c3fa339e1 100644 --- a/server/shared/procBuilder.go +++ b/server/shared/procBuilder.go @@ -89,6 +89,15 @@ func (b *ProcBuilder) Build() ([]*BuildItem, error) { metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, proc, b.Link) environ := b.environmentVariables(metadata, axis) + // add global environment variables for substituting + for k, v := range b.Envs { + if _, exists := environ[k]; exists { + // don't override existing values + continue + } + environ[k] = v + } + // substitute vars substituted, err := b.envsubst(string(y.Data), environ) if err != nil { diff --git a/server/shared/procBuilder_test.go b/server/shared/procBuilder_test.go index c1a72dadd..77bb891c5 100644 --- a/server/shared/procBuilder_test.go +++ b/server/shared/procBuilder_test.go @@ -24,6 +24,74 @@ import ( // TODO(974) move to pipeline/* +func TestGlobalEnvsubst(t *testing.T) { + t.Parallel() + + b := ProcBuilder{ + Envs: map[string]string{ + "KEY_K": "VALUE_V", + "IMAGE": "scratch", + }, + Repo: &model.Repo{}, + Curr: &model.Build{ + Message: "aaa", + }, + Last: &model.Build{}, + Netrc: &model.Netrc{}, + Secs: []*model.Secret{}, + Regs: []*model.Registry{}, + Link: "", + Yamls: []*remote.FileMeta{ + {Data: []byte(` +pipeline: + build: + image: ${IMAGE} + yyy: ${CI_COMMIT_MESSAGE} +`)}, + }, + } + + if buildItems, err := b.Build(); err != nil { + t.Fatal(err) + } else { + fmt.Println(buildItems) + } +} + +func TestMissingGlobalEnvsubst(t *testing.T) { + t.Parallel() + + b := ProcBuilder{ + Envs: map[string]string{ + "KEY_K": "VALUE_V", + "NO_IMAGE": "scratch", + }, + Repo: &model.Repo{}, + Curr: &model.Build{ + Message: "aaa", + }, + Last: &model.Build{}, + Netrc: &model.Netrc{}, + Secs: []*model.Secret{}, + Regs: []*model.Registry{}, + Link: "", + Yamls: []*remote.FileMeta{ + {Data: []byte(` +pipeline: + build: + image: ${IMAGE} + yyy: ${CI_COMMIT_MESSAGE} +`)}, + }, + } + + if _, err := b.Build(); err != nil { + fmt.Println("test rightfully failed") + } else { + t.Fatal("test erroneously succeeded") + } +} + func TestMultilineEnvsubst(t *testing.T) { t.Parallel()