@@ -26,6 +26,7 @@ import (
2626 "k8s.io/apimachinery/pkg/types"
2727 "k8s.io/apimachinery/pkg/util/intstr"
2828 "k8s.io/client-go/rest"
29+ "k8s.io/client-go/tools/record"
2930 ctrl "sigs.k8s.io/controller-runtime"
3031 "sigs.k8s.io/controller-runtime/pkg/client"
3132 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -40,6 +41,7 @@ import (
4041type MCPServerReconciler struct {
4142 client.Client
4243 Scheme * runtime.Scheme
44+ Recorder record.EventRecorder
4345 platformDetector kubernetes.PlatformDetector
4446 detectedPlatform kubernetes.Platform
4547 platformOnce sync.Once
@@ -295,6 +297,9 @@ func (r *MCPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
295297 deployment := & appsv1.Deployment {}
296298 err = r .Get (ctx , types.NamespacedName {Name : mcpServer .Name , Namespace : mcpServer .Namespace }, deployment )
297299 if err != nil && errors .IsNotFound (err ) {
300+ // Validate PodTemplateSpec and update status
301+ r .validateAndUpdatePodTemplateStatus (ctx , mcpServer )
302+
298303 // Define a new deployment
299304 dep := r .deploymentForMCPServer (ctx , mcpServer )
300305 if dep == nil {
@@ -364,6 +369,9 @@ func (r *MCPServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (
364369
365370 // Check if the deployment spec changed
366371 if r .deploymentNeedsUpdate (ctx , deployment , mcpServer ) {
372+ // Validate PodTemplateSpec and update status
373+ r .validateAndUpdatePodTemplateStatus (ctx , mcpServer )
374+
367375 // Update the deployment
368376 newDeployment := r .deploymentForMCPServer (ctx , mcpServer )
369377 deployment .Spec = newDeployment .Spec
@@ -406,6 +414,47 @@ func setImageValidationCondition(mcpServer *mcpv1alpha1.MCPServer, status metav1
406414 })
407415}
408416
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+
409458// handleRestartAnnotation checks if the restart annotation has been updated and triggers a restart if needed
410459// Returns true if a restart was triggered and the reconciliation should be requeued
411460func (r * MCPServerReconciler ) handleRestartAnnotation (ctx context.Context , mcpServer * mcpv1alpha1.MCPServer ) (bool , error ) {
@@ -756,17 +805,22 @@ func (r *MCPServerReconciler) deploymentForMCPServer(ctx context.Context, m *mcp
756805 args = append (args , fmt .Sprintf ("--from-configmap=%s" , configMapRef ))
757806
758807 // 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+ }
770824 }
771825 }
772826 } else {
@@ -794,18 +848,26 @@ func (r *MCPServerReconciler) deploymentForMCPServer(ctx context.Context, m *mcp
794848 defaultSA := mcpServerServiceAccountName (m .Name )
795849 serviceAccount = & defaultSA
796850 }
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+ }
809871 }
810872 }
811873
@@ -1379,21 +1441,19 @@ func (r *MCPServerReconciler) deploymentNeedsUpdate(
13791441 // TODO: Add more comprehensive checks for PodTemplateSpec changes beyond just the args
13801442 // This would involve comparing the actual pod template spec fields with what would be
13811443 // 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
13951452 }
13961453 }
1454+ if ! found {
1455+ return true
1456+ }
13971457 }
13981458
13991459 // Check if the container port has changed
@@ -1447,7 +1507,14 @@ func (r *MCPServerReconciler) deploymentNeedsUpdate(
14471507 defaultSA := mcpServerServiceAccountName (mcpServer .Name )
14481508 serviceAccount = & defaultSA
14491509 }
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 .
14511518 WithServiceAccount (serviceAccount ).
14521519 WithSecrets (mcpServer .Spec .Secrets ).
14531520 Build ()
@@ -1513,7 +1580,6 @@ func (r *MCPServerReconciler) deploymentNeedsUpdate(
15131580 if ! equalOpenTelemetryArgs (otelConfig , container .Args ) {
15141581 return true
15151582 }
1516-
15171583 }
15181584
15191585 // Check if the service account name has changed
0 commit comments