@@ -39,6 +39,7 @@ type API struct {
39
39
watcher watcher.Watcher
40
40
41
41
cacheDuration time.Duration
42
+ execer agentexec.Execer
42
43
cl Lister
43
44
dccli DevcontainerCLI
44
45
clock quartz.Clock
@@ -51,19 +52,15 @@ type API struct {
51
52
devcontainerNames map [string ]struct {} // Track devcontainer names to avoid duplicates.
52
53
knownDevcontainers []codersdk.WorkspaceAgentDevcontainer // Track predefined and runtime-detected devcontainers.
53
54
configFileModifiedTimes map [string ]time.Time // Track when config files were last modified.
55
+
56
+ // experimentalDevcontainersEnabled indicates if the agent is
57
+ // running in experimental mode with devcontainers enabled.
58
+ experimentalDevcontainersEnabled bool
54
59
}
55
60
56
61
// Option is a functional option for API.
57
62
type Option func (* API )
58
63
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
-
67
64
// WithClock sets the quartz.Clock implementation to use.
68
65
// This is primarily used for testing to control time.
69
66
func WithClock (clock quartz.Clock ) Option {
@@ -72,6 +69,21 @@ func WithClock(clock quartz.Clock) Option {
72
69
}
73
70
}
74
71
72
+ // WithExecer sets the agentexec.Execer implementation to use.
73
+ func WithExecer (execer agentexec.Execer ) Option {
74
+ return func (api * API ) {
75
+ api .execer = execer
76
+ }
77
+ }
78
+
79
+ // WithLister sets the agentcontainers.Lister implementation to use.
80
+ // The default implementation uses the Docker CLI to list containers.
81
+ func WithLister (cl Lister ) Option {
82
+ return func (api * API ) {
83
+ api .cl = cl
84
+ }
85
+ }
86
+
75
87
// WithDevcontainerCLI sets the DevcontainerCLI implementation to use.
76
88
// This can be used in tests to modify @devcontainer/cli behavior.
77
89
func WithDevcontainerCLI (dccli DevcontainerCLI ) Option {
@@ -105,30 +117,46 @@ func WithWatcher(w watcher.Watcher) Option {
105
117
}
106
118
107
119
// NewAPI returns a new API with the given options applied.
108
- func NewAPI (logger slog.Logger , options ... Option ) * API {
120
+ //
121
+ //nolint:revive // experimentalDevcontainersEnabled is a control flag.
122
+ func NewAPI (logger slog.Logger , experimentalDevcontainersEnabled bool , options ... Option ) * API {
109
123
ctx , cancel := context .WithCancel (context .Background ())
110
124
api := & API {
111
125
ctx : ctx ,
112
126
cancel : cancel ,
113
127
done : make (chan struct {}),
114
128
logger : logger ,
115
129
clock : quartz .NewReal (),
130
+ execer : agentexec .DefaultExecer ,
116
131
cacheDuration : defaultGetContainersCacheDuration ,
117
132
lockCh : make (chan struct {}, 1 ),
118
133
devcontainerNames : make (map [string ]struct {}),
119
134
knownDevcontainers : []codersdk.WorkspaceAgentDevcontainer {},
120
135
configFileModifiedTimes : make (map [string ]time.Time ),
136
+
137
+ experimentalDevcontainersEnabled : experimentalDevcontainersEnabled ,
121
138
}
122
139
for _ , opt := range options {
123
140
opt (api )
124
141
}
125
- if api .cl == nil {
126
- api .cl = & DockerCLILister {}
127
- }
128
- if api .dccli == nil {
129
- api .dccli = NewDevcontainerCLI (logger .Named ("devcontainer-cli" ), agentexec .DefaultExecer )
130
- }
131
- if api .watcher == nil {
142
+ if api .experimentalDevcontainersEnabled {
143
+ if api .cl == nil {
144
+ api .cl = NewDocker (api .execer )
145
+ }
146
+ if api .dccli == nil {
147
+ api .dccli = NewDevcontainerCLI (logger .Named ("devcontainer-cli" ), api .execer )
148
+ }
149
+ if api .watcher == nil {
150
+ var err error
151
+ api .watcher , err = watcher .NewFSNotify ()
152
+ if err != nil {
153
+ logger .Error (ctx , "create file watcher service failed" , slog .Error (err ))
154
+ api .watcher = watcher .NewNoop ()
155
+ }
156
+ }
157
+ } else {
158
+ api .cl = & NoopLister {}
159
+ api .dccli = & noopDevcontainerCLI {}
132
160
api .watcher = watcher .NewNoop ()
133
161
}
134
162
@@ -187,12 +215,35 @@ func (api *API) start() {
187
215
// Routes returns the HTTP handler for container-related routes.
188
216
func (api * API ) Routes () http.Handler {
189
217
r := chi .NewRouter ()
218
+
219
+ if ! api .experimentalDevcontainersEnabled {
220
+ r .Get ("/" , api .handleDisabledEmptyList )
221
+ r .Get ("/devcontainers" , api .handleDisabled )
222
+ r .Post ("/{id}/recreate" , api .handleDisabled )
223
+
224
+ return r
225
+ }
226
+
190
227
r .Get ("/" , api .handleList )
191
228
r .Get ("/devcontainers" , api .handleListDevcontainers )
192
229
r .Post ("/{id}/recreate" , api .handleRecreate )
230
+
193
231
return r
194
232
}
195
233
234
+ func (api * API ) handleDisabled (w http.ResponseWriter , r * http.Request ) {
235
+ httpapi .Write (r .Context (), w , http .StatusNotImplemented , codersdk.Response {
236
+ Message : "Devcontainers are not enabled in this agent." ,
237
+ Detail : "Devcontainers are not enabled in this agent." ,
238
+ })
239
+ }
240
+
241
+ func (api * API ) handleDisabledEmptyList (w http.ResponseWriter , r * http.Request ) {
242
+ httpapi .Write (r .Context (), w , http .StatusOK , codersdk.WorkspaceAgentListContainersResponse {
243
+ Containers : []codersdk.WorkspaceAgentContainer {},
244
+ })
245
+ }
246
+
196
247
// handleList handles the HTTP request to list containers.
197
248
func (api * API ) handleList (rw http.ResponseWriter , r * http.Request ) {
198
249
select {
0 commit comments