Skip to content

Commit d858820

Browse files
authored
Move Get/List back into workloads.Manager (#1028)
In a previous PR, I decided to move the operations to Get/List workloads out of the main manager interface, and into the status tracker. After working on wiring up the status tracker, I've come to realize that a simpler approach is to keep the Get/List methods in the workload Manager, and have the workload manager call the status manager. This PR undoes the previous move. This has been lifted out of a bigger PR to keep the diff small.
1 parent d023d85 commit d858820

File tree

11 files changed

+56
-107
lines changed

11 files changed

+56
-107
lines changed

cmd/thv/app/common.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ func completeMCPServerNames(cmd *cobra.Command, args []string, _ string) ([]stri
9292
ctx := cmd.Context()
9393

9494
// Create status manager
95-
manager, err := workloads.NewStatusManager(ctx)
95+
manager, err := workloads.NewManager(ctx)
9696
if err != nil {
9797
return nil, cobra.ShellCompDirectiveError
9898
}
@@ -124,7 +124,7 @@ func completeLogsArgs(cmd *cobra.Command, args []string, _ string) ([]string, co
124124
ctx := cmd.Context()
125125

126126
// Create status manager
127-
manager, err := workloads.NewStatusManager(ctx)
127+
manager, err := workloads.NewManager(ctx)
128128
if err != nil {
129129
return []string{"prune"}, cobra.ShellCompDirectiveNoFileComp
130130
}

cmd/thv/app/inspector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func inspectorCmdFunc(cmd *cobra.Command, args []string) error {
168168

169169
func getServerPortAndTransport(ctx context.Context, serverName string) (int, types.TransportType, error) {
170170
// Instantiate the status manager and list all workloads.
171-
manager, err := workloads.NewStatusManager(ctx)
171+
manager, err := workloads.NewManager(ctx)
172172
if err != nil {
173173
return 0, types.TransportTypeSSE, fmt.Errorf("failed to create status manager: %v", err)
174174
}

cmd/thv/app/list.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func listCmdFunc(cmd *cobra.Command, _ []string) error {
3333
ctx := cmd.Context()
3434

3535
// Instantiate the status manager.
36-
manager, err := workloads.NewStatusManager(ctx)
36+
manager, err := workloads.NewManager(ctx)
3737
if err != nil {
3838
return fmt.Errorf("failed to create status manager: %v", err)
3939
}

cmd/thv/app/logs.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ func getLogsDirectory() (string, error) {
126126
}
127127

128128
func getManagedContainerNames(ctx context.Context) (map[string]bool, error) {
129-
manager, err := workloads.NewStatusManager(ctx)
129+
manager, err := workloads.NewManager(ctx)
130130
if err != nil {
131131
return nil, fmt.Errorf("failed to create status manager: %v", err)
132132
}

cmd/thv/app/restart.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,8 @@ func restartCmdFunc(cmd *cobra.Command, args []string) error {
4444
return fmt.Errorf("failed to create workload manager: %v", err)
4545
}
4646

47-
statusManager, err := workloads.NewStatusManager(ctx)
48-
if err != nil {
49-
return fmt.Errorf("failed to create status manager: %v", err)
50-
}
51-
5247
if restartAll {
53-
return restartAllContainers(ctx, statusManager, workloadManager)
48+
return restartAllContainers(ctx, workloadManager)
5449
}
5550

5651
// Restart single workload
@@ -69,13 +64,9 @@ func restartCmdFunc(cmd *cobra.Command, args []string) error {
6964
return nil
7065
}
7166

72-
func restartAllContainers(
73-
ctx context.Context,
74-
statusManager workloads.StatusManager,
75-
workloadManager workloads.Manager,
76-
) error {
67+
func restartAllContainers(ctx context.Context, workloadManager workloads.Manager) error {
7768
// Get all containers (including stopped ones since restart can start stopped containers)
78-
allWorkloads, err := statusManager.ListWorkloads(ctx, true)
69+
allWorkloads, err := workloadManager.ListWorkloads(ctx, true)
7970
if err != nil {
8071
return fmt.Errorf("failed to list allWorkloads: %v", err)
8172
}

cmd/thv/app/stop.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,17 +57,12 @@ func stopCmdFunc(cmd *cobra.Command, args []string) error {
5757
return fmt.Errorf("failed to create workload manager: %v", err)
5858
}
5959

60-
statusManager, err := workloads.NewStatusManager(ctx)
61-
if err != nil {
62-
return fmt.Errorf("failed to create status manager: %v", err)
63-
}
64-
6560
var group *errgroup.Group
6661

6762
// Check if --all flag is set
6863
if stopAll {
6964
// Get list of all running workloads first
70-
workloadList, err := statusManager.ListWorkloads(ctx, false) // false = only running workloads
65+
workloadList, err := workloadManager.ListWorkloads(ctx, false) // false = only running workloads
7166
if err != nil {
7267
return fmt.Errorf("failed to list workloads: %v", err)
7368
}

pkg/api/server.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,12 @@ func Serve(
132132
return fmt.Errorf("failed to create client manager: %v", err)
133133
}
134134

135-
statusManager := workloads.NewStatusManagerFromRuntime(rt)
136135
workloadManager := workloads.NewManagerFromRuntime(rt)
137136

138137
routers := map[string]http.Handler{
139138
"/health": v1.HealthcheckRouter(rt),
140139
"/api/v1beta/version": v1.VersionRouter(),
141-
"/api/v1beta/workloads": v1.WorkloadRouter(workloadManager, statusManager, rt, debugMode),
140+
"/api/v1beta/workloads": v1.WorkloadRouter(workloadManager, rt, debugMode),
142141
"/api/v1beta/registry": v1.RegistryRouter(registryProvider),
143142
"/api/v1beta/discovery": v1.DiscoveryRouter(),
144143
"/api/v1beta/clients": v1.ClientRouter(clientManager),

pkg/api/v1/workloads.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
// WorkloadRoutes defines the routes for workload management.
2323
type WorkloadRoutes struct {
2424
workloadManager workloads.Manager
25-
statusManager workloads.StatusManager
2625
containerRuntime runtime.Runtime
2726
debugMode bool
2827
}
@@ -36,13 +35,11 @@ type WorkloadRoutes struct {
3635
// WorkloadRouter creates a new WorkloadRoutes instance.
3736
func WorkloadRouter(
3837
workloadManager workloads.Manager,
39-
statusManager workloads.StatusManager,
4038
containerRuntime runtime.Runtime,
4139
debugMode bool,
4240
) http.Handler {
4341
routes := WorkloadRoutes{
4442
workloadManager: workloadManager,
45-
statusManager: statusManager,
4643
containerRuntime: containerRuntime,
4744
debugMode: debugMode,
4845
}
@@ -73,7 +70,7 @@ func WorkloadRouter(
7370
func (s *WorkloadRoutes) listWorkloads(w http.ResponseWriter, r *http.Request) {
7471
ctx := r.Context()
7572
listAll := r.URL.Query().Get("all") == "true"
76-
workloadList, err := s.statusManager.ListWorkloads(ctx, listAll)
73+
workloadList, err := s.workloadManager.ListWorkloads(ctx, listAll)
7774
if err != nil {
7875
logger.Errorf("Failed to list workloads: %v", err)
7976
http.Error(w, "Failed to list workloads", http.StatusInternalServerError)
@@ -102,7 +99,7 @@ func (s *WorkloadRoutes) getWorkload(w http.ResponseWriter, r *http.Request) {
10299
ctx := r.Context()
103100
name := chi.URLParam(r, "name")
104101

105-
workload, err := s.statusManager.GetWorkload(ctx, name)
102+
workload, err := s.workloadManager.GetWorkload(ctx, name)
106103
if err != nil {
107104
if errors.Is(err, workloads.ErrWorkloadNotFound) {
108105
http.Error(w, "Workload not found", http.StatusNotFound)

pkg/workloads/manager.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,11 @@ import (
3333
// NOTE: This interface may be split up in future PRs, in particular, operations
3434
// which are only relevant to the CLI/API use case will be split out.
3535
type Manager interface {
36+
// GetWorkload retrieves details of the named workload including its status.
37+
GetWorkload(ctx context.Context, workloadName string) (Workload, error)
38+
// ListWorkloads retrieves the states of all workloads.
39+
// The `listAll` parameter determines whether to include workloads that are not running.
40+
ListWorkloads(ctx context.Context, listAll bool) ([]Workload, error)
3641
// DeleteWorkloads deletes the specified workloads by name.
3742
// It is implemented as an asynchronous operation which returns an errgroup.Group
3843
DeleteWorkloads(ctx context.Context, names []string) (*errgroup.Group, error)
@@ -91,6 +96,44 @@ func NewManagerFromRuntime(runtime rt.Runtime) Manager {
9196
}
9297
}
9398

99+
func (d *defaultManager) GetWorkload(ctx context.Context, workloadName string) (Workload, error) {
100+
// Validate workload name to prevent path traversal attacks
101+
if err := validateWorkloadName(workloadName); err != nil {
102+
return Workload{}, err
103+
}
104+
105+
container, err := d.findContainerByName(ctx, workloadName)
106+
if err != nil {
107+
// Note that `findContainerByName` already wraps the error with a more specific message.
108+
return Workload{}, err
109+
}
110+
111+
return WorkloadFromContainerInfo(container)
112+
}
113+
114+
func (d *defaultManager) ListWorkloads(ctx context.Context, listAll bool) ([]Workload, error) {
115+
// List containers
116+
containers, err := d.runtime.ListWorkloads(ctx)
117+
if err != nil {
118+
return nil, fmt.Errorf("failed to list containers: %v", err)
119+
}
120+
121+
// Filter containers to only show those managed by ToolHive
122+
var workloads []Workload
123+
for _, c := range containers {
124+
// If the caller did not set `listAll` to true, only include running containers.
125+
if labels.IsToolHiveContainer(c.Labels) && (isContainerRunning(&c) || listAll) {
126+
workload, err := WorkloadFromContainerInfo(&c)
127+
if err != nil {
128+
return nil, err
129+
}
130+
workloads = append(workloads, workload)
131+
}
132+
}
133+
134+
return workloads, nil
135+
}
136+
94137
func (d *defaultManager) StopWorkloads(ctx context.Context, names []string) (*errgroup.Group, error) {
95138
// Validate all workload names to prevent path traversal attacks
96139
for _, name := range names {

pkg/workloads/status.go

Lines changed: 0 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,13 @@ package workloads
22

33
import (
44
"context"
5-
"fmt"
65

76
ct "github.com/stacklok/toolhive/pkg/container"
87
rt "github.com/stacklok/toolhive/pkg/container/runtime"
9-
"github.com/stacklok/toolhive/pkg/labels"
108
)
119

1210
// StatusManager is an interface for fetching and retrieving workload statuses.
1311
type StatusManager interface {
14-
// GetWorkload retrieves details of the named workload including its status.
15-
GetWorkload(ctx context.Context, workloadName string) (Workload, error)
16-
// ListWorkloads retrieves the states of all workloads.
17-
// The `listAll` parameter determines whether to include workloads that are not running.
18-
ListWorkloads(ctx context.Context, listAll bool) ([]Workload, error)
1912
// SetWorkloadStatus sets the status of a workload by its name.
2013
SetWorkloadStatus(ctx context.Context, workloadName string, status WorkloadStatus, contextMsg string) error
2114
// DeleteWorkloadStatus removes the status of a workload by its name.
@@ -49,44 +42,6 @@ type runtimeStatusManager struct {
4942
runtime rt.Runtime
5043
}
5144

52-
func (r *runtimeStatusManager) GetWorkload(ctx context.Context, workloadName string) (Workload, error) {
53-
// Validate workload name to prevent path traversal attacks
54-
if err := validateWorkloadName(workloadName); err != nil {
55-
return Workload{}, err
56-
}
57-
58-
container, err := r.findContainerByName(ctx, workloadName)
59-
if err != nil {
60-
// Note that `findContainerByName` already wraps the error with a more specific message.
61-
return Workload{}, err
62-
}
63-
64-
return WorkloadFromContainerInfo(container)
65-
}
66-
67-
func (r *runtimeStatusManager) ListWorkloads(ctx context.Context, listAll bool) ([]Workload, error) {
68-
// List containers
69-
containers, err := r.runtime.ListWorkloads(ctx)
70-
if err != nil {
71-
return nil, fmt.Errorf("failed to list containers: %v", err)
72-
}
73-
74-
// Filter containers to only show those managed by ToolHive
75-
var workloads []Workload
76-
for _, c := range containers {
77-
// If the caller did not set `listAll` to true, only include running containers.
78-
if labels.IsToolHiveContainer(c.Labels) && (isContainerRunning(&c) || listAll) {
79-
workload, err := WorkloadFromContainerInfo(&c)
80-
if err != nil {
81-
return nil, err
82-
}
83-
workloads = append(workloads, workload)
84-
}
85-
}
86-
87-
return workloads, nil
88-
}
89-
9045
func (*runtimeStatusManager) SetWorkloadStatus(_ context.Context, _ string, _ WorkloadStatus, _ string) error {
9146
// Noop
9247
return nil
@@ -96,33 +51,3 @@ func (*runtimeStatusManager) DeleteWorkloadStatus(_ context.Context, _ string) e
9651
// Noop
9752
return nil
9853
}
99-
100-
// Duplicated from the original code - need to de-dupe at some point.
101-
func (r *runtimeStatusManager) findContainerByName(ctx context.Context, name string) (*rt.ContainerInfo, error) {
102-
// List containers to find the one with the given name
103-
containers, err := r.runtime.ListWorkloads(ctx)
104-
if err != nil {
105-
return nil, fmt.Errorf("failed to list containers: %v", err)
106-
}
107-
108-
// Find the container with the given name
109-
for _, c := range containers {
110-
// Check if the container is managed by ToolHive
111-
if !labels.IsToolHiveContainer(c.Labels) {
112-
continue
113-
}
114-
115-
// Check if the container name matches
116-
containerName := labels.GetContainerName(c.Labels)
117-
if containerName == "" {
118-
name = c.Name // Fallback to container name
119-
}
120-
121-
// Check if the name matches (exact match or prefix match)
122-
if containerName == name || c.ID == name {
123-
return &c, nil
124-
}
125-
}
126-
127-
return nil, fmt.Errorf("%w: %s", ErrWorkloadNotFound, name)
128-
}

0 commit comments

Comments
 (0)