mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: include agent metadata in workspace list responses (#27934)
Closes #27933. Related: #27897 (single-agent GET). Agent metadata is only readable via a per-agent watch stream, so reading it across N workspaces costs N+1 requests. This adds a batch read to the list endpoint: ```text GET /api/v2/workspaces?q=param:"pool=demo" include_agent_metadata:task_status ``` - New `include_agent_metadata` search key, repeatable and key-scoped. It expands the response, it does not filter workspaces. - `GetWorkspaces` aggregates the requested keys as JSON behind a `CASE`: without opt-in the response is unchanged and the subquery never runs. Runs only for the returned page, inside the same authorized query. - Agents in the response gain `metadata` (`[]codersdk.WorkspaceAgentMetadata`, `omitempty`), mapped by the `workspace_agent_id` each element carries. The collection script is omitted; it can be long. - `codersdk.WorkspaceFilter` gains `IncludeAgentMetadata []string`. - No wildcard, no schema change, no migration. --- Authored by Coder Agents on behalf of @Emyrk.
This commit is contained in:
+44
-1
@@ -143,7 +143,7 @@ func (api *API) workspace(rw http.ResponseWriter, r *http.Request) {
|
||||
// @Security CoderSessionToken
|
||||
// @Produce json
|
||||
// @Tags Workspaces
|
||||
// @Param q query string false "Search query in the format `key:value`. Available keys are: owner, template, name, status, has-agent, dormant, last_used_after, last_used_before, has-ai-task, has_external_agent, healthy."
|
||||
// @Param q query string false "Search query in the format `key:value`. Available keys are: owner, template, name, status, has-agent, dormant, last_used_after, last_used_before, has-ai-task, has_external_agent, healthy, include_agent_metadata (expands each agent with the named metadata keys rather than filtering; repeat the key for multiple items)."
|
||||
// @Param limit query int false "Page limit"
|
||||
// @Param offset query int false "Page offset"
|
||||
// @Success 200 {object} codersdk.WorkspacesResponse
|
||||
@@ -248,6 +248,17 @@ func (api *API) workspaces(rw http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if len(filter.IncludeAgentMetadata) > 0 {
|
||||
err = attachAgentMetadata(wss, workspaceRows)
|
||||
if err != nil {
|
||||
httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{
|
||||
Message: "Internal error converting agent metadata.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
httpapi.Write(ctx, rw, http.StatusOK, codersdk.WorkspacesResponse{
|
||||
Workspaces: wss,
|
||||
Count: int(workspaceRows[0].Count),
|
||||
@@ -2758,6 +2769,38 @@ func (api *API) workspaceData(ctx context.Context, workspaces []database.Workspa
|
||||
}, nil
|
||||
}
|
||||
|
||||
// attachAgentMetadata maps the agent metadata the workspaces query
|
||||
// aggregated per workspace onto the agents in the converted response.
|
||||
// Each aggregated datum carries its workspace_agent_id.
|
||||
func attachAgentMetadata(workspaces []codersdk.Workspace, rows []database.GetWorkspacesRow) error {
|
||||
byAgent := map[uuid.UUID][]database.WorkspaceAgentMetadatum{}
|
||||
for _, row := range rows {
|
||||
if len(row.AgentMetadata) == 0 {
|
||||
continue
|
||||
}
|
||||
var metadata database.AgentMetadataAggregate
|
||||
err := metadata.Scan(row.AgentMetadata)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("scan agent metadata for workspace %q: %w", row.ID, err)
|
||||
}
|
||||
for _, datum := range metadata {
|
||||
byAgent[datum.WorkspaceAgentID] = append(byAgent[datum.WorkspaceAgentID], datum)
|
||||
}
|
||||
}
|
||||
for wi := range workspaces {
|
||||
resources := workspaces[wi].LatestBuild.Resources
|
||||
for ri := range resources {
|
||||
for ai := range resources[ri].Agents {
|
||||
agent := &resources[ri].Agents[ai]
|
||||
if metadata, ok := byAgent[agent.ID]; ok {
|
||||
agent.Metadata = convertWorkspaceAgentMetadata(metadata)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertWorkspaces(
|
||||
ctx context.Context,
|
||||
logger slog.Logger,
|
||||
|
||||
Reference in New Issue
Block a user