@@ -709,6 +709,170 @@ func TestReconcileStackConfigPolicy_Reconcile(t *testing.T) {
709709 wantErr : false ,
710710 wantRequeueAfter : true ,
711711 },
712+ {
713+ name : "Secret mount is removed when policy is updated to remove it from SecretMounts" ,
714+ args : args {
715+ client : func () k8s.Client {
716+ // Simulates a policy that previously had 2 SecretMounts (test-secret-mount, another-secret-mount)
717+ // but was updated to only have 1 (test-secret-mount). The copied secrets from the previous
718+ // reconciliation still exist, and we expect the cleanup logic to remove the orphaned one.
719+ policyWithOneMount := policyFixture .DeepCopy ()
720+ policyWithOneMount .Spec .Elasticsearch .SecretMounts = []policyv1alpha1.SecretMount {
721+ {
722+ SecretName : "test-secret-mount" ,
723+ MountPath : "/usr/test" ,
724+ },
725+ }
726+
727+ // Source secrets in policy namespace (these don't need owner labels, they're just data sources)
728+ sourceSecret1 := & corev1.Secret {
729+ ObjectMeta : metav1.ObjectMeta {
730+ Name : "test-secret-mount" ,
731+ Namespace : "ns" ,
732+ },
733+ Data : map [string ][]byte {
734+ "idfile.txt" : []byte ("test id file" ),
735+ },
736+ }
737+ sourceSecret2 := & corev1.Secret {
738+ ObjectMeta : metav1.ObjectMeta {
739+ Name : "another-secret-mount" ,
740+ Namespace : "ns" ,
741+ },
742+ Data : map [string ][]byte {
743+ "another-file.txt" : []byte ("another test file" ),
744+ },
745+ }
746+
747+ // Copied secrets in ES namespace (created by previous reconciliation)
748+ // These have the source secret annotation - should be eligible for cleanup
749+ copiedSecret1 := getSecretMountSecret (t , esv1 .StackConfigAdditionalSecretName ("test-es" , "test-secret-mount" ), "ns" , "test-policy" , "ns" , "delete" )
750+ copiedSecret1 .Labels ["elasticsearch.k8s.elastic.co/cluster-name" ] = "test-es"
751+ copiedSecret1 .Annotations = map [string ]string {
752+ "policy.k8s.elastic.co/source-secret-name" : "test-secret-mount" ,
753+ }
754+ copiedSecret2 := getSecretMountSecret (t , esv1 .StackConfigAdditionalSecretName ("test-es" , "another-secret-mount" ), "ns" , "test-policy" , "ns" , "delete" )
755+ copiedSecret2 .Labels ["elasticsearch.k8s.elastic.co/cluster-name" ] = "test-es"
756+ copiedSecret2 .Annotations = map [string ]string {
757+ "policy.k8s.elastic.co/source-secret-name" : "another-secret-mount" ,
758+ }
759+
760+ // Create a third secret owned by the policy but WITHOUT the source secret annotation
761+ // This should NOT be deleted even though it's not in SecretMounts
762+ secretWithoutAnnotation := getSecretMountSecret (t , esv1 .StackConfigAdditionalSecretName ("test-es" , "test-es-other-secret" ), "ns" , "test-policy" , "ns" , "delete" )
763+ secretWithoutAnnotation .Labels ["elasticsearch.k8s.elastic.co/cluster-name" ] = "test-es"
764+
765+ return k8s .NewFakeClient (policyWithOneMount , & esFixture , & secretFixture , sourceSecret1 , sourceSecret2 , copiedSecret1 , copiedSecret2 , secretWithoutAnnotation , esPodFixture )
766+ }(),
767+ licenseChecker : & license.MockLicenseChecker {EnterpriseEnabled : true },
768+ esClientProvider : fakeClientProvider (clusterStateFileSettingsFixture (42 , nil ), nil ),
769+ },
770+ post : func (r ReconcileStackConfigPolicy , recorder record.FakeRecorder ) {
771+ // Verify the first secret exists (reconciled because still in SecretMounts)
772+ var copiedSecret1 corev1.Secret
773+ err := r .Client .Get (context .Background (), types.NamespacedName {
774+ Name : esv1 .StackConfigAdditionalSecretName ("test-es" , "test-secret-mount" ),
775+ Namespace : "ns" ,
776+ }, & copiedSecret1 )
777+ assert .NoError (t , err , "first copied secret should exist (still in SecretMounts)" )
778+
779+ // Verify the second secret was deleted (removed from SecretMounts and has source annotation)
780+ var copiedSecret2 corev1.Secret
781+ err = r .Client .Get (context .Background (), types.NamespacedName {
782+ Name : esv1 .StackConfigAdditionalSecretName ("test-es" , "another-secret-mount" ),
783+ Namespace : "ns" ,
784+ }, & copiedSecret2 )
785+ assert .True (t , apierrors .IsNotFound (err ), "second copied secret should be deleted (removed from SecretMounts)" )
786+
787+ // Verify the secret without annotation was NOT deleted (lacks source secret annotation)
788+ var secretWithoutAnnotation corev1.Secret
789+ err = r .Client .Get (context .Background (), types.NamespacedName {
790+ Name : esv1 .StackConfigAdditionalSecretName ("test-es" , "test-es-other-secret" ),
791+ Namespace : "ns" ,
792+ }, & secretWithoutAnnotation )
793+ assert .NoError (t , err , "secret without source annotation should NOT be deleted" )
794+ },
795+ wantErr : false ,
796+ },
797+ {
798+ name : "All secret mounts removed from policy - all copied secrets should be deleted" ,
799+ args : args {
800+ client : func () k8s.Client {
801+ // Simulates a policy that previously had SecretMounts but was updated to have none.
802+ // All previously copied secrets with source annotations should be cleaned up.
803+ policyWithNoMounts := policyFixture .DeepCopy ()
804+ policyWithNoMounts .Spec .Elasticsearch .SecretMounts = []policyv1alpha1.SecretMount {}
805+
806+ // Source secrets in policy namespace (left over from before, not currently used)
807+ sourceSecret1 := & corev1.Secret {
808+ ObjectMeta : metav1.ObjectMeta {
809+ Name : "test-secret-mount" ,
810+ Namespace : "ns" ,
811+ },
812+ Data : map [string ][]byte {
813+ "idfile.txt" : []byte ("test id file" ),
814+ },
815+ }
816+ sourceSecret2 := & corev1.Secret {
817+ ObjectMeta : metav1.ObjectMeta {
818+ Name : "another-secret-mount" ,
819+ Namespace : "ns" ,
820+ },
821+ Data : map [string ][]byte {
822+ "another-file.txt" : []byte ("another test file" ),
823+ },
824+ }
825+
826+ // Copied secrets in ES namespace (created by previous reconciliation)
827+ // Both have the source secret annotation and should be cleaned up
828+ copiedSecret1 := getSecretMountSecret (t , esv1 .StackConfigAdditionalSecretName ("test-es" , "test-secret-mount" ), "ns" , "test-policy" , "ns" , "delete" )
829+ copiedSecret1 .Labels ["elasticsearch.k8s.elastic.co/cluster-name" ] = "test-es"
830+ copiedSecret1 .Annotations = map [string ]string {
831+ "policy.k8s.elastic.co/source-secret-name" : "test-secret-mount" ,
832+ }
833+ copiedSecret2 := getSecretMountSecret (t , esv1 .StackConfigAdditionalSecretName ("test-es" , "another-secret-mount" ), "ns" , "test-policy" , "ns" , "delete" )
834+ copiedSecret2 .Labels ["elasticsearch.k8s.elastic.co/cluster-name" ] = "test-es"
835+ copiedSecret2 .Annotations = map [string ]string {
836+ "policy.k8s.elastic.co/source-secret-name" : "another-secret-mount" ,
837+ }
838+
839+ // Create a secret owned by the policy but WITHOUT the source secret annotation
840+ // This should NOT be deleted (not a SecretMount-managed secret)
841+ secretWithoutAnnotation := getSecretMountSecret (t , esv1 .StackConfigAdditionalSecretName ("test-es" , "test-es-other-secret" ), "ns" , "test-policy" , "ns" , "delete" )
842+ secretWithoutAnnotation .Labels ["elasticsearch.k8s.elastic.co/cluster-name" ] = "test-es"
843+
844+ return k8s .NewFakeClient (policyWithNoMounts , & esFixture , & secretFixture , sourceSecret1 , sourceSecret2 , copiedSecret1 , copiedSecret2 , secretWithoutAnnotation , esPodFixture )
845+ }(),
846+ licenseChecker : & license.MockLicenseChecker {EnterpriseEnabled : true },
847+ esClientProvider : fakeClientProvider (clusterStateFileSettingsFixture (42 , nil ), nil ),
848+ },
849+ post : func (r ReconcileStackConfigPolicy , recorder record.FakeRecorder ) {
850+ // Verify both copied secrets with source annotations were deleted
851+ var copiedSecret1 corev1.Secret
852+ err := r .Client .Get (context .Background (), types.NamespacedName {
853+ Name : esv1 .StackConfigAdditionalSecretName ("test-es" , "test-secret-mount" ),
854+ Namespace : "ns" ,
855+ }, & copiedSecret1 )
856+ assert .True (t , apierrors .IsNotFound (err ), "first copied secret should be deleted (all SecretMounts removed)" )
857+
858+ var copiedSecret2 corev1.Secret
859+ err = r .Client .Get (context .Background (), types.NamespacedName {
860+ Name : esv1 .StackConfigAdditionalSecretName ("test-es" , "another-secret-mount" ),
861+ Namespace : "ns" ,
862+ }, & copiedSecret2 )
863+ assert .True (t , apierrors .IsNotFound (err ), "second copied secret should be deleted (all SecretMounts removed)" )
864+
865+ // Verify the secret without annotation was NOT deleted
866+ var secretWithoutAnnotation corev1.Secret
867+ err = r .Client .Get (context .Background (), types.NamespacedName {
868+ Name : esv1 .StackConfigAdditionalSecretName ("test-es" , "test-es-other-secret" ),
869+ Namespace : "ns" ,
870+ }, & secretWithoutAnnotation )
871+ assert .NoError (t , err , "secret without source annotation should NOT be deleted" )
872+ },
873+ wantErr : false ,
874+ wantRequeueAfter : true ,
875+ },
712876 }
713877
714878 for _ , tt := range tests {
0 commit comments