Repository: kubernetes-sigs/cluster-api-provider-aws

Issue:
creating-a-cluster.md document should correctly mention ROSA HCP (not just ROSA)
/kind bug

**What steps did you take and what happened:**
`creating-a-cluster.md` document in the repo docs mentions the `ROSA` product. Correctly, it should use ROSA HCP or ROSA Hypershift. This is to be able to distinguish between the hosted control plane ROSA and the classic ROSA setup.

**What did you expect to happen:**
The doc should use ROSA-HCP instead of just ROSA.

**Anything else you would like to add:**
Not really.

**Environment:**
N/A

- Cluster-api-provider-aws version: 
- Kubernetes version: (use `kubectl version`): 
- OS (e.g. from `/etc/os-release`): 

Hints from issue comments:
@k8s-ci-robot: This issue is currently awaiting triage.

If CAPA/CAPI contributors determines this is a relevant issue, they will accept it by applying the `triage/accepted` label and provide further guidance.

The `triage/accepted` label can be added by org members by writing `/triage accepted` in a comment.


<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes-sigs/prow](https://github.com/kubernetes-sigs/prow/issues/new?title=Prow%20issue:) repository.
</details>

Solution patch:
diff --git a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml
index 16bbe6a47..6595b3532 100644
--- a/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml
+++ b/config/crd/bases/controlplane.cluster.x-k8s.io_rosacontrolplanes.yaml
@@ -73,8 +73,8 @@ spec:
                 type: array
               billingAccount:
                 description: |-
-                  BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA clusters.
-                  The cost of running each ROSA cluster will be billed to the infrastructure account in which the cluster
+                  BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA HCP clusters.
+                  The cost of running each ROSA HCP cluster will be billed to the infrastructure account in which the cluster
                   is running.
                 type: string
                 x-kubernetes-validations:
@@ -563,8 +563,8 @@ spec:
                 - message: oidcID is immutable
                   rule: self == oldSelf
               provisionShardID:
-                description: ProvisionShardID defines the shard where rosa control
-                  plane components will be hosted.
+                description: ProvisionShardID defines the shard where ROSA hosted
+                  control plane components will be hosted.
                 type: string
                 x-kubernetes-validations:
                 - message: provisionShardID is immutable
diff --git a/controllers/awscluster_controller.go b/controllers/awscluster_controller.go
index dbc131f6b..0389681f9 100644
--- a/controllers/awscluster_controller.go
+++ b/controllers/awscluster_controller.go
@@ -58,6 +58,10 @@ import (
 	"sigs.k8s.io/cluster-api/util/predicates"
 )
 
+const (
+	deleteRequeueAfter = 20 * time.Second
+)
+
 var defaultAWSSecurityGroupRoles = []infrav1.SecurityGroupRole{
 	infrav1.SecurityGroupAPIServerLB,
 	infrav1.SecurityGroupLB,
@@ -191,21 +195,34 @@ func (r *AWSClusterReconciler) Reconcile(ctx context.Context, req ctrl.Request)
 
 	// Handle deleted clusters
 	if !awsCluster.DeletionTimestamp.IsZero() {
-		return ctrl.Result{}, r.reconcileDelete(ctx, clusterScope)
+		return r.reconcileDelete(ctx, clusterScope)
 	}
 
 	// Handle non-deleted clusters
 	return r.reconcileNormal(clusterScope)
 }
 
-func (r *AWSClusterReconciler) reconcileDelete(ctx context.Context, clusterScope *scope.ClusterScope) error {
+func (r *AWSClusterReconciler) reconcileDelete(ctx context.Context, clusterScope *scope.ClusterScope) (ctrl.Result, error) {
 	if !controllerutil.ContainsFinalizer(clusterScope.AWSCluster, infrav1.ClusterFinalizer) {
 		clusterScope.Info("No finalizer on AWSCluster, skipping deletion reconciliation")
-		return nil
+		return reconcile.Result{}, nil
 	}
 
 	clusterScope.Info("Reconciling AWSCluster delete")
 
+	numDependencies, err := r.dependencyCount(ctx, clusterScope)
+	if err != nil {
+		clusterScope.Error(err, "error getting AWSCluster dependencies")
+		return reconcile.Result{}, err
+	}
+
+	if numDependencies > 0 {
+		clusterScope.Info("AWSCluster cluster still has dependencies - requeue needed", "dependencyCount", numDependencies)
+		return reconcile.Result{RequeueAfter: deleteRequeueAfter}, nil
+	}
+
+	clusterScope.Info("AWSCluster has no dependent CAPI resources, proceeding with its deletion")
+
 	ec2svc := r.getEC2Service(clusterScope)
 	elbsvc := r.getELBService(clusterScope)
 	networkSvc := r.getNetworkService(*clusterScope)
@@ -257,12 +274,12 @@ func (r *AWSClusterReconciler) reconcileDelete(ctx context.Context, clusterScope
 	}
 
 	if len(allErrs) > 0 {
-		return kerrors.NewAggregate(allErrs)
+		return reconcile.Result{}, kerrors.NewAggregate(allErrs)
 	}
 
 	// Cluster is deleted so remove the finalizer.
 	controllerutil.RemoveFinalizer(clusterScope.AWSCluster, infrav1.ClusterFinalizer)
-	return nil
+	return reconcile.Result{}, nil
 }
 
 func (r *AWSClusterReconciler) reconcileLoadBalancer(clusterScope *scope.ClusterScope, awsCluster *infrav1.AWSCluster) (*time.Duration, error) {
@@ -484,3 +501,24 @@ func (r *AWSClusterReconciler) checkForExternalControlPlaneLoadBalancer(clusterS
 		return nil
 	}
 }
+
+func (r *AWSClusterReconciler) dependencyCount(ctx context.Context, clusterScope *scope.ClusterScope) (int, error) {
+	clusterName := clusterScope.Name()
+	namespace := clusterScope.Namespace()
+
+	clusterScope.Info("Looking for AWSCluster dependencies")
+
+	listOptions := []client.ListOption{
+		client.InNamespace(namespace),
+		client.MatchingLabels(map[string]string{clusterv1.ClusterNameLabel: clusterName}),
+	}
+
+	machines := &infrav1.AWSMachineList{}
+	if err := r.Client.List(ctx, machines, listOptions...); err != nil {
+		return 0, fmt.Errorf("failed to list machines for cluster %s/%s: %w", namespace, clusterName, err)
+	}
+
+	clusterScope.Debug("Found dependent machines", "count", len(machines.Items))
+
+	return len(machines.Items), nil
+}
diff --git a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go
index a35a0d2a2..b7bc2f9a4 100644
--- a/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go
+++ b/controlplane/rosa/api/v1beta2/rosacontrolplane_types.go
@@ -154,8 +154,8 @@ type RosaControlPlaneSpec struct { //nolint: maligned
 	// WorkerRoleARN is an AWS IAM role that will be attached to worker instances.
 	WorkerRoleARN string `json:"workerRoleARN"`
 
-	// BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA clusters.
-	// The cost of running each ROSA cluster will be billed to the infrastructure account in which the cluster
+	// BillingAccount is an optional AWS account to use for billing the subscription fees for ROSA HCP clusters.
+	// The cost of running each ROSA HCP cluster will be billed to the infrastructure account in which the cluster
 	// is running.
 	//
 	// +kubebuilder:validation:Optional
@@ -202,7 +202,7 @@ type RosaControlPlaneSpec struct { //nolint: maligned
 	// +optional
 	AuditLogRoleARN string `json:"auditLogRoleARN,omitempty"`
 
-	// ProvisionShardID defines the shard where rosa control plane components will be hosted.
+	// ProvisionShardID defines the shard where ROSA hosted control plane components will be hosted.
 	//
 	// +kubebuilder:validation:XValidation:rule="self == oldSelf", message="provisionShardID is immutable"
 	// +optional
diff --git a/docs/book/src/topics/rosa/creating-a-cluster.md b/docs/book/src/topics/rosa/creating-a-cluster.md
index da220a8b3..bcc8ea7b0 100644
--- a/docs/book/src/topics/rosa/creating-a-cluster.md
+++ b/docs/book/src/topics/rosa/creating-a-cluster.md
@@ -1,8 +1,8 @@
-# Creating a ROSA cluster
+# Creating a ROSA HCP cluster
 
 ## Permissions
 ### Authentication using service account credentials
-CAPA controller requires service account credentials to be able to provision ROSA clusters:
+CAPA controller requires service account credentials to be able to provision ROSA HCP clusters:
 1. Visit [https://console.redhat.com/iam/service-accounts](https://console.redhat.com/iam/service-accounts) and create a service account. If you already have a service account, you can skip this step.
 
    For every newly created service account, make sure to activate the account using the [ROSA command line tool](https://github.com/openshift/rosa). First, log in using your newly created service account
@@ -87,9 +87,9 @@ The SSO offline token is being deprecated and it is recommended to use service a
 
 ## Prerequisites
 
-Follow the guide [here](https://docs.aws.amazon.com/ROSA/latest/userguide/getting-started-hcp.html) up until [Step 3](https://docs.aws.amazon.com/ROSA/latest/userguide/getting-started-hcp.html#getting-started-hcp-step-3) 
+Follow the guide [here](https://docs.aws.amazon.com/ROSA/latest/userguide/getting-started-hcp.html) up until [Step 3](https://docs.aws.amazon.com/ROSA/latest/userguide/getting-started-hcp.html#getting-started-hcp-step-3)
 to install the required tools and setup the prerequisite infrastructure.
-Once Step 3 is done, you will be ready to proceed with creating a ROSA cluster using cluster-api.
+Once Step 3 is done, you will be ready to proceed with creating a ROSA HCP cluster using cluster-api.
 
 ## Creating the cluster
 
@@ -106,11 +106,11 @@ Once Step 3 is done, you will be ready to proceed with creating a ROSA cluster u
     export OPERATOR_ROLES_PREFIX="capi-rosa-quickstart"  # prefix used to create operator roles with `rosa create operator-roles --prefix <PREFIX_NAME>`
 
     # subnet IDs created earlier
-    export PUBLIC_SUBNET_ID="subnet-0b54a1111111111111"   
+    export PUBLIC_SUBNET_ID="subnet-0b54a1111111111111"
     export PRIVATE_SUBNET_ID="subnet-05e72222222222222"
     ```
 
-1. Render the cluster manifest using the ROSA cluster template:
+1. Render the cluster manifest using the ROSA HCP cluster template:
     ```shell
     clusterctl generate cluster <cluster-name> --from templates/cluster-template-rosa.yaml > rosa-capi-cluster.yaml
     ```
@@ -128,7 +128,7 @@ Once Step 3 is done, you will be ready to proceed with creating a ROSA cluster u
     ...
     ```
 
-1. Provide an AWS identity reference  
+1. Provide an AWS identity reference
     ```yaml
     apiVersion: controlplane.cluster.x-k8s.io/v1beta2
     kind: ROSAControlPlane
diff --git a/docs/book/src/topics/rosa/enabling.md b/docs/book/src/topics/rosa/enabling.md
index aeae9ab5e..9dcedfa8b 100644
--- a/docs/book/src/topics/rosa/enabling.md
+++ b/docs/book/src/topics/rosa/enabling.md
@@ -1,6 +1,6 @@
 # Enabling ROSA Support
 
-To enable support for ROSA clusters, the ROSA feature flag must be set to true. This can be done using the **EXP_ROSA** environment variable.
+To enable support for ROSA HCP clusters, the ROSA feature flag must be set to true. This can be done using the **EXP_ROSA** environment variable.
 
 Make sure to set up your AWS environment first as described [here](https://cluster-api.sigs.k8s.io/user/quick-start.html).
 ```shell
diff --git a/docs/book/src/topics/rosa/external-auth.md b/docs/book/src/topics/rosa/external-auth.md
index fb2702397..180ef2869 100644
--- a/docs/book/src/topics/rosa/external-auth.md
+++ b/docs/book/src/topics/rosa/external-auth.md
@@ -1,6 +1,6 @@
 # External Auth Providers (BYOI)
 
-ROSA allows you to Bring Your Own Identity (BYOI) to manage and authenticate cluster users.
+ROSA HCP allows you to Bring Your Own Identity (BYOI) to manage and authenticate cluster users.
 
 ## Enabling
 
@@ -16,7 +16,7 @@ spec:
   ....
 ```
 
-Note: This feauture requires OpenShift version `4.15.5` or newer.
+Note: This feature requires OpenShift version `4.15.5` or newer.
 
 ## Usage
 
diff --git a/docs/book/src/topics/rosa/support.md b/docs/book/src/topics/rosa/support.md
index 1d3dbed96..65b5e6b22 100644
--- a/docs/book/src/topics/rosa/support.md
+++ b/docs/book/src/topics/rosa/support.md
@@ -1,6 +1,6 @@
 # Create issue for ROSA
 
-When creating issue for ROSA-HCP cluster, include the logs for the capa-controller-manager and capi-controller-manager deployment pods. The logs can be saved to text file using the commands below. Also include the yaml files for all the resources used to create the ROSA cluster:
+When creating issue for ROSA HCP cluster, include the logs for the capa-controller-manager and capi-controller-manager deployment pods. The logs can be saved to text file using the commands below. Also include the yaml files for all the resources used to create the ROSA HCP cluster:
 - `Cluster`
 - `ROSAControlPlane`
 - `MachinePool`



Test patch:
diff --git a/controllers/awscluster_controller_test.go b/controllers/awscluster_controller_test.go
index a40a6ed1b..474abaaf9 100644
--- a/controllers/awscluster_controller_test.go
+++ b/controllers/awscluster_controller_test.go
@@ -568,7 +568,7 @@ func TestAWSClusterReconcilerIntegrationTests(t *testing.T) {
 		_, err = reconciler.reconcileNormal(cs)
 		g.Expect(err.Error()).To(ContainSubstring("The maximum number of VPCs has been reached"))
 
-		err = reconciler.reconcileDelete(ctx, cs)
+		_, err = reconciler.reconcileDelete(ctx, cs)
 		g.Expect(err).To(BeNil())
 	})
 	t.Run("Should successfully delete AWSCluster with managed VPC", func(t *testing.T) {
@@ -646,7 +646,7 @@ func TestAWSClusterReconcilerIntegrationTests(t *testing.T) {
 			return sgSvc
 		}
 
-		err = reconciler.reconcileDelete(ctx, cs)
+		_, err = reconciler.reconcileDelete(ctx, cs)
 		g.Expect(err).To(BeNil())
 		expectAWSClusterConditions(g, cs.AWSCluster, []conditionAssertion{
 			{infrav1.LoadBalancerReadyCondition, corev1.ConditionFalse, clusterv1.ConditionSeverityInfo, clusterv1.DeletedReason},
diff --git a/controllers/awscluster_controller_unit_test.go b/controllers/awscluster_controller_unit_test.go
index be59c0a67..0ea2bfaed 100644
--- a/controllers/awscluster_controller_unit_test.go
+++ b/controllers/awscluster_controller_unit_test.go
@@ -420,7 +420,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) {
 					},
 				)
 				g.Expect(err).To(BeNil())
-				err = reconciler.reconcileDelete(ctx, cs)
+				_, err = reconciler.reconcileDelete(ctx, cs)
 				g.Expect(err).To(BeNil())
 				g.Expect(awsCluster.GetFinalizers()).ToNot(ContainElement(infrav1.ClusterFinalizer))
 			})
@@ -449,7 +449,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) {
 					},
 				)
 				g.Expect(err).To(BeNil())
-				err = reconciler.reconcileDelete(ctx, cs)
+				_, err = reconciler.reconcileDelete(ctx, cs)
 				g.Expect(err).ToNot(BeNil())
 				g.Expect(awsCluster.GetFinalizers()).To(ContainElement(infrav1.ClusterFinalizer))
 			})
@@ -474,7 +474,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) {
 					},
 				)
 				g.Expect(err).To(BeNil())
-				err = reconciler.reconcileDelete(ctx, cs)
+				_, err = reconciler.reconcileDelete(ctx, cs)
 				g.Expect(err).ToNot(BeNil())
 				g.Expect(awsCluster.GetFinalizers()).To(ContainElement(infrav1.ClusterFinalizer))
 			})
@@ -499,7 +499,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) {
 					},
 				)
 				g.Expect(err).To(BeNil())
-				err = reconciler.reconcileDelete(ctx, cs)
+				_, err = reconciler.reconcileDelete(ctx, cs)
 				g.Expect(err).ToNot(BeNil())
 				g.Expect(awsCluster.GetFinalizers()).To(ContainElement(infrav1.ClusterFinalizer))
 			})
@@ -524,7 +524,7 @@ func TestAWSClusterReconcileOperations(t *testing.T) {
 					},
 				)
 				g.Expect(err).To(BeNil())
-				err = reconciler.reconcileDelete(ctx, cs)
+				_, err = reconciler.reconcileDelete(ctx, cs)
 				g.Expect(err).ToNot(BeNil())
 				g.Expect(awsCluster.GetFinalizers()).To(ContainElement(infrav1.ClusterFinalizer))
 			})
diff --git a/test/e2e/data/e2e_eks_conf.yaml b/test/e2e/data/e2e_eks_conf.yaml
index 25d88acf5..c3f893afa 100644
--- a/test/e2e/data/e2e_eks_conf.yaml
+++ b/test/e2e/data/e2e_eks_conf.yaml
@@ -80,7 +80,7 @@ providers:
   - name: aws
     type: InfrastructureProvider
     versions:
-      - name: v2.0.99
+      - name: v9.9.99
         # Use manifest from source files
         value: ../../../config/default
         contract: v1beta1