Skip to content

feat(agent): add container list handler #16346

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 13 commits into from
Feb 10, 2025
Merged

Conversation

johnstcn
Copy link
Member

@johnstcn johnstcn commented Jan 30, 2025

Fixes #16268

This PR adds an API endpoint to coderd /api/v2/workspaceagents/:id/containers that allows listing containers visible to the agent. Optional filtering by labels is supported.

A later PR will consume this endpoint and use it to expose this information to users.

Under the hood, this operates in a very similar manner to the listening-ports endpoint -- coderd dials the workspace agent and hits an agent endpoint exposed over tailnet.

This initial implementation only supports listing containers using the Docker CLI. Support for other data sources may be added at a future date.

Sample response:

{
  "containers": [
    {
      "created_at": "2025-02-05T12:53:18.943217568Z",
      "id": "1f119acf43202781f7699534fe3613ce2764369148738a16970fb124233a7c58",
      "name": "affectionate_black",
      "image": "busybox:latest",
      "labels": {

      },
      "running": false,
      "ports": [],
      "status": "exited with code 127: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: \"doesnotexist\": executable file not found in $PATH: unknown",
      "volumes": {

      }
    },
    {
      "created_at": "2025-02-05T12:49:16.182445827Z",
      "id": "972e129e48bcf3420113f0d8d1570d5c3dbb5045ad46cc688c692c76e7cf9382",
      "name": "eloquent_cartwright",
      "image": "busybox:latest",
      "labels": {

      },
      "running": true,
      "ports": [],
      "status": "running",
      "volumes": {
        "testing": "/testing"
      }
    }
  }
]

Sample error response:

{
  "message": "Internal error fetching containers.",
  "detail": "GET http://[fd7a:115c:a1e0:4ca7:9e0e:688f:a0d8:ac55]:4/api/v0/containers: unexpected status code 500: Could not get containers.\n\tError: get containers: run docker ps: exit status 1"
}

@johnstcn johnstcn self-assigned this Jan 30, 2025
@johnstcn johnstcn force-pushed the cj/agent-containers-list branch 4 times, most recently from a17ca58 to 798f1a3 Compare February 4, 2025 21:38
Comment on lines 5 to 18
func Subset[T, U comparable](a, b map[T]U) bool {
for ka, va := range a {
if vb, ok := b[ka]; !ok {
return false
} else if va != vb {
return false
}
}
return true
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review: I couldn't find a stdlib function for this, so rolled my own. I'd love to get rid of this.

@johnstcn johnstcn force-pushed the cj/agent-containers-list branch from 7a55cf7 to a56462e Compare February 5, 2025 15:58
@johnstcn johnstcn marked this pull request as ready for review February 5, 2025 16:15
}
}

// convertDockerVolume converts a Docker volume string to a host path and
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, if there's pre-existing code I'd be happy to use this.

// Running is true if the container is currently running.
Running bool `json:"running"`
// Ports includes ports exposed by the container.
Ports []WorkspaceAgentListeningPort `json:"ports"`
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm re-using this struct, as any exposed port should also function the same way as a "listening port".

Copy link
Contributor

@dannykopping dannykopping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work 👍
Left mostly small comments

return
}

// Filter in-place by labels
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious why we don't pass the label filtering down to where the data is collected?
docker can filter by label

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found that co-locating filtering and caching gets difficult.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point Cian, although I'd question the premise of caching at this stage, did you find it was a requirement to have any form of responsive UI? Is docker ps too slow?

It would be easier to just run multiple docker ps commands in parallel if there are multiple requests inbound.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, perhaps. The caching logic isn't overly complex, and I was mainly copying the pattern from the listening-ports handler. I can remove it if you feel strongly about it, but I suspect it'll be needed -- especially if someone is running lots of containers in their workspace.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, I simply raised it since a little slow is probably fine for the first deliverable, and caching prematurely may solve or hide the wrong problem. I leave it up to you though.

Copy link
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, Cian! Looking past nits and suggestions, in broad strokes I'd like to see:

  • Separate agent/containers package
  • No explicit dependency on docker for testing (e.g. skip)
  • Single "container" output from agent that can be consumed in a generic way in e.g. UI, so that docker can be swapped out for another backend and everything behaves the same
  • s/container/devcontainer/ makes sense to me, but perhaps I'm overlooking something

agent/api.go Outdated
@@ -35,7 +35,11 @@ func (a *agent) apiHandler() http.Handler {
ignorePorts: cpy,
cacheDuration: cacheDuration,
}
ch := &containersHandler{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this level, calling this devcontainers rather than containers would make more sense to me. The idea being that we process devcontainer related stuff on the agent and expose it in a neat package to coderd and don't need logic over there to handle different types.

I might be overlooking something, though, so feel free to dismiss if there's a good reason for the naming.

return
}

// Filter in-place by labels
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point Cian, although I'd question the premise of caching at this stage, did you find it was a requirement to have any form of responsive UI? Is docker ps too slow?

It would be easier to just run multiple docker ps commands in parallel if there are multiple requests inbound.

@johnstcn
Copy link
Member Author

johnstcn commented Feb 6, 2025

@dannykopping @mafredri I think I'm ready for round 2 now. I still need to use agentexec.Execer instead but I think I've addressed all of your comments.

Copy link
Member

@mafredri mafredri left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is looking quite good! Just a few minor things and while testing this PR I realized we don't include mounts in the output. I think that could be relevant, like volumes, but I'll defer to you whether or not we include those.

We should still make the agentexec changes, but other than that, approved!

Copy link
Contributor

@dannykopping dannykopping left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :shipit:

Adds an API endpoint to coderd `/api/v2/workspaceagents/:id/containers`
that allows listing containers visible to the agent. This initial
implementation only supports listing containers using the Docker CLI.
Support for other data sources may be added at a future date.
@johnstcn johnstcn force-pushed the cj/agent-containers-list branch from 2c87b7e to 8b081ac Compare February 10, 2025 11:06
@johnstcn johnstcn merged commit 31b1ff7 into main Feb 10, 2025
35 checks passed
@johnstcn johnstcn deleted the cj/agent-containers-list branch February 10, 2025 11:29
@github-actions github-actions bot locked and limited conversation to collaborators Feb 10, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Agent: allow listing running devcontainers
3 participants