Skip to content

feat: show devcontainer dirty status and allow recreate #17880

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions agent/agentcontainers/acmock/acmock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion agent/agentcontainers/acmock/doc.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package acmock contains a mock implementation of agentcontainers.Lister for use in tests.
package acmock

//go:generate mockgen -destination ./acmock.go -package acmock .. Lister
//go:generate mockgen -destination ./acmock.go -package acmock .. Lister,DevcontainerCLI
56 changes: 38 additions & 18 deletions agent/agentcontainers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,15 @@ func WithClock(clock quartz.Clock) Option {
}
}

// WithCacheDuration sets the cache duration for the API.
// This is used to control how often the API refreshes the list of
// containers. The default is 10 seconds.
func WithCacheDuration(d time.Duration) Option {
return func(api *API) {
api.cacheDuration = d
}
}

// WithExecer sets the agentexec.Execer implementation to use.
func WithExecer(execer agentexec.Execer) Option {
return func(api *API) {
Expand Down Expand Up @@ -336,14 +345,29 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
}

// Check if the container is running and update the known devcontainers.
for _, container := range updated.Containers {
for i := range updated.Containers {
container := &updated.Containers[i]
workspaceFolder := container.Labels[DevcontainerLocalFolderLabel]
configFile := container.Labels[DevcontainerConfigFileLabel]

if workspaceFolder == "" {
continue
}

container.DevcontainerDirty = dirtyStates[workspaceFolder]
if container.DevcontainerDirty {
lastModified, hasModTime := api.configFileModifiedTimes[configFile]
if hasModTime && container.CreatedAt.After(lastModified) {
api.logger.Info(ctx, "new container created after config modification, not marking as dirty",
slog.F("container", container.ID),
slog.F("created_at", container.CreatedAt),
slog.F("config_modified_at", lastModified),
slog.F("file", configFile),
)
container.DevcontainerDirty = false
}
}

// Check if this is already in our known list.
if knownIndex := slices.IndexFunc(api.knownDevcontainers, func(dc codersdk.WorkspaceAgentDevcontainer) bool {
return dc.WorkspaceFolder == workspaceFolder
Expand All @@ -356,7 +380,7 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
}
}
api.knownDevcontainers[knownIndex].Running = container.Running
api.knownDevcontainers[knownIndex].Container = &container
api.knownDevcontainers[knownIndex].Container = container

// Check if this container was created after the config
// file was modified.
Expand Down Expand Up @@ -395,28 +419,14 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
}
}

dirty := dirtyStates[workspaceFolder]
if dirty {
lastModified, hasModTime := api.configFileModifiedTimes[configFile]
if hasModTime && container.CreatedAt.After(lastModified) {
api.logger.Info(ctx, "new container created after config modification, not marking as dirty",
slog.F("container", container.ID),
slog.F("created_at", container.CreatedAt),
slog.F("config_modified_at", lastModified),
slog.F("file", configFile),
)
dirty = false
}
}

api.knownDevcontainers = append(api.knownDevcontainers, codersdk.WorkspaceAgentDevcontainer{
ID: uuid.New(),
Name: name,
WorkspaceFolder: workspaceFolder,
ConfigPath: configFile,
Running: container.Running,
Dirty: dirty,
Container: &container,
Dirty: container.DevcontainerDirty,
Container: container,
})
}

Expand Down Expand Up @@ -510,6 +520,13 @@ func (api *API) handleDevcontainerRecreate(w http.ResponseWriter, r *http.Reques
slog.F("name", api.knownDevcontainers[i].Name),
)
api.knownDevcontainers[i].Dirty = false
// TODO(mafredri): This should be handled by a service that
// updates the devcontainer state periodically and on-demand.
api.knownDevcontainers[i].Container = nil
// Set the modified time to the zero value to indicate that
// the containers list must be refreshed. This will see to
// it that the new container is re-assigned.
api.mtime = time.Time{}
}
return
}
Expand Down Expand Up @@ -579,6 +596,9 @@ func (api *API) markDevcontainerDirty(configPath string, modifiedAt time.Time) {
slog.F("modified_at", modifiedAt),
)
api.knownDevcontainers[i].Dirty = true
if api.knownDevcontainers[i].Container != nil {
api.knownDevcontainers[i].Container.DevcontainerDirty = true
}
}
}
})
Expand Down
163 changes: 0 additions & 163 deletions agent/agentcontainers/api_internal_test.go

This file was deleted.

Loading
Loading