@@ -17,11 +17,15 @@ import (
1717 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1818 apierrors "k8s.io/apimachinery/pkg/api/errors"
1919 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+ "k8s.io/apimachinery/pkg/types"
2021 clusterv1 "sigs.k8s.io/cluster-api/api/v1beta1"
22+ "sigs.k8s.io/cluster-api/controllers/remote"
2123 runtimehooksv1 "sigs.k8s.io/cluster-api/exp/runtime/hooks/api/v1alpha1"
2224 ctrl "sigs.k8s.io/controller-runtime"
2325 ctrlclient "sigs.k8s.io/controller-runtime/pkg/client"
2426
27+ prismgoclient "github.com/nutanix-cloud-native/prism-go-client"
28+
2529 capxv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/github.com/nutanix-cloud-native/cluster-api-provider-nutanix/api/v1beta1"
2630 caaphv1 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/external/sigs.k8s.io/cluster-api-addon-provider-helm/api/v1alpha1"
2731 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/api/v1alpha1"
@@ -31,6 +35,7 @@ import (
3135 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/common/pkg/capi/clustertopology/variables"
3236 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/addons"
3337 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/config"
38+ lifecycleutils "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/lifecycle/utils"
3439 "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/options"
3540 handlersutils "github.com/nutanix-cloud-native/cluster-api-runtime-extensions-nutanix/pkg/handlers/utils"
3641)
@@ -465,6 +470,20 @@ func (n *DefaultKonnectorAgent) BeforeClusterDelete(
465470 return
466471 }
467472
473+ // Check cluster is registered in PC
474+ clusterRegistered , err := isClusterRegisteredInPC (ctx , n .client , cluster , log )
475+ if err != nil {
476+ log .Error (err , "Failed to check if cluster is registered in Prism Central, continuing with deletion anyway" )
477+ // setting response status to success to allow cluster deletion to proceed
478+ resp .SetStatus (runtimehooksv1 .ResponseStatusSuccess )
479+ return
480+ }
481+ if ! clusterRegistered {
482+ log .Info ("Cluster is not registered in Prism Central, skipping cleanup" )
483+ resp .SetStatus (runtimehooksv1 .ResponseStatusSuccess )
484+ return
485+ }
486+
468487 // Check if cleanup is already in progress or completed
469488 cleanupStatus , statusMsg , err := n .checkCleanupStatus (ctx , cluster , log )
470489 if err != nil {
@@ -651,3 +670,112 @@ func (n *DefaultKonnectorAgent) checkCleanupStatus(
651670 log .Info ("HelmChartProxy exists, cleanup not started" , "name" , hcp .Name )
652671 return cleanupStatusNotStarted , "HelmChartProxy exists and needs to be deleted" , nil
653672}
673+
674+ // isClusterRegisteredInPC checks if the cluster is registered in Prism Central by calling
675+ // the Konnector GetClusterRegistration API using the cluster's kube-system namespace UUID.
676+ func isClusterRegisteredInPC (
677+ ctx context.Context ,
678+ client ctrlclient.Client ,
679+ cluster * clusterv1.Cluster ,
680+ log logr.Logger ,
681+ ) (bool , error ) {
682+ // Get cluster config to extract PC endpoint
683+ varMap := variables .ClusterVariablesToVariablesMap (cluster .Spec .Topology .Variables )
684+ clusterConfigVar , err := variables .Get [apivariables.ClusterConfigSpec ](
685+ varMap ,
686+ v1alpha1 .ClusterConfigVariableName ,
687+ )
688+ if err != nil {
689+ return false , fmt .Errorf ("failed to read clusterConfig variable: %w" , err )
690+ }
691+
692+ if clusterConfigVar .Nutanix == nil || clusterConfigVar .Nutanix .PrismCentralEndpoint .URL == "" {
693+ return false , fmt .Errorf ("prism central endpoint not configured" )
694+ }
695+
696+ prismCentralEndpointSpec := clusterConfigVar .Nutanix .PrismCentralEndpoint
697+ host , port , err := prismCentralEndpointSpec .ParseURL ()
698+ if err != nil {
699+ return false , fmt .Errorf ("failed to parse prism central endpoint URL: %w" , err )
700+ }
701+
702+ // Get konnector agent variable to access its credentials secret
703+ k8sAgentVar , err := variables .Get [apivariables.NutanixKonnectorAgent ](
704+ varMap ,
705+ v1alpha1 .ClusterConfigVariableName ,
706+ "addons" , v1alpha1 .KonnectorAgentVariableName ,
707+ )
708+ if err != nil {
709+ return false , fmt .Errorf ("failed to read konnector agent variable: %w" , err )
710+ }
711+
712+ if k8sAgentVar .Credentials == nil || k8sAgentVar .Credentials .SecretRef .Name == "" {
713+ return false , fmt .Errorf ("konnector agent credentials secret not configured" )
714+ }
715+
716+ // Get credentials from konnector agent addon Secret
717+ credentialsSecret := & corev1.Secret {}
718+ err = client .Get (ctx , types.NamespacedName {
719+ Namespace : cluster .Namespace ,
720+ Name : k8sAgentVar .Credentials .SecretRef .Name ,
721+ }, credentialsSecret )
722+ if err != nil {
723+ return false , fmt .Errorf ("failed to get credentials secret: %w" , err )
724+ }
725+
726+ usernameData , ok := credentialsSecret .Data ["username" ]
727+ if ! ok {
728+ return false , fmt .Errorf ("credentials secret does not contain 'username' key" )
729+ }
730+ passwordData , ok := credentialsSecret .Data ["password" ]
731+ if ! ok {
732+ return false , fmt .Errorf ("credentials secret does not contain 'password' key" )
733+ }
734+
735+ // Create credentials struct
736+ credentials := prismgoclient.Credentials {
737+ Endpoint : fmt .Sprintf ("%s:%d" , host , port ),
738+ URL : fmt .Sprintf ("https://%s:%d" , host , port ),
739+ Username : string (usernameData ),
740+ Password : string (passwordData ),
741+ Insecure : prismCentralEndpointSpec .Insecure ,
742+ Port : fmt .Sprintf ("%d" , port ),
743+ }
744+
745+ // Get kube-system namespace UUID from the cluster
746+ clusterKey := ctrlclient .ObjectKeyFromObject (cluster )
747+ remoteClient , err := remote .NewClusterClient (ctx , "" , client , clusterKey )
748+ if err != nil {
749+ return false , fmt .Errorf ("failed to create remote cluster client: %w" , err )
750+ }
751+
752+ kubeSystemNS := & corev1.Namespace {}
753+ err = remoteClient .Get (ctx , types.NamespacedName {Name : "kube-system" }, kubeSystemNS )
754+ if err != nil {
755+ return false , fmt .Errorf ("failed to get kube-system namespace from cluster(%s): %w" , cluster .Name , err )
756+ }
757+
758+ clusterUUID := string (kubeSystemNS .UID )
759+
760+ // Get trust bundle if insecure is false
761+ var trustBundle string
762+ if ! prismCentralEndpointSpec .Insecure {
763+ trustBundle = prismCentralEndpointSpec .AdditionalTrustBundle
764+ }
765+
766+ // Create Prism Central Konnector client
767+ prismCentralKonnectorClient , err := lifecycleutils .NewPrismCentralKonnectorClient (& credentials , trustBundle )
768+ if err != nil {
769+ return false , fmt .Errorf ("failed to create prism central konnector client: %w" , err )
770+ }
771+
772+ // Call GetClusterRegistration API
773+ _ , err = prismCentralKonnectorClient .GetClusterRegistration (ctx , clusterUUID )
774+ if err != nil {
775+ return false , fmt .Errorf ("failed to get cluster(%s) registration: %w" , clusterUUID , err )
776+ }
777+
778+ // If we got here, the cluster is registered
779+ log .Info ("Cluster is registered in Prism Central" , "clusterUUID" , clusterUUID )
780+ return true , nil
781+ }
0 commit comments