mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add merge_strategy support for coder_env resources (#23107)
## Description Implements the server-side merge logic for the `merge_strategy` attribute added to `coder_env` in [terraform-provider-coder v2.15.0](https://github.com/coder/terraform-provider-coder/pull/489). This allows template authors to control how duplicate environment variable names are combined across multiple `coder_env` resources. Relates to https://github.com/coder/coder/issues/21885 ## Supported strategies | Strategy | Behavior | |----------|----------| | `replace` (default) | Last value wins — backward compatible | | `append` | Joins values with `:` separator (e.g. PATH additions) | | `prepend` | Prepends value with `:` separator | | `error` | Fails the build if the variable is already defined | ## Example ```hcl resource "coder_env" "path_tools" { agent_id = coder_agent.dev.id name = "PATH" value = "/home/coder/tools/bin" merge_strategy = "append" } ``` ## Changes - **Proto**: Added `merge_strategy` field to `Env` message in `provisioner.proto` - **State reader**: Updated `agentEnvAttributes` struct and proto construction in `resources.go` - **Merge logic**: Added `mergeExtraEnvs()` function in `provisionerdserver.go` with strategy-aware merging for both agent envs and devcontainer subagent envs - **Tests**: 15 unit tests covering all strategies, edge cases (empty values, mixed strategies, multiple appends) - **Dependency**: Bumped `terraform-provider-coder` v2.14.0 → v2.15.0 - **Fixtures**: Updated `duplicate-env-keys` test fixtures and golden files ## Ordering When multiple resources `append` or `prepend` to the same key, they are processed in alphabetical order by Terraform resource address (per the determinism fix in #22706).
This commit is contained in:
@@ -2834,12 +2834,11 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
||||
}
|
||||
|
||||
env := make(map[string]string)
|
||||
// For now, we only support adding extra envs, not overriding
|
||||
// existing ones or performing other manipulations. In future
|
||||
// we may write these to a separate table so we can perform
|
||||
// conditional logic on the agent.
|
||||
for _, e := range prAgent.ExtraEnvs {
|
||||
env[e.Name] = e.Value
|
||||
// Apply extra envs with merge strategy support.
|
||||
// When multiple coder_env resources define the same name,
|
||||
// the merge_strategy controls how values are combined.
|
||||
if err := MergeExtraEnvs(env, prAgent.ExtraEnvs); err != nil {
|
||||
return err
|
||||
}
|
||||
// Allow the agent defined envs to override extra envs.
|
||||
for k, v := range prAgent.Env {
|
||||
@@ -3435,14 +3434,54 @@ func insertDevcontainerSubagent(
|
||||
return subAgentID, nil
|
||||
}
|
||||
|
||||
// MergeExtraEnvs applies extra environment variables to the given map,
|
||||
// respecting the merge_strategy field on each env. When merge_strategy
|
||||
// is empty or "replace", the value overwrites any existing entry.
|
||||
// "append" and "prepend" join values with a ":" separator (PATH-style).
|
||||
// "error" causes a failure if the key already exists.
|
||||
func MergeExtraEnvs(env map[string]string, extraEnvs []*sdkproto.Env) error {
|
||||
for _, e := range extraEnvs {
|
||||
strategy := e.GetMergeStrategy()
|
||||
if strategy == "" {
|
||||
strategy = "replace"
|
||||
}
|
||||
existing, exists := env[e.GetName()]
|
||||
switch strategy {
|
||||
case "error":
|
||||
if exists {
|
||||
return xerrors.Errorf(
|
||||
"duplicate env var %q: merge_strategy is %q but variable is already defined",
|
||||
e.GetName(), strategy,
|
||||
)
|
||||
}
|
||||
env[e.GetName()] = e.GetValue()
|
||||
case "append":
|
||||
if exists && existing != "" {
|
||||
env[e.GetName()] = existing + ":" + e.GetValue()
|
||||
} else {
|
||||
env[e.GetName()] = e.GetValue()
|
||||
}
|
||||
case "prepend":
|
||||
if exists && existing != "" {
|
||||
env[e.GetName()] = e.GetValue() + ":" + existing
|
||||
} else {
|
||||
env[e.GetName()] = e.GetValue()
|
||||
}
|
||||
default: // "replace"
|
||||
env[e.GetName()] = e.GetValue()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func encodeSubagentEnvs(envs []*sdkproto.Env) (pqtype.NullRawMessage, error) {
|
||||
if len(envs) == 0 {
|
||||
return pqtype.NullRawMessage{}, nil
|
||||
}
|
||||
|
||||
subAgentEnvs := make(map[string]string, len(envs))
|
||||
for _, env := range envs {
|
||||
subAgentEnvs[env.GetName()] = env.GetValue()
|
||||
if err := MergeExtraEnvs(subAgentEnvs, envs); err != nil {
|
||||
return pqtype.NullRawMessage{}, err
|
||||
}
|
||||
|
||||
data, err := json.Marshal(subAgentEnvs)
|
||||
|
||||
Reference in New Issue
Block a user