Skip to content

Commit c21211a

Browse files
authored
Update client config when workload group changes (#1442)
1 parent 4996fd6 commit c21211a

File tree

9 files changed

+311
-77
lines changed

9 files changed

+311
-77
lines changed

cmd/thv/app/group.go

Lines changed: 72 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ import (
1010

1111
"github.com/spf13/cobra"
1212

13+
"github.com/stacklok/toolhive/pkg/client"
14+
runtime "github.com/stacklok/toolhive/pkg/container/runtime"
15+
"github.com/stacklok/toolhive/pkg/core"
1316
"github.com/stacklok/toolhive/pkg/groups"
1417
"github.com/stacklok/toolhive/pkg/validation"
1518
"github.com/stacklok/toolhive/pkg/workloads"
@@ -141,10 +144,15 @@ func groupRmCmdFunc(cmd *cobra.Command, args []string) error {
141144
return fmt.Errorf("failed to create workloads manager: %w", err)
142145
}
143146

144-
// Get all workloads in the group
145-
groupWorkloads, err := workloadsManager.ListWorkloadsInGroup(ctx, groupName)
147+
// Get all workloads and filter for the group
148+
allWorkloads, err := workloadsManager.ListWorkloads(ctx, true) // listAll=true to include stopped workloads
146149
if err != nil {
147-
return fmt.Errorf("failed to list workloads in group: %w", err)
150+
return fmt.Errorf("failed to list workloads: %w", err)
151+
}
152+
153+
groupWorkloads, err := workloads.FilterByGroup(allWorkloads, groupName)
154+
if err != nil {
155+
return fmt.Errorf("failed to filter workloads by group: %w", err)
148156
}
149157

150158
// Show warning and get user confirmation
@@ -160,9 +168,9 @@ func groupRmCmdFunc(cmd *cobra.Command, args []string) error {
160168
// Handle workloads if any exist
161169
if len(groupWorkloads) > 0 {
162170
if withWorkloadsFlag {
163-
err = deleteWorkloadsInGroup(ctx, groupWorkloads, groupName)
171+
err = deleteWorkloadsInGroup(ctx, workloadsManager, groupWorkloads, groupName)
164172
} else {
165-
err = removeWorkloadsMembershipFromGroup(ctx, groupWorkloads, groupName)
173+
err = moveWorkloadsToGroup(ctx, workloadsManager, groupWorkloads, groupName, groups.DefaultGroup)
166174
}
167175
}
168176
if err != nil {
@@ -177,7 +185,7 @@ func groupRmCmdFunc(cmd *cobra.Command, args []string) error {
177185
return nil
178186
}
179187

180-
func showWarningAndGetConfirmation(groupName string, groupWorkloads []string) (bool, error) {
188+
func showWarningAndGetConfirmation(groupName string, groupWorkloads []core.Workload) (bool, error) {
181189
if len(groupWorkloads) == 0 {
182190
return true, nil
183191
}
@@ -192,9 +200,9 @@ func showWarningAndGetConfirmation(groupName string, groupWorkloads []string) (b
192200
fmt.Printf(" The following %d workload(s) will be affected:\n", len(groupWorkloads))
193201
for _, workload := range groupWorkloads {
194202
if withWorkloadsFlag {
195-
fmt.Printf(" - %s (will be DELETED)\n", workload)
203+
fmt.Printf(" - %s (will be DELETED)\n", workload.Name)
196204
} else {
197-
fmt.Printf(" - %s (will be moved to the 'default' group)\n", workload)
205+
fmt.Printf(" - %s (will be moved to the 'default' group)\n", workload.Name)
198206
}
199207
}
200208

@@ -221,15 +229,20 @@ func showWarningAndGetConfirmation(groupName string, groupWorkloads []string) (b
221229
return true, nil
222230
}
223231

224-
func deleteWorkloadsInGroup(ctx context.Context, groupWorkloads []string, groupName string) error {
225-
// Delete workloads
226-
workloadManager, err := workloads.NewManager(ctx)
227-
if err != nil {
228-
return fmt.Errorf("failed to create workload manager: %w", err)
232+
func deleteWorkloadsInGroup(
233+
ctx context.Context,
234+
workloadManager workloads.Manager,
235+
groupWorkloads []core.Workload,
236+
groupName string,
237+
) error {
238+
// Extract workload names for deletion
239+
var workloadNames []string
240+
for _, workload := range groupWorkloads {
241+
workloadNames = append(workloadNames, workload.Name)
229242
}
230243

231244
// Delete all workloads in the group
232-
group, err := workloadManager.DeleteWorkloads(ctx, groupWorkloads)
245+
group, err := workloadManager.DeleteWorkloads(ctx, workloadNames)
233246
if err != nil {
234247
return fmt.Errorf("failed to delete workloads in group: %w", err)
235248
}
@@ -243,20 +256,56 @@ func deleteWorkloadsInGroup(ctx context.Context, groupWorkloads []string, groupN
243256
return nil
244257
}
245258

246-
// removeWorkloadsFromGroup removes the group membership from the workloads
247-
// in the group.
248-
func removeWorkloadsMembershipFromGroup(ctx context.Context, groupWorkloads []string, groupName string) error {
249-
workloadManager, err := workloads.NewManager(ctx)
250-
if err != nil {
251-
return fmt.Errorf("failed to create workload manager: %w", err)
259+
// moveWorkloadsToGroup moves all workloads in the specified group to a new group.
260+
func moveWorkloadsToGroup(
261+
ctx context.Context,
262+
workloadManager workloads.Manager,
263+
groupWorkloads []core.Workload,
264+
groupFrom string,
265+
groupTo string,
266+
) error {
267+
268+
// Extract workload names for the move operation
269+
var workloadNames []string
270+
for _, workload := range groupWorkloads {
271+
workloadNames = append(workloadNames, workload.Name)
252272
}
253273

254-
// Remove group membership from all workloads
255-
if err := workloadManager.MoveToDefaultGroup(ctx, groupWorkloads, groupName); err != nil {
274+
// Update workload runconfigs to point to the new group
275+
if err := workloadManager.MoveToGroup(ctx, workloadNames, groupFrom, groupTo); err != nil {
256276
return fmt.Errorf("failed to move workloads to default group: %w", err)
257277
}
258278

259-
fmt.Printf("Removed %d workload(s) from group '%s'\n", len(groupWorkloads), groupName)
279+
// Update client configurations for the moved workloads
280+
err := updateClientConfigurations(ctx, groupWorkloads, groupFrom, groupTo)
281+
if err != nil {
282+
return fmt.Errorf("failed to update client configurations with new group: %w", err)
283+
}
284+
285+
fmt.Printf("Moved %d workload(s) from group '%s' to group '%s'\n", len(groupWorkloads), groupFrom, groupTo)
286+
return nil
287+
}
288+
289+
func updateClientConfigurations(ctx context.Context, groupWorkloads []core.Workload, groupFrom string, groupTo string) error {
290+
clientManager, err := client.NewManager(ctx)
291+
if err != nil {
292+
return fmt.Errorf("failed to create client manager: %w", err)
293+
}
294+
295+
for _, w := range groupWorkloads {
296+
// Only update client configurations for running workloads
297+
if w.Status != runtime.WorkloadStatusRunning {
298+
continue
299+
}
300+
301+
if err := clientManager.RemoveServerFromClients(ctx, w.Name, groupFrom); err != nil {
302+
return fmt.Errorf("failed to remove server %s from client configurations: %w", w.Name, err)
303+
}
304+
if err := clientManager.AddServerToClients(ctx, w.Name, w.URL, string(w.TransportType), groupTo); err != nil {
305+
return fmt.Errorf("failed to add server %s to client configurations: %w", w.Name, err)
306+
}
307+
}
308+
260309
return nil
261310
}
262311

pkg/api/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ func Serve(
190190
"/api/v1beta/discovery": v1.DiscoveryRouter(),
191191
"/api/v1beta/clients": v1.ClientRouter(clientManager, workloadManager, groupManager),
192192
"/api/v1beta/secrets": v1.SecretsRouter(),
193-
"/api/v1beta/groups": v1.GroupsRouter(groupManager, workloadManager),
193+
"/api/v1beta/groups": v1.GroupsRouter(groupManager, workloadManager, clientManager),
194194
}
195195

196196
// Only mount docs router if enabled

pkg/api/v1/groups.go

Lines changed: 89 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package v1
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"net/http"
78

89
"github.com/go-chi/chi/v5"
910

11+
"github.com/stacklok/toolhive/pkg/client"
12+
"github.com/stacklok/toolhive/pkg/container/runtime"
13+
"github.com/stacklok/toolhive/pkg/core"
1014
"github.com/stacklok/toolhive/pkg/errors"
1115
"github.com/stacklok/toolhive/pkg/groups"
1216
"github.com/stacklok/toolhive/pkg/logger"
@@ -18,13 +22,15 @@ import (
1822
type GroupsRoutes struct {
1923
groupManager groups.Manager
2024
workloadManager workloads.Manager
25+
clientManager client.Manager
2126
}
2227

2328
// GroupsRouter creates a new GroupsRoutes instance.
24-
func GroupsRouter(groupManager groups.Manager, workloadManager workloads.Manager) http.Handler {
29+
func GroupsRouter(groupManager groups.Manager, workloadManager workloads.Manager, clientManager client.Manager) http.Handler {
2530
routes := GroupsRoutes{
2631
groupManager: groupManager,
2732
workloadManager: workloadManager,
33+
clientManager: clientManager,
2834
}
2935

3036
r := chi.NewRouter()
@@ -202,42 +208,27 @@ func (s *GroupsRoutes) deleteGroup(w http.ResponseWriter, r *http.Request) {
202208
// Get the with-workloads flag from query parameter
203209
withWorkloads := r.URL.Query().Get("with-workloads") == "true"
204210

205-
// Get all workloads in the group
206-
groupWorkloads, err := s.workloadManager.ListWorkloadsInGroup(ctx, name)
211+
// Get all workloads and filter for the group
212+
allWorkloads, err := s.workloadManager.ListWorkloads(ctx, true) // listAll=true to include stopped workloads
207213
if err != nil {
208-
logger.Errorf("Failed to list workloads in group %s: %v", name, err)
209-
http.Error(w, "Failed to list workloads in group", http.StatusInternalServerError)
214+
logger.Errorf("Failed to list workloads: %v", err)
215+
http.Error(w, "Failed to list workloads", http.StatusInternalServerError)
216+
return
217+
}
218+
219+
groupWorkloads, err := workloads.FilterByGroup(allWorkloads, name)
220+
if err != nil {
221+
logger.Errorf("Failed to filter workloads by group %s: %v", name, err)
222+
http.Error(w, "Failed to filter workloads by group", http.StatusInternalServerError)
210223
return
211224
}
212225

213226
// Handle workloads if any exist
214227
if len(groupWorkloads) > 0 {
215-
if withWorkloads {
216-
// Delete all workloads in the group
217-
group, err := s.workloadManager.DeleteWorkloads(ctx, groupWorkloads)
218-
if err != nil {
219-
logger.Errorf("Failed to delete workloads in group %s: %v", name, err)
220-
http.Error(w, "Failed to delete workloads in group", http.StatusInternalServerError)
221-
return
222-
}
223-
224-
// Wait for the deletion to complete
225-
if err := group.Wait(); err != nil {
226-
logger.Errorf("Failed to delete workloads in group %s: %v", name, err)
227-
http.Error(w, "Failed to delete workloads in group", http.StatusInternalServerError)
228-
return
229-
}
230-
231-
logger.Infof("Deleted %d workload(s) from group '%s'", len(groupWorkloads), name)
232-
} else {
233-
// Move workloads to default group
234-
if err := s.workloadManager.MoveToDefaultGroup(ctx, groupWorkloads, name); err != nil {
235-
logger.Errorf("Failed to move workloads to default group: %v", err)
236-
http.Error(w, "Failed to move workloads to default group", http.StatusInternalServerError)
237-
return
238-
}
239-
240-
logger.Infof("Moved %d workload(s) from group '%s' to default group", len(groupWorkloads), name)
228+
if err := s.handleWorkloadsForGroupDeletion(ctx, name, groupWorkloads, withWorkloads); err != nil {
229+
logger.Errorf("Failed to handle workloads for group %s: %v", name, err)
230+
http.Error(w, "Failed to handle workloads", http.StatusInternalServerError)
231+
return
241232
}
242233
}
243234

@@ -252,6 +243,73 @@ func (s *GroupsRoutes) deleteGroup(w http.ResponseWriter, r *http.Request) {
252243
w.WriteHeader(http.StatusNoContent)
253244
}
254245

246+
// handleWorkloadsForGroupDeletion handles workloads when deleting a group
247+
func (s *GroupsRoutes) handleWorkloadsForGroupDeletion(
248+
ctx context.Context,
249+
groupName string,
250+
groupWorkloads []core.Workload,
251+
withWorkloads bool,
252+
) error {
253+
// Extract workload names
254+
var workloadNames []string
255+
for _, workload := range groupWorkloads {
256+
workloadNames = append(workloadNames, workload.Name)
257+
}
258+
259+
if withWorkloads {
260+
// Delete all workloads in the group
261+
group, err := s.workloadManager.DeleteWorkloads(ctx, workloadNames)
262+
if err != nil {
263+
return fmt.Errorf("failed to delete workloads in group %s: %w", groupName, err)
264+
}
265+
266+
// Wait for the deletion to complete
267+
if err := group.Wait(); err != nil {
268+
return fmt.Errorf("failed to delete workloads in group %s: %w", groupName, err)
269+
}
270+
271+
logger.Infof("Deleted %d workload(s) from group '%s'", len(groupWorkloads), groupName)
272+
} else {
273+
// Move workloads to default group
274+
if err := s.workloadManager.MoveToGroup(ctx, workloadNames, groupName, groups.DefaultGroup); err != nil {
275+
return fmt.Errorf("failed to move workloads to default group: %w", err)
276+
}
277+
278+
// Update client configurations for the moved workloads
279+
if err := s.updateClientConfigurations(ctx, groupWorkloads, groupName, groups.DefaultGroup); err != nil {
280+
return fmt.Errorf("failed to update client configurations: %w", err)
281+
}
282+
283+
logger.Infof("Moved %d workload(s) from group '%s' to default group", len(groupWorkloads), groupName)
284+
}
285+
286+
return nil
287+
}
288+
289+
// updateClientConfigurations updates client configurations when workloads are moved between groups
290+
func (s *GroupsRoutes) updateClientConfigurations(
291+
ctx context.Context,
292+
groupWorkloads []core.Workload,
293+
groupFrom string,
294+
groupTo string,
295+
) error {
296+
for _, w := range groupWorkloads {
297+
// Only update client configurations for running workloads
298+
if w.Status != runtime.WorkloadStatusRunning {
299+
continue
300+
}
301+
302+
if err := s.clientManager.RemoveServerFromClients(ctx, w.Name, groupFrom); err != nil {
303+
return fmt.Errorf("failed to remove server %s from client configurations: %w", w.Name, err)
304+
}
305+
if err := s.clientManager.AddServerToClients(ctx, w.Name, w.URL, string(w.TransportType), groupTo); err != nil {
306+
return fmt.Errorf("failed to add server %s to client configurations: %w", w.Name, err)
307+
}
308+
}
309+
310+
return nil
311+
}
312+
255313
// Response types
256314

257315
type groupListResponse struct {

0 commit comments

Comments
 (0)