Skip to content

Commit 9ccaf86

Browse files
authored
fix(agent/agentcontainers): always derive devcontainer name from workspace folder (#18666)
1 parent 715c7b0 commit 9ccaf86

File tree

2 files changed

+100
-7
lines changed

2 files changed

+100
-7
lines changed

agent/agentcontainers/api.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,25 @@ func WithDevcontainers(devcontainers []codersdk.WorkspaceAgentDevcontainer, scri
211211
if dc.Status == "" {
212212
dc.Status = codersdk.WorkspaceAgentDevcontainerStatusStarting
213213
}
214+
logger := api.logger.With(
215+
slog.F("devcontainer_id", dc.ID),
216+
slog.F("devcontainer_name", dc.Name),
217+
slog.F("workspace_folder", dc.WorkspaceFolder),
218+
slog.F("config_path", dc.ConfigPath),
219+
)
220+
221+
// Devcontainers have a name originating from Terraform, but
222+
// we need to ensure that the name is unique. We will use
223+
// the workspace folder name to generate a unique agent name,
224+
// and if that fails, we will fall back to the devcontainers
225+
// original name.
226+
name, usingWorkspaceFolder := api.makeAgentName(dc.WorkspaceFolder, dc.Name)
227+
if name != dc.Name {
228+
logger = logger.With(slog.F("devcontainer_name", name))
229+
logger.Debug(api.ctx, "updating devcontainer name", slog.F("devcontainer_old_name", dc.Name))
230+
dc.Name = name
231+
api.usingWorkspaceFolderName[dc.WorkspaceFolder] = usingWorkspaceFolder
232+
}
214233

215234
api.knownDevcontainers[dc.WorkspaceFolder] = dc
216235
api.devcontainerNames[dc.Name] = true
@@ -223,12 +242,7 @@ func WithDevcontainers(devcontainers []codersdk.WorkspaceAgentDevcontainer, scri
223242
}
224243
}
225244
if api.devcontainerLogSourceIDs[dc.WorkspaceFolder] == uuid.Nil {
226-
api.logger.Error(api.ctx, "devcontainer log source ID not found for devcontainer",
227-
slog.F("devcontainer_id", dc.ID),
228-
slog.F("devcontainer_name", dc.Name),
229-
slog.F("workspace_folder", dc.WorkspaceFolder),
230-
slog.F("config_path", dc.ConfigPath),
231-
)
245+
logger.Error(api.ctx, "devcontainer log source ID not found for devcontainer")
232246
}
233247
}
234248
}
@@ -872,7 +886,7 @@ func (api *API) getContainers() (codersdk.WorkspaceAgentListContainersResponse,
872886
devcontainers = append(devcontainers, dc)
873887
}
874888
slices.SortFunc(devcontainers, func(a, b codersdk.WorkspaceAgentDevcontainer) int {
875-
return strings.Compare(a.Name, b.Name)
889+
return strings.Compare(a.WorkspaceFolder, b.WorkspaceFolder)
876890
})
877891
}
878892

agent/agentcontainers/api_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,3 +2596,82 @@ func fakeContainer(t *testing.T, mut ...func(*codersdk.WorkspaceAgentContainer))
25962596
}
25972597
return ct
25982598
}
2599+
2600+
func TestWithDevcontainersNameGeneration(t *testing.T) {
2601+
t.Parallel()
2602+
2603+
if runtime.GOOS == "windows" {
2604+
t.Skip("Dev Container tests are not supported on Windows")
2605+
}
2606+
2607+
devcontainers := []codersdk.WorkspaceAgentDevcontainer{
2608+
{
2609+
ID: uuid.New(),
2610+
Name: "original-name",
2611+
WorkspaceFolder: "/home/coder/foo/project",
2612+
ConfigPath: "/home/coder/foo/project/.devcontainer/devcontainer.json",
2613+
},
2614+
{
2615+
ID: uuid.New(),
2616+
Name: "another-name",
2617+
WorkspaceFolder: "/home/coder/bar/project",
2618+
ConfigPath: "/home/coder/bar/project/.devcontainer/devcontainer.json",
2619+
},
2620+
}
2621+
2622+
scripts := []codersdk.WorkspaceAgentScript{
2623+
{ID: devcontainers[0].ID, LogSourceID: uuid.New()},
2624+
{ID: devcontainers[1].ID, LogSourceID: uuid.New()},
2625+
}
2626+
2627+
logger := testutil.Logger(t)
2628+
2629+
// This should trigger the WithDevcontainers code path where names are generated
2630+
api := agentcontainers.NewAPI(logger,
2631+
agentcontainers.WithDevcontainers(devcontainers, scripts),
2632+
agentcontainers.WithContainerCLI(&fakeContainerCLI{
2633+
containers: codersdk.WorkspaceAgentListContainersResponse{
2634+
Containers: []codersdk.WorkspaceAgentContainer{
2635+
fakeContainer(t, func(c *codersdk.WorkspaceAgentContainer) {
2636+
c.ID = "some-container-id-1"
2637+
c.FriendlyName = "container-name-1"
2638+
c.Labels[agentcontainers.DevcontainerLocalFolderLabel] = "/home/coder/baz/project"
2639+
c.Labels[agentcontainers.DevcontainerConfigFileLabel] = "/home/coder/baz/project/.devcontainer/devcontainer.json"
2640+
}),
2641+
},
2642+
},
2643+
}),
2644+
agentcontainers.WithDevcontainerCLI(&fakeDevcontainerCLI{}),
2645+
agentcontainers.WithSubAgentClient(&fakeSubAgentClient{}),
2646+
agentcontainers.WithWatcher(watcher.NewNoop()),
2647+
)
2648+
defer api.Close()
2649+
api.Start()
2650+
2651+
r := chi.NewRouter()
2652+
r.Mount("/", api.Routes())
2653+
2654+
ctx := context.Background()
2655+
2656+
err := api.RefreshContainers(ctx)
2657+
require.NoError(t, err, "RefreshContainers should not error")
2658+
2659+
// Initial request returns the initial data.
2660+
req := httptest.NewRequest(http.MethodGet, "/", nil).
2661+
WithContext(ctx)
2662+
rec := httptest.NewRecorder()
2663+
r.ServeHTTP(rec, req)
2664+
2665+
require.Equal(t, http.StatusOK, rec.Code)
2666+
var response codersdk.WorkspaceAgentListContainersResponse
2667+
err = json.NewDecoder(rec.Body).Decode(&response)
2668+
require.NoError(t, err)
2669+
2670+
// Verify the devcontainers have the expected names.
2671+
require.Len(t, response.Devcontainers, 3, "should have two devcontainers")
2672+
assert.NotEqual(t, "original-name", response.Devcontainers[2].Name, "first devcontainer should not keep original name")
2673+
assert.Equal(t, "project", response.Devcontainers[2].Name, "first devcontainer should use the project folder name")
2674+
assert.NotEqual(t, "another-name", response.Devcontainers[0].Name, "second devcontainer should not keep original name")
2675+
assert.Equal(t, "bar-project", response.Devcontainers[0].Name, "second devcontainer should has a collision and uses the folder name with a prefix")
2676+
assert.Equal(t, "baz-project", response.Devcontainers[1].Name, "third devcontainer should use the folder name with a prefix since it collides with the first two")
2677+
}

0 commit comments

Comments
 (0)
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