Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit31b1ff7

Browse files
authored
feat(agent): add container list handler (#16346)
Fixes#16268- Adds `/api/v2/workspaceagents/:id/containers` coderd endpoint that allows listing containersvisible to the agent. Optional filtering by labels is supported.- Adds go tools to the `coder-dylib` CI step so we can generate mocks if needed
1 parent7076c4e commit31b1ff7

File tree

22 files changed

+1654
-2
lines changed

22 files changed

+1654
-2
lines changed

‎.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
# Generated files
2+
agent/agentcontainers/acmock/acmock.golinguist-generated=true
23
coderd/apidoc/docs.golinguist-generated=true
34
docs/reference/api/*.mdlinguist-generated=true
45
docs/reference/cli/*.mdlinguist-generated=true

‎.github/workflows/ci.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -961,6 +961,15 @@ jobs:
961961
-name:Setup Go
962962
uses:./.github/actions/setup-go
963963

964+
# Needed to build dylibs.
965+
-name:go install tools
966+
run:|
967+
go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.30
968+
go install storj.io/drpc/cmd/protoc-gen-go-drpc@v0.0.34
969+
go install golang.org/x/tools/cmd/goimports@latest
970+
go install github.com/mikefarah/yq/v4@v4.44.3
971+
go install go.uber.org/mock/mockgen@v0.5.0
972+
964973
-name:Install rcodesign
965974
if:${{ github.repository_owner == 'coder' && github.ref == 'refs/heads/main' }}
966975
run:|

‎Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,8 @@ GEN_FILES := \
563563
site/e2e/provisionerGenerated.ts\
564564
examples/examples.gen.json\
565565
$(TAILNETTEST_MOCKS)\
566-
coderd/database/pubsub/psmock/psmock.go
566+
coderd/database/pubsub/psmock/psmock.go\
567+
agent/agentcontainers/acmock/acmock.go
567568

568569

569570
# all gen targets should be added here and to gen/mark-fresh
@@ -629,6 +630,9 @@ coderd/database/dbmock/dbmock.go: coderd/database/db.go coderd/database/querier.
629630
coderd/database/pubsub/psmock/psmock.go: coderd/database/pubsub/pubsub.go
630631
go generate ./coderd/database/pubsub/psmock
631632

633+
agent/agentcontainers/acmock/acmock.go: agent/agentcontainers/containers.go
634+
go generate ./agent/agentcontainers/acmock/
635+
632636
$(TAILNETTEST_MOCKS): tailnet/coordinator.go tailnet/service.go
633637
go generate ./tailnet/tailnettest/
634638

‎agent/agent.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"tailscale.com/util/clientmetric"
3434

3535
"cdr.dev/slog"
36+
"github.com/coder/coder/v2/agent/agentcontainers"
3637
"github.com/coder/coder/v2/agent/agentexec"
3738
"github.com/coder/coder/v2/agent/agentscripts"
3839
"github.com/coder/coder/v2/agent/agentssh"
@@ -82,6 +83,7 @@ type Options struct {
8283
ServiceBannerRefreshInterval time.Duration
8384
BlockFileTransferbool
8485
Execer agentexec.Execer
86+
ContainerLister agentcontainers.Lister
8587
}
8688

8789
typeClientinterface {
@@ -122,7 +124,7 @@ func New(options Options) Agent {
122124
options.ScriptDataDir=options.TempDir
123125
}
124126
ifoptions.ExchangeToken==nil {
125-
options.ExchangeToken=func(ctx context.Context) (string,error) {
127+
options.ExchangeToken=func(_ context.Context) (string,error) {
126128
return"",nil
127129
}
128130
}
@@ -144,6 +146,9 @@ func New(options Options) Agent {
144146
ifoptions.Execer==nil {
145147
options.Execer=agentexec.DefaultExecer
146148
}
149+
ifoptions.ContainerLister==nil {
150+
options.ContainerLister=agentcontainers.NewDocker(options.Execer)
151+
}
147152

148153
hardCtx,hardCancel:=context.WithCancel(context.Background())
149154
gracefulCtx,gracefulCancel:=context.WithCancel(hardCtx)
@@ -178,6 +183,7 @@ func New(options Options) Agent {
178183
prometheusRegistry:prometheusRegistry,
179184
metrics:newAgentMetrics(prometheusRegistry),
180185
execer:options.Execer,
186+
lister:options.ContainerLister,
181187
}
182188
// Initially, we have a closed channel, reflecting the fact that we are not initially connected.
183189
// Each time we connect we replace the channel (while holding the closeMutex) with a new one
@@ -247,6 +253,7 @@ type agent struct {
247253
// labeled in Coder with the agent + workspace.
248254
metrics*agentMetrics
249255
execer agentexec.Execer
256+
lister agentcontainers.Lister
250257
}
251258

252259
func (a*agent)TailnetConn()*tailnet.Conn {

‎agent/agentcontainers/acmock/acmock.go

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

‎agent/agentcontainers/acmock/doc.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// Package acmock contains a mock implementation of agentcontainers.Lister for use in tests.
2+
package acmock
3+
4+
//go:generate mockgen -destination ./acmock.go -package acmock .. Lister

‎agent/agentcontainers/containers.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package agentcontainers
2+
3+
import (
4+
"context"
5+
"errors"
6+
"net/http"
7+
"slices"
8+
"time"
9+
10+
"golang.org/x/xerrors"
11+
12+
"github.com/coder/coder/v2/coderd/httpapi"
13+
"github.com/coder/coder/v2/codersdk"
14+
"github.com/coder/quartz"
15+
)
16+
17+
const (
18+
defaultGetContainersCacheDuration=10*time.Second
19+
dockerCreatedAtTimeFormat="2006-01-02 15:04:05 -0700 MST"
20+
getContainersTimeout=5*time.Second
21+
)
22+
23+
typedevcontainersHandlerstruct {
24+
cacheDuration time.Duration
25+
clLister
26+
clock quartz.Clock
27+
28+
// lockCh protects the below fields. We use a channel instead of a mutex so we
29+
// can handle cancellation properly.
30+
lockChchanstruct{}
31+
containers*codersdk.WorkspaceAgentListContainersResponse
32+
mtime time.Time
33+
}
34+
35+
// Option is a functional option for devcontainersHandler.
36+
typeOptionfunc(*devcontainersHandler)
37+
38+
// WithLister sets the agentcontainers.Lister implementation to use.
39+
// The default implementation uses the Docker CLI to list containers.
40+
funcWithLister(clLister)Option {
41+
returnfunc(ch*devcontainersHandler) {
42+
ch.cl=cl
43+
}
44+
}
45+
46+
// New returns a new devcontainersHandler with the given options applied.
47+
funcNew(options...Option) http.Handler {
48+
ch:=&devcontainersHandler{
49+
lockCh:make(chanstruct{},1),
50+
}
51+
for_,opt:=rangeoptions {
52+
opt(ch)
53+
}
54+
returnch
55+
}
56+
57+
func (ch*devcontainersHandler)ServeHTTP(rw http.ResponseWriter,r*http.Request) {
58+
select {
59+
case<-r.Context().Done():
60+
// Client went away.
61+
return
62+
default:
63+
ct,err:=ch.getContainers(r.Context())
64+
iferr!=nil {
65+
iferrors.Is(err,context.Canceled) {
66+
httpapi.Write(r.Context(),rw,http.StatusRequestTimeout, codersdk.Response{
67+
Message:"Could not get containers.",
68+
Detail:"Took too long to list containers.",
69+
})
70+
return
71+
}
72+
httpapi.Write(r.Context(),rw,http.StatusInternalServerError, codersdk.Response{
73+
Message:"Could not get containers.",
74+
Detail:err.Error(),
75+
})
76+
return
77+
}
78+
79+
httpapi.Write(r.Context(),rw,http.StatusOK,ct)
80+
}
81+
}
82+
83+
func (ch*devcontainersHandler)getContainers(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse,error) {
84+
select {
85+
case<-ctx.Done():
86+
return codersdk.WorkspaceAgentListContainersResponse{},ctx.Err()
87+
default:
88+
ch.lockCh<-struct{}{}
89+
}
90+
deferfunc() {
91+
<-ch.lockCh
92+
}()
93+
94+
// make zero-value usable
95+
ifch.cacheDuration==0 {
96+
ch.cacheDuration=defaultGetContainersCacheDuration
97+
}
98+
ifch.cl==nil {
99+
ch.cl=&DockerCLILister{}
100+
}
101+
ifch.containers==nil {
102+
ch.containers=&codersdk.WorkspaceAgentListContainersResponse{}
103+
}
104+
ifch.clock==nil {
105+
ch.clock=quartz.NewReal()
106+
}
107+
108+
now:=ch.clock.Now()
109+
ifnow.Sub(ch.mtime)<ch.cacheDuration {
110+
// Return a copy of the cached data to avoid accidental modification by the caller.
111+
cpy:= codersdk.WorkspaceAgentListContainersResponse{
112+
Containers:slices.Clone(ch.containers.Containers),
113+
Warnings:slices.Clone(ch.containers.Warnings),
114+
}
115+
returncpy,nil
116+
}
117+
118+
timeoutCtx,timeoutCancel:=context.WithTimeout(ctx,getContainersTimeout)
119+
defertimeoutCancel()
120+
updated,err:=ch.cl.List(timeoutCtx)
121+
iferr!=nil {
122+
return codersdk.WorkspaceAgentListContainersResponse{},xerrors.Errorf("get containers: %w",err)
123+
}
124+
ch.containers=&updated
125+
ch.mtime=now
126+
127+
// Return a copy of the cached data to avoid accidental modification by the
128+
// caller.
129+
cpy:= codersdk.WorkspaceAgentListContainersResponse{
130+
Containers:slices.Clone(ch.containers.Containers),
131+
Warnings:slices.Clone(ch.containers.Warnings),
132+
}
133+
returncpy,nil
134+
}
135+
136+
// Lister is an interface for listing containers visible to the
137+
// workspace agent.
138+
typeListerinterface {
139+
// List returns a list of containers visible to the workspace agent.
140+
// This should include running and stopped containers.
141+
List(ctx context.Context) (codersdk.WorkspaceAgentListContainersResponse,error)
142+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp