9
9
"os/exec"
10
10
11
11
"github.com/adrg/xdg"
12
+ "golang.org/x/sync/errgroup"
12
13
13
14
"github.com/stacklok/toolhive/pkg/client"
14
15
"github.com/stacklok/toolhive/pkg/config"
@@ -31,8 +32,12 @@ type Manager interface {
31
32
// DeleteWorkload deletes a container and its associated proxy process.
32
33
// The container will be stopped if it is still running.
33
34
DeleteWorkload (ctx context.Context , name string ) error
34
- // StopWorkload stops a container and its associated proxy process.
35
- StopWorkload (ctx context.Context , name string ) error
35
+ // StopWorkload stops the named workload.
36
+ // It is implemented as an asynchronous operation which returns a errgroup.Group
37
+ StopWorkload (ctx context.Context , name string ) (* errgroup.Group , error )
38
+ // StopAllWorkloads stops all running workloads.
39
+ // It is implemented as an asynchronous operation which returns an errgroup.Group
40
+ StopAllWorkloads (ctx context.Context ) (* errgroup.Group , error )
36
41
// RunWorkload runs a container in the foreground.
37
42
RunWorkload (ctx context.Context , runConfig * runner.RunConfig ) error
38
43
// RunWorkloadDetached runs a container in the background.
@@ -158,44 +163,45 @@ func (d *defaultManager) DeleteWorkload(ctx context.Context, name string) error
158
163
return nil
159
164
}
160
165
161
- func (d * defaultManager ) StopWorkload (ctx context.Context , name string ) error {
162
- // Find the container ID
163
- containerID , err := d .findContainerID (ctx , name )
166
+ func (d * defaultManager ) StopWorkload (ctx context.Context , name string ) ( * errgroup. Group , error ) {
167
+ // Find the container
168
+ container , err := d .findContainerByName (ctx , name )
164
169
if err != nil {
165
- return err
170
+ return nil , err
166
171
}
167
172
168
- // Check if the container is running
169
- running , err := d .runtime .IsWorkloadRunning (ctx , containerID )
170
- if err != nil {
171
- return fmt .Errorf ("failed to check if container is running: %v" , err )
172
- }
173
+ containerID := container .ID
174
+ containerBaseName := labels .GetContainerBaseName (container .Labels )
175
+ running := isContainerRunning (container )
173
176
174
177
if ! running {
175
- return fmt .Errorf ("%w: %s" , ErrContainerNotRunning , name )
178
+ return nil , fmt .Errorf ("%w: %s" , ErrContainerNotRunning , name )
176
179
}
177
180
178
- // Get the base container name
179
- containerBaseName , _ := d .getContainerBaseName (ctx , containerID )
180
-
181
- // Stop the proxy process
182
- proxy .StopProcess (containerBaseName )
181
+ workload := stopWorkloadRequest {Name : containerBaseName , ID : containerID }
182
+ // Do the actual stop operation in the background, and return an error group.
183
+ return d .stopWorkloads (ctx , []stopWorkloadRequest {workload }), nil
184
+ }
183
185
184
- // Stop the container
185
- err = d .stopContainer (ctx , containerID , name )
186
+ func (d * defaultManager ) StopAllWorkloads (ctx context.Context ) (* errgroup.Group , error ) {
187
+ // Get list of all running workloads.
188
+ containers , err := d .runtime .ListWorkloads (ctx )
186
189
if err != nil {
187
- return err
190
+ return nil , fmt . Errorf ( "failed to list containers: %v" , err )
188
191
}
189
192
190
- if shouldRemoveClientConfig () {
191
- if err := removeClientConfigurations (name ); err != nil {
192
- logger .Warnf ("Warning: Failed to remove client configurations: %v" , err )
193
- } else {
194
- logger .Infof ("Client configurations for %s removed" , name )
193
+ // Duplicates the logic of GetWorkloads, but is simple enough that it's not
194
+ // worth duplicating.
195
+ stopRequests := make ([]stopWorkloadRequest , 0 , len (containers ))
196
+ for _ , c := range containers {
197
+ // If the caller did not set `listAll` to true, only include running containers.
198
+ if labels .IsToolHiveContainer (c .Labels ) && isContainerRunning (& c ) {
199
+ req := stopWorkloadRequest {Name : labels .GetContainerBaseName (c .Labels ), ID : c .ID }
200
+ stopRequests = append (stopRequests , req )
195
201
}
196
202
}
197
203
198
- return nil
204
+ return d . stopWorkloads ( ctx , stopRequests ), nil
199
205
}
200
206
201
207
func (* defaultManager ) RunWorkload (ctx context.Context , runConfig * runner.RunConfig ) error {
@@ -449,14 +455,6 @@ func (d *defaultManager) RestartWorkload(ctx context.Context, name string) error
449
455
return d .RunWorkloadDetached (mcpRunner .Config )
450
456
}
451
457
452
- func (d * defaultManager ) findContainerID (ctx context.Context , name string ) (string , error ) {
453
- c , err := d .findContainerByName (ctx , name )
454
- if err != nil {
455
- return "" , err
456
- }
457
- return c .ID , nil
458
- }
459
-
460
458
func (d * defaultManager ) findContainerByName (ctx context.Context , name string ) (* rt.ContainerInfo , error ) {
461
459
// List containers to find the one with the given name
462
460
containers , err := d .runtime .ListWorkloads (ctx )
@@ -486,22 +484,6 @@ func (d *defaultManager) findContainerByName(ctx context.Context, name string) (
486
484
return nil , fmt .Errorf ("%w: %s" , ErrContainerNotFound , name )
487
485
}
488
486
489
- // getContainerBaseName gets the base container name from the container labels
490
- func (d * defaultManager ) getContainerBaseName (ctx context.Context , containerID string ) (string , error ) {
491
- containers , err := d .runtime .ListWorkloads (ctx )
492
- if err != nil {
493
- return "" , fmt .Errorf ("failed to list containers: %v" , err )
494
- }
495
-
496
- for _ , c := range containers {
497
- if c .ID == containerID {
498
- return labels .GetContainerBaseName (c .Labels ), nil
499
- }
500
- }
501
-
502
- return "" , fmt .Errorf ("container %s not found" , containerID )
503
- }
504
-
505
487
// stopContainer stops the container
506
488
func (d * defaultManager ) stopContainer (ctx context.Context , containerID , containerName string ) error {
507
489
logger .Infof ("Stopping container %s..." , containerName )
@@ -594,3 +576,39 @@ func (*defaultManager) cleanupTempPermissionProfile(ctx context.Context, baseNam
594
576
595
577
return nil
596
578
}
579
+
580
+ // Internal type used when stopping workloads.
581
+ type stopWorkloadRequest struct {
582
+ Name string
583
+ ID string
584
+ }
585
+
586
+ // stopWorkloads stops the named workloads concurrently.
587
+ // It assumes that the workloads exist in the running state.
588
+ func (d * defaultManager ) stopWorkloads (ctx context.Context , workloads []stopWorkloadRequest ) * errgroup.Group {
589
+ group := errgroup.Group {}
590
+ for _ , workload := range workloads {
591
+ group .Go (func () error {
592
+ // Stop the proxy process
593
+ proxy .StopProcess (workload .Name )
594
+
595
+ // Stop the container
596
+ err := d .stopContainer (ctx , workload .ID , workload .Name )
597
+ if err != nil {
598
+ return err
599
+ }
600
+
601
+ if shouldRemoveClientConfig () {
602
+ if err := removeClientConfigurations (workload .Name ); err != nil {
603
+ logger .Warnf ("Warning: Failed to remove client configurations: %v" , err )
604
+ } else {
605
+ logger .Infof ("Client configurations for %s removed" , workload .Name )
606
+ }
607
+ }
608
+
609
+ return nil
610
+ })
611
+ }
612
+
613
+ return & group
614
+ }
0 commit comments