From a0753f3eb4dd7326c5b30c613c2a1915ae8bb241 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 25 Jun 2025 13:42:55 +0000 Subject: [PATCH 1/5] feat(agent/agentcontainers): add feature options as envs --- agent/agentcontainers/api.go | 20 ++- agent/agentcontainers/api_test.go | 116 ++++++++++++++++++ agent/agentcontainers/devcontainercli.go | 29 +++++ agent/agentcontainers/devcontainercli_test.go | 98 +++++++++++++++ 4 files changed, 257 insertions(+), 6 deletions(-) diff --git a/agent/agentcontainers/api.go b/agent/agentcontainers/api.go index 7ba29e45df7f4..fba5babce5003 100644 --- a/agent/agentcontainers/api.go +++ b/agent/agentcontainers/api.go @@ -1302,6 +1302,7 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c } var ( + featureOptionsAsEnvs []string appsWithPossibleDuplicates []SubAgentApp workspaceFolder = DevcontainerDefaultContainerWorkspaceFolder ) @@ -1313,12 +1314,14 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c ) readConfig := func() (DevcontainerConfig, error) { - return api.dccli.ReadConfig(ctx, dc.WorkspaceFolder, dc.ConfigPath, []string{ - fmt.Sprintf("CODER_WORKSPACE_AGENT_NAME=%s", subAgentConfig.Name), - fmt.Sprintf("CODER_WORKSPACE_OWNER_NAME=%s", api.ownerName), - fmt.Sprintf("CODER_WORKSPACE_NAME=%s", api.workspaceName), - fmt.Sprintf("CODER_URL=%s", api.subAgentURL), - }) + return api.dccli.ReadConfig(ctx, dc.WorkspaceFolder, dc.ConfigPath, + append(featureOptionsAsEnvs, []string{ + fmt.Sprintf("CODER_WORKSPACE_AGENT_NAME=%s", subAgentConfig.Name), + fmt.Sprintf("CODER_WORKSPACE_OWNER_NAME=%s", api.ownerName), + fmt.Sprintf("CODER_WORKSPACE_NAME=%s", api.workspaceName), + fmt.Sprintf("CODER_URL=%s", api.subAgentURL), + }...), + ) } if config, err = readConfig(); err != nil { @@ -1334,6 +1337,11 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c workspaceFolder = config.Workspace.WorkspaceFolder + featureOptionsAsEnvs = config.MergedConfiguration.Features.OptionsAsEnvs() + if len(featureOptionsAsEnvs) > 0 { + configOutdated = true + } + // NOTE(DanielleMaywood): // We only want to take an agent name specified in the root customization layer. // This restricts the ability for a feature to specify the agent name. We may revisit diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index 7c4988630e164..177a6d844d0c2 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -2060,6 +2060,122 @@ func TestAPI(t *testing.T) { require.Len(t, fSAC.created, 1) }) + t.Run("ReadConfigWithFeatureOptions", func(t *testing.T) { + t.Parallel() + + if runtime.GOOS == "windows" { + t.Skip("Dev Container tests are not supported on Windows (this test uses mocks but fails due to Windows paths)") + } + + var ( + ctx = testutil.Context(t, testutil.WaitMedium) + logger = testutil.Logger(t) + mClock = quartz.NewMock(t) + mCCLI = acmock.NewMockContainerCLI(gomock.NewController(t)) + fSAC = &fakeSubAgentClient{ + logger: logger.Named("fakeSubAgentClient"), + createErrC: make(chan error, 1), + } + fDCCLI = &fakeDevcontainerCLI{ + readConfig: agentcontainers.DevcontainerConfig{ + MergedConfiguration: agentcontainers.DevcontainerMergedConfiguration{ + Features: agentcontainers.DevcontainerFeatures{ + "./code-server": map[string]any{ + "port": 9090, + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": map[string]any{ + "moby": "false", + }, + }, + }, + Workspace: agentcontainers.DevcontainerWorkspace{ + WorkspaceFolder: "/workspaces/coder", + }, + }, + readConfigErrC: make(chan func(envs []string) error, 2), + } + + testContainer = codersdk.WorkspaceAgentContainer{ + ID: "test-container-id", + FriendlyName: "test-container", + Image: "test-image", + Running: true, + CreatedAt: time.Now(), + Labels: map[string]string{ + agentcontainers.DevcontainerLocalFolderLabel: "/workspaces/coder", + agentcontainers.DevcontainerConfigFileLabel: "/workspaces/coder/.devcontainer/devcontainer.json", + }, + } + ) + + coderBin, err := os.Executable() + require.NoError(t, err) + + // Mock the `List` function to always return our test container. + mCCLI.EXPECT().List(gomock.Any()).Return(codersdk.WorkspaceAgentListContainersResponse{ + Containers: []codersdk.WorkspaceAgentContainer{testContainer}, + }, nil).AnyTimes() + + // Mock the steps used for injecting the coder agent. + gomock.InOrder( + mCCLI.EXPECT().DetectArchitecture(gomock.Any(), testContainer.ID).Return(runtime.GOARCH, nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "mkdir", "-p", "/.coder-agent").Return(nil, nil), + mCCLI.EXPECT().Copy(gomock.Any(), testContainer.ID, coderBin, "/.coder-agent/coder").Return(nil), + mCCLI.EXPECT().ExecAs(gomock.Any(), testContainer.ID, "root", "chmod", "0755", "/.coder-agent", "/.coder-agent/coder").Return(nil, nil), + ) + + mClock.Set(time.Now()).MustWait(ctx) + tickerTrap := mClock.Trap().TickerFunc("updaterLoop") + + api := agentcontainers.NewAPI(logger, + agentcontainers.WithClock(mClock), + agentcontainers.WithContainerCLI(mCCLI), + agentcontainers.WithDevcontainerCLI(fDCCLI), + agentcontainers.WithSubAgentClient(fSAC), + agentcontainers.WithSubAgentURL("test-subagent-url"), + agentcontainers.WithWatcher(watcher.NewNoop()), + agentcontainers.WithManifestInfo("test-user", "test-workspace"), + ) + api.Init() + defer api.Close() + + // Close before api.Close() defer to avoid deadlock after test. + defer close(fSAC.createErrC) + defer close(fDCCLI.readConfigErrC) + + // Allow agent creation and injection to succeed. + testutil.RequireSend(ctx, t, fSAC.createErrC, nil) + + testutil.RequireSend(ctx, t, fDCCLI.readConfigErrC, func(envs []string) error { + assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=coder") + assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace") + assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") + assert.Contains(t, envs, "CODER_URL=test-subagent-url") + // First call should not have feature envs. + assert.NotContains(t, envs, "FEATURE_CODE_SERVER_PORT=9090") + assert.NotContains(t, envs, "FEATURE_DOCKER_IN_DOCKER_MOBY=false") + return nil + }) + + testutil.RequireSend(ctx, t, fDCCLI.readConfigErrC, func(envs []string) error { + assert.Contains(t, envs, "CODER_WORKSPACE_AGENT_NAME=coder") + assert.Contains(t, envs, "CODER_WORKSPACE_NAME=test-workspace") + assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") + assert.Contains(t, envs, "CODER_URL=test-subagent-url") + // Second call should have feature envs from the first config read. + assert.Contains(t, envs, "FEATURE_CODE_SERVER_PORT=9090") + assert.Contains(t, envs, "FEATURE_DOCKER_IN_DOCKER_MOBY=false") + return nil + }) + + // Wait until the ticker has been registered. + tickerTrap.MustWait(ctx).MustRelease(ctx) + tickerTrap.Close() + + // Verify agent was created successfully + require.Len(t, fSAC.created, 1) + }) + t.Run("CommandEnv", func(t *testing.T) { t.Parallel() diff --git a/agent/agentcontainers/devcontainercli.go b/agent/agentcontainers/devcontainercli.go index 5a9460c945187..2b183305e616f 100644 --- a/agent/agentcontainers/devcontainercli.go +++ b/agent/agentcontainers/devcontainercli.go @@ -6,7 +6,10 @@ import ( "context" "encoding/json" "errors" + "fmt" "io" + "slices" + "strings" "golang.org/x/xerrors" @@ -26,12 +29,38 @@ type DevcontainerConfig struct { type DevcontainerMergedConfiguration struct { Customizations DevcontainerMergedCustomizations `json:"customizations,omitempty"` + Features DevcontainerFeatures `json:"features,omitempty"` } type DevcontainerMergedCustomizations struct { Coder []CoderCustomization `json:"coder,omitempty"` } +type DevcontainerFeatures map[string]any + +func (f DevcontainerFeatures) OptionsAsEnvs() []string { + var env []string + for k, v := range f { + vv, ok := v.(map[string]any) + if !ok { + continue + } + // Take the last part of the key as the feature name. + k = k[strings.LastIndex(k, "/")+1:] + // Removing trailing : and anything following it + if idx := strings.Index(k, ":"); idx != -1 { + k = k[:idx] + } + k = strings.ReplaceAll(k, "-", "_") + for k2, v2 := range vv { + k2 = strings.ReplaceAll(k2, "-", "_") + env = append(env, fmt.Sprintf("FEATURE_%s_%s=%s", strings.ToUpper(k), strings.ToUpper(k2), fmt.Sprintf("%v", v2))) + } + } + slices.Sort(env) + return env +} + type DevcontainerConfiguration struct { Customizations DevcontainerCustomizations `json:"customizations,omitempty"` } diff --git a/agent/agentcontainers/devcontainercli_test.go b/agent/agentcontainers/devcontainercli_test.go index ed600c6d1ee49..b46cc8ed63650 100644 --- a/agent/agentcontainers/devcontainercli_test.go +++ b/agent/agentcontainers/devcontainercli_test.go @@ -637,3 +637,101 @@ func removeDevcontainerByID(t *testing.T, pool *dockertest.Pool, id string) { assert.NoError(t, err, "remove container failed") } } + +func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + features agentcontainers.DevcontainerFeatures + want []string + }{ + { + name: "code-server feature", + features: agentcontainers.DevcontainerFeatures{ + "./code-server": map[string]any{ + "port": 9090, + }, + }, + want: []string{ + "FEATURE_CODE_SERVER_PORT=9090", + }, + }, + { + name: "docker-in-docker feature", + features: agentcontainers.DevcontainerFeatures{ + "ghcr.io/devcontainers/features/docker-in-docker:2": map[string]any{ + "moby": "false", + }, + }, + want: []string{ + "FEATURE_DOCKER_IN_DOCKER_MOBY=false", + }, + }, + { + name: "multiple features with multiple options", + features: agentcontainers.DevcontainerFeatures{ + "./code-server": map[string]any{ + "port": 9090, + "password": "secret", + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": map[string]any{ + "moby": "false", + "docker-dash-compose-version": "v2", + }, + }, + want: []string{ + "FEATURE_CODE_SERVER_PORT=9090", + "FEATURE_CODE_SERVER_PASSWORD=secret", + "FEATURE_DOCKER_IN_DOCKER_MOBY=false", + "FEATURE_DOCKER_IN_DOCKER_DOCKER_DASH_COMPOSE_VERSION=v2", + }, + }, + { + name: "feature with non-map value (should be ignored)", + features: agentcontainers.DevcontainerFeatures{ + "./code-server": map[string]any{ + "port": 9090, + }, + "./invalid-feature": "not-a-map", + }, + want: []string{ + "FEATURE_CODE_SERVER_PORT=9090", + }, + }, + { + name: "real config example", + features: agentcontainers.DevcontainerFeatures{ + "./code-server": map[string]any{ + "port": 9090, + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": map[string]any{ + "moby": "false", + }, + }, + want: []string{ + "FEATURE_CODE_SERVER_PORT=9090", + "FEATURE_DOCKER_IN_DOCKER_MOBY=false", + }, + }, + { + name: "empty features", + features: agentcontainers.DevcontainerFeatures{}, + want: []string{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + got := tt.features.OptionsAsEnvs() + + require.Len(t, got, len(tt.want), "number of environment variables should match") + + for _, expected := range tt.want { + assert.Contains(t, got, expected, "expected environment variable not found") + } + }) + } +} From 28bf9a4abf4e7376b1edfe4489b4eb4c48518f5b Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 25 Jun 2025 14:07:50 +0000 Subject: [PATCH 2/5] docs --- agent/agentcontainers/devcontainercli.go | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/agent/agentcontainers/devcontainercli.go b/agent/agentcontainers/devcontainercli.go index 2b183305e616f..c3d54440ef0fa 100644 --- a/agent/agentcontainers/devcontainercli.go +++ b/agent/agentcontainers/devcontainercli.go @@ -38,6 +38,23 @@ type DevcontainerMergedCustomizations struct { type DevcontainerFeatures map[string]any +// OptionsAsEnvs converts the DevcontainerFeatures into a list of +// environment variables that can be used to set feature options. +// The format is FEATURE__=. +// For example, if the feature is: +// +// "ghcr.io/coder/devcontainer-features/code-server:1": { +// "port": 9090, +// } +// +// It will produce: +// +// FEATURE_CODE_SERVER_PORT=9090 +// +// Note that the feature name is derived from the last part of the key, +// so "ghcr.io/coder/devcontainer-features/code-server:1" becomes +// "CODE_SERVER". The version part (e.g. ":1") is removed, and dashes in +// the feature and option names are replaced with underscores. func (f DevcontainerFeatures) OptionsAsEnvs() []string { var env []string for k, v := range f { @@ -45,9 +62,9 @@ func (f DevcontainerFeatures) OptionsAsEnvs() []string { if !ok { continue } - // Take the last part of the key as the feature name. + // Take the last part of the key as the feature name/path. k = k[strings.LastIndex(k, "/")+1:] - // Removing trailing : and anything following it + // Remove ":" and anything following it. if idx := strings.Index(k, ":"); idx != -1 { k = k[:idx] } From c3f11ac7f1c92ec994b3f921eee11f0b6903cc08 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 25 Jun 2025 14:09:55 +0000 Subject: [PATCH 3/5] cleanup test --- agent/agentcontainers/devcontainercli_test.go | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/agent/agentcontainers/devcontainercli_test.go b/agent/agentcontainers/devcontainercli_test.go index b46cc8ed63650..099501d58e973 100644 --- a/agent/agentcontainers/devcontainercli_test.go +++ b/agent/agentcontainers/devcontainercli_test.go @@ -13,6 +13,7 @@ import ( "strings" "testing" + "github.com/google/go-cmp/cmp" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/stretchr/testify/assert" @@ -681,10 +682,10 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { }, }, want: []string{ - "FEATURE_CODE_SERVER_PORT=9090", "FEATURE_CODE_SERVER_PASSWORD=secret", - "FEATURE_DOCKER_IN_DOCKER_MOBY=false", + "FEATURE_CODE_SERVER_PORT=9090", "FEATURE_DOCKER_IN_DOCKER_DOCKER_DASH_COMPOSE_VERSION=v2", + "FEATURE_DOCKER_IN_DOCKER_MOBY=false", }, }, { @@ -717,7 +718,7 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { { name: "empty features", features: agentcontainers.DevcontainerFeatures{}, - want: []string{}, + want: nil, }, } @@ -726,11 +727,8 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { t.Parallel() got := tt.features.OptionsAsEnvs() - - require.Len(t, got, len(tt.want), "number of environment variables should match") - - for _, expected := range tt.want { - assert.Contains(t, got, expected, "expected environment variable not found") + if diff := cmp.Diff(tt.want, got); diff != "" { + require.Failf(t, "OptionsAsEnvs() mismatch (-want +got):\n%s", diff) } }) } From 4df9fe66bc7e284ee71f609e2c772f869f130a61 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 25 Jun 2025 14:13:13 +0000 Subject: [PATCH 4/5] add real config --- agent/agentcontainers/devcontainercli_test.go | 28 +++++++++++++------ 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/agent/agentcontainers/devcontainercli_test.go b/agent/agentcontainers/devcontainercli_test.go index 099501d58e973..8ec08b159e6d8 100644 --- a/agent/agentcontainers/devcontainercli_test.go +++ b/agent/agentcontainers/devcontainercli_test.go @@ -3,6 +3,7 @@ package agentcontainers_test import ( "bytes" "context" + "encoding/json" "errors" "flag" "fmt" @@ -642,6 +643,22 @@ func removeDevcontainerByID(t *testing.T, pool *dockertest.Pool, id string) { func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { t.Parallel() + realConfigJSON := `{ + "mergedConfiguration": { + "features": { + "./code-server": { + "port": 9090 + }, + "ghcr.io/devcontainers/features/docker-in-docker:2": { + "moby": "false" + } + } + } + }` + var realConfig agentcontainers.DevcontainerConfig + err := json.Unmarshal([]byte(realConfigJSON), &realConfig) + require.NoError(t, err, "unmarshal JSON payload") + tests := []struct { name string features agentcontainers.DevcontainerFeatures @@ -701,15 +718,8 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { }, }, { - name: "real config example", - features: agentcontainers.DevcontainerFeatures{ - "./code-server": map[string]any{ - "port": 9090, - }, - "ghcr.io/devcontainers/features/docker-in-docker:2": map[string]any{ - "moby": "false", - }, - }, + name: "real config example", + features: realConfig.MergedConfiguration.Features, want: []string{ "FEATURE_CODE_SERVER_PORT=9090", "FEATURE_DOCKER_IN_DOCKER_MOBY=false", From 0602b0fc82660b87fa21c26a193d29b8fe041e21 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 25 Jun 2025 14:25:01 +0000 Subject: [PATCH 5/5] _OPTION_ --- agent/agentcontainers/api_test.go | 8 ++++---- agent/agentcontainers/devcontainercli.go | 6 +++--- agent/agentcontainers/devcontainercli_test.go | 18 +++++++++--------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/agent/agentcontainers/api_test.go b/agent/agentcontainers/api_test.go index 177a6d844d0c2..f61f4c24bfc2b 100644 --- a/agent/agentcontainers/api_test.go +++ b/agent/agentcontainers/api_test.go @@ -2152,8 +2152,8 @@ func TestAPI(t *testing.T) { assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") assert.Contains(t, envs, "CODER_URL=test-subagent-url") // First call should not have feature envs. - assert.NotContains(t, envs, "FEATURE_CODE_SERVER_PORT=9090") - assert.NotContains(t, envs, "FEATURE_DOCKER_IN_DOCKER_MOBY=false") + assert.NotContains(t, envs, "FEATURE_CODE_SERVER_OPTION_PORT=9090") + assert.NotContains(t, envs, "FEATURE_DOCKER_IN_DOCKER_OPTION_MOBY=false") return nil }) @@ -2163,8 +2163,8 @@ func TestAPI(t *testing.T) { assert.Contains(t, envs, "CODER_WORKSPACE_OWNER_NAME=test-user") assert.Contains(t, envs, "CODER_URL=test-subagent-url") // Second call should have feature envs from the first config read. - assert.Contains(t, envs, "FEATURE_CODE_SERVER_PORT=9090") - assert.Contains(t, envs, "FEATURE_DOCKER_IN_DOCKER_MOBY=false") + assert.Contains(t, envs, "FEATURE_CODE_SERVER_OPTION_PORT=9090") + assert.Contains(t, envs, "FEATURE_DOCKER_IN_DOCKER_OPTION_MOBY=false") return nil }) diff --git a/agent/agentcontainers/devcontainercli.go b/agent/agentcontainers/devcontainercli.go index c3d54440ef0fa..4d3a93ae99f4e 100644 --- a/agent/agentcontainers/devcontainercli.go +++ b/agent/agentcontainers/devcontainercli.go @@ -40,7 +40,7 @@ type DevcontainerFeatures map[string]any // OptionsAsEnvs converts the DevcontainerFeatures into a list of // environment variables that can be used to set feature options. -// The format is FEATURE__=. +// The format is FEATURE__OPTION_=. // For example, if the feature is: // // "ghcr.io/coder/devcontainer-features/code-server:1": { @@ -49,7 +49,7 @@ type DevcontainerFeatures map[string]any // // It will produce: // -// FEATURE_CODE_SERVER_PORT=9090 +// FEATURE_CODE_SERVER_OPTION_PORT=9090 // // Note that the feature name is derived from the last part of the key, // so "ghcr.io/coder/devcontainer-features/code-server:1" becomes @@ -71,7 +71,7 @@ func (f DevcontainerFeatures) OptionsAsEnvs() []string { k = strings.ReplaceAll(k, "-", "_") for k2, v2 := range vv { k2 = strings.ReplaceAll(k2, "-", "_") - env = append(env, fmt.Sprintf("FEATURE_%s_%s=%s", strings.ToUpper(k), strings.ToUpper(k2), fmt.Sprintf("%v", v2))) + env = append(env, fmt.Sprintf("FEATURE_%s_OPTION_%s=%s", strings.ToUpper(k), strings.ToUpper(k2), fmt.Sprintf("%v", v2))) } } slices.Sort(env) diff --git a/agent/agentcontainers/devcontainercli_test.go b/agent/agentcontainers/devcontainercli_test.go index 8ec08b159e6d8..523b47d93563c 100644 --- a/agent/agentcontainers/devcontainercli_test.go +++ b/agent/agentcontainers/devcontainercli_test.go @@ -672,7 +672,7 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { }, }, want: []string{ - "FEATURE_CODE_SERVER_PORT=9090", + "FEATURE_CODE_SERVER_OPTION_PORT=9090", }, }, { @@ -683,7 +683,7 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { }, }, want: []string{ - "FEATURE_DOCKER_IN_DOCKER_MOBY=false", + "FEATURE_DOCKER_IN_DOCKER_OPTION_MOBY=false", }, }, { @@ -699,10 +699,10 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { }, }, want: []string{ - "FEATURE_CODE_SERVER_PASSWORD=secret", - "FEATURE_CODE_SERVER_PORT=9090", - "FEATURE_DOCKER_IN_DOCKER_DOCKER_DASH_COMPOSE_VERSION=v2", - "FEATURE_DOCKER_IN_DOCKER_MOBY=false", + "FEATURE_CODE_SERVER_OPTION_PASSWORD=secret", + "FEATURE_CODE_SERVER_OPTION_PORT=9090", + "FEATURE_DOCKER_IN_DOCKER_OPTION_DOCKER_DASH_COMPOSE_VERSION=v2", + "FEATURE_DOCKER_IN_DOCKER_OPTION_MOBY=false", }, }, { @@ -714,15 +714,15 @@ func TestDevcontainerFeatures_OptionsAsEnvs(t *testing.T) { "./invalid-feature": "not-a-map", }, want: []string{ - "FEATURE_CODE_SERVER_PORT=9090", + "FEATURE_CODE_SERVER_OPTION_PORT=9090", }, }, { name: "real config example", features: realConfig.MergedConfiguration.Features, want: []string{ - "FEATURE_CODE_SERVER_PORT=9090", - "FEATURE_DOCKER_IN_DOCKER_MOBY=false", + "FEATURE_CODE_SERVER_OPTION_PORT=9090", + "FEATURE_DOCKER_IN_DOCKER_OPTION_MOBY=false", }, }, { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy