Skip to content

Commit 3bd909a

Browse files
committed
complete coverage
1 parent 82d20ca commit 3bd909a

File tree

6 files changed

+209
-44
lines changed

6 files changed

+209
-44
lines changed

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

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,49 @@
11
package v1alpha1
22

33
import (
4+
"context"
45
"fmt"
56
"regexp"
67
"text/template"
8+
9+
"k8s.io/apimachinery/pkg/runtime"
10+
ctrl "sigs.k8s.io/controller-runtime"
11+
"sigs.k8s.io/controller-runtime/pkg/webhook"
12+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
713
)
814

15+
// SetupWebhookWithManager registers the webhook with the manager
16+
func (r *VirtualMCPCompositeToolDefinition) SetupWebhookWithManager(mgr ctrl.Manager) error {
17+
return ctrl.NewWebhookManagedBy(mgr).
18+
For(r).
19+
Complete()
20+
}
21+
22+
//nolint:lll // kubebuilder webhook marker cannot be split
23+
// +kubebuilder:webhook:path=/validate-toolhive-stacklok-dev-v1alpha1-virtualmcpcompositetooldefinition,mutating=false,failurePolicy=fail,sideEffects=None,groups=toolhive.stacklok.dev,resources=virtualmcpcompositetooldefinitions,verbs=create;update,versions=v1alpha1,name=vvirtualmcpcompositetooldefinition.kb.io,admissionReviewVersions=v1
24+
25+
var _ webhook.CustomValidator = &VirtualMCPCompositeToolDefinition{}
26+
27+
// ValidateCreate implements webhook.CustomValidator
28+
func (r *VirtualMCPCompositeToolDefinition) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
29+
return nil, r.Validate()
30+
}
31+
32+
// ValidateUpdate implements webhook.CustomValidator
33+
//
34+
//nolint:lll // function signature cannot be shortened
35+
func (r *VirtualMCPCompositeToolDefinition) ValidateUpdate(_ context.Context, _ runtime.Object, _ runtime.Object) (admission.Warnings, error) {
36+
return nil, r.Validate()
37+
}
38+
39+
// ValidateDelete implements webhook.CustomValidator
40+
func (*VirtualMCPCompositeToolDefinition) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
41+
// No validation needed on delete
42+
return nil, nil
43+
}
44+
945
// Validate performs validation for VirtualMCPCompositeToolDefinition
10-
// This method can be called by the controller during reconciliation
46+
// This method can be called by the controller during reconciliation or by the webhook
1147
func (r *VirtualMCPCompositeToolDefinition) Validate() error {
1248
var errors []string
1349

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

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,45 @@
11
package v1alpha1
22

33
import (
4+
"context"
45
"fmt"
6+
7+
"k8s.io/apimachinery/pkg/runtime"
8+
ctrl "sigs.k8s.io/controller-runtime"
9+
"sigs.k8s.io/controller-runtime/pkg/webhook"
10+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
511
)
612

13+
// SetupWebhookWithManager registers the webhook with the manager
14+
func (r *VirtualMCPServer) SetupWebhookWithManager(mgr ctrl.Manager) error {
15+
return ctrl.NewWebhookManagedBy(mgr).
16+
For(r).
17+
Complete()
18+
}
19+
20+
//nolint:lll // kubebuilder webhook marker cannot be split
21+
// +kubebuilder:webhook:path=/validate-toolhive-stacklok-dev-v1alpha1-virtualmcpserver,mutating=false,failurePolicy=fail,sideEffects=None,groups=toolhive.stacklok.dev,resources=virtualmcpservers,verbs=create;update,versions=v1alpha1,name=vvirtualmcpserver.kb.io,admissionReviewVersions=v1
22+
23+
var _ webhook.CustomValidator = &VirtualMCPServer{}
24+
25+
// ValidateCreate implements webhook.CustomValidator
26+
func (r *VirtualMCPServer) ValidateCreate(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
27+
return nil, r.Validate()
28+
}
29+
30+
// ValidateUpdate implements webhook.CustomValidator
31+
func (r *VirtualMCPServer) ValidateUpdate(_ context.Context, _ runtime.Object, _ runtime.Object) (admission.Warnings, error) {
32+
return nil, r.Validate()
33+
}
34+
35+
// ValidateDelete implements webhook.CustomValidator
36+
func (*VirtualMCPServer) ValidateDelete(_ context.Context, _ runtime.Object) (admission.Warnings, error) {
37+
// No validation needed on delete
38+
return nil, nil
39+
}
40+
741
// Validate performs validation for VirtualMCPServer
8-
// This method can be called by the controller during reconciliation
42+
// This method can be called by the controller during reconciliation or by the webhook
943
func (r *VirtualMCPServer) Validate() error {
1044
// Validate GroupRef is set (required field)
1145
if r.Spec.GroupRef.Name == "" {

cmd/thv-operator/main.go

Lines changed: 55 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package main
55
import (
66
"context"
77
"flag"
8+
"fmt"
89
"os"
910
"strings"
1011

@@ -72,12 +73,44 @@ func main() {
7273
}
7374

7475
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options)
75-
7676
if err != nil {
7777
setupLog.Error(err, "unable to start manager")
7878
os.Exit(1)
7979
}
8080

81+
if err := setupControllersAndWebhooks(mgr); err != nil {
82+
setupLog.Error(err, "unable to setup controllers and webhooks")
83+
os.Exit(1)
84+
}
85+
86+
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
87+
setupLog.Error(err, "unable to set up health check")
88+
os.Exit(1)
89+
}
90+
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
91+
setupLog.Error(err, "unable to set up ready check")
92+
os.Exit(1)
93+
}
94+
95+
podNamespace, _ := os.LookupEnv("POD_NAMESPACE")
96+
// Set up telemetry service - only runs when elected as leader
97+
telemetryService := telemetry.NewService(mgr.GetClient(), podNamespace)
98+
if err := mgr.Add(&telemetry.LeaderTelemetryRunnable{
99+
TelemetryService: telemetryService,
100+
}); err != nil {
101+
setupLog.Error(err, "unable to add telemetry runnable")
102+
os.Exit(1)
103+
}
104+
105+
setupLog.Info("starting manager")
106+
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
107+
setupLog.Error(err, "problem running manager")
108+
os.Exit(1)
109+
}
110+
}
111+
112+
// setupControllersAndWebhooks sets up all controllers and webhooks with the manager
113+
func setupControllersAndWebhooks(mgr ctrl.Manager) error {
81114
// Set up field indexing for MCPServer.Spec.GroupRef
82115
if err := mgr.GetFieldIndexer().IndexField(
83116
context.Background(),
@@ -91,8 +124,7 @@ func main() {
91124
return []string{mcpServer.Spec.GroupRef}
92125
},
93126
); err != nil {
94-
setupLog.Error(err, "unable to create field index for spec.groupRef")
95-
os.Exit(1)
127+
return fmt.Errorf("unable to create field index for spec.groupRef: %w", err)
96128
}
97129

98130
// Create a shared platform detector for all controllers
@@ -105,80 +137,61 @@ func main() {
105137
ImageValidation: validation.ImageValidationAlwaysAllow,
106138
}
107139

108-
if err = rec.SetupWithManager(mgr); err != nil {
109-
setupLog.Error(err, "unable to create controller", "controller", "MCPServer")
110-
os.Exit(1)
140+
if err := rec.SetupWithManager(mgr); err != nil {
141+
return fmt.Errorf("unable to create controller MCPServer: %w", err)
111142
}
112143

113144
// Register MCPToolConfig controller
114-
if err = (&controllers.ToolConfigReconciler{
145+
if err := (&controllers.ToolConfigReconciler{
115146
Client: mgr.GetClient(),
116147
Scheme: mgr.GetScheme(),
117148
}).SetupWithManager(mgr); err != nil {
118-
setupLog.Error(err, "unable to create controller", "controller", "MCPToolConfig")
119-
os.Exit(1)
149+
return fmt.Errorf("unable to create controller MCPToolConfig: %w", err)
120150
}
121151

122152
// Register MCPExternalAuthConfig controller
123-
if err = (&controllers.MCPExternalAuthConfigReconciler{
153+
if err := (&controllers.MCPExternalAuthConfigReconciler{
124154
Client: mgr.GetClient(),
125155
Scheme: mgr.GetScheme(),
126156
}).SetupWithManager(mgr); err != nil {
127-
setupLog.Error(err, "unable to create controller", "controller", "MCPExternalAuthConfig")
128-
os.Exit(1)
157+
return fmt.Errorf("unable to create controller MCPExternalAuthConfig: %w", err)
129158
}
130159

131160
// Register MCPRemoteProxy controller
132-
if err = (&controllers.MCPRemoteProxyReconciler{
161+
if err := (&controllers.MCPRemoteProxyReconciler{
133162
Client: mgr.GetClient(),
134163
Scheme: mgr.GetScheme(),
135164
PlatformDetector: ctrlutil.NewSharedPlatformDetector(),
136165
}).SetupWithManager(mgr); err != nil {
137-
setupLog.Error(err, "unable to create controller", "controller", "MCPRemoteProxy")
138-
os.Exit(1)
166+
return fmt.Errorf("unable to create controller MCPRemoteProxy: %w", err)
139167
}
140168

141169
// Only register MCPRegistry controller if feature flag is enabled
142170
rec.ImageValidation = validation.ImageValidationRegistryEnforcing
143171

144-
if err = (controllers.NewMCPRegistryReconciler(mgr.GetClient(), mgr.GetScheme())).SetupWithManager(mgr); err != nil {
145-
setupLog.Error(err, "unable to create controller", "controller", "MCPRegistry")
146-
os.Exit(1)
172+
if err := (controllers.NewMCPRegistryReconciler(mgr.GetClient(), mgr.GetScheme())).SetupWithManager(mgr); err != nil {
173+
return fmt.Errorf("unable to create controller MCPRegistry: %w", err)
147174
}
148175

149176
// Set up MCPGroup controller
150-
if err = (&controllers.MCPGroupReconciler{
177+
if err := (&controllers.MCPGroupReconciler{
151178
Client: mgr.GetClient(),
152179
}).SetupWithManager(mgr); err != nil {
153-
setupLog.Error(err, "unable to create controller", "controller", "MCPGroup")
154-
os.Exit(1)
180+
return fmt.Errorf("unable to create controller MCPGroup: %w", err)
155181
}
156-
//+kubebuilder:scaffold:builder
157182

158-
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
159-
setupLog.Error(err, "unable to set up health check")
160-
os.Exit(1)
161-
}
162-
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
163-
setupLog.Error(err, "unable to set up ready check")
164-
os.Exit(1)
183+
// Set up VirtualMCPServer webhook
184+
if err := (&mcpv1alpha1.VirtualMCPServer{}).SetupWebhookWithManager(mgr); err != nil {
185+
return fmt.Errorf("unable to create webhook VirtualMCPServer: %w", err)
165186
}
166187

167-
podNamespace, _ := os.LookupEnv("POD_NAMESPACE")
168-
// Set up telemetry service - only runs when elected as leader
169-
telemetryService := telemetry.NewService(mgr.GetClient(), podNamespace)
170-
if err := mgr.Add(&telemetry.LeaderTelemetryRunnable{
171-
TelemetryService: telemetryService,
172-
}); err != nil {
173-
setupLog.Error(err, "unable to add telemetry runnable")
174-
os.Exit(1)
188+
// Set up VirtualMCPCompositeToolDefinition webhook
189+
if err := (&mcpv1alpha1.VirtualMCPCompositeToolDefinition{}).SetupWebhookWithManager(mgr); err != nil {
190+
return fmt.Errorf("unable to create webhook VirtualMCPCompositeToolDefinition: %w", err)
175191
}
192+
//+kubebuilder:scaffold:builder
176193

177-
setupLog.Info("starting manager")
178-
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
179-
setupLog.Error(err, "problem running manager")
180-
os.Exit(1)
181-
}
194+
return nil
182195
}
183196

184197
// getDefaultNamespaces returns a map of namespaces to cache.Config for the operator to watch.

config/webhook/manifests.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ kind: ValidatingWebhookConfiguration
44
metadata:
55
name: validating-webhook-configuration
66
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /validate-toolhive-stacklok-dev-v1alpha1-virtualmcpcompositetooldefinition
14+
failurePolicy: Fail
15+
name: vvirtualmcpcompositetooldefinition.kb.io
16+
rules:
17+
- apiGroups:
18+
- toolhive.stacklok.dev
19+
apiVersions:
20+
- v1alpha1
21+
operations:
22+
- CREATE
23+
- UPDATE
24+
resources:
25+
- virtualmcpcompositetooldefinitions
26+
sideEffects: None
727
- admissionReviewVersions:
828
- v1
929
clientConfig:
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{{- if .Values.webhook.enabled }}
2+
apiVersion: admissionregistration.k8s.io/v1
3+
kind: ValidatingWebhookConfiguration
4+
metadata:
5+
name: {{ include "operator.fullname" . }}-validating-webhook-configuration
6+
labels:
7+
{{- include "operator.labels" . | nindent 4 }}
8+
{{- if .Values.webhook.certManager.enabled }}
9+
annotations:
10+
cert-manager.io/inject-ca-from: {{ .Release.Namespace }}/{{ include "operator.fullname" . }}-serving-cert
11+
{{- end }}
12+
webhooks:
13+
- admissionReviewVersions:
14+
- v1
15+
clientConfig:
16+
service:
17+
name: {{ include "operator.fullname" . }}-webhook-service
18+
namespace: {{ .Release.Namespace }}
19+
path: /validate-toolhive-stacklok-dev-v1alpha1-virtualmcpserver
20+
failurePolicy: Fail
21+
name: vvirtualmcpserver.kb.io
22+
rules:
23+
- apiGroups:
24+
- toolhive.stacklok.dev
25+
apiVersions:
26+
- v1alpha1
27+
operations:
28+
- CREATE
29+
- UPDATE
30+
resources:
31+
- virtualmcpservers
32+
sideEffects: None
33+
- admissionReviewVersions:
34+
- v1
35+
clientConfig:
36+
service:
37+
name: {{ include "operator.fullname" . }}-webhook-service
38+
namespace: {{ .Release.Namespace }}
39+
path: /validate-toolhive-stacklok-dev-v1alpha1-virtualmcpcompositetooldefinition
40+
failurePolicy: Fail
41+
name: vvirtualmcpcompositetooldefinition.kb.io
42+
rules:
43+
- apiGroups:
44+
- toolhive.stacklok.dev
45+
apiVersions:
46+
- v1alpha1
47+
operations:
48+
- CREATE
49+
- UPDATE
50+
resources:
51+
- virtualmcpcompositetooldefinitions
52+
sideEffects: None
53+
{{- end }}

deploy/charts/operator/values.yaml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,15 @@ operator:
188188
# -- Affinity settings for the operator pod
189189
affinity: {}
190190

191+
# -- Webhook configuration for admission control
192+
webhook:
193+
# -- Enable webhook admission control
194+
enabled: false
195+
# -- Cert-manager integration for webhook certificates
196+
certManager:
197+
# -- Use cert-manager to generate webhook certificates
198+
enabled: false
199+
191200
# -- All values for the registry API deployment and associated resources
192201
registryAPI:
193202
# -- Container image for the registry API

0 commit comments

Comments
 (0)