@@ -26,6 +26,7 @@ import (
26
26
"k8s.io/apimachinery/pkg/types"
27
27
"k8s.io/apimachinery/pkg/util/intstr"
28
28
"k8s.io/client-go/rest"
29
+ "k8s.io/client-go/tools/record"
29
30
ctrl "sigs.k8s.io/controller-runtime"
30
31
"sigs.k8s.io/controller-runtime/pkg/client"
31
32
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -40,6 +41,7 @@ import (
40
41
type MCPServerReconciler struct {
41
42
client.Client
42
43
Scheme * runtime.Scheme
44
+ Recorder record.EventRecorder
43
45
platformDetector kubernetes.PlatformDetector
44
46
detectedPlatform kubernetes.Platform
45
47
platformOnce sync.Once
@@ -295,6 +297,9 @@ func (r *MCPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
295
297
deployment := & appsv1.Deployment {}
296
298
err = r .Get (ctx , types.NamespacedName {Name : mcpServer .Name , Namespace : mcpServer .Namespace }, deployment )
297
299
if err != nil && errors .IsNotFound (err ) {
300
+ // Validate PodTemplateSpec and update status
301
+ r .validateAndUpdatePodTemplateStatus (ctx , mcpServer )
302
+
298
303
// Define a new deployment
299
304
dep := r .deploymentForMCPServer (ctx , mcpServer )
300
305
if dep == nil {
@@ -364,6 +369,9 @@ func (r *MCPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
364
369
365
370
// Check if the deployment spec changed
366
371
if r .deploymentNeedsUpdate (ctx , deployment , mcpServer ) {
372
+ // Validate PodTemplateSpec and update status
373
+ r .validateAndUpdatePodTemplateStatus (ctx , mcpServer )
374
+
367
375
// Update the deployment
368
376
newDeployment := r .deploymentForMCPServer (ctx , mcpServer )
369
377
deployment .Spec = newDeployment .Spec
@@ -406,6 +414,47 @@ func setImageValidationCondition(mcpServer *mcpv1alpha1.MCPServer, status metav1
406
414
})
407
415
}
408
416
417
+ // validateAndUpdatePodTemplateStatus validates the PodTemplateSpec and updates the MCPServer status
418
+ // with appropriate conditions and events
419
+ func (r * MCPServerReconciler ) validateAndUpdatePodTemplateStatus (ctx context.Context , mcpServer * mcpv1alpha1.MCPServer ) {
420
+ ctxLogger := log .FromContext (ctx )
421
+
422
+ // Only validate if PodTemplateSpec is provided
423
+ if mcpServer .Spec .PodTemplateSpec == nil || mcpServer .Spec .PodTemplateSpec .Raw == nil {
424
+ return
425
+ }
426
+
427
+ _ , err := NewMCPServerPodTemplateSpecBuilder (mcpServer .Spec .PodTemplateSpec )
428
+ if err != nil {
429
+ // Record event for invalid PodTemplateSpec
430
+ r .Recorder .Eventf (mcpServer , corev1 .EventTypeWarning , "InvalidPodTemplateSpec" ,
431
+ "Failed to parse PodTemplateSpec: %v. Deployment will continue without pod customizations." , err )
432
+
433
+ // Set condition for invalid PodTemplateSpec
434
+ meta .SetStatusCondition (& mcpServer .Status .Conditions , metav1.Condition {
435
+ Type : "PodTemplateValid" ,
436
+ Status : metav1 .ConditionFalse ,
437
+ ObservedGeneration : mcpServer .Generation ,
438
+ Reason : "InvalidPodTemplateSpec" ,
439
+ Message : fmt .Sprintf ("Failed to parse PodTemplateSpec: %v. Deployment continues without customizations." , err ),
440
+ })
441
+ } else {
442
+ // Set condition for valid PodTemplateSpec
443
+ meta .SetStatusCondition (& mcpServer .Status .Conditions , metav1.Condition {
444
+ Type : "PodTemplateValid" ,
445
+ Status : metav1 .ConditionTrue ,
446
+ ObservedGeneration : mcpServer .Generation ,
447
+ Reason : "ValidPodTemplateSpec" ,
448
+ Message : "PodTemplateSpec is valid" ,
449
+ })
450
+ }
451
+
452
+ // Update status with the condition
453
+ if statusErr := r .Status ().Update (ctx , mcpServer ); statusErr != nil {
454
+ ctxLogger .Error (statusErr , "Failed to update MCPServer status with PodTemplateSpec validation" )
455
+ }
456
+ }
457
+
409
458
// handleRestartAnnotation checks if the restart annotation has been updated and triggers a restart if needed
410
459
// Returns true if a restart was triggered and the reconciliation should be requeued
411
460
func (r * MCPServerReconciler ) handleRestartAnnotation (ctx context.Context , mcpServer * mcpv1alpha1.MCPServer ) (bool , error ) {
@@ -756,17 +805,22 @@ func (r *MCPServerReconciler) deploymentForMCPServer(ctx context.Context, m *mcp
756
805
args = append (args , fmt .Sprintf ("--from-configmap=%s" , configMapRef ))
757
806
758
807
// Also add pod template patch for secrets (same as regular flags approach)
759
- finalPodTemplateSpec := NewMCPServerPodTemplateSpecBuilder (m .Spec .PodTemplateSpec ).
760
- WithSecrets (m .Spec .Secrets ).
761
- Build ()
762
- // Add pod template patch if we have one
763
- if finalPodTemplateSpec != nil {
764
- podTemplatePatch , err := json .Marshal (finalPodTemplateSpec )
765
- if err != nil {
766
- ctxLogger := log .FromContext (ctx )
767
- ctxLogger .Error (err , "Failed to marshal pod template spec" )
768
- } else {
769
- args = append (args , fmt .Sprintf ("--k8s-pod-patch=%s" , string (podTemplatePatch )))
808
+ builder , err := NewMCPServerPodTemplateSpecBuilder (m .Spec .PodTemplateSpec )
809
+ if err != nil {
810
+ ctxLogger := log .FromContext (ctx )
811
+ ctxLogger .Error (err , "Invalid PodTemplateSpec in MCPServer spec, continuing without customizations" )
812
+ // Continue without pod template patch - the deployment will still work
813
+ } else {
814
+ finalPodTemplateSpec := builder .WithSecrets (m .Spec .Secrets ).Build ()
815
+ // Add pod template patch if we have one
816
+ if finalPodTemplateSpec != nil {
817
+ podTemplatePatch , err := json .Marshal (finalPodTemplateSpec )
818
+ if err != nil {
819
+ ctxLogger := log .FromContext (ctx )
820
+ ctxLogger .Error (err , "Failed to marshal pod template spec" )
821
+ } else {
822
+ args = append (args , fmt .Sprintf ("--k8s-pod-patch=%s" , string (podTemplatePatch )))
823
+ }
770
824
}
771
825
}
772
826
} else {
@@ -794,18 +848,26 @@ func (r *MCPServerReconciler) deploymentForMCPServer(ctx context.Context, m *mcp
794
848
defaultSA := mcpServerServiceAccountName (m .Name )
795
849
serviceAccount = & defaultSA
796
850
}
797
- finalPodTemplateSpec := NewMCPServerPodTemplateSpecBuilder (m .Spec .PodTemplateSpec ).
798
- WithServiceAccount (serviceAccount ).
799
- WithSecrets (m .Spec .Secrets ).
800
- Build ()
801
- // Add pod template patch if we have one
802
- if finalPodTemplateSpec != nil {
803
- podTemplatePatch , err := json .Marshal (finalPodTemplateSpec )
804
- if err != nil {
805
- ctxLogger := log .FromContext (ctx )
806
- ctxLogger .Error (err , "Failed to marshal pod template spec" )
807
- } else {
808
- args = append (args , fmt .Sprintf ("--k8s-pod-patch=%s" , string (podTemplatePatch )))
851
+
852
+ builder , err := NewMCPServerPodTemplateSpecBuilder (m .Spec .PodTemplateSpec )
853
+ if err != nil {
854
+ ctxLogger := log .FromContext (ctx )
855
+ ctxLogger .Error (err , "Invalid PodTemplateSpec in MCPServer spec, continuing without customizations" )
856
+ // Continue without pod template patch - the deployment will still work
857
+ } else {
858
+ finalPodTemplateSpec := builder .
859
+ WithServiceAccount (serviceAccount ).
860
+ WithSecrets (m .Spec .Secrets ).
861
+ Build ()
862
+ // Add pod template patch if we have one
863
+ if finalPodTemplateSpec != nil {
864
+ podTemplatePatch , err := json .Marshal (finalPodTemplateSpec )
865
+ if err != nil {
866
+ ctxLogger := log .FromContext (ctx )
867
+ ctxLogger .Error (err , "Failed to marshal pod template spec" )
868
+ } else {
869
+ args = append (args , fmt .Sprintf ("--k8s-pod-patch=%s" , string (podTemplatePatch )))
870
+ }
809
871
}
810
872
}
811
873
@@ -1379,21 +1441,19 @@ func (r *MCPServerReconciler) deploymentNeedsUpdate(
1379
1441
// TODO: Add more comprehensive checks for PodTemplateSpec changes beyond just the args
1380
1442
// This would involve comparing the actual pod template spec fields with what would be
1381
1443
// generated by the operator, rather than just checking the command-line arguments.
1382
- if mcpServer .Spec .PodTemplateSpec != nil {
1383
- podTemplatePatch , err := json .Marshal (mcpServer .Spec .PodTemplateSpec )
1384
- if err == nil {
1385
- podTemplatePatchArg := fmt .Sprintf ("--k8s-pod-patch=%s" , string (podTemplatePatch ))
1386
- found := false
1387
- for _ , arg := range container .Args {
1388
- if arg == podTemplatePatchArg {
1389
- found = true
1390
- break
1391
- }
1392
- }
1393
- if ! found {
1394
- return true
1444
+ if mcpServer .Spec .PodTemplateSpec != nil && mcpServer .Spec .PodTemplateSpec .Raw != nil {
1445
+ // Use the raw bytes directly since PodTemplateSpec is now a RawExtension
1446
+ podTemplatePatchArg := fmt .Sprintf ("--k8s-pod-patch=%s" , string (mcpServer .Spec .PodTemplateSpec .Raw ))
1447
+ found := false
1448
+ for _ , arg := range container .Args {
1449
+ if arg == podTemplatePatchArg {
1450
+ found = true
1451
+ break
1395
1452
}
1396
1453
}
1454
+ if ! found {
1455
+ return true
1456
+ }
1397
1457
}
1398
1458
1399
1459
// Check if the container port has changed
@@ -1447,7 +1507,14 @@ func (r *MCPServerReconciler) deploymentNeedsUpdate(
1447
1507
defaultSA := mcpServerServiceAccountName (mcpServer .Name )
1448
1508
serviceAccount = & defaultSA
1449
1509
}
1450
- expectedPodTemplateSpec := NewMCPServerPodTemplateSpecBuilder (mcpServer .Spec .PodTemplateSpec ).
1510
+
1511
+ builder , err := NewMCPServerPodTemplateSpecBuilder (mcpServer .Spec .PodTemplateSpec )
1512
+ if err != nil {
1513
+ // If we can't parse the PodTemplateSpec, consider it as needing update
1514
+ return true
1515
+ }
1516
+
1517
+ expectedPodTemplateSpec := builder .
1451
1518
WithServiceAccount (serviceAccount ).
1452
1519
WithSecrets (mcpServer .Spec .Secrets ).
1453
1520
Build ()
@@ -1513,7 +1580,6 @@ func (r *MCPServerReconciler) deploymentNeedsUpdate(
1513
1580
if ! equalOpenTelemetryArgs (otelConfig , container .Args ) {
1514
1581
return true
1515
1582
}
1516
-
1517
1583
}
1518
1584
1519
1585
// Check if the service account name has changed
0 commit comments