@@ -39,6 +39,7 @@ type API struct {
3939watcher watcher.Watcher
4040
4141cacheDuration time.Duration
42+ execer agentexec.Execer
4243cl Lister
4344dccli DevcontainerCLI
4445clock 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 {
113121done :make (chan struct {}),
114122logger :logger ,
115123clock :quartz .NewReal (),
124+ execer :agentexec .DefaultExecer ,
116125cacheDuration :defaultGetContainersCacheDuration ,
117126lockCh :make (chan struct {},1 ),
118127devcontainerNames :make (map [string ]struct {}),
@@ -123,30 +132,46 @@ func NewAPI(logger slog.Logger, options ...Option) *API {
123132opt (api )
124133}
125134if api .cl == nil {
126- api .cl = & DockerCLILister {}
135+ api .cl = NewDocker ( api . execer )
127136}
128137if api .dccli == nil {
129- api .dccli = NewDevcontainerCLI (logger .Named ("devcontainer-cli" ),agentexec . DefaultExecer )
138+ api .dccli = NewDevcontainerCLI (logger .Named ("devcontainer-cli" ),api . execer )
130139}
131140if 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.
136163for _ ,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 () {
150175defer close (api .done )
151176
152177for {
@@ -187,9 +212,11 @@ func (api *API) start() {
187212// Routes returns the HTTP handler for container-related routes.
188213func (api * API )Routes () http.Handler {
189214r := chi .NewRouter ()
215+
190216r .Get ("/" ,api .handleList )
191217r .Get ("/devcontainers" ,api .handleListDevcontainers )
192218r .Post ("/{id}/recreate" ,api .handleRecreate )
219+
193220return r
194221}
195222