@@ -39,6 +39,7 @@ type API struct {
3939 watcher watcher.Watcher
4040
4141 cacheDuration time.Duration
42+ execer agentexec.Execer
4243 cl Lister
4344 dccli DevcontainerCLI
4445 clock quartz.Clock
@@ -56,14 +57,6 @@ type API struct {
5657// Option is a functional option for API.
5758type Option func (* API )
5859
59- // WithLister sets the agentcontainers.Lister implementation to use.
60- // The default implementation uses the Docker CLI to list containers.
61- func WithLister (cl Lister ) Option {
62- return func (api * API ) {
63- api .cl = cl
64- }
65- }
66-
6760// WithClock sets the quartz.Clock implementation to use.
6861// This is primarily used for testing to control time.
6962func WithClock (clock quartz.Clock ) Option {
@@ -72,6 +65,21 @@ func WithClock(clock quartz.Clock) Option {
7265 }
7366}
7467
68+ // WithExecer sets the agentexec.Execer implementation to use.
69+ func WithExecer (execer agentexec.Execer ) Option {
70+ return func (api * API ) {
71+ api .execer = execer
72+ }
73+ }
74+
75+ // WithLister sets the agentcontainers.Lister implementation to use.
76+ // The default implementation uses the Docker CLI to list containers.
77+ func WithLister (cl Lister ) Option {
78+ return func (api * API ) {
79+ api .cl = cl
80+ }
81+ }
82+
7583// WithDevcontainerCLI sets the DevcontainerCLI implementation to use.
7684// This can be used in tests to modify @devcontainer/cli behavior.
7785func WithDevcontainerCLI (dccli DevcontainerCLI ) Option {
@@ -113,6 +121,7 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
113121 done : make (chan struct {}),
114122 logger : logger ,
115123 clock : quartz .NewReal (),
124+ execer : agentexec .DefaultExecer ,
116125 cacheDuration : defaultGetContainersCacheDuration ,
117126 lockCh : make (chan struct {}, 1 ),
118127 devcontainerNames : make (map [string ]struct {}),
@@ -123,30 +132,46 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
123132 opt (api )
124133 }
125134 if api .cl == nil {
126- api .cl = & DockerCLILister {}
135+ api .cl = NewDocker ( api . execer )
127136 }
128137 if api .dccli == nil {
129- api .dccli = NewDevcontainerCLI (logger .Named ("devcontainer-cli" ), agentexec . DefaultExecer )
138+ api .dccli = NewDevcontainerCLI (logger .Named ("devcontainer-cli" ), api . execer )
130139 }
131140 if api .watcher == nil {
132- api .watcher = watcher .NewNoop ()
141+ var err error
142+ api .watcher , err = watcher .NewFSNotify ()
143+ if err != nil {
144+ logger .Error (ctx , "create file watcher service failed" , slog .Error (err ))
145+ api .watcher = watcher .NewNoop ()
146+ }
133147 }
134148
149+ go api .loop ()
150+
151+ return api
152+ }
153+
154+ // SignalReady signals the API that we are ready to begin watching for
155+ // file changes. This is used to prime the cache with the current list
156+ // of containers and to start watching the devcontainer config files for
157+ // changes. It should be called after the agent ready.
158+ func (api * API ) SignalReady () {
159+ // Prime the cache with the current list of containers.
160+ _ , _ = api .cl .List (api .ctx )
161+
135162 // Make sure we watch the devcontainer config files for changes.
136163 for _ , devcontainer := range api .knownDevcontainers {
137- if devcontainer .ConfigPath != "" {
138- if err := api .watcher .Add (devcontainer .ConfigPath ); err != nil {
139- api .logger .Error (ctx , "watch devcontainer config file failed" , slog .Error (err ), slog .F ("file" , devcontainer .ConfigPath ))
140- }
164+ if devcontainer .ConfigPath == "" {
165+ continue
141166 }
142- }
143167
144- go api .start ()
145-
146- return api
168+ if err := api .watcher .Add (devcontainer .ConfigPath ); err != nil {
169+ api .logger .Error (api .ctx , "watch devcontainer config file failed" , slog .Error (err ), slog .F ("file" , devcontainer .ConfigPath ))
170+ }
171+ }
147172}
148173
149- func (api * API ) start () {
174+ func (api * API ) loop () {
150175 defer close (api .done )
151176
152177 for {
@@ -187,9 +212,11 @@ func (api *API) start() {
187212// Routes returns the HTTP handler for container-related routes.
188213func (api * API ) Routes () http.Handler {
189214 r := chi .NewRouter ()
215+
190216 r .Get ("/" , api .handleList )
191217 r .Get ("/devcontainers" , api .handleListDevcontainers )
192218 r .Post ("/{id}/recreate" , api .handleRecreate )
219+
193220 return r
194221}
195222
0 commit comments