@@ -42,22 +42,23 @@ const (
42
42
// API is responsible for container-related operations in the agent.
43
43
// It provides methods to list and manage containers.
44
44
type API struct {
45
- ctx context.Context
46
- cancel context.CancelFunc
47
- watcherDone chan struct {}
48
- updaterDone chan struct {}
49
- initialUpdateDone chan struct {} // Closed after first update in updaterLoop.
50
- updateTrigger chan chan error // Channel to trigger manual refresh.
51
- updateInterval time.Duration // Interval for periodic container updates.
52
- logger slog.Logger
53
- watcher watcher.Watcher
54
- execer agentexec.Execer
55
- ccli ContainerCLI
56
- dccli DevcontainerCLI
57
- clock quartz.Clock
58
- scriptLogger func (logSourceID uuid.UUID ) ScriptLogger
59
- subAgentClient SubAgentClient
60
- subAgentURL string
45
+ ctx context.Context
46
+ cancel context.CancelFunc
47
+ watcherDone chan struct {}
48
+ updaterDone chan struct {}
49
+ initialUpdateDone chan struct {} // Closed after first update in updaterLoop.
50
+ updateTrigger chan chan error // Channel to trigger manual refresh.
51
+ updateInterval time.Duration // Interval for periodic container updates.
52
+ logger slog.Logger
53
+ watcher watcher.Watcher
54
+ execer agentexec.Execer
55
+ ccli ContainerCLI
56
+ containerLabelIncludeFilter map [string ]string // Labels to filter containers by.
57
+ dccli DevcontainerCLI
58
+ clock quartz.Clock
59
+ scriptLogger func (logSourceID uuid.UUID ) ScriptLogger
60
+ subAgentClient SubAgentClient
61
+ subAgentURL string
61
62
62
63
mu sync.RWMutex
63
64
closed bool
@@ -106,6 +107,16 @@ func WithContainerCLI(ccli ContainerCLI) Option {
106
107
}
107
108
}
108
109
110
+ // WithContainerLabelIncludeFilter sets a label filter for containers.
111
+ // This option can be given multiple times to filter by multiple labels.
112
+ // The behavior is such that only containers matching one or more of the
113
+ // provided labels will be included.
114
+ func WithContainerLabelIncludeFilter (label , value string ) Option {
115
+ return func (api * API ) {
116
+ api .containerLabelIncludeFilter [label ] = value
117
+ }
118
+ }
119
+
109
120
// WithDevcontainerCLI sets the DevcontainerCLI implementation to use.
110
121
// This can be used in tests to modify @devcontainer/cli behavior.
111
122
func WithDevcontainerCLI (dccli DevcontainerCLI ) Option {
@@ -198,24 +209,25 @@ func WithScriptLogger(scriptLogger func(logSourceID uuid.UUID) ScriptLogger) Opt
198
209
func NewAPI (logger slog.Logger , options ... Option ) * API {
199
210
ctx , cancel := context .WithCancel (context .Background ())
200
211
api := & API {
201
- ctx : ctx ,
202
- cancel : cancel ,
203
- watcherDone : make (chan struct {}),
204
- updaterDone : make (chan struct {}),
205
- initialUpdateDone : make (chan struct {}),
206
- updateTrigger : make (chan chan error ),
207
- updateInterval : defaultUpdateInterval ,
208
- logger : logger ,
209
- clock : quartz .NewReal (),
210
- execer : agentexec .DefaultExecer ,
211
- subAgentClient : noopSubAgentClient {},
212
- devcontainerNames : make (map [string ]bool ),
213
- knownDevcontainers : make (map [string ]codersdk.WorkspaceAgentDevcontainer ),
214
- configFileModifiedTimes : make (map [string ]time.Time ),
215
- recreateSuccessTimes : make (map [string ]time.Time ),
216
- recreateErrorTimes : make (map [string ]time.Time ),
217
- scriptLogger : func (uuid.UUID ) ScriptLogger { return noopScriptLogger {} },
218
- injectedSubAgentProcs : make (map [string ]subAgentProcess ),
212
+ ctx : ctx ,
213
+ cancel : cancel ,
214
+ watcherDone : make (chan struct {}),
215
+ updaterDone : make (chan struct {}),
216
+ initialUpdateDone : make (chan struct {}),
217
+ updateTrigger : make (chan chan error ),
218
+ updateInterval : defaultUpdateInterval ,
219
+ logger : logger ,
220
+ clock : quartz .NewReal (),
221
+ execer : agentexec .DefaultExecer ,
222
+ subAgentClient : noopSubAgentClient {},
223
+ containerLabelIncludeFilter : make (map [string ]string ),
224
+ devcontainerNames : make (map [string ]bool ),
225
+ knownDevcontainers : make (map [string ]codersdk.WorkspaceAgentDevcontainer ),
226
+ configFileModifiedTimes : make (map [string ]time.Time ),
227
+ recreateSuccessTimes : make (map [string ]time.Time ),
228
+ recreateErrorTimes : make (map [string ]time.Time ),
229
+ scriptLogger : func (uuid.UUID ) ScriptLogger { return noopScriptLogger {} },
230
+ injectedSubAgentProcs : make (map [string ]subAgentProcess ),
219
231
}
220
232
// The ctx and logger must be set before applying options to avoid
221
233
// nil pointer dereference.
@@ -266,7 +278,7 @@ func (api *API) watcherLoop() {
266
278
continue
267
279
}
268
280
269
- now := api .clock .Now ("watcherLoop" )
281
+ now := api .clock .Now ("agentcontainers" , " watcherLoop" )
270
282
switch {
271
283
case event .Has (fsnotify .Create | fsnotify .Write ):
272
284
api .logger .Debug (api .ctx , "devcontainer config file changed" , slog .F ("file" , event .Name ))
@@ -333,9 +345,9 @@ func (api *API) updaterLoop() {
333
345
}
334
346
335
347
return nil // Always nil to keep the ticker going.
336
- }, "updaterLoop" )
348
+ }, "agentcontainers" , " updaterLoop" )
337
349
defer func () {
338
- if err := ticker .Wait ("updaterLoop" ); err != nil && ! errors .Is (err , context .Canceled ) {
350
+ if err := ticker .Wait ("agentcontainers" , " updaterLoop" ); err != nil && ! errors .Is (err , context .Canceled ) {
339
351
api .logger .Error (api .ctx , "updater loop ticker failed" , slog .Error (err ))
340
352
}
341
353
}()
@@ -481,6 +493,22 @@ func (api *API) processUpdatedContainersLocked(ctx context.Context, updated code
481
493
slog .F ("config_file" , configFile ),
482
494
)
483
495
496
+ if len (api .containerLabelIncludeFilter ) > 0 {
497
+ var ok bool
498
+ for label , value := range api .containerLabelIncludeFilter {
499
+ if v , found := container .Labels [label ]; found && v == value {
500
+ ok = true
501
+ }
502
+ }
503
+ // Verbose debug logging is fine here since typically filters
504
+ // are only used in development or testing environments.
505
+ if ! ok {
506
+ logger .Debug (ctx , "container does not match include filter, ignoring dev container" , slog .F ("container_labels" , container .Labels ), slog .F ("include_filter" , api .containerLabelIncludeFilter ))
507
+ continue
508
+ }
509
+ logger .Debug (ctx , "container matches include filter, processing dev container" , slog .F ("container_labels" , container .Labels ), slog .F ("include_filter" , api .containerLabelIncludeFilter ))
510
+ }
511
+
484
512
if dc , ok := api .knownDevcontainers [workspaceFolder ]; ok {
485
513
// If no config path is set, this devcontainer was defined
486
514
// in Terraform without the optional config file. Assume the
@@ -781,7 +809,7 @@ func (api *API) recreateDevcontainer(dc codersdk.WorkspaceAgentDevcontainer, con
781
809
dc .Container .DevcontainerStatus = dc .Status
782
810
}
783
811
api .knownDevcontainers [dc .WorkspaceFolder ] = dc
784
- api .recreateErrorTimes [dc .WorkspaceFolder ] = api .clock .Now ("recreate" , "errorTimes" )
812
+ api .recreateErrorTimes [dc .WorkspaceFolder ] = api .clock .Now ("agentcontainers" , " recreate" , "errorTimes" )
785
813
api .mu .Unlock ()
786
814
return
787
815
}
@@ -803,7 +831,7 @@ func (api *API) recreateDevcontainer(dc codersdk.WorkspaceAgentDevcontainer, con
803
831
dc .Container .DevcontainerStatus = dc .Status
804
832
}
805
833
dc .Dirty = false
806
- api .recreateSuccessTimes [dc .WorkspaceFolder ] = api .clock .Now ("recreate" , "successTimes" )
834
+ api .recreateSuccessTimes [dc .WorkspaceFolder ] = api .clock .Now ("agentcontainers" , " recreate" , "successTimes" )
807
835
api .knownDevcontainers [dc .WorkspaceFolder ] = dc
808
836
api .mu .Unlock ()
809
837
0 commit comments