Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/thv-operator/api/v1beta1/mcpserver_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpcrd"
)

func TestSessionStorageConfigJSONRoundtrip(t *testing.T) {
Expand Down Expand Up @@ -128,7 +128,7 @@ func TestVirtualMCPServerSpecRateLimitingJSONRoundtrip(t *testing.T) {
Provider: "redis",
Address: "redis.default.svc.cluster.local:6379",
},
Config: vmcpconfig.Config{
Config: vmcpcrd.Config{
RateLimiting: &RateLimitConfig{
Shared: &RateLimitBucket{MaxTokens: 10, RefillPeriod: metav1.Duration{Duration: time.Minute}},
PerUser: &RateLimitBucket{
Expand Down
22 changes: 21 additions & 1 deletion cmd/thv-operator/api/v1beta1/v1beta1test/virtualmcpserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@
package v1beta1test

import (
"encoding/json"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"

mcpv1beta1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1beta1"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpcrd"
"github.com/stacklok/toolhive/pkg/vmcp/config"
)

Expand Down Expand Up @@ -42,8 +45,25 @@ func WithVMCPGroupRef(name string) VirtualMCPServerOption {
}

// WithVMCPConfig sets the vMCP server configuration.
//
// The CRD spec field is the operator-owned vmcpcrd.Config mirror, but tests
// author fixtures using the runtime config.Config model. Because the two are
// field-for-field identical (enforced by the AssertNoDrift + round-trip tests),
// this option transcodes the runtime config into the CRD mirror via JSON. Any
// transcode error indicates a real drift between the two schemas and is fatal to
// the test rather than silently dropped.
func WithVMCPConfig(cfg config.Config) VirtualMCPServerOption {
return func(v *mcpv1beta1.VirtualMCPServer) { v.Spec.Config = cfg }
return func(v *mcpv1beta1.VirtualMCPServer) {
data, err := json.Marshal(cfg)
if err != nil {
panic("v1beta1test: marshal config.Config: " + err.Error())
}
var mirror vmcpcrd.Config
if err := json.Unmarshal(data, &mirror); err != nil {
panic("v1beta1test: unmarshal into vmcpcrd.Config: " + err.Error())
}
v.Spec.Config = mirror
}
}

// WithVMCPIncomingAuth sets the incoming auth configuration.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ package v1beta1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/stacklok/toolhive/pkg/vmcp/config"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpcrd"
)

// VirtualMCPCompositeToolDefinitionSpec defines the desired state of VirtualMCPCompositeToolDefinition.
// This embeds the CompositeToolConfig from pkg/vmcp/config to share the configuration model
// between CLI and operator usage.
type VirtualMCPCompositeToolDefinitionSpec struct {
config.CompositeToolConfig `json:",inline"` // nolint:revive // inline is valid
// The embedded type is the operator-owned vmcpcrd.CompositeToolConfig mirror (a field-for-field
// duplicate of pkg/vmcp/config.CompositeToolConfig) so the CRD schema is generated solely from
// operator-owned types. See the vmcpcrd package doc for the decoupling rationale.
vmcpcrd.CompositeToolConfig `json:",inline"` // nolint:revive // inline is valid
}

// VirtualMCPCompositeToolDefinitionStatus defines the observed state of VirtualMCPCompositeToolDefinition
Expand Down Expand Up @@ -129,9 +132,10 @@ type VirtualMCPCompositeToolDefinitionList struct {

// Validate performs validation for VirtualMCPCompositeToolDefinition
// This method is called by the controller during reconciliation
// It delegates to the shared ValidateCompositeToolConfig in pkg/vmcp/config
// It delegates to the operator-owned ValidateCompositeToolConfig in the vmcpcrd mirror,
// kept behaviourally in lock-step with pkg/vmcp/config.
func (r *VirtualMCPCompositeToolDefinition) Validate() error {
return config.ValidateCompositeToolConfig("spec", &r.Spec.CompositeToolConfig)
return vmcpcrd.ValidateCompositeToolConfig("spec", &r.Spec.CompositeToolConfig)
}

// GetValidationErrors returns a list of validation errors
Expand Down
8 changes: 4 additions & 4 deletions cmd/thv-operator/api/v1beta1/virtualmcpserver_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"

"github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpcrd"
vmcptypes "github.com/stacklok/toolhive/pkg/vmcp"
"github.com/stacklok/toolhive/pkg/vmcp/config"
)

// VirtualMCPServerSpec defines the desired state of VirtualMCPServer
Expand Down Expand Up @@ -83,7 +83,7 @@ type VirtualMCPServerSpec struct {
// Config is the Virtual MCP server configuration.
// The audit config from here is also supported, but not required.
// +optional
Config config.Config `json:"config,omitempty"`
Config vmcpcrd.Config `json:"config,omitempty"`

// TelemetryConfigRef references an MCPTelemetryConfig resource for shared telemetry configuration.
// The referenced MCPTelemetryConfig must exist in the same namespace as this VirtualMCPServer.
Expand Down Expand Up @@ -623,7 +623,7 @@ func (r *VirtualMCPServer) validateEmbeddingServer() error {
// optimizer with default values so the embedding server is actually used.
// The controller emits a Kubernetes event for this case.
if hasRef && !hasOptimizer {
r.Spec.Config.Optimizer = &config.OptimizerConfig{}
r.Spec.Config.Optimizer = &vmcpcrd.OptimizerConfig{}
}

return nil
Expand Down Expand Up @@ -726,7 +726,7 @@ func (r *VirtualMCPServer) validateCompositeTools() error {
toolNames[tool.Name] = true

// Use shared validation
if err := config.ValidateCompositeToolConfig(
if err := vmcpcrd.ValidateCompositeToolConfig(
fmt.Sprintf("spec.config.compositeTools[%d]", i), tool,
); err != nil {
return err
Expand Down
38 changes: 19 additions & 19 deletions cmd/thv-operator/api/v1beta1/virtualmcpserver_types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

"github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpcrd"
vmcp "github.com/stacklok/toolhive/pkg/vmcp"
"github.com/stacklok/toolhive/pkg/vmcp/config"
)

func TestVirtualMCPServerPhaseTransitions(t *testing.T) {
Expand Down Expand Up @@ -147,8 +147,8 @@ func TestVirtualMCPServerDefaultValues(t *testing.T) {
},
Spec: VirtualMCPServerSpec{
GroupRef: &MCPGroupRef{Name: "test-group"},
Config: config.Config{
Aggregation: &config.AggregationConfig{
Config: vmcpcrd.Config{
Aggregation: &vmcpcrd.AggregationConfig{
ConflictResolution: "", // Should default to "prefix"
},
},
Expand Down Expand Up @@ -205,29 +205,29 @@ func TestConflictResolutionStrategies(t *testing.T) {
tests := []struct {
name string
strategy vmcp.ConflictResolutionStrategy
configValue *config.ConflictResolutionConfig
configValue *vmcpcrd.ConflictResolutionConfig
isValid bool
}{
{
name: "prefix_strategy_with_format",
strategy: vmcp.ConflictStrategyPrefix,
configValue: &config.ConflictResolutionConfig{
configValue: &vmcpcrd.ConflictResolutionConfig{
PrefixFormat: "{workload}_",
},
isValid: true,
},
{
name: "priority_strategy_with_order",
strategy: vmcp.ConflictStrategyPriority,
configValue: &config.ConflictResolutionConfig{
configValue: &vmcpcrd.ConflictResolutionConfig{
PriorityOrder: []string{"github", "jira", "slack"},
},
isValid: true,
},
{
name: "manual_strategy",
strategy: vmcp.ConflictStrategyManual,
configValue: &config.ConflictResolutionConfig{},
configValue: &vmcpcrd.ConflictResolutionConfig{},
isValid: true,
},
}
Expand All @@ -239,8 +239,8 @@ func TestConflictResolutionStrategies(t *testing.T) {
vmcpServer := &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
GroupRef: &MCPGroupRef{Name: "test-group"},
Config: config.Config{
Aggregation: &config.AggregationConfig{
Config: vmcpcrd.Config{
Aggregation: &vmcpcrd.AggregationConfig{
ConflictResolution: tt.strategy,
ConflictResolutionConfig: tt.configValue,
},
Expand Down Expand Up @@ -320,13 +320,13 @@ func TestCompositeToolStepDependencies(t *testing.T) {

tests := []struct {
name string
steps []config.WorkflowStepConfig
steps []vmcpcrd.WorkflowStepConfig
isValid bool
errMsg string
}{
{
name: "valid_sequential_dependencies",
steps: []config.WorkflowStepConfig{
steps: []vmcpcrd.WorkflowStepConfig{
{ID: "step1", Type: "tool", Tool: "backend.tool1"},
{ID: "step2", Type: "tool", Tool: "backend.tool2", DependsOn: []string{"step1"}},
{ID: "step3", Type: "tool", Tool: "backend.tool3", DependsOn: []string{"step2"}},
Expand All @@ -335,7 +335,7 @@ func TestCompositeToolStepDependencies(t *testing.T) {
},
{
name: "valid_parallel_steps",
steps: []config.WorkflowStepConfig{
steps: []vmcpcrd.WorkflowStepConfig{
{ID: "step1", Type: "tool", Tool: "backend.tool1"},
{ID: "step2", Type: "tool", Tool: "backend.tool2"},
{ID: "step3", Type: "tool", Tool: "backend.tool3", DependsOn: []string{"step1", "step2"}},
Expand All @@ -344,7 +344,7 @@ func TestCompositeToolStepDependencies(t *testing.T) {
},
{
name: "valid_forward_reference",
steps: []config.WorkflowStepConfig{
steps: []vmcpcrd.WorkflowStepConfig{
{ID: "step1", Type: "tool", Tool: "backend.tool1", DependsOn: []string{"step2"}},
{ID: "step2", Type: "tool", Tool: "backend.tool2"},
},
Expand All @@ -359,8 +359,8 @@ func TestCompositeToolStepDependencies(t *testing.T) {
server := &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
GroupRef: &MCPGroupRef{Name: "test-group"},
Config: config.Config{
CompositeTools: []config.CompositeToolConfig{
Config: vmcpcrd.Config{
CompositeTools: []vmcpcrd.CompositeToolConfig{
{
Name: "test-workflow",
Description: "Test workflow",
Expand Down Expand Up @@ -411,8 +411,8 @@ func TestValidateEmbeddingServer(t *testing.T) {
server: &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
GroupRef: &MCPGroupRef{Name: "test-group"},
Config: config.Config{
Optimizer: &config.OptimizerConfig{},
Config: vmcpcrd.Config{
Optimizer: &vmcpcrd.OptimizerConfig{},
},
EmbeddingServerRef: &EmbeddingServerRef{
Name: "my-embedding",
Expand All @@ -426,8 +426,8 @@ func TestValidateEmbeddingServer(t *testing.T) {
server: &VirtualMCPServer{
Spec: VirtualMCPServerSpec{
GroupRef: &MCPGroupRef{Name: "test-group"},
Config: config.Config{
Optimizer: &config.OptimizerConfig{},
Config: vmcpcrd.Config{
Optimizer: &vmcpcrd.OptimizerConfig{},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import (
ctrlutil "github.com/stacklok/toolhive/cmd/thv-operator/pkg/controllerutil"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/runconfig/configmap/checksum"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/virtualmcpserverstatus"
vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpcrd"
"github.com/stacklok/toolhive/pkg/vmcp/workloads"
)

Expand Down Expand Up @@ -1802,9 +1802,9 @@ func TestVirtualMCPServerContainerNeedsUpdate(t *testing.T) {
},
Spec: mcpv1beta1.VirtualMCPServerSpec{
GroupRef: &mcpv1beta1.MCPGroupRef{Name: testGroupName},
Config: vmcpconfig.Config{
Config: vmcpcrd.Config{
Group: testGroupName,
Operational: &vmcpconfig.OperationalConfig{
Operational: &vmcpcrd.OperationalConfig{
LogLevel: "debug",
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"github.com/stacklok/toolhive/cmd/thv-operator/internal/testutil"
ctrlutil "github.com/stacklok/toolhive/cmd/thv-operator/pkg/controllerutil"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/runconfig/configmap/checksum"
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/vmcpcrd"
vmcpconfig "github.com/stacklok/toolhive/pkg/vmcp/config"
"github.com/stacklok/toolhive/pkg/vmcp/workloads"
)
Expand Down Expand Up @@ -170,8 +171,8 @@ func TestBuildContainerArgsForVmcp(t *testing.T) {
},
Spec: mcpv1beta1.VirtualMCPServerSpec{
GroupRef: &mcpv1beta1.MCPGroupRef{Name: "test-group"},
Config: vmcpconfig.Config{
Operational: &vmcpconfig.OperationalConfig{
Config: vmcpcrd.Config{
Operational: &vmcpcrd.OperationalConfig{
LogLevel: "debug",
},
},
Expand Down
Loading
Loading