1
1
package app
2
2
3
3
import (
4
+ "bufio"
5
+ "context"
4
6
"fmt"
5
7
"os"
6
8
"sort"
@@ -10,6 +12,7 @@ import (
10
12
"github.com/spf13/cobra"
11
13
12
14
"github.com/stacklok/toolhive/pkg/groups"
15
+ "github.com/stacklok/toolhive/pkg/workloads"
13
16
)
14
17
15
18
var groupCmd = & cobra.Command {
@@ -33,6 +36,17 @@ var groupListCmd = &cobra.Command{
33
36
RunE : groupListCmdFunc ,
34
37
}
35
38
39
+ var groupRmCmd = & cobra.Command {
40
+ Use : "rm [group-name]" ,
41
+ Short : "Remove a group and remove workloads from it" ,
42
+ Long : "Remove a group and remove all MCP servers from it. By default, this only removes the group " +
43
+ "membership from workloads without deleting them. Use --with-workloads to also delete the workloads. " ,
44
+ Args : cobra .ExactArgs (1 ),
45
+ RunE : groupRmCmdFunc ,
46
+ }
47
+
48
+ var withWorkloadsFlag bool
49
+
36
50
func groupCreateCmdFunc (cmd * cobra.Command , args []string ) error {
37
51
groupName := args [0 ]
38
52
ctx := cmd .Context ()
@@ -90,7 +104,153 @@ func groupListCmdFunc(cmd *cobra.Command, _ []string) error {
90
104
return nil
91
105
}
92
106
107
+ func groupRmCmdFunc (cmd * cobra.Command , args []string ) error {
108
+ groupName := args [0 ]
109
+ ctx := cmd .Context ()
110
+
111
+ if strings .EqualFold (groupName , groups .DefaultGroup ) {
112
+ return fmt .Errorf ("cannot delete the %s group" , groups .DefaultGroup )
113
+ }
114
+
115
+ groupManager , err := groups .NewManager ()
116
+ if err != nil {
117
+ return fmt .Errorf ("failed to create group manager: %w" , err )
118
+ }
119
+
120
+ // Check if group exists
121
+ exists , err := groupManager .Exists (ctx , groupName )
122
+ if err != nil {
123
+ return fmt .Errorf ("failed to check if group exists: %w" , err )
124
+ }
125
+ if ! exists {
126
+ return fmt .Errorf ("group '%s' does not exist" , groupName )
127
+ }
128
+
129
+ // Get all workloads in the group
130
+ groupWorkloads , err := groupManager .ListWorkloadsInGroup (ctx , groupName )
131
+ if err != nil {
132
+ return fmt .Errorf ("failed to list workloads in group: %w" , err )
133
+ }
134
+
135
+ // Show warning and get user confirmation
136
+ confirmed , err := showWarningAndGetConfirmation (groupName , groupWorkloads )
137
+ if err != nil {
138
+ return err
139
+ }
140
+
141
+ if ! confirmed {
142
+ return nil
143
+ }
144
+
145
+ // Handle workloads if any exist
146
+ if len (groupWorkloads ) > 0 {
147
+ if withWorkloadsFlag {
148
+ err = deleteWorkloadsInGroup (ctx , groupWorkloads , groupName )
149
+ } else {
150
+ err = removeWorkloadsMembershipFromGroup (ctx , groupWorkloads , groupName )
151
+ }
152
+ }
153
+ if err != nil {
154
+ return err
155
+ }
156
+
157
+ if err = groupManager .Delete (ctx , groupName ); err != nil {
158
+ return fmt .Errorf ("failed to delete group: %w" , err )
159
+ }
160
+
161
+ fmt .Printf ("Group '%s' deleted successfully\n " , groupName )
162
+ return nil
163
+ }
164
+
165
+ func showWarningAndGetConfirmation (groupName string , groupWorkloads []string ) (bool , error ) {
166
+ if len (groupWorkloads ) == 0 {
167
+ return true , nil
168
+ }
169
+
170
+ // Show warning and get user confirmation
171
+ if withWorkloadsFlag {
172
+ fmt .Printf ("⚠️ WARNING: This will delete group '%s' and DELETE all workloads belonging to it.\n " , groupName )
173
+ } else {
174
+ fmt .Printf ("⚠️ WARNING: This will delete group '%s' and move all workloads to the 'default' group\n " , groupName )
175
+ }
176
+
177
+ fmt .Printf (" The following %d workload(s) will be affected:\n " , len (groupWorkloads ))
178
+ for _ , workload := range groupWorkloads {
179
+ if withWorkloadsFlag {
180
+ fmt .Printf (" - %s (will be DELETED)\n " , workload )
181
+ } else {
182
+ fmt .Printf (" - %s (will be moved to the 'default' group)\n " , workload )
183
+ }
184
+ }
185
+
186
+ if withWorkloadsFlag {
187
+ fmt .Printf ("\n This action cannot be undone. Are you sure you want to continue? [y/N]: " )
188
+ } else {
189
+ fmt .Printf ("\n Are you sure you want to continue? [y/N]: " )
190
+ }
191
+
192
+ // Read user input
193
+ reader := bufio .NewReader (os .Stdin )
194
+ response , err := reader .ReadString ('\n' )
195
+ if err != nil {
196
+ return false , fmt .Errorf ("failed to read user input: %w" , err )
197
+ }
198
+
199
+ // Check if user confirmed
200
+ response = strings .TrimSpace (strings .ToLower (response ))
201
+ if response != "y" && response != "yes" {
202
+ fmt .Println ("Group deletion cancelled." )
203
+ return false , nil
204
+ }
205
+
206
+ return true , nil
207
+ }
208
+
209
+ func deleteWorkloadsInGroup (ctx context.Context , groupWorkloads []string , groupName string ) error {
210
+ // Delete workloads
211
+ workloadManager , err := workloads .NewManager (ctx )
212
+ if err != nil {
213
+ return fmt .Errorf ("failed to create workload manager: %w" , err )
214
+ }
215
+
216
+ // Delete all workloads in the group
217
+ group , err := workloadManager .DeleteWorkloads (ctx , groupWorkloads )
218
+ if err != nil {
219
+ return fmt .Errorf ("failed to delete workloads in group: %w" , err )
220
+ }
221
+
222
+ // Wait for the deletion to complete
223
+ if err := group .Wait (); err != nil {
224
+ return fmt .Errorf ("failed to delete workloads in group: %w" , err )
225
+ }
226
+
227
+ fmt .Printf ("Deleted %d workload(s) from group '%s'\n " , len (groupWorkloads ), groupName )
228
+ return nil
229
+ }
230
+
231
+ // removeWorkloadsFromGroup removes the group membership from the workloads
232
+ // in the group.
233
+ func removeWorkloadsMembershipFromGroup (ctx context.Context , groupWorkloads []string , groupName string ) error {
234
+ workloadManager , err := workloads .NewManager (ctx )
235
+ if err != nil {
236
+ return fmt .Errorf ("failed to create workload manager: %w" , err )
237
+ }
238
+
239
+ // Remove group membership from all workloads
240
+ if err := workloadManager .MoveToDefaultGroup (ctx , groupWorkloads , groupName ); err != nil {
241
+ return fmt .Errorf ("failed to move workloads to default group: %w" , err )
242
+ }
243
+
244
+ fmt .Printf ("Removed %d workload(s) from group '%s'\n " , len (groupWorkloads ), groupName )
245
+ return nil
246
+ }
247
+
93
248
func init () {
94
249
groupCmd .AddCommand (groupCreateCmd )
95
250
groupCmd .AddCommand (groupListCmd )
251
+ groupCmd .AddCommand (groupRmCmd )
252
+
253
+ // Add --with-workloads flag to group rm command
254
+ groupRmCmd .Flags ().BoolVar (& withWorkloadsFlag , "with-workloads" , false ,
255
+ "Delete all workloads in the group along with the group" )
96
256
}
0 commit comments