Skip to content

Commit 2bf7d4b

Browse files
authored
Merge branch 'main' into deprecated-fields
2 parents e4b7dc1 + 73707af commit 2bf7d4b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+3029
-518
lines changed

.github/workflows/claude.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,6 @@ jobs:
3434

3535
- name: Run Claude Code
3636
id: claude
37-
uses: anthropics/claude-code-action@777ffcbfc9d2e2b07f3cfec41b7c7eadedd1f0dc # v1
37+
uses: anthropics/claude-code-action@e8bad572273ce919ba15fec95aef0ce974464753 # v1
3838
with:
3939
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

.github/workflows/operator-ci.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,6 @@ name: Operator CI
33
on:
44
workflow_call:
55
workflow_dispatch:
6-
pull_request:
7-
paths:
8-
- 'deploy/charts/operator-crds/**'
96

107
permissions:
118
contents: read
@@ -154,9 +151,9 @@ jobs:
154151
# but we want to make sure renovate bumps the versions when new ones are released. Doing that with
155152
# just the number is a bit more difficult and i like simple things.
156153
version: [
157-
"kindest/node:v1.31.9",
158-
"kindest/node:v1.32.5",
159-
"kindest/node:v1.33.1"
154+
"kindest/node:v1.32.8",
155+
"kindest/node:v1.33.4",
156+
"kindest/node:v1.34.0"
160157
]
161158

162159
steps:

cmd/thv-operator/controllers/mcpserver_controller.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,6 @@ var defaultRBACRules = []rbacv1.PolicyRule{
8484
// mcpContainerName is the name of the mcp container used in pod templates
8585
const mcpContainerName = "mcp"
8686

87-
// trueValue is the string value "true" used for environment variable comparisons
88-
const trueValue = "true"
89-
9087
// Restart annotation keys for triggering pod restart
9188
const (
9289
RestartedAtAnnotationKey = "mcpserver.toolhive.stacklok.dev/restarted-at"
@@ -752,7 +749,7 @@ func (r *MCPServerReconciler) deploymentForMCPServer(ctx context.Context, m *mcp
752749
volumes := []corev1.Volume{}
753750

754751
// Check if global ConfigMap mode is enabled via environment variable
755-
useConfigMap := os.Getenv("TOOLHIVE_USE_CONFIGMAP") == trueValue
752+
useConfigMap := true
756753
if useConfigMap {
757754
// Also add pod template patch for secrets and service account (same as regular flags approach)
758755
// If service account is not specified, use the default MCP server service account

cmd/thv-operator/controllers/mcpserver_pod_template_test.go

Lines changed: 0 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -294,112 +294,6 @@ func TestDeploymentForMCPServerWithSecrets(t *testing.T) {
294294
assert.NotContains(t, arg, "--secret=", "No secret CLI arguments should be present")
295295
}
296296
}
297-
298-
func TestDeploymentForMCPServerWithEnvVars(t *testing.T) {
299-
t.Parallel()
300-
// Create a test MCPServer with environment variables
301-
mcpServer := &mcpv1alpha1.MCPServer{
302-
ObjectMeta: metav1.ObjectMeta{
303-
Name: "test-mcp-server-env",
304-
Namespace: "default",
305-
},
306-
Spec: mcpv1alpha1.MCPServerSpec{
307-
Image: "test-image:latest",
308-
Transport: "stdio",
309-
Port: 8080,
310-
Env: []mcpv1alpha1.EnvVar{
311-
{
312-
Name: "API_KEY",
313-
Value: "secret-key-123",
314-
},
315-
{
316-
Name: "DEBUG_MODE",
317-
Value: "true",
318-
},
319-
},
320-
},
321-
}
322-
323-
// Create a new scheme for this test to avoid race conditions
324-
s := runtime.NewScheme()
325-
_ = scheme.AddToScheme(s)
326-
s.AddKnownTypes(mcpv1alpha1.GroupVersion, &mcpv1alpha1.MCPServer{})
327-
s.AddKnownTypes(mcpv1alpha1.GroupVersion, &mcpv1alpha1.MCPServerList{})
328-
329-
// Create a reconciler with the scheme
330-
r := &MCPServerReconciler{
331-
Scheme: s,
332-
}
333-
334-
// Generate the deployment
335-
ctx := context.Background()
336-
deployment := r.deploymentForMCPServer(ctx, mcpServer)
337-
require.NotNil(t, deployment, "Deployment should not be nil")
338-
339-
// Check that environment variables are passed as --env flags in the container args
340-
container := deployment.Spec.Template.Spec.Containers[0]
341-
342-
// Verify that the environment variables are NOT set as container environment variables
343-
for _, env := range container.Env {
344-
assert.NotEqual(t, "API_KEY", env.Name, "API_KEY should not be set as container env var")
345-
assert.NotEqual(t, "DEBUG_MODE", env.Name, "DEBUG_MODE should not be set as container env var")
346-
}
347-
348-
// Verify that the environment variables are passed as --env flags in the args
349-
apiKeyArgFound := false
350-
debugModeArgFound := false
351-
for _, arg := range container.Args {
352-
if arg == "--env=API_KEY=secret-key-123" {
353-
apiKeyArgFound = true
354-
}
355-
if arg == "--env=DEBUG_MODE=true" {
356-
debugModeArgFound = true
357-
}
358-
}
359-
assert.True(t, apiKeyArgFound, "API_KEY should be passed as --env flag")
360-
assert.True(t, debugModeArgFound, "DEBUG_MODE should be passed as --env flag")
361-
}
362-
363-
func TestDeploymentForMCPServerWithProxyMode(t *testing.T) {
364-
t.Parallel()
365-
366-
// Create a test MCPServer with ProxyMode
367-
mcpServer := &mcpv1alpha1.MCPServer{
368-
ObjectMeta: metav1.ObjectMeta{
369-
Name: "test-proxy-mode",
370-
Namespace: "default",
371-
},
372-
Spec: mcpv1alpha1.MCPServerSpec{
373-
Image: "test-image:latest",
374-
Transport: "stdio",
375-
Port: 8080,
376-
ProxyMode: "streamable-http",
377-
},
378-
}
379-
380-
// Create a reconciler
381-
s := runtime.NewScheme()
382-
_ = scheme.AddToScheme(s)
383-
s.AddKnownTypes(mcpv1alpha1.GroupVersion, &mcpv1alpha1.MCPServer{})
384-
s.AddKnownTypes(mcpv1alpha1.GroupVersion, &mcpv1alpha1.MCPServerList{})
385-
r := &MCPServerReconciler{Scheme: s}
386-
387-
// Generate deployment and check for --proxy-mode flag
388-
deployment := r.deploymentForMCPServer(context.Background(), mcpServer)
389-
require.NotNil(t, deployment)
390-
391-
// Verify --proxy-mode flag is present
392-
container := deployment.Spec.Template.Spec.Containers[0]
393-
found := false
394-
for _, arg := range container.Args {
395-
if arg == "--proxy-mode=streamable-http" {
396-
found = true
397-
break
398-
}
399-
}
400-
assert.True(t, found, "--proxy-mode=streamable-http flag should be present in container args")
401-
}
402-
403297
func TestProxyRunnerSecurityContext(t *testing.T) {
404298
t.Parallel()
405299

cmd/thv-operator/controllers/mcpserver_resource_overrides_test.go

Lines changed: 0 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -320,29 +320,6 @@ func TestResourceOverrides(t *testing.T) {
320320
assert.Equal(t, tt.expectedServiceLabels, service.Labels)
321321
assert.Equal(t, tt.expectedServiceAnns, service.Annotations)
322322

323-
// For test case with Vault Agent Injection, verify --env-file-dir argument is present
324-
if tt.name == "with Vault Agent Injection pod template annotations" {
325-
require.Len(t, deployment.Spec.Template.Spec.Containers, 1)
326-
container := deployment.Spec.Template.Spec.Containers[0]
327-
328-
// Check that --env-file-dir=/vault/secrets argument is present
329-
found := false
330-
for _, arg := range container.Args {
331-
if arg == "--env-file-dir=/vault/secrets" {
332-
found = true
333-
break
334-
}
335-
}
336-
assert.True(t, found, "Expected --env-file-dir=/vault/secrets argument when vault.hashicorp.com/agent-inject=true")
337-
338-
// Verify pod template has the expected Vault annotations
339-
expectedTemplateAnnotations := map[string]string{
340-
"vault.hashicorp.com/agent-inject": "true",
341-
"vault.hashicorp.com/role": "toolhive-mcp-workloads",
342-
}
343-
assert.Equal(t, expectedTemplateAnnotations, deployment.Spec.Template.Annotations)
344-
}
345-
346323
// For test cases with environment variables, verify they are set correctly
347324
if tt.name == "with proxy environment variables" || tt.name == "with both metadata overrides and proxy environment variables" {
348325
require.Len(t, deployment.Spec.Template.Spec.Containers, 1)
@@ -428,41 +405,6 @@ func TestMergeStringMaps(t *testing.T) {
428405
}
429406
}
430407

431-
func TestDeploymentNeedsUpdateServiceAccount(t *testing.T) {
432-
t.Parallel()
433-
434-
scheme := runtime.NewScheme()
435-
require.NoError(t, mcpv1alpha1.AddToScheme(scheme))
436-
437-
client := fake.NewClientBuilder().WithScheme(scheme).Build()
438-
r := &MCPServerReconciler{
439-
Client: client,
440-
Scheme: scheme,
441-
}
442-
443-
mcpServer := &mcpv1alpha1.MCPServer{
444-
ObjectMeta: metav1.ObjectMeta{
445-
Name: "test-server",
446-
Namespace: "default",
447-
},
448-
Spec: mcpv1alpha1.MCPServerSpec{
449-
Image: "test-image",
450-
Port: 8080,
451-
},
452-
}
453-
454-
// Create a deployment using the current implementation
455-
ctx := context.Background()
456-
deployment := r.deploymentForMCPServer(ctx, mcpServer)
457-
require.NotNil(t, deployment)
458-
459-
// Test with the current deployment - this should NOT need update
460-
needsUpdate := r.deploymentNeedsUpdate(context.Background(), deployment, mcpServer)
461-
462-
// With the service account bug fixed, this should now return false
463-
assert.False(t, needsUpdate, "deploymentNeedsUpdate should return false when deployment matches MCPServer spec")
464-
}
465-
466408
func TestDeploymentNeedsUpdateProxyEnv(t *testing.T) {
467409
t.Parallel()
468410

@@ -651,75 +593,3 @@ func TestDeploymentNeedsUpdateProxyEnv(t *testing.T) {
651593
})
652594
}
653595
}
654-
655-
func TestDeploymentNeedsUpdateToolsFilter(t *testing.T) {
656-
t.Parallel()
657-
658-
scheme := runtime.NewScheme()
659-
require.NoError(t, mcpv1alpha1.AddToScheme(scheme))
660-
661-
client := fake.NewClientBuilder().WithScheme(scheme).Build()
662-
r := &MCPServerReconciler{
663-
Client: client,
664-
Scheme: scheme,
665-
}
666-
667-
tests := []struct {
668-
name string
669-
initialToolsFilter []string
670-
newToolsFilter []string
671-
expectEnvChange bool
672-
}{
673-
{
674-
name: "empty tools filter",
675-
initialToolsFilter: nil,
676-
newToolsFilter: nil,
677-
expectEnvChange: false,
678-
},
679-
{
680-
name: "tools filter not changed",
681-
initialToolsFilter: []string{"tool1", "tool2"},
682-
newToolsFilter: []string{"tool1", "tool2"},
683-
expectEnvChange: false,
684-
},
685-
{
686-
name: "tools filter changed",
687-
initialToolsFilter: []string{"tool1", "tool2"},
688-
newToolsFilter: []string{"tool2", "tool3"},
689-
expectEnvChange: true,
690-
},
691-
{
692-
name: "tools filter change order",
693-
initialToolsFilter: []string{"tool1", "tool2"},
694-
newToolsFilter: []string{"tool2", "tool1"},
695-
expectEnvChange: false,
696-
},
697-
}
698-
699-
for _, tt := range tests {
700-
t.Run(tt.name, func(t *testing.T) {
701-
t.Parallel()
702-
703-
mcpServer := &mcpv1alpha1.MCPServer{
704-
ObjectMeta: metav1.ObjectMeta{
705-
Name: "test-server",
706-
Namespace: "default",
707-
},
708-
Spec: mcpv1alpha1.MCPServerSpec{
709-
Image: "test-image",
710-
Port: 8080,
711-
ToolsFilter: tt.initialToolsFilter,
712-
},
713-
}
714-
715-
ctx := context.Background()
716-
deployment := r.deploymentForMCPServer(ctx, mcpServer)
717-
require.NotNil(t, deployment)
718-
719-
mcpServer.Spec.ToolsFilter = tt.newToolsFilter
720-
721-
needsUpdate := r.deploymentNeedsUpdate(context.Background(), deployment, mcpServer)
722-
assert.Equal(t, tt.expectEnvChange, needsUpdate)
723-
})
724-
}
725-
}

cmd/thv-operator/controllers/mcpserver_runconfig.go

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"sigs.k8s.io/controller-runtime/pkg/log"
2222

2323
mcpv1alpha1 "github.com/stacklok/toolhive/cmd/thv-operator/api/v1alpha1"
24+
"github.com/stacklok/toolhive/cmd/thv-operator/pkg/oidc"
2425
"github.com/stacklok/toolhive/pkg/authz"
2526
"github.com/stacklok/toolhive/pkg/operator/accessors"
2627
"github.com/stacklok/toolhive/pkg/runner"
@@ -306,8 +307,9 @@ func (r *MCPServerReconciler) createRunConfigFromMCPServer(m *mcpv1alpha1.MCPSer
306307
return nil, fmt.Errorf("failed to process AuthzConfig: %w", err)
307308
}
308309

309-
// Add OIDC authentication configuration if specified
310-
addOIDCConfigOptions(&options, m.Spec.OIDCConfig)
310+
if err := r.addOIDCConfigOptions(ctx, &options, m); err != nil {
311+
return nil, fmt.Errorf("failed to process OIDCConfig: %w", err)
312+
}
311313

312314
// Add audit configuration if specified
313315
addAuditConfigOptions(&options, m.Spec.Audit)
@@ -732,34 +734,38 @@ func (r *MCPServerReconciler) addAuthzConfigOptions(
732734
}
733735

734736
// addOIDCConfigOptions adds OIDC authentication configuration options to the builder options
735-
func addOIDCConfigOptions(
737+
func (r *MCPServerReconciler) addOIDCConfigOptions(
738+
ctx context.Context,
736739
options *[]runner.RunConfigBuilderOption,
737-
oidcConfig *mcpv1alpha1.OIDCConfigRef,
738-
) {
739-
if oidcConfig == nil {
740-
return
741-
}
740+
m *mcpv1alpha1.MCPServer,
741+
) error {
742742

743-
// Handle inline OIDC configuration
744-
if oidcConfig.Type == mcpv1alpha1.OIDCConfigTypeInline && oidcConfig.Inline != nil {
745-
inline := oidcConfig.Inline
743+
// Use the OIDC resolver to get configuration
744+
resolver := oidc.NewResolver(r.Client)
745+
oidcConfig, err := resolver.Resolve(ctx, m)
746+
if err != nil {
747+
return fmt.Errorf("failed to resolve OIDC configuration: %w", err)
748+
}
746749

747-
// Add OIDC config to options
748-
*options = append(*options, runner.WithOIDCConfig(
749-
inline.Issuer,
750-
inline.Audience,
751-
inline.JWKSURL,
752-
inline.IntrospectionURL,
753-
inline.ClientID,
754-
inline.ClientSecret,
755-
inline.ThvCABundlePath,
756-
inline.JWKSAuthTokenPath,
757-
"", // resourceURL - not available in InlineOIDCConfig
758-
inline.JWKSAllowPrivateIP,
759-
))
750+
if oidcConfig == nil {
751+
return nil
760752
}
761753

762-
// ConfigMap and Kubernetes types are not currently supported for OIDC configuration
754+
// Add OIDC config to options
755+
*options = append(*options, runner.WithOIDCConfig(
756+
oidcConfig.Issuer,
757+
oidcConfig.Audience,
758+
oidcConfig.JWKSURL,
759+
oidcConfig.IntrospectionURL,
760+
oidcConfig.ClientID,
761+
oidcConfig.ClientSecret,
762+
oidcConfig.ThvCABundlePath,
763+
oidcConfig.JWKSAuthTokenPath,
764+
oidcConfig.ResourceURL,
765+
oidcConfig.JWKSAllowPrivateIP,
766+
))
767+
768+
return nil
763769
}
764770

765771
// addAuditConfigOptions adds audit configuration options to the builder options

0 commit comments

Comments
 (0)