Skip to content

Commit 77998ec

Browse files
authored
Move Workload to core package (#1274)
1 parent ae53271 commit 77998ec

File tree

14 files changed

+158
-141
lines changed

14 files changed

+158
-141
lines changed

cmd/thv/app/list.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/spf13/cobra"
1010

11+
"github.com/stacklok/toolhive/pkg/core"
1112
"github.com/stacklok/toolhive/pkg/logger"
1213
"github.com/stacklok/toolhive/pkg/workloads"
1314
)
@@ -80,7 +81,7 @@ func listCmdFunc(cmd *cobra.Command, _ []string) error {
8081
}
8182

8283
// printJSONOutput prints workload information in JSON format
83-
func printJSONOutput(workloadList []workloads.Workload) error {
84+
func printJSONOutput(workloadList []core.Workload) error {
8485
// Marshal to JSON
8586
jsonData, err := json.MarshalIndent(workloadList, "", " ")
8687
if err != nil {
@@ -94,7 +95,7 @@ func printJSONOutput(workloadList []workloads.Workload) error {
9495

9596
// printMCPServersOutput prints MCP servers configuration in JSON format
9697
// This format is compatible with client configuration files
97-
func printMCPServersOutput(workloadList []workloads.Workload) error {
98+
func printMCPServersOutput(workloadList []core.Workload) error {
9899
// Create a map to hold the MCP servers configuration
99100
mcpServers := make(map[string]map[string]string)
100101

@@ -120,7 +121,7 @@ func printMCPServersOutput(workloadList []workloads.Workload) error {
120121
}
121122

122123
// printTextOutput prints workload information in text format
123-
func printTextOutput(workloadList []workloads.Workload) {
124+
func printTextOutput(workloadList []core.Workload) {
124125
// Create a tabwriter for pretty output
125126
w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0)
126127
fmt.Fprintln(w, "NAME\tPACKAGE\tSTATUS\tURL\tPORT\tTOOL TYPE\tGROUP\tCREATED AT")

docs/server/docs.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/server/swagger.yaml

Lines changed: 55 additions & 55 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/api/v1/workloads.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/go-chi/chi/v5"
1111

1212
"github.com/stacklok/toolhive/pkg/container/runtime"
13+
"github.com/stacklok/toolhive/pkg/core"
1314
thverrors "github.com/stacklok/toolhive/pkg/errors"
1415
"github.com/stacklok/toolhive/pkg/groups"
1516
"github.com/stacklok/toolhive/pkg/logger"
@@ -122,7 +123,7 @@ func (s *WorkloadRoutes) listWorkloads(w http.ResponseWriter, r *http.Request) {
122123
// @Tags workloads
123124
// @Produce json
124125
// @Param name path string true "Workload name"
125-
// @Success 200 {object} workloads.Workload
126+
// @Success 200 {object} core.Workload
126127
// @Failure 404 {string} string "Not Found"
127128
// @Router /api/v1beta/workloads/{name} [get]
128129
func (s *WorkloadRoutes) getWorkload(w http.ResponseWriter, r *http.Request) {
@@ -534,7 +535,7 @@ func (*WorkloadRoutes) exportWorkload(w http.ResponseWriter, r *http.Request) {
534535
// @Description Response containing a list of workloads
535536
type workloadListResponse struct {
536537
// List of container information for each workload
537-
Workloads []workloads.Workload `json:"workloads"`
538+
Workloads []core.Workload `json:"workloads"`
538539
}
539540

540541
// createRequest represents the request to create a new workload

pkg/core/workload.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Package core provides the core domain model for the ToolHive system.
2+
package core
3+
4+
import (
5+
"time"
6+
7+
"github.com/stacklok/toolhive/pkg/container/runtime"
8+
"github.com/stacklok/toolhive/pkg/transport/types"
9+
)
10+
11+
// Workload is a domain model representing a workload in the system.
12+
// This is used in our API to hide details of the underlying runtime.
13+
type Workload struct {
14+
// Name is the name of the workload.
15+
// It is used as a unique identifier.
16+
Name string `json:"name"`
17+
// Package specifies the Workload Package used to create this Workload.
18+
Package string `json:"package"`
19+
// URL is the URL of the workload exposed by the ToolHive proxy.
20+
URL string `json:"url"`
21+
// Port is the port on which the workload is exposed.
22+
// This is embedded in the URL.
23+
Port int `json:"port"`
24+
// ToolType is the type of tool this workload represents.
25+
// For now, it will always be "mcp" - representing an MCP server.
26+
ToolType string `json:"tool_type"`
27+
// TransportType is the type of transport used for this workload.
28+
TransportType types.TransportType `json:"transport_type"`
29+
// Status is the current status of the workload.
30+
Status runtime.WorkloadStatus `json:"status"`
31+
// StatusContext provides additional context about the workload's status.
32+
// The exact meaning is determined by the status and the underlying runtime.
33+
StatusContext string `json:"status_context,omitempty"`
34+
// CreatedAt is the timestamp when the workload was created.
35+
CreatedAt time.Time `json:"created_at"`
36+
// Labels are the container labels (excluding standard ToolHive labels)
37+
Labels map[string]string `json:"labels,omitempty"`
38+
// Group is the name of the group this workload belongs to, if any.
39+
Group string `json:"group,omitempty"`
40+
// ToolsFilter is the filter on tools applied to the workload.
41+
ToolsFilter []string `json:"tools,omitempty"`
42+
}

pkg/workloads/file_status.go

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/gofrs/flock"
1414

1515
rt "github.com/stacklok/toolhive/pkg/container/runtime"
16+
"github.com/stacklok/toolhive/pkg/core"
1617
"github.com/stacklok/toolhive/pkg/logger"
1718
)
1819

@@ -89,8 +90,8 @@ func (f *fileStatusManager) CreateWorkloadStatus(ctx context.Context, workloadNa
8990
}
9091

9192
// GetWorkload retrieves the status of a workload by its name.
92-
func (f *fileStatusManager) GetWorkload(ctx context.Context, workloadName string) (Workload, error) {
93-
result := Workload{Name: workloadName}
93+
func (f *fileStatusManager) GetWorkload(ctx context.Context, workloadName string) (core.Workload, error) {
94+
result := core.Workload{Name: workloadName}
9495
fileFound := false
9596

9697
err := f.withFileReadLock(ctx, workloadName, func(statusFilePath string) error {
@@ -114,15 +115,15 @@ func (f *fileStatusManager) GetWorkload(ctx context.Context, workloadName string
114115
return nil
115116
})
116117
if err != nil {
117-
return Workload{}, err
118+
return core.Workload{}, err
118119
}
119120

120121
// If file was found and workload is running, get additional info from runtime
121122
if fileFound && result.Status == rt.WorkloadStatusRunning {
122123
// TODO: Find discrepancies between the file and runtime workload.
123124
runtimeResult, err := f.getWorkloadFromRuntime(ctx, workloadName)
124125
if err != nil {
125-
return Workload{}, err
126+
return core.Workload{}, err
126127
}
127128
// Use runtime data but preserve file-based status info
128129
fileStatus := result.Status
@@ -144,7 +145,7 @@ func (f *fileStatusManager) GetWorkload(ctx context.Context, workloadName string
144145
return f.getWorkloadFromRuntime(ctx, workloadName)
145146
}
146147

147-
func (f *fileStatusManager) ListWorkloads(ctx context.Context, listAll bool, labelFilters []string) ([]Workload, error) {
148+
func (f *fileStatusManager) ListWorkloads(ctx context.Context, listAll bool, labelFilters []string) ([]core.Workload, error) {
148149
// Parse the filters into a format we can use for matching.
149150
parsedFilters, err := parseLabelFilters(labelFilters)
150151
if err != nil {
@@ -170,7 +171,7 @@ func (f *fileStatusManager) ListWorkloads(ctx context.Context, listAll bool, lab
170171
}
171172

172173
// Create result map to avoid duplicates and merge data
173-
workloadMap := make(map[string]Workload)
174+
workloadMap := make(map[string]core.Workload)
174175

175176
// First, add all runtime workloads
176177
for _, container := range runtimeContainers {
@@ -198,7 +199,7 @@ func (f *fileStatusManager) ListWorkloads(ctx context.Context, listAll bool, lab
198199
}
199200

200201
// Convert map to slice and apply filters
201-
var workloads []Workload
202+
var workloads []core.Workload
202203
for _, workload := range workloadMap {
203204
// Apply listAll filter
204205
if !listAll && workload.Status != rt.WorkloadStatusRunning {
@@ -386,17 +387,17 @@ func (*fileStatusManager) writeStatusFile(statusFilePath string, statusFile work
386387
}
387388

388389
// getWorkloadFromRuntime retrieves workload information from the runtime.
389-
func (f *fileStatusManager) getWorkloadFromRuntime(ctx context.Context, workloadName string) (Workload, error) {
390+
func (f *fileStatusManager) getWorkloadFromRuntime(ctx context.Context, workloadName string) (core.Workload, error) {
390391
info, err := f.runtime.GetWorkloadInfo(ctx, workloadName)
391392
if err != nil {
392-
return Workload{}, fmt.Errorf("failed to get workload info from runtime: %w", err)
393+
return core.Workload{}, fmt.Errorf("failed to get workload info from runtime: %w", err)
393394
}
394395

395396
return WorkloadFromContainerInfo(&info)
396397
}
397398

398399
// getWorkloadsFromFiles retrieves all workloads from status files.
399-
func (f *fileStatusManager) getWorkloadsFromFiles() (map[string]Workload, error) {
400+
func (f *fileStatusManager) getWorkloadsFromFiles() (map[string]core.Workload, error) {
400401
// Ensure base directory exists
401402
if err := f.ensureBaseDir(); err != nil {
402403
return nil, fmt.Errorf("failed to ensure base directory: %w", err)
@@ -408,7 +409,7 @@ func (f *fileStatusManager) getWorkloadsFromFiles() (map[string]Workload, error)
408409
return nil, fmt.Errorf("failed to list status files: %w", err)
409410
}
410411

411-
workloads := make(map[string]Workload)
412+
workloads := make(map[string]core.Workload)
412413
for _, file := range files {
413414
// Extract workload name from filename (remove .json extension)
414415
workloadName := strings.TrimSuffix(filepath.Base(file), ".json")
@@ -421,7 +422,7 @@ func (f *fileStatusManager) getWorkloadsFromFiles() (map[string]Workload, error)
421422
}
422423

423424
// Create workload from file data
424-
workload := Workload{
425+
workload := core.Workload{
425426
Name: workloadName,
426427
Status: statusFile.Status,
427428
StatusContext: statusFile.StatusContext,

pkg/workloads/file_status_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515

1616
rt "github.com/stacklok/toolhive/pkg/container/runtime"
1717
"github.com/stacklok/toolhive/pkg/container/runtime/mocks"
18+
"github.com/stacklok/toolhive/pkg/core"
1819
"github.com/stacklok/toolhive/pkg/logger"
1920
)
2021

@@ -379,7 +380,7 @@ func TestFileStatusManager_ListWorkloads(t *testing.T) {
379380
setupRuntimeMock func(*mocks.MockRuntime)
380381
expectedCount int
381382
expectedError string
382-
checkWorkloads func([]Workload)
383+
checkWorkloads func([]core.Workload)
383384
}{
384385
{
385386
name: "empty directory",
@@ -403,7 +404,7 @@ func TestFileStatusManager_ListWorkloads(t *testing.T) {
403404
m.EXPECT().ListWorkloads(gomock.Any()).Return([]rt.ContainerInfo{}, nil)
404405
},
405406
expectedCount: 1,
406-
checkWorkloads: func(workloads []Workload) {
407+
checkWorkloads: func(workloads []core.Workload) {
407408
assert.Equal(t, "test-workload", workloads[0].Name)
408409
assert.Equal(t, rt.WorkloadStatusStarting, workloads[0].Status)
409410
},
@@ -442,7 +443,7 @@ func TestFileStatusManager_ListWorkloads(t *testing.T) {
442443
m.EXPECT().ListWorkloads(gomock.Any()).Return([]rt.ContainerInfo{info}, nil)
443444
},
444445
expectedCount: 1,
445-
checkWorkloads: func(workloads []Workload) {
446+
checkWorkloads: func(workloads []core.Workload) {
446447
assert.Equal(t, "running-workload", workloads[0].Name)
447448
assert.Equal(t, rt.WorkloadStatusRunning, workloads[0].Status)
448449
},
@@ -540,10 +541,10 @@ func TestFileStatusManager_ListWorkloads(t *testing.T) {
540541
m.EXPECT().ListWorkloads(gomock.Any()).Return(containers, nil)
541542
},
542543
expectedCount: 2,
543-
checkWorkloads: func(workloads []Workload) {
544+
checkWorkloads: func(workloads []core.Workload) {
544545
// Find the merged workload
545-
var mergedWorkload *Workload
546-
var runtimeOnlyWorkload *Workload
546+
var mergedWorkload *core.Workload
547+
var runtimeOnlyWorkload *core.Workload
547548
for i := range workloads {
548549
switch workloads[i].Name {
549550
case "merge-workload":

0 commit comments

Comments
 (0)