From 979b8e0e5f334dad365e01de4e9d448b8fc22a16 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Thu, 12 Mar 2026 12:08:24 +0000 Subject: [PATCH] feat: add merge_strategy attribute to coder_env resource Add an optional merge_strategy attribute that controls how environment variables are combined when multiple coder_env resources define the same name. Valid values: replace (default), append, prepend, error. - Default 'replace' preserves backward compatibility - ForceNew: true ensures strategy changes recreate the resource - Validated with StringInSlice matching existing codebase patterns - Added tests for default value, all valid values, and invalid rejection - Updated docs with merge_strategy description and PATH append example --- docs/resources/env.md | 1 + provider/env.go | 10 ++++++ provider/env_test.go | 75 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/docs/resources/env.md b/docs/resources/env.md index 3a5a7f3a..860c5170 100644 --- a/docs/resources/env.md +++ b/docs/resources/env.md @@ -44,6 +44,7 @@ resource "coder_env" "internal_api_url" { ### Optional +- `merge_strategy` (String) Controls how this environment variable is merged when multiple coder_env resources define the same name. `replace` (default): last value wins. `append`: appends to existing value with a colon `:` separator. `prepend`: prepends to existing value with a colon `:` separator. `error`: fail the build if another coder_env defines the same name. When multiple resources append or prepend to the same name, they are applied in alphabetical order by Terraform resource address. - `value` (String) The value of the environment variable. ### Read-Only diff --git a/provider/env.go b/provider/env.go index d45201ee..e00e7fa4 100644 --- a/provider/env.go +++ b/provider/env.go @@ -45,6 +45,16 @@ func envResource() *schema.Resource { ForceNew: true, Optional: true, }, + "merge_strategy": { + Type: schema.TypeString, + Description: "Controls how this environment variable is merged when multiple coder_env resources define the same name. `replace` (default): last value wins. `append`: appends to existing value with a colon `:` separator. `prepend`: prepends to existing value with a colon `:` separator. `error`: fail the build if another coder_env defines the same name. When multiple resources append or prepend to the same name, they are applied in alphabetical order by Terraform resource address.", + ForceNew: true, + Optional: true, + Default: "replace", + ValidateFunc: validation.StringInSlice([]string{ + "replace", "append", "prepend", "error", + }, false), + }, }, } } diff --git a/provider/env_test.go b/provider/env_test.go index a5892254..124e9f91 100644 --- a/provider/env_test.go +++ b/provider/env_test.go @@ -117,3 +117,78 @@ func TestEnvNoName(t *testing.T) { }}, }) } + +func TestEnvDefaultMergeStrategy(t *testing.T) { + t.Parallel() + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + resource "coder_env" "example" { + agent_id = "king" + name = "FOO" + value = "bar" + }`, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + env := state.Modules[0].Resources["coder_env.example"] + require.NotNil(t, env) + require.Equal(t, "replace", env.Primary.Attributes["merge_strategy"]) + return nil + }, + }}, + }) +} + +func TestEnvValidMergeStrategies(t *testing.T) { + t.Parallel() + for _, strategy := range []string{"replace", "append", "prepend", "error"} { + t.Run(strategy, func(t *testing.T) { + t.Parallel() + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + resource "coder_env" "example" { + agent_id = "king" + name = "FOO" + value = "bar" + merge_strategy = "` + strategy + `" + }`, + Check: func(state *terraform.State) error { + require.Len(t, state.Modules, 1) + require.Len(t, state.Modules[0].Resources, 1) + env := state.Modules[0].Resources["coder_env.example"] + require.NotNil(t, env) + require.Equal(t, strategy, env.Primary.Attributes["merge_strategy"]) + return nil + }, + }}, + }) + }) + } +} + +func TestEnvInvalidMergeStrategy(t *testing.T) { + t.Parallel() + resource.Test(t, resource.TestCase{ + ProviderFactories: coderFactory(), + IsUnitTest: true, + Steps: []resource.TestStep{{ + Config: ` + provider "coder" {} + resource "coder_env" "example" { + agent_id = "king" + name = "FOO" + value = "bar" + merge_strategy = "concat" + }`, + ExpectError: regexp.MustCompile(`expected merge_strategy to be one of`), + }}, + }) +}