@@ -781,3 +781,62 @@ func getCounterMetric(metricFamilyName, controllerName string) (float64, error)
781781
782782 return 0 , fmt .Errorf ("failed to find %q metric" , metricFamilyName )
783783}
784+
785+ func TestReconcile_KubeconfigSecretResourceVersionChange (t * testing.T ) {
786+ g := NewWithT (t )
787+ ctx := context .Background ()
788+
789+ testCluster := & clusterv1.Cluster {
790+ ObjectMeta : metav1.ObjectMeta {
791+ Name : "test-kubeconfig-rv" ,
792+ Namespace : metav1 .NamespaceDefault ,
793+ },
794+ }
795+ clusterKey := client .ObjectKeyFromObject (testCluster )
796+ g .Expect (env .CreateAndWait (ctx , testCluster )).To (Succeed ())
797+ defer func () { g .Expect (client .IgnoreNotFound (env .CleanupAndWait (ctx , testCluster ))).To (Succeed ()) }()
798+
799+ opts := Options {
800+ SecretClient : env .GetClient (),
801+ Client : ClientOptions {
802+ UserAgent : remote .DefaultClusterAPIUserAgent ("test-controller-manager" ),
803+ Timeout : 10 * time .Second ,
804+ },
805+ Cache : CacheOptions {
806+ Indexes : []CacheOptionsIndex {NodeProviderIDIndex },
807+ },
808+ }
809+ accessorConfig := buildClusterAccessorConfig (env .GetScheme (), opts , nil )
810+ cc := & clusterCache {
811+ client : env .GetAPIReader (),
812+ clusterAccessorConfig : accessorConfig ,
813+ clusterAccessors : make (map [client.ObjectKey ]* clusterAccessor ),
814+ cacheCtx : context .Background (),
815+ }
816+
817+ // Set Cluster.Status.InfrastructureReady == true
818+ patch := client .MergeFrom (testCluster .DeepCopy ())
819+ testCluster .Status .Initialization = & clusterv1.ClusterInitializationStatus {InfrastructureProvisioned : true }
820+ g .Expect (env .Status ().Patch (ctx , testCluster , patch )).To (Succeed ())
821+
822+ // Create kubeconfig Secret
823+ kubeconfigSecret := kubeconfig .GenerateSecret (testCluster , kubeconfig .FromEnvTestConfig (env .Config , testCluster ))
824+ g .Expect (env .CreateAndWait (ctx , kubeconfigSecret )).To (Succeed ())
825+ defer func () { g .Expect (env .CleanupAndWait (ctx , kubeconfigSecret )).To (Succeed ()) }()
826+
827+ // Initial reconcile to connect
828+ res , err := cc .Reconcile (ctx , reconcile.Request {NamespacedName : clusterKey })
829+ g .Expect (err ).ToNot (HaveOccurred ())
830+ g .Expect (res .RequeueAfter ).To (BeNumerically (">=" , accessorConfig .HealthProbe .Interval - 2 * time .Second ))
831+ g .Expect (cc .getClusterAccessor (clusterKey ).Connected (ctx )).To (BeTrue ())
832+
833+ // Simulate kubeconfig Secret update (resourceVersion change)
834+ kubeconfigSecret .Data ["dummy" ] = []byte ("changed" )
835+ g .Expect (env .Update (ctx , kubeconfigSecret )).To (Succeed ())
836+
837+ // Reconcile again, should detect update and disconnect
838+ _ = cc .getClusterAccessor (clusterKey ) // ensure accessor is present
839+ res , err = cc .Reconcile (ctx , reconcile.Request {NamespacedName : clusterKey })
840+ g .Expect (err ).ToNot (HaveOccurred ())
841+ g .Expect (cc .getClusterAccessor (clusterKey ).Connected (ctx )).To (BeFalse ())
842+ }
0 commit comments