Skip to content

Commit 410241d

Browse files
committed
feat: show devcontainer dirty status and allow recreate
Updates #16424
1 parent c7917ea commit 410241d

File tree

14 files changed

+425
-35
lines changed

14 files changed

+425
-35
lines changed

agent/agentcontainers/acmock/acmock.go

Lines changed: 47 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

agent/agentcontainers/acmock/doc.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
// Package acmock contains a mock implementation of agentcontainers.Lister for use in tests.
22
package acmock
33

4-
//go:generate mockgen -destination ./acmock.go -package acmock .. Lister
4+
//go:generate mockgen -destination ./acmock.go -package acmock .. Lister,DevcontainerCLI

agent/agentcontainers/api.go

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -336,14 +336,29 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
336336
}
337337

338338
// Check if the container is running and update the known devcontainers.
339-
for _, container := range updated.Containers {
339+
for i := range updated.Containers {
340+
container := &updated.Containers[i]
340341
workspaceFolder := container.Labels[DevcontainerLocalFolderLabel]
341342
configFile := container.Labels[DevcontainerConfigFileLabel]
342343

343344
if workspaceFolder == "" {
344345
continue
345346
}
346347

348+
container.DevcontainerDirty = dirtyStates[workspaceFolder]
349+
if container.DevcontainerDirty {
350+
lastModified, hasModTime := api.configFileModifiedTimes[configFile]
351+
if hasModTime && container.CreatedAt.After(lastModified) {
352+
api.logger.Info(ctx, "new container created after config modification, not marking as dirty",
353+
slog.F("container", container.ID),
354+
slog.F("created_at", container.CreatedAt),
355+
slog.F("config_modified_at", lastModified),
356+
slog.F("file", configFile),
357+
)
358+
container.DevcontainerDirty = false
359+
}
360+
}
361+
347362
// Check if this is already in our known list.
348363
if knownIndex := slices.IndexFunc(api.knownDevcontainers, func(dc codersdk.WorkspaceAgentDevcontainer) bool {
349364
return dc.WorkspaceFolder == workspaceFolder
@@ -356,7 +371,7 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
356371
}
357372
}
358373
api.knownDevcontainers[knownIndex].Running = container.Running
359-
api.knownDevcontainers[knownIndex].Container = &container
374+
api.knownDevcontainers[knownIndex].Container = container
360375

361376
// Check if this container was created after the config
362377
// file was modified.
@@ -395,28 +410,14 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
395410
}
396411
}
397412

398-
dirty := dirtyStates[workspaceFolder]
399-
if dirty {
400-
lastModified, hasModTime := api.configFileModifiedTimes[configFile]
401-
if hasModTime && container.CreatedAt.After(lastModified) {
402-
api.logger.Info(ctx, "new container created after config modification, not marking as dirty",
403-
slog.F("container", container.ID),
404-
slog.F("created_at", container.CreatedAt),
405-
slog.F("config_modified_at", lastModified),
406-
slog.F("file", configFile),
407-
)
408-
dirty = false
409-
}
410-
}
411-
412413
api.knownDevcontainers = append(api.knownDevcontainers, codersdk.WorkspaceAgentDevcontainer{
413414
ID: uuid.New(),
414415
Name: name,
415416
WorkspaceFolder: workspaceFolder,
416417
ConfigPath: configFile,
417418
Running: container.Running,
418-
Dirty: dirty,
419-
Container: &container,
419+
Dirty: container.DevcontainerDirty,
420+
Container: container,
420421
})
421422
}
422423

@@ -510,6 +511,7 @@ func (api *API) handleDevcontainerRecreate(w http.ResponseWriter, r *http.Reques
510511
slog.F("name", api.knownDevcontainers[i].Name),
511512
)
512513
api.knownDevcontainers[i].Dirty = false
514+
api.knownDevcontainers[i].Container = nil
513515
}
514516
return
515517
}
@@ -579,6 +581,9 @@ func (api *API) markDevcontainerDirty(configPath string, modifiedAt time.Time) {
579581
slog.F("modified_at", modifiedAt),
580582
)
581583
api.knownDevcontainers[i].Dirty = true
584+
if api.knownDevcontainers[i].Container != nil {
585+
api.knownDevcontainers[i].Container.DevcontainerDirty = true
586+
}
582587
}
583588
}
584589
})

agent/agentcontainers/api_test.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,9 @@ func TestAPI(t *testing.T) {
660660
require.NoError(t, err)
661661
require.Len(t, response.Devcontainers, 1)
662662
assert.False(t, response.Devcontainers[0].Dirty,
663+
"devcontainer should not be marked as dirty initially")
664+
require.NotNil(t, response.Devcontainers[0].Container, "container should not be nil")
665+
assert.False(t, response.Devcontainers[0].Container.DevcontainerDirty,
663666
"container should not be marked as dirty initially")
664667

665668
// Verify the watcher is watching the config file.
@@ -689,6 +692,9 @@ func TestAPI(t *testing.T) {
689692
require.Len(t, response.Devcontainers, 1)
690693
assert.True(t, response.Devcontainers[0].Dirty,
691694
"container should be marked as dirty after config file was modified")
695+
require.NotNil(t, response.Devcontainers[0].Container, "container should not be nil")
696+
assert.True(t, response.Devcontainers[0].Container.DevcontainerDirty,
697+
"container should be marked as dirty after config file was modified")
692698

693699
mClock.Advance(time.Minute).MustWait(ctx)
694700

@@ -707,7 +713,10 @@ func TestAPI(t *testing.T) {
707713
require.NoError(t, err)
708714
require.Len(t, response.Devcontainers, 1)
709715
assert.False(t, response.Devcontainers[0].Dirty,
710-
"dirty flag should be cleared after container recreation")
716+
"dirty flag should be cleared on the devcontainer after container recreation")
717+
require.NotNil(t, response.Devcontainers[0].Container, "container should not be nil")
718+
assert.False(t, response.Devcontainers[0].Container.DevcontainerDirty,
719+
"dirty flag should be cleared on the container after container recreation")
711720
})
712721
}
713722

coderd/apidoc/docs.go

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 39 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1326,6 +1326,7 @@ func New(options *Options) *API {
13261326
r.Get("/listening-ports", api.workspaceAgentListeningPorts)
13271327
r.Get("/connection", api.workspaceAgentConnection)
13281328
r.Get("/containers", api.workspaceAgentListContainers)
1329+
r.Post("/containers/devcontainers/container/{container}/recreate", api.workspaceAgentRecreateDevcontainer)
13291330
r.Get("/coordinate", api.workspaceAgentClientCoordinate)
13301331

13311332
// PTY is part of workspaceAppServer.

0 commit comments

Comments
 (0)