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:
@@ -7,7 +7,7 @@
|
|||||||
"last_seen_at": "====[timestamp]=====",
|
"last_seen_at": "====[timestamp]=====",
|
||||||
"name": "test-daemon",
|
"name": "test-daemon",
|
||||||
"version": "v0.0.0-devel",
|
"version": "v0.0.0-devel",
|
||||||
"api_version": "1.15",
|
"api_version": "1.16",
|
||||||
"provisioners": [
|
"provisioners": [
|
||||||
"echo"
|
"echo"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
package provisionerdserver_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/coder/coder/v2/coderd/provisionerdserver"
|
||||||
|
sdkproto "github.com/coder/coder/v2/provisionersdk/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMergeExtraEnvs(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
initial map[string]string
|
||||||
|
envs []*sdkproto.Env
|
||||||
|
expected map[string]string
|
||||||
|
expectErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: nil,
|
||||||
|
expected: map[string]string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "default_replace",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "FOO", Value: "bar"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"FOO": "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "explicit_replace",
|
||||||
|
initial: map[string]string{"FOO": "old"},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "FOO", Value: "new", MergeStrategy: "replace"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"FOO": "new"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_strategy_defaults_to_replace",
|
||||||
|
initial: map[string]string{"FOO": "old"},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "FOO", Value: "new", MergeStrategy: ""},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"FOO": "new"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "append_to_existing",
|
||||||
|
initial: map[string]string{"PATH": "/usr/bin"},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/custom/bin", MergeStrategy: "append"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/usr/bin:/custom/bin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "append_no_existing",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/custom/bin", MergeStrategy: "append"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/custom/bin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "append_to_empty_value",
|
||||||
|
initial: map[string]string{"PATH": ""},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/custom/bin", MergeStrategy: "append"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/custom/bin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prepend_to_existing",
|
||||||
|
initial: map[string]string{"PATH": "/usr/bin"},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/custom/bin", MergeStrategy: "prepend"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/custom/bin:/usr/bin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "prepend_no_existing",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/custom/bin", MergeStrategy: "prepend"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/custom/bin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error_no_duplicate",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "FOO", Value: "bar", MergeStrategy: "error"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"FOO": "bar"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error_with_duplicate",
|
||||||
|
initial: map[string]string{"FOO": "existing"},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "FOO", Value: "new", MergeStrategy: "error"},
|
||||||
|
},
|
||||||
|
expectErr: "duplicate env var",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple_appends_same_key",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/a/bin", MergeStrategy: "append"},
|
||||||
|
{Name: "PATH", Value: "/b/bin", MergeStrategy: "append"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/a/bin:/b/bin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "multiple_prepends_same_key",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/a/bin", MergeStrategy: "prepend"},
|
||||||
|
{Name: "PATH", Value: "/b/bin", MergeStrategy: "prepend"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/b/bin:/a/bin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed_strategies",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/first", MergeStrategy: "append"},
|
||||||
|
{Name: "PATH", Value: "/override", MergeStrategy: "replace"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{"PATH": "/override"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mixed_keys",
|
||||||
|
initial: map[string]string{},
|
||||||
|
envs: []*sdkproto.Env{
|
||||||
|
{Name: "PATH", Value: "/a", MergeStrategy: "append"},
|
||||||
|
{Name: "HOME", Value: "/home/user"},
|
||||||
|
{Name: "PATH", Value: "/b", MergeStrategy: "append"},
|
||||||
|
},
|
||||||
|
expected: map[string]string{
|
||||||
|
"PATH": "/a:/b",
|
||||||
|
"HOME": "/home/user",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range tests {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
env := make(map[string]string)
|
||||||
|
for k, v := range tc.initial {
|
||||||
|
env[k] = v
|
||||||
|
}
|
||||||
|
err := provisionerdserver.MergeExtraEnvs(env, tc.envs)
|
||||||
|
if tc.expectErr != "" {
|
||||||
|
require.ErrorContains(t, err, tc.expectErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.expected, env)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2834,12 +2834,11 @@ func InsertWorkspaceResource(ctx context.Context, db database.Store, jobID uuid.
|
|||||||
}
|
}
|
||||||
|
|
||||||
env := make(map[string]string)
|
env := make(map[string]string)
|
||||||
// For now, we only support adding extra envs, not overriding
|
// Apply extra envs with merge strategy support.
|
||||||
// existing ones or performing other manipulations. In future
|
// When multiple coder_env resources define the same name,
|
||||||
// we may write these to a separate table so we can perform
|
// the merge_strategy controls how values are combined.
|
||||||
// conditional logic on the agent.
|
if err := MergeExtraEnvs(env, prAgent.ExtraEnvs); err != nil {
|
||||||
for _, e := range prAgent.ExtraEnvs {
|
return err
|
||||||
env[e.Name] = e.Value
|
|
||||||
}
|
}
|
||||||
// Allow the agent defined envs to override extra envs.
|
// Allow the agent defined envs to override extra envs.
|
||||||
for k, v := range prAgent.Env {
|
for k, v := range prAgent.Env {
|
||||||
@@ -3435,14 +3434,54 @@ func insertDevcontainerSubagent(
|
|||||||
return subAgentID, nil
|
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) {
|
func encodeSubagentEnvs(envs []*sdkproto.Env) (pqtype.NullRawMessage, error) {
|
||||||
if len(envs) == 0 {
|
if len(envs) == 0 {
|
||||||
return pqtype.NullRawMessage{}, nil
|
return pqtype.NullRawMessage{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
subAgentEnvs := make(map[string]string, len(envs))
|
subAgentEnvs := make(map[string]string, len(envs))
|
||||||
for _, env := range envs {
|
if err := MergeExtraEnvs(subAgentEnvs, envs); err != nil {
|
||||||
subAgentEnvs[env.GetName()] = env.GetValue()
|
return pqtype.NullRawMessage{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.Marshal(subAgentEnvs)
|
data, err := json.Marshal(subAgentEnvs)
|
||||||
|
|||||||
@@ -0,0 +1,119 @@
|
|||||||
|
# Environment variables
|
||||||
|
|
||||||
|
Use the
|
||||||
|
[`coder_env`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/env)
|
||||||
|
resource to inject environment variables into your workspace agents. This is
|
||||||
|
useful for configuring tools, setting paths, and passing configuration to
|
||||||
|
development environments.
|
||||||
|
|
||||||
|
## Basic usage
|
||||||
|
|
||||||
|
```tf
|
||||||
|
resource "coder_agent" "dev" {
|
||||||
|
os = "linux"
|
||||||
|
arch = "amd64"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "coder_env" "go_path" {
|
||||||
|
agent_id = coder_agent.dev.id
|
||||||
|
name = "GOPATH"
|
||||||
|
value = "/home/coder/go"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each `coder_env` resource sets a single environment variable on the specified
|
||||||
|
agent. You can define multiple `coder_env` resources targeting the same agent.
|
||||||
|
|
||||||
|
## Merge strategies
|
||||||
|
|
||||||
|
When multiple `coder_env` resources define the same variable name, use the
|
||||||
|
`merge_strategy` attribute to control how values are combined:
|
||||||
|
|
||||||
|
| Strategy | Behavior |
|
||||||
|
|-----------------------|-----------------------------------------------------|
|
||||||
|
| `replace` _(default)_ | Last value wins. Backward compatible. |
|
||||||
|
| `append` | Appends to the existing value with `:` separator. |
|
||||||
|
| `prepend` | Prepends to the existing value with `:` separator. |
|
||||||
|
| `error` | Fails the build if the variable is already defined. |
|
||||||
|
|
||||||
|
The `append` and `prepend` strategies use `:` as a separator, which matches
|
||||||
|
the convention for `PATH`-style variables on Unix systems.
|
||||||
|
|
||||||
|
### Example: Appending to PATH
|
||||||
|
|
||||||
|
Multiple `coder_env` resources can each add directories to `PATH`:
|
||||||
|
|
||||||
|
```tf
|
||||||
|
resource "coder_env" "path_tools" {
|
||||||
|
agent_id = coder_agent.dev.id
|
||||||
|
name = "PATH"
|
||||||
|
value = "/home/coder/tools/bin"
|
||||||
|
merge_strategy = "append"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "coder_env" "path_go" {
|
||||||
|
agent_id = coder_agent.dev.id
|
||||||
|
name = "PATH"
|
||||||
|
value = "/home/coder/go/bin"
|
||||||
|
merge_strategy = "append"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This produces `PATH` with the value
|
||||||
|
`/home/coder/tools/bin:/home/coder/go/bin`.
|
||||||
|
|
||||||
|
### Example: Preventing duplicates
|
||||||
|
|
||||||
|
Use `error` to catch accidental duplicate definitions:
|
||||||
|
|
||||||
|
```tf
|
||||||
|
resource "coder_env" "editor" {
|
||||||
|
agent_id = coder_agent.dev.id
|
||||||
|
name = "EDITOR"
|
||||||
|
value = "vim"
|
||||||
|
merge_strategy = "error"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If another `coder_env` resource also sets `EDITOR`, the build fails with
|
||||||
|
a clear error message.
|
||||||
|
|
||||||
|
## Ordering
|
||||||
|
|
||||||
|
When multiple `coder_env` resources append or prepend to the same variable,
|
||||||
|
they are processed in alphabetical order by their
|
||||||
|
[Terraform resource address](https://developer.hashicorp.com/terraform/cli/state/resource-addressing).
|
||||||
|
In the PATH example above, `coder_env.path_go` is processed before
|
||||||
|
`coder_env.path_tools` because `path_go` sorts before `path_tools`
|
||||||
|
alphabetically.
|
||||||
|
|
||||||
|
## Agent env override
|
||||||
|
|
||||||
|
The `env` block inside a `coder_agent` resource always takes final precedence
|
||||||
|
over any `coder_env` resources. If both define the same variable, the
|
||||||
|
`coder_agent` value wins regardless of `merge_strategy`. This override happens
|
||||||
|
after `coder_env` resources are merged, so `merge_strategy = "error"` does not
|
||||||
|
trigger when the conflict is with the agent's `env` block — only when two
|
||||||
|
`coder_env` resources define the same key:
|
||||||
|
|
||||||
|
```tf
|
||||||
|
resource "coder_agent" "dev" {
|
||||||
|
os = "linux"
|
||||||
|
arch = "amd64"
|
||||||
|
env = {
|
||||||
|
PATH = "/usr/local/bin:/usr/bin:/bin"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# This value is ignored because coder_agent.dev.env sets PATH directly.
|
||||||
|
resource "coder_env" "extra_path" {
|
||||||
|
agent_id = coder_agent.dev.id
|
||||||
|
name = "PATH"
|
||||||
|
value = "/home/coder/bin"
|
||||||
|
merge_strategy = "append"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
See the
|
||||||
|
[Coder Terraform provider documentation](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/env)
|
||||||
|
for the complete `coder_env` reference.
|
||||||
@@ -139,6 +139,17 @@ resource "coder_app" "zed" {
|
|||||||
Check out our [module registry](https://registry.coder.com/modules) for
|
Check out our [module registry](https://registry.coder.com/modules) for
|
||||||
additional Coder apps from the team and our OSS community.
|
additional Coder apps from the team and our OSS community.
|
||||||
|
|
||||||
|
## Environment variables
|
||||||
|
|
||||||
|
Use the
|
||||||
|
[`coder_env`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/env)
|
||||||
|
resource to inject environment variables into workspace agents. Multiple
|
||||||
|
resources can target the same variable using
|
||||||
|
[merge strategies](./environment-variables.md) like `append` and `prepend`,
|
||||||
|
which is useful for building up `PATH`-style variables across modules.
|
||||||
|
|
||||||
|
See [Environment variables](./environment-variables.md) for details.
|
||||||
|
|
||||||
## Running scripts on workspace lifecycle
|
## Running scripts on workspace lifecycle
|
||||||
|
|
||||||
The
|
The
|
||||||
|
|||||||
@@ -629,6 +629,11 @@
|
|||||||
"description": "Control resource persistence",
|
"description": "Control resource persistence",
|
||||||
"path": "./admin/templates/extending-templates/resource-persistence.md"
|
"path": "./admin/templates/extending-templates/resource-persistence.md"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"title": "Environment Variables",
|
||||||
|
"description": "Inject environment variables into workspaces using coder_env",
|
||||||
|
"path": "./admin/templates/extending-templates/environment-variables.md"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"title": "Terraform Variables",
|
"title": "Terraform Variables",
|
||||||
"description": "Use variables to manage template state",
|
"description": "Use variables to manage template state",
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ require (
|
|||||||
github.com/coder/quartz v0.3.0
|
github.com/coder/quartz v0.3.0
|
||||||
github.com/coder/retry v1.5.1
|
github.com/coder/retry v1.5.1
|
||||||
github.com/coder/serpent v0.14.0
|
github.com/coder/serpent v0.14.0
|
||||||
github.com/coder/terraform-provider-coder/v2 v2.14.0
|
github.com/coder/terraform-provider-coder/v2 v2.15.0
|
||||||
github.com/coder/websocket v1.8.14
|
github.com/coder/websocket v1.8.14
|
||||||
github.com/coder/wgtunnel v0.2.0
|
github.com/coder/wgtunnel v0.2.0
|
||||||
github.com/coreos/go-oidc/v3 v3.17.0
|
github.com/coreos/go-oidc/v3 v3.17.0
|
||||||
|
|||||||
@@ -351,8 +351,8 @@ github.com/coder/tailscale v1.1.1-0.20260313130012-33e050fd4bd9 h1:y9SeiKzMyyip1
|
|||||||
github.com/coder/tailscale v1.1.1-0.20260313130012-33e050fd4bd9/go.mod h1:q+R4UL4pPb0CpaSNVUTDsg0kZeL/OlqjRNO9XbJxU5g=
|
github.com/coder/tailscale v1.1.1-0.20260313130012-33e050fd4bd9/go.mod h1:q+R4UL4pPb0CpaSNVUTDsg0kZeL/OlqjRNO9XbJxU5g=
|
||||||
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0=
|
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e h1:JNLPDi2P73laR1oAclY6jWzAbucf70ASAvf5mh2cME0=
|
||||||
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
|
github.com/coder/terraform-config-inspect v0.0.0-20250107175719-6d06d90c630e/go.mod h1:Gz/z9Hbn+4KSp8A2FBtNszfLSdT2Tn/uAKGuVqqWmDI=
|
||||||
github.com/coder/terraform-provider-coder/v2 v2.14.0 h1:5lwDIyqyyX+j5BkZzgnnkDx+QM9Py4CosDh+XzixoVs=
|
github.com/coder/terraform-provider-coder/v2 v2.15.0 h1:sdKV3JvwlL7FNuSfaba0pm2WsPTBG7d0H6lmbzX+q4M=
|
||||||
github.com/coder/terraform-provider-coder/v2 v2.14.0/go.mod h1:++c+FmMAFj8+H8lxstoaGBmTM3YFSxnVRxSkkdAG+YA=
|
github.com/coder/terraform-provider-coder/v2 v2.15.0/go.mod h1:++c+FmMAFj8+H8lxstoaGBmTM3YFSxnVRxSkkdAG+YA=
|
||||||
github.com/coder/trivy v0.0.0-20250807211036-0bb0acd620a8 h1:VYB/6cIIKsVkwXOAWbqpj4Ux+WwF/XTnRyvHcwfHZ7A=
|
github.com/coder/trivy v0.0.0-20250807211036-0bb0acd620a8 h1:VYB/6cIIKsVkwXOAWbqpj4Ux+WwF/XTnRyvHcwfHZ7A=
|
||||||
github.com/coder/trivy v0.0.0-20250807211036-0bb0acd620a8/go.mod h1:O73tP+UvJlI2GQZD060Jt0sf+6alKcGAgORh6sgB0+M=
|
github.com/coder/trivy v0.0.0-20250807211036-0bb0acd620a8/go.mod h1:O73tP+UvJlI2GQZD060Jt0sf+6alKcGAgORh6sgB0+M=
|
||||||
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
|
|||||||
@@ -118,9 +118,10 @@ type agentAppAttributes struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type agentEnvAttributes struct {
|
type agentEnvAttributes struct {
|
||||||
AgentID string `mapstructure:"agent_id"`
|
AgentID string `mapstructure:"agent_id"`
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
Value string `mapstructure:"value"`
|
Value string `mapstructure:"value"`
|
||||||
|
MergeStrategy string `mapstructure:"merge_strategy"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type agentScriptAttributes struct {
|
type agentScriptAttributes struct {
|
||||||
@@ -648,8 +649,9 @@ func ConvertState(ctx context.Context, modules []*tfjson.StateModule, rawGraph s
|
|||||||
}
|
}
|
||||||
|
|
||||||
env := &proto.Env{
|
env := &proto.Env{
|
||||||
Name: attrs.Name,
|
Name: attrs.Name,
|
||||||
Value: attrs.Value,
|
Value: attrs.Value,
|
||||||
|
MergeStrategy: attrs.MergeStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
envAgentLoop:
|
envAgentLoop:
|
||||||
|
|||||||
@@ -382,12 +382,14 @@ func TestConvertResources(t *testing.T) {
|
|||||||
Architecture: "amd64",
|
Architecture: "amd64",
|
||||||
ExtraEnvs: []*proto.Env{
|
ExtraEnvs: []*proto.Env{
|
||||||
{
|
{
|
||||||
Name: "PATH",
|
Name: "PATH",
|
||||||
Value: "/a/bin",
|
Value: "/a/bin",
|
||||||
|
MergeStrategy: "append",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "PATH",
|
Name: "PATH",
|
||||||
Value: "/b/bin",
|
Value: "/b/bin",
|
||||||
|
MergeStrategy: "append",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "UNIQUE",
|
Name: "UNIQUE",
|
||||||
|
|||||||
+4
-2
@@ -21,11 +21,13 @@
|
|||||||
"extra_envs": [
|
"extra_envs": [
|
||||||
{
|
{
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/a/bin"
|
"value": "/a/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/b/bin"
|
"value": "/b/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "UNIQUE",
|
"name": "UNIQUE",
|
||||||
|
|||||||
+4
-2
@@ -22,11 +22,13 @@
|
|||||||
"extra_envs": [
|
"extra_envs": [
|
||||||
{
|
{
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/a/bin"
|
"value": "/a/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/b/bin"
|
"value": "/b/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "UNIQUE",
|
"name": "UNIQUE",
|
||||||
|
|||||||
Generated
Vendored
+4
-2
@@ -44,7 +44,8 @@
|
|||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"values": {
|
"values": {
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/a/bin"
|
"value": "/a/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
"sensitive_values": {}
|
"sensitive_values": {}
|
||||||
},
|
},
|
||||||
@@ -57,7 +58,8 @@
|
|||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
"values": {
|
"values": {
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/b/bin"
|
"value": "/b/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
"sensitive_values": {}
|
"sensitive_values": {}
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
Vendored
+4
-2
@@ -60,7 +60,8 @@
|
|||||||
"agent_id": "aaaaaaaa-1111-2222-3333-444444444444",
|
"agent_id": "aaaaaaaa-1111-2222-3333-444444444444",
|
||||||
"id": "bbbbbbbb-1111-2222-3333-444444444444",
|
"id": "bbbbbbbb-1111-2222-3333-444444444444",
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/a/bin"
|
"value": "/a/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
"sensitive_values": {},
|
"sensitive_values": {},
|
||||||
"depends_on": [
|
"depends_on": [
|
||||||
@@ -78,7 +79,8 @@
|
|||||||
"agent_id": "aaaaaaaa-1111-2222-3333-444444444444",
|
"agent_id": "aaaaaaaa-1111-2222-3333-444444444444",
|
||||||
"id": "cccccccc-1111-2222-3333-444444444444",
|
"id": "cccccccc-1111-2222-3333-444444444444",
|
||||||
"name": "PATH",
|
"name": "PATH",
|
||||||
"value": "/b/bin"
|
"value": "/b/bin",
|
||||||
|
"merge_strategy": "append"
|
||||||
},
|
},
|
||||||
"sensitive_values": {},
|
"sensitive_values": {},
|
||||||
"depends_on": [
|
"depends_on": [
|
||||||
|
|||||||
@@ -75,9 +75,12 @@ import "github.com/coder/coder/v2/apiversion"
|
|||||||
// API v1.15:
|
// API v1.15:
|
||||||
// - Removed `stop_modules` from CompleteJob. Was a duplicate of start_modules
|
// - Removed `stop_modules` from CompleteJob. Was a duplicate of start_modules
|
||||||
// - Add `id`, `subagent_id`, `apps`, `scripts` and `envs` to `provisioner.Devcontainer`
|
// - Add `id`, `subagent_id`, `apps`, `scripts` and `envs` to `provisioner.Devcontainer`
|
||||||
|
//
|
||||||
|
// API v1.16:
|
||||||
|
// - Added `merge_strategy` field to `provisioner.Env` message
|
||||||
const (
|
const (
|
||||||
CurrentMajor = 1
|
CurrentMajor = 1
|
||||||
CurrentMinor = 15
|
CurrentMinor = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
// CurrentVersion is the current provisionerd API version.
|
// CurrentVersion is the current provisionerd API version.
|
||||||
|
|||||||
Generated
+514
-500
File diff suppressed because it is too large
Load Diff
@@ -225,6 +225,10 @@ message DisplayApps {
|
|||||||
message Env {
|
message Env {
|
||||||
string name = 1;
|
string name = 1;
|
||||||
string value = 2;
|
string value = 2;
|
||||||
|
// merge_strategy controls how this env var is merged when multiple
|
||||||
|
// coder_env resources define the same name. Valid values: "replace"
|
||||||
|
// (default), "append", "prepend", "error".
|
||||||
|
string merge_strategy = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Script represents a script to be run on the workspace.
|
// Script represents a script to be run on the workspace.
|
||||||
|
|||||||
Generated
+9
@@ -287,6 +287,12 @@ export interface DisplayApps {
|
|||||||
export interface Env {
|
export interface Env {
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
/**
|
||||||
|
* merge_strategy controls how this env var is merged when multiple
|
||||||
|
* coder_env resources define the same name. Valid values: "replace"
|
||||||
|
* (default), "append", "prepend", "error".
|
||||||
|
*/
|
||||||
|
mergeStrategy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Script represents a script to be run on the workspace. */
|
/** Script represents a script to be run on the workspace. */
|
||||||
@@ -1052,6 +1058,9 @@ export const Env = {
|
|||||||
if (message.value !== "") {
|
if (message.value !== "") {
|
||||||
writer.uint32(18).string(message.value);
|
writer.uint32(18).string(message.value);
|
||||||
}
|
}
|
||||||
|
if (message.mergeStrategy !== "") {
|
||||||
|
writer.uint32(26).string(message.mergeStrategy);
|
||||||
|
}
|
||||||
return writer;
|
return writer;
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user