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

Commit00b5f56

Browse files
authored
feat(agent/agentcontainers): add devcontainers list endpoint (#17389)
This change allows listing both predefined and runtime-detecteddevcontainers, as well as showing whether or not the devcontainer isrunning and which container represents it.Fixescoder/internal#478
1 parentc8c4de5 commit00b5f56

File tree

5 files changed

+487
-19
lines changed

5 files changed

+487
-19
lines changed

‎agent/agentcontainers/api.go

Lines changed: 120 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ package agentcontainers
33
import (
44
"context"
55
"errors"
6+
"fmt"
67
"net/http"
8+
"path"
79
"slices"
10+
"strings"
811
"time"
912

1013
"github.com/go-chi/chi/v5"
14+
"github.com/google/uuid"
1115
"golang.org/x/xerrors"
1216

1317
"cdr.dev/slog"
@@ -31,11 +35,13 @@ type API struct {
3135
dccliDevcontainerCLI
3236
clock quartz.Clock
3337

34-
// lockCh protects the below fields. We use a channel instead of a mutex so we
35-
// can handle cancellation properly.
36-
lockChchanstruct{}
37-
containers codersdk.WorkspaceAgentListContainersResponse
38-
mtime time.Time
38+
// lockCh protects the below fields. We use a channel instead of a
39+
// mutex so we can handle cancellation properly.
40+
lockChchanstruct{}
41+
containers codersdk.WorkspaceAgentListContainersResponse
42+
mtime time.Time
43+
devcontainerNamesmap[string]struct{}// Track devcontainer names to avoid duplicates.
44+
knownDevcontainers []codersdk.WorkspaceAgentDevcontainer// Track predefined and runtime-detected devcontainers.
3945
}
4046

4147
// Option is a functional option for API.
@@ -55,12 +61,29 @@ func WithDevcontainerCLI(dccli DevcontainerCLI) Option {
5561
}
5662
}
5763

64+
// WithDevcontainers sets the known devcontainers for the API. This
65+
// allows the API to be aware of devcontainers defined in the workspace
66+
// agent manifest.
67+
funcWithDevcontainers(devcontainers []codersdk.WorkspaceAgentDevcontainer)Option {
68+
returnfunc(api*API) {
69+
iflen(devcontainers)>0 {
70+
api.knownDevcontainers=slices.Clone(devcontainers)
71+
api.devcontainerNames=make(map[string]struct{},len(devcontainers))
72+
for_,devcontainer:=rangedevcontainers {
73+
api.devcontainerNames[devcontainer.Name]=struct{}{}
74+
}
75+
}
76+
}
77+
}
78+
5879
// NewAPI returns a new API with the given options applied.
5980
funcNewAPI(logger slog.Logger,options...Option)*API {
6081
api:=&API{
61-
clock:quartz.NewReal(),
62-
cacheDuration:defaultGetContainersCacheDuration,
63-
lockCh:make(chanstruct{},1),
82+
clock:quartz.NewReal(),
83+
cacheDuration:defaultGetContainersCacheDuration,
84+
lockCh:make(chanstruct{},1),
85+
devcontainerNames:make(map[string]struct{}),
86+
knownDevcontainers: []codersdk.WorkspaceAgentDevcontainer{},
6487
}
6588
for_,opt:=rangeoptions {
6689
opt(api)
@@ -79,6 +102,7 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
79102
func (api*API)Routes() http.Handler {
80103
r:=chi.NewRouter()
81104
r.Get("/",api.handleList)
105+
r.Get("/devcontainers",api.handleListDevcontainers)
82106
r.Post("/{id}/recreate",api.handleRecreate)
83107
returnr
84108
}
@@ -121,12 +145,11 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
121145
select {
122146
case<-ctx.Done():
123147
return codersdk.WorkspaceAgentListContainersResponse{},ctx.Err()
124-
default:
125-
api.lockCh<-struct{}{}
148+
caseapi.lockCh<-struct{}{}:
149+
deferfunc() {
150+
<-api.lockCh
151+
}()
126152
}
127-
deferfunc() {
128-
<-api.lockCh
129-
}()
130153

131154
now:=api.clock.Now()
132155
ifnow.Sub(api.mtime)<api.cacheDuration {
@@ -142,6 +165,53 @@ func (api *API) getContainers(ctx context.Context) (codersdk.WorkspaceAgentListC
142165
api.containers=updated
143166
api.mtime=now
144167

168+
// Reset all known devcontainers to not running.
169+
fori:=rangeapi.knownDevcontainers {
170+
api.knownDevcontainers[i].Running=false
171+
api.knownDevcontainers[i].Container=nil
172+
}
173+
174+
// Check if the container is running and update the known devcontainers.
175+
for_,container:=rangeupdated.Containers {
176+
workspaceFolder:=container.Labels[DevcontainerLocalFolderLabel]
177+
ifworkspaceFolder!="" {
178+
// Check if this is already in our known list.
179+
ifknownIndex:=slices.IndexFunc(api.knownDevcontainers,func(dc codersdk.WorkspaceAgentDevcontainer)bool {
180+
returndc.WorkspaceFolder==workspaceFolder
181+
});knownIndex!=-1 {
182+
// Update existing entry with runtime information.
183+
ifapi.knownDevcontainers[knownIndex].ConfigPath=="" {
184+
api.knownDevcontainers[knownIndex].ConfigPath=container.Labels[DevcontainerConfigFileLabel]
185+
}
186+
api.knownDevcontainers[knownIndex].Running=container.Running
187+
api.knownDevcontainers[knownIndex].Container=&container
188+
continue
189+
}
190+
191+
// If not in our known list, add as a runtime detected entry.
192+
name:=path.Base(workspaceFolder)
193+
if_,ok:=api.devcontainerNames[name];ok {
194+
// Try to find a unique name by appending a number.
195+
fori:=2; ;i++ {
196+
newName:=fmt.Sprintf("%s-%d",name,i)
197+
if_,ok:=api.devcontainerNames[newName];!ok {
198+
name=newName
199+
break
200+
}
201+
}
202+
}
203+
api.devcontainerNames[name]=struct{}{}
204+
api.knownDevcontainers=append(api.knownDevcontainers, codersdk.WorkspaceAgentDevcontainer{
205+
ID:uuid.New(),
206+
Name:name,
207+
WorkspaceFolder:workspaceFolder,
208+
ConfigPath:container.Labels[DevcontainerConfigFileLabel],
209+
Running:container.Running,
210+
Container:&container,
211+
})
212+
}
213+
}
214+
145215
returncopyListContainersResponse(api.containers),nil
146216
}
147217

@@ -158,7 +228,7 @@ func (api *API) handleRecreate(w http.ResponseWriter, r *http.Request) {
158228
return
159229
}
160230

161-
containers,err:=api.cl.List(ctx)
231+
containers,err:=api.getContainers(ctx)
162232
iferr!=nil {
163233
httpapi.Write(ctx,w,http.StatusInternalServerError, codersdk.Response{
164234
Message:"Could not list containers",
@@ -203,3 +273,39 @@ func (api *API) handleRecreate(w http.ResponseWriter, r *http.Request) {
203273

204274
w.WriteHeader(http.StatusNoContent)
205275
}
276+
277+
// handleListDevcontainers handles the HTTP request to list known devcontainers.
278+
func (api*API)handleListDevcontainers(w http.ResponseWriter,r*http.Request) {
279+
ctx:=r.Context()
280+
281+
// Run getContainers to detect the latest devcontainers and their state.
282+
_,err:=api.getContainers(ctx)
283+
iferr!=nil {
284+
httpapi.Write(ctx,w,http.StatusInternalServerError, codersdk.Response{
285+
Message:"Could not list containers",
286+
Detail:err.Error(),
287+
})
288+
return
289+
}
290+
291+
select {
292+
case<-ctx.Done():
293+
return
294+
caseapi.lockCh<-struct{}{}:
295+
}
296+
devcontainers:=slices.Clone(api.knownDevcontainers)
297+
<-api.lockCh
298+
299+
slices.SortFunc(devcontainers,func(a,b codersdk.WorkspaceAgentDevcontainer)int {
300+
ifcmp:=strings.Compare(a.WorkspaceFolder,b.WorkspaceFolder);cmp!=0 {
301+
returncmp
302+
}
303+
returnstrings.Compare(a.ConfigPath,b.ConfigPath)
304+
})
305+
306+
response:= codersdk.WorkspaceAgentDevcontainersResponse{
307+
Devcontainers:devcontainers,
308+
}
309+
310+
httpapi.Write(ctx,w,http.StatusOK,response)
311+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp