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

Commitf41275e

Browse files
feat(agent/agentcontainers): auto detect dev containers (#18950)
Relates tocoder/internal#711This PR implements a project discovery mechanism that searches for anydev container projects and makes them visible in the UI so that they canbe started. To make the wording on the site more clear, "Rebuild" hasbeen changed to "Start" when there is no container associated with aknown dev container configuration. I've also made it so that site willshow the dev container config path when there is no other nameavailable.### Design decisionsJust want to ensure my explanation for a few design decisions are noteddown:- We only search for dev container configurations inside gitrepositories- We only search for these git repositories if they're at the top levelor a direct child of the agent directory.This limited approach is to reduce the amount of files we ultimatelywalk when trying to find these projects. It makes sense to limit it toonly the agent directory, although I'm open to expanding how deep wesearch.
1 parentc6efe64 commitf41275e

File tree

11 files changed

+473
-26
lines changed

11 files changed

+473
-26
lines changed

‎agent/agent.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1168,7 +1168,7 @@ func (a *agent) handleManifest(manifestOK *checkpoint) func(ctx context.Context,
11681168
// return existing devcontainers but actual container detection
11691169
// and creation will be deferred.
11701170
a.containerAPI.Init(
1171-
agentcontainers.WithManifestInfo(manifest.OwnerName,manifest.WorkspaceName,manifest.AgentName),
1171+
agentcontainers.WithManifestInfo(manifest.OwnerName,manifest.WorkspaceName,manifest.AgentName,manifest.Directory),
11721172
agentcontainers.WithDevcontainers(manifest.Devcontainers,manifest.Scripts),
11731173
agentcontainers.WithSubAgentClient(agentcontainers.NewSubAgentClientFromAPI(a.logger,aAPI)),
11741174
)

‎agent/agentcontainers/api.go‎

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8+
"io/fs"
89
"maps"
910
"net/http"
1011
"os"
@@ -21,6 +22,7 @@ import (
2122
"github.com/fsnotify/fsnotify"
2223
"github.com/go-chi/chi/v5"
2324
"github.com/google/uuid"
25+
"github.com/spf13/afero"
2426
"golang.org/x/xerrors"
2527

2628
"cdr.dev/slog"
@@ -56,10 +58,12 @@ type API struct {
5658
cancel context.CancelFunc
5759
watcherDonechanstruct{}
5860
updaterDonechanstruct{}
61+
discoverDonechanstruct{}
5962
updateTriggerchanchanerror// Channel to trigger manual refresh.
6063
updateInterval time.Duration// Interval for periodic container updates.
6164
logger slog.Logger
6265
watcher watcher.Watcher
66+
fs afero.Fs
6367
execer agentexec.Execer
6468
commandEnvCommandEnv
6569
ccliContainerCLI
@@ -71,9 +75,12 @@ type API struct {
7175
subAgentURLstring
7276
subAgentEnv []string
7377

74-
ownerNamestring
75-
workspaceNamestring
76-
parentAgentstring
78+
projectDiscoverybool// If we should perform project discovery or not.
79+
80+
ownerNamestring
81+
workspaceNamestring
82+
parentAgentstring
83+
agentDirectorystring
7784

7885
mu sync.RWMutex// Protects the following fields.
7986
initDonechanstruct{}// Closed by Init.
@@ -192,11 +199,12 @@ func WithSubAgentEnv(env ...string) Option {
192199

193200
// WithManifestInfo sets the owner name, and workspace name
194201
// for the sub-agent.
195-
funcWithManifestInfo(owner,workspace,parentAgentstring)Option {
202+
funcWithManifestInfo(owner,workspace,parentAgent,agentDirectorystring)Option {
196203
returnfunc(api*API) {
197204
api.ownerName=owner
198205
api.workspaceName=workspace
199206
api.parentAgent=parentAgent
207+
api.agentDirectory=agentDirectory
200208
}
201209
}
202210

@@ -261,6 +269,21 @@ func WithWatcher(w watcher.Watcher) Option {
261269
}
262270
}
263271

272+
// WithFileSystem sets the file system used for discovering projects.
273+
funcWithFileSystem(fileSystem afero.Fs)Option {
274+
returnfunc(api*API) {
275+
api.fs=fileSystem
276+
}
277+
}
278+
279+
// WithProjectDiscovery sets if the API should attempt to discover
280+
// projects on the filesystem.
281+
funcWithProjectDiscovery(projectDiscoverybool)Option {
282+
returnfunc(api*API) {
283+
api.projectDiscovery=projectDiscovery
284+
}
285+
}
286+
264287
// ScriptLogger is an interface for sending devcontainer logs to the
265288
// controlplane.
266289
typeScriptLoggerinterface {
@@ -331,6 +354,9 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
331354
api.watcher=watcher.NewNoop()
332355
}
333356
}
357+
ifapi.fs==nil {
358+
api.fs=afero.NewOsFs()
359+
}
334360
ifapi.subAgentClient.Load()==nil {
335361
varcSubAgentClient=noopSubAgentClient{}
336362
api.subAgentClient.Store(&c)
@@ -372,13 +398,119 @@ func (api *API) Start() {
372398
return
373399
}
374400

401+
ifapi.projectDiscovery&&api.agentDirectory!="" {
402+
api.discoverDone=make(chanstruct{})
403+
404+
goapi.discover()
405+
}
406+
375407
api.watcherDone=make(chanstruct{})
376408
api.updaterDone=make(chanstruct{})
377409

378410
goapi.watcherLoop()
379411
goapi.updaterLoop()
380412
}
381413

414+
func (api*API)discover() {
415+
deferclose(api.discoverDone)
416+
deferapi.logger.Debug(api.ctx,"project discovery finished")
417+
api.logger.Debug(api.ctx,"project discovery started")
418+
419+
iferr:=api.discoverDevcontainerProjects();err!=nil {
420+
api.logger.Error(api.ctx,"discovering dev container projects",slog.Error(err))
421+
}
422+
423+
iferr:=api.RefreshContainers(api.ctx);err!=nil {
424+
api.logger.Error(api.ctx,"refreshing containers after discovery",slog.Error(err))
425+
}
426+
}
427+
428+
func (api*API)discoverDevcontainerProjects()error {
429+
isGitProject,err:=afero.DirExists(api.fs,filepath.Join(api.agentDirectory,".git"))
430+
iferr!=nil {
431+
returnxerrors.Errorf(".git dir exists: %w",err)
432+
}
433+
434+
// If the agent directory is a git project, we'll search
435+
// the project for any `.devcontainer/devcontainer.json`
436+
// files.
437+
ifisGitProject {
438+
returnapi.discoverDevcontainersInProject(api.agentDirectory)
439+
}
440+
441+
// The agent directory is _not_ a git project, so we'll
442+
// search the top level of the agent directory for any
443+
// git projects, and search those.
444+
entries,err:=afero.ReadDir(api.fs,api.agentDirectory)
445+
iferr!=nil {
446+
returnxerrors.Errorf("read agent directory: %w",err)
447+
}
448+
449+
for_,entry:=rangeentries {
450+
if!entry.IsDir() {
451+
continue
452+
}
453+
454+
isGitProject,err=afero.DirExists(api.fs,filepath.Join(api.agentDirectory,entry.Name(),".git"))
455+
iferr!=nil {
456+
returnxerrors.Errorf(".git dir exists: %w",err)
457+
}
458+
459+
// If this directory is a git project, we'll search
460+
// it for any `.devcontainer/devcontainer.json` files.
461+
ifisGitProject {
462+
iferr:=api.discoverDevcontainersInProject(filepath.Join(api.agentDirectory,entry.Name()));err!=nil {
463+
returnerr
464+
}
465+
}
466+
}
467+
468+
returnnil
469+
}
470+
471+
func (api*API)discoverDevcontainersInProject(projectPathstring)error {
472+
devcontainerConfigPaths:= []string{
473+
"/.devcontainer/devcontainer.json",
474+
"/.devcontainer.json",
475+
}
476+
477+
returnafero.Walk(api.fs,projectPath,func(pathstring,info fs.FileInfo,_error)error {
478+
ifinfo.IsDir() {
479+
returnnil
480+
}
481+
482+
for_,relativeConfigPath:=rangedevcontainerConfigPaths {
483+
if!strings.HasSuffix(path,relativeConfigPath) {
484+
continue
485+
}
486+
487+
workspaceFolder:=strings.TrimSuffix(path,relativeConfigPath)
488+
489+
api.logger.Debug(api.ctx,"discovered dev container project",slog.F("workspace_folder",workspaceFolder))
490+
491+
api.mu.Lock()
492+
if_,found:=api.knownDevcontainers[workspaceFolder];!found {
493+
api.logger.Debug(api.ctx,"adding dev container project",slog.F("workspace_folder",workspaceFolder))
494+
495+
dc:= codersdk.WorkspaceAgentDevcontainer{
496+
ID:uuid.New(),
497+
Name:"",// Updated later based on container state.
498+
WorkspaceFolder:workspaceFolder,
499+
ConfigPath:path,
500+
Status:"",// Updated later based on container state.
501+
Dirty:false,// Updated later based on config file changes.
502+
Container:nil,
503+
}
504+
505+
api.knownDevcontainers[workspaceFolder]=dc
506+
}
507+
api.mu.Unlock()
508+
}
509+
510+
returnnil
511+
})
512+
}
513+
382514
func (api*API)watcherLoop() {
383515
deferclose(api.watcherDone)
384516
deferapi.logger.Debug(api.ctx,"watcher loop stopped")
@@ -1808,6 +1940,9 @@ func (api *API) Close() error {
18081940
ifapi.updaterDone!=nil {
18091941
<-api.updaterDone
18101942
}
1943+
ifapi.discoverDone!=nil {
1944+
<-api.discoverDone
1945+
}
18111946

18121947
// Wait for all async tasks to complete.
18131948
api.asyncWg.Wait()

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp