From bc1bb3846be5b3d6586c9bc9c6d9c33b01aafb7a Mon Sep 17 00:00:00 2001 From: Ashish Date: Thu, 23 Apr 2026 12:22:04 +0530 Subject: [PATCH] feat(azure): propagate HCP Azure resource ID annotation to control plane namespace Add a new annotation constant HCPAzureResourceIDAnnotation that carries the Azure resource ID set by Cluster Service on the HostedCluster CR. The hostedcluster controller now propagates this annotation to the hosted control plane namespace for Azure platform clusters. Refer https://redhat.atlassian.net/browse/AROSLSRE-687 and https://issues.redhat.com/browse/AROSLSRE-447 for more details. Signed-off-by: Ashish --- api/hypershift/v1beta1/hostedcluster_types.go | 3 + .../hostedcluster/hostedcluster_controller.go | 22 ++++ .../hostedcluster_controller_test.go | 104 ++++++++++++++++++ .../hypershift/v1beta1/hostedcluster_types.go | 3 + 4 files changed, 132 insertions(+) diff --git a/api/hypershift/v1beta1/hostedcluster_types.go b/api/hypershift/v1beta1/hostedcluster_types.go index 74b50f24ad3..c282b4904b1 100644 --- a/api/hypershift/v1beta1/hostedcluster_types.go +++ b/api/hypershift/v1beta1/hostedcluster_types.go @@ -78,6 +78,9 @@ const ( // AWSKMSProviderImage is an annotation that allows the specification of the AWS kms provider image. // Upstream code located at: https://github.com/kubernetes-sigs/aws-encryption-provider AWSKMSProviderImage = "hypershift.openshift.io/aws-kms-provider-image" + // ManagedAzureResourceIDAnnotation is an annotation set by Cluster Service on the HostedCluster CR + // containing the Azure resource ID. It is propagated to the hosted control plane namespace. + ManagedAzureResourceIDAnnotation = "azure.microsoft.com/hcp-cluster-azure-resource-id" // IBMCloudKMSProviderImage is an annotation that allows the specification of the IBM Cloud kms provider image. IBMCloudKMSProviderImage = "hypershift.openshift.io/ibmcloud-kms-provider-image" // PortierisImageAnnotation is an annotation that allows the specification of the portieries component diff --git a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go index 403cbda28ec..e23eb0ecb71 100644 --- a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go +++ b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller.go @@ -1399,6 +1399,8 @@ func (r *HostedClusterReconciler) reconcile(ctx context.Context, req ctrl.Reques // Enable observability operator monitoring metrics.EnableOBOMonitoring(controlPlaneNamespace) + propagateAzureResourceIDAnnotation(hcluster, controlPlaneNamespace) + return nil }) if err != nil { @@ -5382,6 +5384,26 @@ func computeGCPPSCCondition(gcpPSCList hyperv1.GCPPrivateServiceConnectList, con return computeEndpointServiceCondition(resourceConditions, conditionType, hyperv1.GCPErrorReason, hyperv1.GCPSuccessReason, "GCPPrivateServiceConnect conditions not found") } +// propagateAzureResourceIDAnnotation copies the Azure resource ID annotation set by Cluster Service +// on the HostedCluster CR to the control plane namespace, or removes it if no longer present. +// This annotation is consumed by ARO-HCP logging and observability components to correlate the +// HostedCluster with the corresponding Azure resources. +func propagateAzureResourceIDAnnotation(hcluster *hyperv1.HostedCluster, ns *corev1.Namespace) { + if !azureutil.IsAroHCP() { + // If the platform is not ARO-HCP, ensure the annotation is not set on the namespace and return early + delete(ns.Annotations, hyperv1.ManagedAzureResourceIDAnnotation) + return + } + if resourceID, exists := hcluster.Annotations[hyperv1.ManagedAzureResourceIDAnnotation]; exists { + if ns.Annotations == nil { + ns.Annotations = make(map[string]string) + } + ns.Annotations[hyperv1.ManagedAzureResourceIDAnnotation] = resourceID + } else { + delete(ns.Annotations, hyperv1.ManagedAzureResourceIDAnnotation) + } +} + func computeAzurePLSCondition(azPLSList hyperv1.AzurePrivateLinkServiceList, conditionType hyperv1.ConditionType) metav1.Condition { resourceConditions := make([][]metav1.Condition, len(azPLSList.Items)) for i, pls := range azPLSList.Items { diff --git a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go index e86cc223640..daf4fe8995b 100644 --- a/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go +++ b/hypershift-operator/controllers/hostedcluster/hostedcluster_controller_test.go @@ -7141,3 +7141,107 @@ func TestKasServingCertHashFromEndpoint(t *testing.T) { }) } } + +func TestPropagateAzureResourceIDAnnotation(t *testing.T) { + const testResourceID = "/subscriptions/00000000/resourceGroups/rg/providers/Microsoft.RedHatOpenShift/hcpOpenShiftClusters/cluster1" + + tests := []struct { + name string + isAroHCP bool + hcluster *hyperv1.HostedCluster + nsAnnotations map[string]string + expectedAnnotation string + }{ + { + name: "When ARO-HCP cluster has resource ID annotation, it should propagate to namespace", + isAroHCP: true, + hcluster: &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + hyperv1.ManagedAzureResourceIDAnnotation: testResourceID, + }, + }, + }, + nsAnnotations: nil, + expectedAnnotation: testResourceID, + }, + { + name: "When ARO-HCP cluster has no resource ID annotation, it should remove it from namespace", + isAroHCP: true, + hcluster: &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{}, + }, + nsAnnotations: map[string]string{ + hyperv1.ManagedAzureResourceIDAnnotation: testResourceID, + }, + expectedAnnotation: "", + }, + { + name: "When non-ARO-HCP cluster has resource ID annotation, it should not propagate to namespace", + hcluster: &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + hyperv1.ManagedAzureResourceIDAnnotation: testResourceID, + }, + }, + }, + nsAnnotations: nil, + expectedAnnotation: "", + }, + { + name: "When non-ARO-HCP cluster has resource ID on namespace, it should remove it", + hcluster: &hyperv1.HostedCluster{}, + nsAnnotations: map[string]string{ + hyperv1.ManagedAzureResourceIDAnnotation: testResourceID, + }, + expectedAnnotation: "", + }, + { + name: "When ARO-HCP cluster has resource ID annotation, it should update existing namespace annotation", + isAroHCP: true, + hcluster: &hyperv1.HostedCluster{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: map[string]string{ + hyperv1.ManagedAzureResourceIDAnnotation: testResourceID, + }, + }, + }, + nsAnnotations: map[string]string{ + hyperv1.ManagedAzureResourceIDAnnotation: "old-value", + "some-other-annotation": "preserved", + }, + expectedAnnotation: testResourceID, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + if tc.isAroHCP { + azureutil.SetAsAroHCPTest(t) + } + g := NewGomegaWithT(t) + ns := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Annotations: tc.nsAnnotations, + }, + } + + propagateAzureResourceIDAnnotation(tc.hcluster, ns) + + if tc.expectedAnnotation != "" { + g.Expect(ns.Annotations).To(HaveKeyWithValue( + hyperv1.ManagedAzureResourceIDAnnotation, tc.expectedAnnotation, + )) + } else { + g.Expect(ns.Annotations).ToNot(HaveKey(hyperv1.ManagedAzureResourceIDAnnotation)) + } + + // Verify other annotations are not disturbed + for k, v := range tc.nsAnnotations { + if k != hyperv1.ManagedAzureResourceIDAnnotation { + g.Expect(ns.Annotations).To(HaveKeyWithValue(k, v)) + } + } + }) + } +} diff --git a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go index 74b50f24ad3..c282b4904b1 100644 --- a/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go +++ b/vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/hostedcluster_types.go @@ -78,6 +78,9 @@ const ( // AWSKMSProviderImage is an annotation that allows the specification of the AWS kms provider image. // Upstream code located at: https://github.com/kubernetes-sigs/aws-encryption-provider AWSKMSProviderImage = "hypershift.openshift.io/aws-kms-provider-image" + // ManagedAzureResourceIDAnnotation is an annotation set by Cluster Service on the HostedCluster CR + // containing the Azure resource ID. It is propagated to the hosted control plane namespace. + ManagedAzureResourceIDAnnotation = "azure.microsoft.com/hcp-cluster-azure-resource-id" // IBMCloudKMSProviderImage is an annotation that allows the specification of the IBM Cloud kms provider image. IBMCloudKMSProviderImage = "hypershift.openshift.io/ibmcloud-kms-provider-image" // PortierisImageAnnotation is an annotation that allows the specification of the portieries component