@@ -78,6 +78,9 @@ func (v *WorkspaceKindValidator) ValidateCreate(ctx context.Context, obj runtime
78
78
// validate the extra environment variables
79
79
allErrs = append (allErrs , validateExtraEnv (workspaceKind )... )
80
80
81
+ // validate the ports configuration
82
+ allErrs = append (allErrs , validatePorts (workspaceKind )... )
83
+
81
84
// generate helper maps for imageConfig values
82
85
imageConfigIdMap := make (map [string ]kubefloworgv1beta1.ImageConfigValue )
83
86
imageConfigRedirectMap := make (map [string ]string )
@@ -156,6 +159,11 @@ func (v *WorkspaceKindValidator) ValidateUpdate(ctx context.Context, oldObj, new
156
159
allErrs = append (allErrs , validateExtraEnv (newWorkspaceKind )... )
157
160
}
158
161
162
+ // validate the ports configuration
163
+ if ! equality .Semantic .DeepEqual (newWorkspaceKind .Spec .PodTemplate .Ports , oldWorkspaceKind .Spec .PodTemplate .Ports ) {
164
+ allErrs = append (allErrs , validatePorts (newWorkspaceKind )... )
165
+ }
166
+
159
167
// calculate changes to imageConfig values
160
168
var shouldValidateImageConfigRedirects = false
161
169
toValidateImageConfigIds := make (map [string ]bool )
@@ -516,6 +524,61 @@ func (v *WorkspaceKindValidator) validatePodTemplatePodMetadata(workspaceKind *k
516
524
return errs
517
525
}
518
526
527
+ // validatePorts validates the ports in podTemplate.ports of WorkspaceKind
528
+ func validatePorts (workspaceKind * kubefloworgv1beta1.WorkspaceKind ) []* field.Error {
529
+ var errs []* field.Error
530
+
531
+ ports := workspaceKind .Spec .PodTemplate .Ports
532
+ portsPath := field .NewPath ("spec" , "podTemplate" , "ports" )
533
+
534
+ // if no ports are defined, we can skip validation
535
+ if len (ports ) == 0 {
536
+ return errs
537
+ }
538
+
539
+ // collect all available port IDs from imageConfig values
540
+ availablePortIds := make (map [string ]bool )
541
+ for _ , imageConfigValue := range workspaceKind .Spec .PodTemplate .Options .ImageConfig .Values {
542
+ for _ , imagePort := range imageConfigValue .Spec .Ports {
543
+ availablePortIds [imagePort .Id ] = true
544
+ }
545
+ }
546
+
547
+ // track seen port IDs to ensure uniqueness
548
+ seenPortIds := make (map [string ]bool )
549
+
550
+ // validate each port in the ports array
551
+ for i , port := range ports {
552
+ portId := port .PortId
553
+ portPath := portsPath .Index (i )
554
+ portIdPath := portPath .Child ("portId" )
555
+
556
+ // validate that portId is not empty (should be caught by CRD validation, but be safe)
557
+ if portId == "" {
558
+ errs = append (errs , field .Required (portIdPath , "portId is required" ))
559
+ continue
560
+ }
561
+
562
+ // validate that each port has a unique portId
563
+ if seenPortIds [portId ] {
564
+ errs = append (errs , field .Duplicate (portIdPath , portId ))
565
+ } else {
566
+ seenPortIds [portId ] = true
567
+ }
568
+
569
+ // validate that the portId references a valid port from imageConfig
570
+ if ! availablePortIds [portId ] {
571
+ errs = append (errs , field .Invalid (portIdPath , portId ,
572
+ fmt .Sprintf ("portId %q does not match any port defined in imageConfig values" , portId )))
573
+ }
574
+
575
+ // httpProxy is optional, so no validation needed for nil
576
+ // The structure validation is handled by the CRD schema
577
+ }
578
+
579
+ return errs
580
+ }
581
+
519
582
// validateExtraEnv validates the extra environment variables in a WorkspaceKind
520
583
func validateExtraEnv (workspaceKind * kubefloworgv1beta1.WorkspaceKind ) []* field.Error {
521
584
var errs []* field.Error
0 commit comments