Skip to content

ARO-24037 feat(azure): add ACR pull identity to worker cloud.conf and VMSS#8472

Open
twolff-gh wants to merge 6 commits into
openshift:mainfrom
twolff-gh:ARO-24037-acr-pull-umi-cloud-conf
Open

ARO-24037 feat(azure): add ACR pull identity to worker cloud.conf and VMSS#8472
twolff-gh wants to merge 6 commits into
openshift:mainfrom
twolff-gh:ARO-24037-acr-pull-umi-cloud-conf

Conversation

@twolff-gh
Copy link
Copy Markdown

@twolff-gh twolff-gh commented May 9, 2026

What this PR does / why we need it:

Adds an optional ImageRegistryCredentials field to AzurePlatformSpec that configures worker nodes to pull images from Azure Container Registry using a user-assigned managed identity.

When set, CPO writes userAssignedIdentityID into the worker cloud.conf and the NodePool controller attaches the MI to the VMSS via CAPZ.

This gives kubelet's ACR credential provider the identity it needs to authenticate without image pull secrets.

Which issue(s) this PR fixes:

Fixes ARO-24037

Special notes for your reviewer:

cloud-provider-azure accepts both ARM resource ID and client ID formats in userAssignedIdentityID — see auth_func.go#L80

We use the ARM resource ID since CAPZ also needs it for VMSS identity attachment.

Only the worker cloud.conf (ConfigMap) is modified — the CP cloud.conf (Secret) is unchanged.

Checklist:

  • Subject and description added to both, commit and PR.
  • Relevant issues have been referenced.
  • This change includes docs.
  • This change includes unit tests.

Summary by CodeRabbit

  • New Features

    • Azure platform spec can optionally specify Image Registry Credentials using a user-assigned managed identity for pulling from Azure Container Registry.
  • Behavior Changes

    • Control-plane cloud config includes the configured user-assigned identity.
    • Worker machine templates are configured to use the specified user-assigned identity when present.
  • Tests

    • Added tests for config generation, secret handling (no credential leakage), machine-template wiring, and JSON compatibility.

@openshift-merge-bot
Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: LGTM mode

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label May 9, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 9, 2026

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci openshift-ci Bot added do-not-merge/needs-area needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. area/api Indicates the PR includes changes for the API labels May 9, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

These changes add optional image registry credentials to AzurePlatformSpec to specify a user-assigned managed identity (resourceID) for pulling images from Azure Container Registry. The control-plane operator propagates the managed identity ID into the generated Azure cloud config when present. The nodepool controller configures worker VM identity and user-assigned identities on Azure VM scale sets using the provided resourceID. Unit tests were added/updated to verify API serialization compatibility, cloud config output, secret contents, and machine template identity wiring.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API_Server as Kubernetes API
    participant CPO as Control-Plane Operator
    participant NPC as NodePool Controller
    participant Azure as Azure (VMSS / ACR)

    User->>API_Server: Create/Update HostedCluster/NodePool\nwith platform.azure.imageRegistryCredentials.managedIdentity.resourceID
    API_Server->>CPO: CPO watches HostedCluster spec
    CPO->>CPO: adaptConfig reads managedIdentity.resourceID
    CPO->>API_Server: Write ConfigMap (cloud.conf) including userAssignedIdentityID
    API_Server->>NPC: NPC watches HostedCluster/NodePool spec
    NPC->>NPC: azureMachineTemplateSpec reads managedIdentity.resourceID
    NPC->>Azure: Create/Update VMSS with\nidentity: UserAssigned + UserAssignedIdentities[providerID]
    Azure->>Azure: VMSS uses managed identity to pull images from ACR
    Azure->>NPC: VMSS status/events
Loading

Possibly related PRs

  • openshift/hypershift#7157: Related changes touching Azure cloud.conf creation/ownership and managed identity fields in control-plane/operator cloud config.

Suggested reviewers

  • enxebre
  • muraee
  • devguyio
🚥 Pre-merge checks | ✅ 11 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (11 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding ACR pull identity support to worker cloud.conf and VMSS configuration for Azure platform.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed Custom check for Ginkgo test names is not applicable. PR uses only standard Go tests with t.Run() subtests. All test names are static and descriptive with no dynamic values.
Test Structure And Quality ✅ Passed Tests follow quality standards: single responsibility, meaningful assertion messages, consistent with codebase patterns, and no cluster operations requiring cleanup.
Microshift Test Compatibility ✅ Passed No Ginkgo e2e tests are added in this PR. All new tests are standard Go unit tests using the testing package. The custom check only applies to Ginkgo e2e tests.
Single Node Openshift (Sno) Test Compatibility ✅ Passed This PR adds only standard Go unit tests (using testing.T), not Ginkgo e2e tests. The custom check specifically targets Ginkgo e2e tests with multi-node assumptions. No Ginkgo e2e tests are added.
Topology-Aware Scheduling Compatibility ✅ Passed PR adds ACR pull identity support with no scheduling constraints. Only API types, configuration, and Azure VM template changes. No pod affinity, topology spread, PDB, or topology-dependent logic.
Ote Binary Stdout Contract ✅ Passed No stdout violations detected. PR changes contain only library/controller code with no main(), init(), TestMain(), or Ginkgo suite setup functions. No stdout writes found.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No Ginkgo e2e tests are added in this PR. All tests are standard Go unit tests using func TestName(t *testing.T) syntax, so the IPv6/disconnected network check does not apply.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@openshift-ci openshift-ci Bot added area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/azure PR/issue for Azure (AzurePlatform) platform and removed do-not-merge/needs-area labels May 9, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@hypershift-operator/controllers/nodepool/azure.go`:
- Around line 248-256: The code is prepending capzutil.ProviderIDPrefix to the
user-assigned identity but CAPZ v1.21 requires a raw ARM resource ID; update the
block that populates azureMachineTemplate.Template.Spec.UserAssignedIdentities
so the ProviderID is set directly from
hostedCluster.Spec.Platform.Azure.AcrImagePullManagedIdentityID (remove
capzutil.ProviderIDPrefix) while keeping the same append logic and
VMIdentityUserAssigned assignment on
azureMachineTemplate.Template.Spec.Identity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 651c0b52-b089-404a-a89f-e3b0947abee4

📥 Commits

Reviewing files that changed from the base of the PR and between bded456 and 5491fa7.

⛔ Files ignored due to path filters (40)
  • api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !**/zz_generated*.go, !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AutoNodeKarpenter.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AutoNodeKarpenter.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • client/applyconfiguration/hypershift/v1beta1/azureplatformspec.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.azure.testsuite.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/testdata/zz_fixture_TestConfigWithAcrMI.yaml is excluded by !**/testdata/**
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • docs/content/reference/api.md is excluded by !docs/content/reference/api.md
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/azure.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*.go, !**/zz_generated*
📒 Files selected for processing (5)
  • api/hypershift/v1beta1/azure.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/azure_test.go

Comment thread hypershift-operator/controllers/nodepool/azure.go Outdated
@twolff-gh
Copy link
Copy Markdown
Author

/retest

@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from 5491fa7 to ba0c25b Compare May 11, 2026 17:33
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 11, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
api/hypershift/v1beta1/azure.go (2)

558-567: ⚡ Quick win

Consider adding detailed component validation for consistency.

The validation pattern for AzureManagedIdentityResourceID uses a single rule that checks the overall format, but other ARM resource IDs in this file (e.g., encryptionSetID at lines 336-340, subnetID at lines 428-435) include multiple detailed rules validating each component:

  • Subscription ID as a valid UUID (8-4-4-4-12 format)
  • Resource group name constraints (1-90 chars, valid characters, no trailing period)
  • Resource/identity name constraints

Adding similar granular validation would improve input validation consistency and provide clearer error messages when individual components are malformed.

✨ Suggested additional validation rules
 // +kubebuilder:validation:XValidation:rule="self.lowerAscii().matches('^/subscriptions/[^/]+/resourcegroups/[^/]+/providers/microsoft.managedidentity/userassignedidentities/[^/]+$')",message="must be an ARM resource ID in the format /subscriptions/{subscriptionID}/resourceGroups/{resourceGroupName}/providers/Microsoft.ManagedIdentity/userAssignedIdentities/{identityName}"
+// +kubebuilder:validation:XValidation:rule="self.split('/')[2].matches('^[{]?[0-9a-fA-F]{8}-([0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}[}]?$')",message="the subscriptionID must be a valid UUID in the form 8-4-4-4-12"
+// +kubebuilder:validation:XValidation:rule=`self.split('/')[4].matches('[a-zA-Z0-9-_\\(\\)\\.]{1,90}')`,message="the resourceGroupName should be between 1 and 90 characters, consisting only of alphanumeric characters, hyphens, underscores, periods and parenthesis"
+// +kubebuilder:validation:XValidation:rule="!self.split('/')[4].endsWith('.')",message="the resourceGroupName must not end with a period (.)"
+// +kubebuilder:validation:XValidation:rule="self.split('/')[8].matches('[a-zA-Z0-9-_]{1,128}')",message="the identityName should be between 1 and 128 characters, consisting only of alphanumeric characters, hyphens and underscores"
 // +kubebuilder:validation:MinLength=1
 // +kubebuilder:validation:MaxLength=512
 type AzureManagedIdentityResourceID string
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/hypershift/v1beta1/azure.go` around lines 558 - 567, Update the
AzureManagedIdentityResourceID validation to add component-level checks similar
to encryptionSetID and subnetID: keep the existing overall XValidation rule but
add additional XValidation or kubebuilder:validation patterns that (1) validate
the subscription ID is a UUID (8-4-4-4-12), (2) validate the resourceGroupName
follows 1-90 char rules (allowed chars, no trailing period), and (3) validate
the userAssignedIdentities/{identityName} segment enforces resource name
constraints (length and allowed characters); target the
AzureManagedIdentityResourceID type and mirror the same annotation style used
for encryptionSetID and subnetID so each component produces a clear, specific
error when invalid.

472-479: 💤 Low value

Consider whether ImageRegistryCredentials should be immutable.

Many infrastructure fields in AzurePlatformSpec are marked immutable (e.g., location, resourceGroup, vnetID, subnetID). Since ImageRegistryCredentials configures VM scale set identity attachment, consider whether it should also be immutable once set, as changing VMSS identities post-creation can have operational implications.

If mutability is intentional for operational flexibility, this is fine—just confirming the design choice.

💭 Optional immutability constraint
 // imageRegistryCredentials configures authentication for worker nodes pulling images
 // from Azure Container Registry (ACR) using a user-assigned managed identity.
 // When set, the identity is attached to worker VM scale sets and its resource ID is written
 // into the worker cloud provider config so kubelet's credential provider can authenticate
 // to ACR without image pull secrets.
 //
+// +kubebuilder:validation:XValidation:rule="!has(oldSelf.imageRegistryCredentials) || has(self.imageRegistryCredentials)",message="imageRegistryCredentials cannot be removed once set"
 // +optional
 ImageRegistryCredentials *AzureImageRegistryCredentials `json:"imageRegistryCredentials,omitempty"`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/hypershift/v1beta1/azure.go` around lines 472 - 479, Decide and enforce
immutability for the ImageRegistryCredentials field: either mark
ImageRegistryCredentials on AzurePlatformSpec as immutable by adding the
kubebuilder validation tag (e.g., +kubebuilder:validation:Immutable) and update
the field comment to state it cannot be changed post-creation (since changing
VMSS identities is disruptive), or if mutability is intentional, add a clear
comment on ImageRegistryCredentials explaining why it must remain mutable and
document any operational steps/impacts; update any validation logic or docs
accordingly and reference the AzurePlatformSpec, ImageRegistryCredentials, and
analogous immutable fields (location, resourceGroup, vnetID, subnetID) when
making the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@api/hypershift/v1beta1/azure.go`:
- Around line 558-567: Update the AzureManagedIdentityResourceID validation to
add component-level checks similar to encryptionSetID and subnetID: keep the
existing overall XValidation rule but add additional XValidation or
kubebuilder:validation patterns that (1) validate the subscription ID is a UUID
(8-4-4-4-12), (2) validate the resourceGroupName follows 1-90 char rules
(allowed chars, no trailing period), and (3) validate the
userAssignedIdentities/{identityName} segment enforces resource name constraints
(length and allowed characters); target the AzureManagedIdentityResourceID type
and mirror the same annotation style used for encryptionSetID and subnetID so
each component produces a clear, specific error when invalid.
- Around line 472-479: Decide and enforce immutability for the
ImageRegistryCredentials field: either mark ImageRegistryCredentials on
AzurePlatformSpec as immutable by adding the kubebuilder validation tag (e.g.,
+kubebuilder:validation:Immutable) and update the field comment to state it
cannot be changed post-creation (since changing VMSS identities is disruptive),
or if mutability is intentional, add a clear comment on ImageRegistryCredentials
explaining why it must remain mutable and document any operational
steps/impacts; update any validation logic or docs accordingly and reference the
AzurePlatformSpec, ImageRegistryCredentials, and analogous immutable fields
(location, resourceGroup, vnetID, subnetID) when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: f0a439d2-15ae-489a-aa26-a3dbf24251a3

📥 Commits

Reviewing files that changed from the base of the PR and between 5491fa7 and ba0c25b.

⛔ Files ignored due to path filters (27)
  • api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !**/zz_generated*.go, !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
📒 Files selected for processing (1)
  • api/hypershift/v1beta1/azure.go

@codecov
Copy link
Copy Markdown

codecov Bot commented May 11, 2026

Codecov Report

❌ Patch coverage is 80.00000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 43.75%. Comparing base (7f1af37) to head (5193431).

Files with missing lines Patch % Lines
hypershift-operator/controllers/nodepool/azure.go 80.00% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8472      +/-   ##
==========================================
+ Coverage   40.00%   43.75%   +3.75%     
==========================================
  Files         751      279     -472     
  Lines       92863    45961   -46902     
==========================================
- Hits        37147    20109   -17038     
+ Misses      53024    24314   -28710     
+ Partials     2692     1538    -1154     
Files with missing lines Coverage Δ
hypershift-operator/controllers/nodepool/azure.go 89.70% <80.00%> (-0.88%) ⬇️

... and 472 files with indirect coverage changes

Flag Coverage Δ
cmd-support ?
cpo-hostedcontrolplane ?
cpo-other 40.14% <ø> (ø)
hypershift-operator 50.53% <80.00%> (+0.01%) ⬆️
other 31.54% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from ba0c25b to b80128b Compare May 11, 2026 19:39
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
hypershift-operator/controllers/nodepool/azure.go (1)

248-257: ⚠️ Potential issue | 🔴 Critical | ⚖️ Poor tradeoff

Remove the azure:// prefix from ProviderID on line 254.

CAPZ v1.21.0 expects the raw ARM resource ID format starting with /subscriptions/ without the azure:// prefix. Adding the prefix causes parsing failures with error: "invalid resource ID: resource id 'azure:///subscriptions/...' must start with '/'".

Diff
-				ProviderID: capzutil.ProviderIDPrefix + resourceID,
+				ProviderID: resourceID,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hypershift-operator/controllers/nodepool/azure.go` around lines 248 - 257,
The ProviderID is being constructed with an "azure://" prefix which CAPZ v1.21.0
rejects; in the hostedCluster block that sets
azureMachineTemplate.Template.Spec.UserAssignedIdentities, strip any leading
"azure://" (or "azure:///" variants) from
hostedCluster.Spec.Platform.Azure.ImageRegistryCredentials.ManagedIdentity.ResourceID
so resourceID starts with "/subscriptions/..." before concatenating with
capzutil.ProviderIDPrefix and assigning to ProviderID; update the code that
computes resourceID in that block (referencing hostedCluster,
azureMachineTemplate.Template.Spec.UserAssignedIdentities and ProviderID) to
perform this sanitization.
🧹 Nitpick comments (1)
api/hypershift/v1beta1/azure_test.go (1)

20-102: LGTM! Good N-1/N+1 compatibility coverage.

The test correctly verifies:

  • JSON serialization includes the field when set
  • JSON omits the field when zero-valued (via omitzero)
  • Legacy code (N-1) can unmarshal JSON from current version (N)
  • Current version (N) preserves zero value when unmarshaling from N-1

The test follows table-driven patterns and "When ... it should ..." naming conventions as per coding guidelines.

♻️ Optional: Consider using gomega for more expressive assertions
 import (
 	"encoding/json"
 	"strings"
 	"testing"
+	. "github.com/onsi/gomega"
 )

 func TestAzurePlatformSpecSerializationCompatibility(t *testing.T) {
+	g := NewWithT(t)
 	// ...
 	if tt.expectFieldInJSON {
-		if !strings.Contains(jsonStr, `"imageRegistryCredentials"`) {
-			t.Errorf("expected imageRegistryCredentials in JSON, got: %s", jsonStr)
-		}
+		g.Expect(jsonStr).To(ContainSubstring(`"imageRegistryCredentials"`))

This is purely optional; the current assertions work correctly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@api/hypershift/v1beta1/azure_test.go` around lines 20 - 102, The reviewer
gave LGTM and suggested an optional refactor to use gomega for more expressive
assertions; to apply this, update
TestAzurePlatformSpecSerializationCompatibility to use gomega's NewWithT(t) and
Expect(...) assertions instead of t.Fatalf/t.Errorf and manual if checks for
JSON contents and field values (references:
TestAzurePlatformSpecSerializationCompatibility, AzurePlatformSpec,
azurePlatformSpecNMinus1, ImageRegistryCredentials, ManagedIdentity.ResourceID);
add the gomega import and convert the key assertions (marshal/unmarshal errors,
presence/absence of `"imageRegistryCredentials"`, expected ResourceID, Location
and zero-value checks) to Expect-style calls while preserving the same test
cases and logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@hypershift-operator/controllers/nodepool/azure.go`:
- Around line 248-257: The ProviderID is being constructed with an "azure://"
prefix which CAPZ v1.21.0 rejects; in the hostedCluster block that sets
azureMachineTemplate.Template.Spec.UserAssignedIdentities, strip any leading
"azure://" (or "azure:///" variants) from
hostedCluster.Spec.Platform.Azure.ImageRegistryCredentials.ManagedIdentity.ResourceID
so resourceID starts with "/subscriptions/..." before concatenating with
capzutil.ProviderIDPrefix and assigning to ProviderID; update the code that
computes resourceID in that block (referencing hostedCluster,
azureMachineTemplate.Template.Spec.UserAssignedIdentities and ProviderID) to
perform this sanitization.

---

Nitpick comments:
In `@api/hypershift/v1beta1/azure_test.go`:
- Around line 20-102: The reviewer gave LGTM and suggested an optional refactor
to use gomega for more expressive assertions; to apply this, update
TestAzurePlatformSpecSerializationCompatibility to use gomega's NewWithT(t) and
Expect(...) assertions instead of t.Fatalf/t.Errorf and manual if checks for
JSON contents and field values (references:
TestAzurePlatformSpecSerializationCompatibility, AzurePlatformSpec,
azurePlatformSpecNMinus1, ImageRegistryCredentials, ManagedIdentity.ResourceID);
add the gomega import and convert the key assertions (marshal/unmarshal errors,
presence/absence of `"imageRegistryCredentials"`, expected ResourceID, Location
and zero-value checks) to Expect-style calls while preserving the same test
cases and logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 457f2777-b2e1-49ac-af2d-51f62f556244

📥 Commits

Reviewing files that changed from the base of the PR and between ba0c25b and b80128b.

⛔ Files ignored due to path filters (43)
  • api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !**/zz_generated*.go, !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • client/applyconfiguration/hypershift/v1beta1/azureimageregistrycredentials.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/azureplatformspec.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/userassignedmanagedidentity.go is excluded by !client/**
  • client/applyconfiguration/utils.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.azure.testsuite.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/testdata/zz_fixture_TestConfigWithAcrMI.yaml is excluded by !**/testdata/**
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • docs/content/reference/api.md is excluded by !docs/content/reference/api.md
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/azure.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*.go, !**/zz_generated*
📒 Files selected for processing (6)
  • api/hypershift/v1beta1/azure.go
  • api/hypershift/v1beta1/azure_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/azure_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go
  • hypershift-operator/controllers/nodepool/azure_test.go

@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch 2 times, most recently from 2889a65 to 5a90422 Compare May 12, 2026 17:51
@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 12, 2026 17:57 Inactive
@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from 5a90422 to 76d2a13 Compare May 12, 2026 22:13
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@api/hypershift/v1beta1/azure.go`:
- Around line 558-567: Update the AzureManagedIdentityResourceID type
validation: change the kubebuilder:validation:MinLength from 129 to 132 and
replace the permissive component regex with component-level validation similar
to encryptionSetID—require a UUID pattern for the subscriptionID, constrain
resourceGroupName to allowed characters and length (1–90), and enforce
identityName minimum length 3 and allowed characters; keep the overall
XValidation rule to match the complete ARM path but ensure its subpatterns
validate each component (subscription, resourceGroupName,
userAssignedIdentities/{identityName}) to mirror Azure's documented constraints.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Enterprise

Run ID: 29e87b7e-4823-400b-a997-52ab9f368df6

📥 Commits

Reviewing files that changed from the base of the PR and between 5a90422 and 76d2a13.

⛔ Files ignored due to path filters (43)
  • api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !**/zz_generated*.go, !**/zz_generated*
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedclusters.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/AAA_ungated.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterUpdateAcceptRisks.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ClusterVersionOperatorConfiguration.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDC.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUIDAndExtraClaimMappings.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ExternalOIDCWithUpstreamParity.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/GCPPlatform.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HCPEtcdBackup.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/HyperShiftOnlyDynamicResourceAllocation.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/ImageStreamImportMode.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/KMSEncryptionProvider.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/OpenStack.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • api/hypershift/v1beta1/zz_generated.featuregated-crd-manifests/hostedcontrolplanes.hypershift.openshift.io/TLSAdherence.yaml is excluded by !**/zz_generated.featuregated-crd-manifests/**
  • client/applyconfiguration/hypershift/v1beta1/azurecontainerregistrycredentials.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/azureplatformspec.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/userassignedmanagedidentity.go is excluded by !client/**
  • client/applyconfiguration/utils.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.azure.testsuite.yaml is excluded by !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedclusters-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-CustomNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-Default.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • cmd/install/assets/crds/hypershift-operator/zz_generated.crd-manifests/hostedcontrolplanes-Hypershift-TechPreviewNoUpgrade.crd.yaml is excluded by !**/zz_generated.crd-manifests/**, !cmd/install/assets/**/*.yaml
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/testdata/zz_fixture_TestConfigWithAcrMI.yaml is excluded by !**/testdata/**
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
  • docs/content/reference/api.md is excluded by !docs/content/reference/api.md
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/azure.go is excluded by !vendor/**, !**/vendor/**
  • vendor/github.com/openshift/hypershift/api/hypershift/v1beta1/zz_generated.deepcopy.go is excluded by !vendor/**, !**/vendor/**, !**/zz_generated*.go, !**/zz_generated*
📒 Files selected for processing (6)
  • api/hypershift/v1beta1/azure.go
  • api/hypershift/v1beta1/azure_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go
  • hypershift-operator/controllers/nodepool/azure.go
  • hypershift-operator/controllers/nodepool/azure_test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config.go
  • hypershift-operator/controllers/nodepool/azure.go
  • api/hypershift/v1beta1/azure_test.go
  • hypershift-operator/controllers/nodepool/azure_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/cloud_controller_manager/azure/config_test.go

Comment thread api/hypershift/v1beta1/azure.go Outdated
@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 12, 2026 22:19 Inactive
@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 13, 2026 16:54 Inactive
@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from 44affdc to 80070fa Compare May 13, 2026 17:14
@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 13, 2026 17:17 Inactive
@twolff-gh twolff-gh requested review from JoelSpeed and bryan-cox May 13, 2026 17:59
@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from 80070fa to 1286f9c Compare May 13, 2026 18:14
@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 13, 2026 18:21 Inactive
@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from 1286f9c to 8d18f2e Compare May 13, 2026 22:18
@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 13, 2026 22:22 Inactive
@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from 8d18f2e to 3bfca05 Compare May 13, 2026 22:57
@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 13, 2026 23:00 Inactive
// HostedCluster, but it must be in the same Azure AD tenant. The management cluster's
// CAPZ identity must have Microsoft.ManagedIdentity/userAssignedIdentities/*/assign/action
// on the identity's scope to attach it to worker VM scale sets at creation time.
type AzureContainerRegistryCredentials struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to have taken a step backwards since the last review? Why did we lose the intermediate ManagedIdentity struct? Why did we lose the type alias with the validations assigned?

Also, on the thread where I mentioned splitting the subscriptionID, resourceGroupName and identityName into three separate fields, there was no actual response to say why we shouldn't do that

It seemed like @bryan-cox was in favour of that?

The current field has a hard UX for the end user, when most of the string is static, lets build that for them

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that's how we did it in other places for ARO HCP. However, I am amenable to keeping it like this if that is what the ARO HCP team wants.

This part of the API is not external as far as I know, and the only consumers are the ARO HCP folks themselves—unless that is incorrect, @bennerv?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This appears to have taken a step backwards since the last review? Why did we lose the intermediate ManagedIdentity struct? Why did we lose the type alias with the validations assigned?

Also, on the thread where I mentioned splitting the subscriptionID, resourceGroupName and identityName into three separate fields, there was no actual response to say why we shouldn't do that

I had attempted to accommodate by iterating on the PR locally and ended up walking back those changes. The fields got eaten during that. I'll be restoring them.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there was no actual response to say why we shouldn't do that

Apologies, I conflated the responses.

I mentioned splitting the subscriptionID, resourceGroupName and identityName

There are several places where the ARM API takes a resource ID directly - VNets, NSGs, others.

In this case it makes sense because it gets passed down to CAPZ that uses it directly. Decomposing it and then recomposing makes sense when they are independently meaningful to the API, but here I think they make more sense attached to the context of the full string being used as-is down further.

I'd argue that here it makes it harder for the customer because they have access to the full string directly with the proper sub and rg for the managed ID, and they would instead need to copy/paste the components out of the string or use code to present the fields properly. I don't know how most customers do this now though - copy/paste?

Copy link
Copy Markdown
Author

@twolff-gh twolff-gh May 14, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But I'll need clarification from @bennerv

@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from 3bfca05 to f14d7bb Compare May 14, 2026 14:59
@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 14, 2026
Add types for configuring ACR image pull authentication via user-assigned
managed identity:
- AzureManagedIdentityResourceID: typed string with ARM resource ID
  CEL validation (MinLength=131, MaxLength=345)
- UserAssignedManagedIdentity: wraps ResourceID for extensibility
- AzureContainerRegistryCredentials: wraps ManagedIdentity, optional
  on AzurePlatformSpec

The identity is attached to worker VMSS by CAPZ and its resource ID is
written into worker cloud.conf for kubelet ACR credential provider
authentication. The identity must be in the same Azure AD tenant but
may reside in any subscription or resource group.

Ref: ARO-24037

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from f14d7bb to ecca3f6 Compare May 14, 2026 15:22
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 14, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 14, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: twolff-gh
Once this PR has been reviewed and has the lgtm label, please assign sjenning for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 14, 2026 15:28 Inactive
@twolff-gh twolff-gh force-pushed the ARO-24037-acr-pull-umi-cloud-conf branch from ecca3f6 to 5193431 Compare May 14, 2026 15:40
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 14, 2026

@twolff-gh: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/verify-deps ecca3f6 link true /test verify-deps
ci/prow/e2e-gke f14d7bb link false /test e2e-gke

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@github-actions github-actions Bot temporarily deployed to docs-preview/pr-8472 May 14, 2026 15:44 Inactive
twolff-gh and others added 5 commits May 14, 2026 08:57
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When AzureContainerRegistryCredentials is set on the HostedCluster, append
the managed identity to AzureMachineTemplate.UserAssignedIdentities so CAPZ
attaches it to worker VMs at creation time.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ker cloud.conf

When AzureContainerRegistryCredentials.ManagedIdentity.ResourceID is set,
CPO writes userAssignedIdentityID into the worker cloud.conf ConfigMap so
kubelet's ACR credential provider can authenticate to ACR via IMDS.

The value is intentionally omitted from the control plane Secret path
(adaptConfigSecret) to avoid changing control plane component auth.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add YAML-driven envtest cases validating CEL rules on
AzureContainerRegistryCredentials.ManagedIdentity.ResourceID:
- Valid ARM resource ID (pass)
- Lowercase ARM segments (pass)
- Invalid format, wrong provider, trailing slash (fail)
- Removal after creation (pass, mutable)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api Indicates the PR includes changes for the API area/cli Indicates the PR includes changes for CLI area/control-plane-operator Indicates the PR includes changes for the control plane operator - in an OCP release area/documentation Indicates the PR includes changes for documentation area/hypershift-operator Indicates the PR includes changes for the hypershift operator and API - outside an OCP release area/platform/azure PR/issue for Azure (AzurePlatform) platform

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants