Skip to content

Commit 2bbbafb

Browse files
authored
Merge branch 'main' into update-registry-from-release
2 parents e2b7f9f + 1927930 commit 2bbbafb

File tree

10 files changed

+160
-20
lines changed

10 files changed

+160
-20
lines changed

.github/workflows/claude.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
runs-on: ubuntu-latest
2222
timeout-minutes: 20
2323
permissions:
24-
contents: read
24+
contents: write
2525
pull-requests: read
2626
issues: read
2727
id-token: write

cmd/thv-operator/api/v1alpha1/virtualmcpserver_types.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,12 @@ type ErrorHandling struct {
330330

331331
// OperationalConfig defines operational settings
332332
type OperationalConfig struct {
333+
// LogLevel sets the logging level for the Virtual MCP server.
334+
// Set to "debug" to enable debug logging. When not set, defaults to info level.
335+
// +kubebuilder:validation:Enum=debug
336+
// +optional
337+
LogLevel string `json:"logLevel,omitempty"`
338+
333339
// Timeouts configures timeout settings
334340
// +optional
335341
Timeouts *TimeoutConfig `json:"timeouts,omitempty"`

cmd/thv-operator/controllers/virtualmcpserver_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -812,6 +812,12 @@ func (r *VirtualMCPServerReconciler) containerNeedsUpdate(
812812
return true
813813
}
814814

815+
// Check if container args have changed (includes --debug flag from logLevel)
816+
expectedArgs := r.buildContainerArgsForVmcp(vmcp)
817+
if !reflect.DeepEqual(container.Args, expectedArgs) {
818+
return true
819+
}
820+
815821
// Check if environment variables have changed
816822
expectedEnv := r.buildEnvVarsForVmcp(ctx, vmcp, workloadNames)
817823
if !reflect.DeepEqual(container.Env, expectedEnv) {

cmd/thv-operator/controllers/virtualmcpserver_controller_test.go

Lines changed: 71 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,8 @@ func TestVirtualMCPServerContainerNeedsUpdate(t *testing.T) {
14011401
Ports: []corev1.ContainerPort{
14021402
{ContainerPort: 4483},
14031403
},
1404-
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
1404+
Args: reconciler.buildContainerArgsForVmcp(vmcp),
1405+
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
14051406
},
14061407
},
14071408
ServiceAccountName: "wrong-service-account",
@@ -1412,6 +1413,69 @@ func TestVirtualMCPServerContainerNeedsUpdate(t *testing.T) {
14121413
vmcp: vmcp,
14131414
expectedUpdate: true,
14141415
},
1416+
{
1417+
name: "log level change to debug needs update",
1418+
deployment: &appsv1.Deployment{
1419+
Spec: appsv1.DeploymentSpec{
1420+
Template: corev1.PodTemplateSpec{
1421+
Spec: corev1.PodSpec{
1422+
Containers: []corev1.Container{
1423+
{
1424+
Name: "vmcp",
1425+
Image: getVmcpImage(),
1426+
Ports: []corev1.ContainerPort{
1427+
{ContainerPort: 4483},
1428+
},
1429+
Args: []string{"serve", "--config=/etc/vmcp-config/config.yaml", "--host=0.0.0.0", "--port=4483"},
1430+
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
1431+
},
1432+
},
1433+
ServiceAccountName: vmcpServiceAccountName(vmcp.Name),
1434+
},
1435+
},
1436+
},
1437+
},
1438+
vmcp: &mcpv1alpha1.VirtualMCPServer{
1439+
ObjectMeta: metav1.ObjectMeta{
1440+
Name: testVmcpName,
1441+
Namespace: "default",
1442+
},
1443+
Spec: mcpv1alpha1.VirtualMCPServerSpec{
1444+
GroupRef: mcpv1alpha1.GroupRef{
1445+
Name: testGroupName,
1446+
},
1447+
Operational: &mcpv1alpha1.OperationalConfig{
1448+
LogLevel: "debug",
1449+
},
1450+
},
1451+
},
1452+
expectedUpdate: true,
1453+
},
1454+
{
1455+
name: "log level removed from debug needs update",
1456+
deployment: &appsv1.Deployment{
1457+
Spec: appsv1.DeploymentSpec{
1458+
Template: corev1.PodTemplateSpec{
1459+
Spec: corev1.PodSpec{
1460+
Containers: []corev1.Container{
1461+
{
1462+
Name: "vmcp",
1463+
Image: getVmcpImage(),
1464+
Ports: []corev1.ContainerPort{
1465+
{ContainerPort: 4483},
1466+
},
1467+
Args: []string{"serve", "--config=/etc/vmcp-config/config.yaml", "--host=0.0.0.0", "--port=4483", "--debug"},
1468+
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
1469+
},
1470+
},
1471+
ServiceAccountName: vmcpServiceAccountName(vmcp.Name),
1472+
},
1473+
},
1474+
},
1475+
},
1476+
vmcp: vmcp,
1477+
expectedUpdate: true,
1478+
},
14151479
{
14161480
name: "no changes - no update needed",
14171481
deployment: &appsv1.Deployment{
@@ -1425,7 +1489,8 @@ func TestVirtualMCPServerContainerNeedsUpdate(t *testing.T) {
14251489
Ports: []corev1.ContainerPort{
14261490
{ContainerPort: 4483},
14271491
},
1428-
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
1492+
Args: reconciler.buildContainerArgsForVmcp(vmcp),
1493+
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
14291494
},
14301495
},
14311496
ServiceAccountName: vmcpServiceAccountName(vmcp.Name),
@@ -1786,7 +1851,8 @@ func TestVirtualMCPServerDeploymentNeedsUpdate(t *testing.T) {
17861851
Ports: []corev1.ContainerPort{
17871852
{ContainerPort: 4483},
17881853
},
1789-
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
1854+
Args: reconciler.buildContainerArgsForVmcp(vmcp),
1855+
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
17901856
},
17911857
},
17921858
ServiceAccountName: vmcpServiceAccountName(vmcp.Name),
@@ -1817,7 +1883,8 @@ func TestVirtualMCPServerDeploymentNeedsUpdate(t *testing.T) {
18171883
Ports: []corev1.ContainerPort{
18181884
{ContainerPort: 4483},
18191885
},
1820-
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
1886+
Args: reconciler.buildContainerArgsForVmcp(vmcp),
1887+
Env: reconciler.buildEnvVarsForVmcp(context.Background(), vmcp, []string{}),
18211888
},
18221889
},
18231890
ServiceAccountName: vmcpServiceAccountName(vmcp.Name),

cmd/thv-operator/controllers/virtualmcpserver_deployment.go

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ import (
2424
)
2525

2626
const (
27+
// Log level configuration
28+
logLevelDebug = "debug" // Debug log level value
29+
2730
// Network configuration
2831
vmcpDefaultPort = int32(4483) // Default port for VirtualMCPServer service (matches vmcp server port)
2932

@@ -71,7 +74,7 @@ func (r *VirtualMCPServerReconciler) deploymentForVirtualMCPServer(
7174
replicas := int32(1)
7275

7376
// Build deployment components using helper functions
74-
args := r.buildContainerArgsForVmcp()
77+
args := r.buildContainerArgsForVmcp(vmcp)
7578
volumeMounts, volumes := r.buildVolumesForVmcp(vmcp)
7679
env := r.buildEnvVarsForVmcp(ctx, vmcp, workloadNames)
7780
deploymentLabels, deploymentAnnotations := r.buildDeploymentMetadataForVmcp(ls, vmcp)
@@ -141,13 +144,24 @@ func (r *VirtualMCPServerReconciler) deploymentForVirtualMCPServer(
141144
}
142145

143146
// buildContainerArgsForVmcp builds the container arguments for vmcp
144-
func (*VirtualMCPServerReconciler) buildContainerArgsForVmcp() []string {
145-
return []string{
147+
func (*VirtualMCPServerReconciler) buildContainerArgsForVmcp(
148+
vmcp *mcpv1alpha1.VirtualMCPServer,
149+
) []string {
150+
args := []string{
146151
"serve",
147152
"--config=/etc/vmcp-config/config.yaml",
148153
"--host=0.0.0.0", // Listen on all interfaces for Kubernetes service routing
149154
"--port=4483", // Standard vmcp port
150155
}
156+
157+
// Add --debug flag if log level is set to debug
158+
// Note: vmcp binary currently only supports --debug flag, not other log levels
159+
// The flag must be passed at startup because logger.Initialize() runs before config is loaded
160+
if vmcp.Spec.Operational != nil && vmcp.Spec.Operational.LogLevel == logLevelDebug {
161+
args = append(args, "--debug")
162+
}
163+
164+
return args
151165
}
152166

153167
// buildVolumesForVmcp builds volumes and volume mounts for vmcp
@@ -200,12 +214,6 @@ func (r *VirtualMCPServerReconciler) buildEnvVarsForVmcp(
200214
Value: vmcp.Namespace,
201215
})
202216

203-
// TODO: Add log level from operational config when Operational is not nil
204-
//nolint:staticcheck // Empty branch reserved for future log level configuration
205-
if vmcp.Spec.Operational != nil {
206-
// Log level env var will be added here
207-
}
208-
209217
// Mount OIDC client secret
210218
env = append(env, r.buildOIDCEnvVars(vmcp)...)
211219

cmd/thv-operator/controllers/virtualmcpserver_deployment_test.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,56 @@ func TestDeploymentForVirtualMCPServer(t *testing.T) {
7979
func TestBuildContainerArgsForVmcp(t *testing.T) {
8080
t.Parallel()
8181

82-
r := &VirtualMCPServerReconciler{}
83-
args := r.buildContainerArgsForVmcp()
82+
tests := []struct {
83+
name string
84+
vmcp *mcpv1alpha1.VirtualMCPServer
85+
wantArgs []string
86+
}{
87+
{
88+
name: "without log level",
89+
vmcp: &mcpv1alpha1.VirtualMCPServer{
90+
ObjectMeta: metav1.ObjectMeta{
91+
Name: "test-vmcp",
92+
Namespace: "default",
93+
},
94+
Spec: mcpv1alpha1.VirtualMCPServerSpec{
95+
GroupRef: mcpv1alpha1.GroupRef{
96+
Name: "test-group",
97+
},
98+
},
99+
},
100+
wantArgs: []string{"serve", "--config=/etc/vmcp-config/config.yaml", "--host=0.0.0.0", "--port=4483"},
101+
},
102+
{
103+
name: "with log level debug",
104+
vmcp: &mcpv1alpha1.VirtualMCPServer{
105+
ObjectMeta: metav1.ObjectMeta{
106+
Name: "test-vmcp",
107+
Namespace: "default",
108+
},
109+
Spec: mcpv1alpha1.VirtualMCPServerSpec{
110+
GroupRef: mcpv1alpha1.GroupRef{
111+
Name: "test-group",
112+
},
113+
Operational: &mcpv1alpha1.OperationalConfig{
114+
LogLevel: "debug",
115+
},
116+
},
117+
},
118+
wantArgs: []string{"serve", "--config=/etc/vmcp-config/config.yaml", "--host=0.0.0.0", "--port=4483", "--debug"},
119+
},
120+
}
84121

85-
assert.Contains(t, args, "serve")
86-
assert.Contains(t, args, "--config=/etc/vmcp-config/config.yaml")
122+
for _, tt := range tests {
123+
tt := tt // capture range variable
124+
t.Run(tt.name, func(t *testing.T) {
125+
t.Parallel()
126+
r := &VirtualMCPServerReconciler{}
127+
args := r.buildContainerArgsForVmcp(tt.vmcp)
128+
129+
assert.Equal(t, tt.wantArgs, args)
130+
})
131+
}
87132
}
88133

89134
// TestBuildVolumesForVmcp tests volume and volume mount generation

deploy/charts/operator-crds/Chart.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ apiVersion: v2
22
name: toolhive-operator-crds
33
description: A Helm chart for installing the ToolHive Operator CRDs into Kubernetes.
44
type: application
5-
version: 0.0.74
5+
version: 0.0.75
66
appVersion: "0.0.1"

deploy/charts/operator-crds/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ToolHive Operator CRDs Helm Chart
22

3-
![Version: 0.0.74](https://img.shields.io/badge/Version-0.0.74-informational?style=flat-square)
3+
![Version: 0.0.75](https://img.shields.io/badge/Version-0.0.75-informational?style=flat-square)
44
![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat-square)
55

66
A Helm chart for installing the ToolHive Operator CRDs into Kubernetes.

deploy/charts/operator-crds/crds/toolhive.stacklok.dev_virtualmcpservers.yaml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,13 @@ spec:
624624
failures before marking unhealthy
625625
type: integer
626626
type: object
627+
logLevel:
628+
description: |-
629+
LogLevel sets the logging level for the Virtual MCP server.
630+
Set to "debug" to enable debug logging. When not set, defaults to info level.
631+
enum:
632+
- debug
633+
type: string
627634
timeouts:
628635
description: Timeouts configures timeout settings
629636
properties:

docs/operator/crd-api.md

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

0 commit comments

Comments
 (0)