@@ -577,6 +577,264 @@ var _ = Describe("DevWorkspace Controller", func() {
577577 }
578578 })
579579
580+ It ("Sorts automount secrets in consistent order" , func () {
581+ By ("Creating automount secrets in non-sorted order" )
582+ secretZ := generateSecret ("secret-z" , corev1 .SecretTypeOpaque )
583+ secretZ .Labels [constants .DevWorkspaceMountLabel ] = "true"
584+ secretZ .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/z"
585+ createObject (secretZ )
586+ defer deleteObject (secretZ )
587+
588+ secretA := generateSecret ("secret-a" , corev1 .SecretTypeOpaque )
589+ secretA .Labels [constants .DevWorkspaceMountLabel ] = "true"
590+ secretA .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/a"
591+ createObject (secretA )
592+ defer deleteObject (secretA )
593+
594+ secretM := generateSecret ("secret-m" , corev1 .SecretTypeOpaque )
595+ secretM .Labels [constants .DevWorkspaceMountLabel ] = "true"
596+ secretM .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/m"
597+ createObject (secretM )
598+ defer deleteObject (secretM )
599+
600+ // Create secrets with numeric suffixes to test numeric sorting
601+ secret15 := generateSecret ("automount-secret-15" , corev1 .SecretTypeOpaque )
602+ secret15 .Labels [constants .DevWorkspaceMountLabel ] = "true"
603+ secret15 .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/15"
604+ createObject (secret15 )
605+ defer deleteObject (secret15 )
606+
607+ secret02 := generateSecret ("automount-secret-02" , corev1 .SecretTypeOpaque )
608+ secret02 .Labels [constants .DevWorkspaceMountLabel ] = "true"
609+ secret02 .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/02"
610+ createObject (secret02 )
611+ defer deleteObject (secret02 )
612+
613+ secret08 := generateSecret ("automount-secret-08" , corev1 .SecretTypeOpaque )
614+ secret08 .Labels [constants .DevWorkspaceMountLabel ] = "true"
615+ secret08 .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/08"
616+ createObject (secret08 )
617+ defer deleteObject (secret08 )
618+
619+ By ("Creating DevWorkspace" )
620+ createDevWorkspace (devWorkspaceName , "test-devworkspace.yaml" )
621+ devworkspace := getExistingDevWorkspace (devWorkspaceName )
622+ workspaceID := devworkspace .Status .DevWorkspaceId
623+
624+ By ("Manually making Routing ready to continue" )
625+ markRoutingReady (testURL , common .DevWorkspaceRoutingName (workspaceID ))
626+
627+ deploy := & appsv1.Deployment {}
628+ deployNN := namespacedName (common .DeploymentName (workspaceID ), testNamespace )
629+ Eventually (func () error {
630+ return k8sClient .Get (ctx , deployNN , deploy )
631+ }, timeout , interval ).Should (Succeed (), "Getting workspace deployment from cluster" )
632+
633+ By ("Verifying secrets are sorted in deployment volumes" )
634+
635+ expectedSecretNames := []string {"secret-a" , "secret-m" , "secret-z" , "automount-secret-02" , "automount-secret-08" , "automount-secret-15" }
636+ var automountVolumes []corev1.Volume
637+ for _ , vol := range deploy .Spec .Template .Spec .Volumes {
638+ if vol .Secret != nil {
639+ for _ , name := range expectedSecretNames {
640+ if vol .Name == name && vol .Secret .SecretName == name {
641+ automountVolumes = append (automountVolumes , vol )
642+ break
643+ }
644+ }
645+ }
646+ }
647+
648+ // Verify we found all expected volumes
649+ Expect (automountVolumes ).Should (HaveLen (6 ), "Should have 6 automount secret volumes" )
650+
651+ // Verify volumes are in sorted order (alphabetically by volume name, which matches secret name)
652+ expectedOrder := []string {
653+ "automount-secret-02" ,
654+ "automount-secret-08" ,
655+ "automount-secret-15" ,
656+ "secret-a" ,
657+ "secret-m" ,
658+ "secret-z" ,
659+ }
660+
661+ actualOrder := make ([]string , len (automountVolumes ))
662+ for i , vol := range automountVolumes {
663+ actualOrder [i ] = vol .Name
664+ }
665+
666+ Expect (actualOrder ).Should (Equal (expectedOrder ), "Automount secret volumes should be sorted alphabetically by volume name" )
667+ })
668+
669+ It ("Sorts automount configmaps in consistent order" , func () {
670+ By ("Creating automount configmaps in non-sorted order" )
671+ configmapZ := generateConfigMap ("configmap-z" )
672+ configmapZ .Labels [constants .DevWorkspaceMountLabel ] = "true"
673+ configmapZ .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/z"
674+ createObject (configmapZ )
675+ defer deleteObject (configmapZ )
676+
677+ configmapA := generateConfigMap ("configmap-a" )
678+ configmapA .Labels [constants .DevWorkspaceMountLabel ] = "true"
679+ configmapA .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/a"
680+ createObject (configmapA )
681+ defer deleteObject (configmapA )
682+
683+ configmapM := generateConfigMap ("configmap-m" )
684+ configmapM .Labels [constants .DevWorkspaceMountLabel ] = "true"
685+ configmapM .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/m"
686+ createObject (configmapM )
687+ defer deleteObject (configmapM )
688+
689+ // Create configmaps with numeric suffixes to test numeric sorting
690+ configmap15 := generateConfigMap ("automount-cm-15" )
691+ configmap15 .Labels [constants .DevWorkspaceMountLabel ] = "true"
692+ configmap15 .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/15"
693+ createObject (configmap15 )
694+ defer deleteObject (configmap15 )
695+
696+ configmap02 := generateConfigMap ("automount-cm-02" )
697+ configmap02 .Labels [constants .DevWorkspaceMountLabel ] = "true"
698+ configmap02 .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/02"
699+ createObject (configmap02 )
700+ defer deleteObject (configmap02 )
701+
702+ configmap08 := generateConfigMap ("automount-cm-08" )
703+ configmap08 .Labels [constants .DevWorkspaceMountLabel ] = "true"
704+ configmap08 .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/08"
705+ createObject (configmap08 )
706+ defer deleteObject (configmap08 )
707+
708+ By ("Creating DevWorkspace" )
709+ createDevWorkspace (devWorkspaceName , "test-devworkspace.yaml" )
710+ devworkspace := getExistingDevWorkspace (devWorkspaceName )
711+ workspaceID := devworkspace .Status .DevWorkspaceId
712+
713+ By ("Manually making Routing ready to continue" )
714+ markRoutingReady (testURL , common .DevWorkspaceRoutingName (workspaceID ))
715+
716+ deploy := & appsv1.Deployment {}
717+ deployNN := namespacedName (common .DeploymentName (workspaceID ), testNamespace )
718+ Eventually (func () error {
719+ return k8sClient .Get (ctx , deployNN , deploy )
720+ }, timeout , interval ).Should (Succeed (), "Getting workspace deployment from cluster" )
721+
722+ By ("Verifying configmaps are sorted in deployment volumes" )
723+
724+ expectedConfigMapNames := []string {"configmap-a" , "configmap-m" , "configmap-z" , "automount-cm-02" , "automount-cm-08" , "automount-cm-15" }
725+ var automountVolumes []corev1.Volume
726+ for _ , vol := range deploy .Spec .Template .Spec .Volumes {
727+ if vol .ConfigMap != nil {
728+ for _ , name := range expectedConfigMapNames {
729+ if vol .Name == name && vol .ConfigMap .Name == name {
730+ automountVolumes = append (automountVolumes , vol )
731+ break
732+ }
733+ }
734+ }
735+ }
736+
737+ // Verify we found all expected volumes
738+ Expect (automountVolumes ).Should (HaveLen (6 ), "Should have 6 automount configmap volumes" )
739+
740+ // Verify volumes are in sorted order (alphabetically by volume name, which matches configmap name)
741+ expectedOrder := []string {
742+ "automount-cm-02" ,
743+ "automount-cm-08" ,
744+ "automount-cm-15" ,
745+ "configmap-a" ,
746+ "configmap-m" ,
747+ "configmap-z" ,
748+ }
749+
750+ actualOrder := make ([]string , len (automountVolumes ))
751+ for i , vol := range automountVolumes {
752+ actualOrder [i ] = vol .Name
753+ }
754+
755+ Expect (actualOrder ).Should (Equal (expectedOrder ), "Automount configmap volumes should be sorted alphabetically by volume name" )
756+ })
757+
758+ It ("Sorts mixed automount secrets and configmaps together" , func () {
759+ By ("Creating automount secrets and configmaps in non-sorted order" )
760+ secretB := generateSecret ("secret-b" , corev1 .SecretTypeOpaque )
761+ secretB .Labels [constants .DevWorkspaceMountLabel ] = "true"
762+ secretB .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/b"
763+ createObject (secretB )
764+ defer deleteObject (secretB )
765+
766+ secretD := generateSecret ("secret-d" , corev1 .SecretTypeOpaque )
767+ secretD .Labels [constants .DevWorkspaceMountLabel ] = "true"
768+ secretD .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/secret/d"
769+ createObject (secretD )
770+ defer deleteObject (secretD )
771+
772+ configmapA := generateConfigMap ("configmap-a" )
773+ configmapA .Labels [constants .DevWorkspaceMountLabel ] = "true"
774+ configmapA .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/a"
775+ createObject (configmapA )
776+ defer deleteObject (configmapA )
777+
778+ configmapC := generateConfigMap ("configmap-c" )
779+ configmapC .Labels [constants .DevWorkspaceMountLabel ] = "true"
780+ configmapC .Annotations [constants .DevWorkspaceMountPathAnnotation ] = "/configmap/c"
781+ createObject (configmapC )
782+ defer deleteObject (configmapC )
783+
784+ By ("Creating DevWorkspace" )
785+ createDevWorkspace (devWorkspaceName , "test-devworkspace.yaml" )
786+ devworkspace := getExistingDevWorkspace (devWorkspaceName )
787+ workspaceID := devworkspace .Status .DevWorkspaceId
788+
789+ By ("Manually making Routing ready to continue" )
790+ markRoutingReady (testURL , common .DevWorkspaceRoutingName (workspaceID ))
791+
792+ deploy := & appsv1.Deployment {}
793+ deployNN := namespacedName (common .DeploymentName (workspaceID ), testNamespace )
794+ Eventually (func () error {
795+ return k8sClient .Get (ctx , deployNN , deploy )
796+ }, timeout , interval ).Should (Succeed (), "Getting workspace deployment from cluster" )
797+
798+ By ("Verifying secrets and configmaps are sorted together" )
799+ expectedNames := []string {"configmap-a" , "configmap-c" , "secret-b" , "secret-d" }
800+ var automountVolumes []corev1.Volume
801+ for _ , vol := range deploy .Spec .Template .Spec .Volumes {
802+ if vol .Secret != nil {
803+ for _ , name := range expectedNames {
804+ if vol .Name == name && vol .Secret .SecretName == name {
805+ automountVolumes = append (automountVolumes , vol )
806+ break
807+ }
808+ }
809+ }
810+ if vol .ConfigMap != nil {
811+ for _ , name := range expectedNames {
812+ if vol .Name == name && vol .ConfigMap .Name == name {
813+ automountVolumes = append (automountVolumes , vol )
814+ break
815+ }
816+ }
817+ }
818+ }
819+
820+ Expect (automountVolumes ).Should (HaveLen (4 ), "Should have 4 automount volumes (2 secrets + 2 configmaps)" )
821+
822+ // All volumes should be sorted together alphabetically
823+ expectedOrder := []string {
824+ "configmap-a" ,
825+ "configmap-c" ,
826+ "secret-b" ,
827+ "secret-d" ,
828+ }
829+
830+ actualOrder := make ([]string , len (automountVolumes ))
831+ for i , vol := range automountVolumes {
832+ actualOrder [i ] = vol .Name
833+ }
834+
835+ Expect (actualOrder ).Should (Equal (expectedOrder ), "Automount volumes (secrets and configmaps) should be sorted together alphabetically" )
836+ })
837+
580838 It ("Detects changes to automount resources and reconciles" , func () {
581839 // NOTE: timeout for this test is reduced, as eventually DWO will reconcile the workspace by coincidence and notice
582840 // the automount secret.
0 commit comments