8000 feat(agent/agentcontainers): implement ignore customization for devcontainers by mafredri · Pull Request #18530 · coder/coder · GitHub
[go: up one dir, main page]

Skip to content

feat(agent/agentcontainers): implement ignore customization for devcontainers #18530

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 5 commits into from
Jun 24, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
rewrite
  • Loading branch information
mafredri committed Jun 24, 2025
commit e72c98ffa9811acaac3b53d94da85dbc8cf670bf
84 changes: 41 additions & 43 deletions agent/agentcontainers/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1042,7 +1042,7 @@ func (api *API) markDevcontainerDirty(configPath string, modifiedAt time.Time) {
logger.Info(api.ctx, "marking devcontainer as dirty")
dc.Dirty = true
}
if api.ignoredDevcontainers[dc.WorkspaceFolder] {
if _, ok := api.ignoredDevcontainers[dc.WorkspaceFolder]; ok {
logger.Debug(api.ctx, "clearing devcontainer ignored state")
delete(api.ignoredDevcontainers, dc.WorkspaceFolder) // Allow re-reading config.
}
Expand All @@ -1051,37 +1051,6 @@ func (api *API) markDevcontainerDirty(configPath string, modifiedAt time.Time) {
}
}

// markDevcontainerIgnored marks a devcontainer as ignored. Must not be called
// with the API lock held.
func (api *API) markDevcontainerIgnored(ctx context.Context, dc codersdk.WorkspaceAgentDevcontainer, proc subAgentProcess) subAgentProcess {
logger := api.logger.With(
slog.F("devcontainer_id", dc.ID),
slog.F("devcontainer_name", dc.Name),
slog.F("workspace_folder", dc.WorkspaceFolder),
slog.F("config_path", dc.ConfigPath),
)

// We only allow ignore to be set in the root customization layer to
// prevent weird interactions with devcontainer features.
logger.Debug(ctx, "marking devcontainer as ignored")
proc.stop()
if proc.agent.ID != uuid.Nil {
// If we stop the subagent, we also need to delete it.
client := *api.subAgentClient.Load()
if err := client.Delete(ctx, proc.agent.ID); err != nil {
api.logger.Error(ctx, "delete subagent failed", slog.Error(err), slog.F("subagent_id", proc.agent.ID))
}
}
// Reset agent and containerID to force config re-reading if ignore is toggled.
proc.agent = SubAgent{}
proc.containerID = ""
api.mu.Lock()
api.ignoredDevcontainers[dc.WorkspaceFolder] = true
api.mu.Unlock()

return proc
}

// cleanupSubAgents removes subagents that are no longer managed by
// this agent. This is usually only run at startup to ensure a clean
// slate. This method has an internal timeout to prevent blocking
Expand Down Expand Up @@ -1172,11 +1141,24 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
return xerrors.Errorf("read devcontainer config: %w", err)
}

if config.Configuration.Customizations.Coder.Ignore {
api.mu.Unlock()
proc = api.markDevcontainerIgnored(ctx, dc, proc)
api.mu.Lock()
ignore := config.Configuration.Customizations.Coder.Ignore
if ignore {
proc.stop()
if proc.agent.ID != uuid.Nil {
// Unlock while doing the delete operation.
api.mu.Unlock()
client := *api.subAgentClient.Load()
if err := client.Delete(ctx, proc.agent.ID); err != nil {
api.mu.Lock()
return xerrors.Errorf("delete subagent: %w", err)
}
api.mu.Lock()
}
// Reset agent and containerID to force config re-reading if ignore is toggled.
proc.agent = SubAgent{}
proc.containerID = ""
api.injectedSubAgentProcs[dc.WorkspaceFolder] = proc
api.ignoredDevcontainers[dc.WorkspaceFolder] = ignore
return nil
}
}
Expand Down Expand Up @@ -1219,7 +1201,11 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
ranSubAgent := false

// Clean up if injection fails.
var ignored, setIgnored bool
defer func() {
if setIgnored {
api.ignoredDevcontainers[dc.WorkspaceFolder] = ignored
}
if !ranSubAgent {
proc.stop()
if !api.closed {
Expand Down Expand Up @@ -1273,7 +1259,6 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
}

var (
ignore bool
appsWithPossibleDuplicates []SubAgentApp
workspaceFolder = DevcontainerDefaultContainerWorkspaceFolder
)
Expand All @@ -1297,13 +1282,15 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
return err
}

ignore = config.Configuration.Customizations.Coder.Ignore
workspaceFolder = config.Workspace.WorkspaceFolder

if ignore {
// We only allow ignore to be set in the root customization layer to
// prevent weird interactions with devcontainer features.
ignored, setIgnored = config.Configuration.Customizations.Coder.Ignore, true
if ignored {
return nil
}

workspaceFolder = config.Workspace.WorkspaceFolder

// 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
Expand Down Expand Up @@ -1350,8 +1337,19 @@ func (api *API) maybeInjectSubAgentIntoContainerLocked(ctx context.Context, dc c
api.logger.Error(ctx, "unable to read devcontainer config", slog.Error(err))
}

if ignore {
proc = api.markDevcontainerIgnored(ctx, dc, proc)
if ignored {
proc.stop()
if proc.agent.ID != uuid.Nil {
// If we stop the subagent, we also need to delete it.
client := *api.subAgentClient.Load()
if err := client.Delete(ctx, proc.agent.ID); err != nil {
return xerrors.Errorf("delete subagent: %w", err)
}
}
// Reset agent and containerID to force config re-reading if
// ignore is toggled.
proc.agent = SubAgent{}
proc.containerID = ""
return nil
}

Expand Down
2 changes: 1 addition & 1 deletion agent/agentcontainers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2234,7 +2234,7 @@ func TestAPI(t *testing.T) {

assert.Empty(t, response.Devcontainers, "devcontainer should be filtered out when ignore=true again")
assert.Len(t, response.Containers, 1, "regular container should still be listed")
assert.Len(t, fakeSAC.deleted, 1, "sub agent should be deleted when ignore=true")
require.Len(t, fakeSAC.deleted, 1, "sub agent should be deleted when ignore=true")
assert.Equal(t, createdAgentID, fakeSAC.deleted[0], "the same sub agent that was created should be deleted")
})
}
Expand Down
0