Skip to content

feat: add containerRegistry to ARM API for ACR pull via managed identity (ARO-24037) - #5562

Open
Todd Wolff (twolff-gh) wants to merge 1 commit into
Azure:mainfrom
twolff-gh:ARO-24037-acr-pull-identity-arm-update
Open

feat: add containerRegistry to ARM API for ACR pull via managed identity (ARO-24037)#5562
Todd Wolff (twolff-gh) wants to merge 1 commit into
Azure:mainfrom
twolff-gh:ARO-24037-acr-pull-identity-arm-update

Conversation

@twolff-gh

@twolff-gh Todd Wolff (twolff-gh) commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Add properties.containerRegistry.managedIdentity to the v20260901preview ARM API, allowing customers to configure a user-assigned managed identity for worker node ACR image pulls.

ARO-24037

What changes

  • ARM API: ContainerRegistryProfile on HcpOpenShiftClusterProperties (v20260901preview). Day-2 mutable via PATCH. Set, change, or clear (null).
  • Admission: OCP >= 4.22 version gate.
  • Validation: Resource type check, managed resource group rejection, struct-level platform immutability preserved.
  • Backend: ContainerRegistryPullCredentialsPermissionValidation — checks CAPZ has assign/action on the MI. Re-validates on day-2 MI changes via InputKeyedClusterValidation.
  • Dispatch + CS conversion: Wired through clusterUpdateDispatchConfig for day-2 updates and clearing.
  • Cross-version preservation: Older API versions preserve the field through round-trips.

Testing

  • E2E (v20260901preview, skips until deployed): Create cluster with MI → pull from private ACR → day-2 update to different MI → verify pull with new MI.
  • Integration: PATCH to add containerRegistry, PATCH null to clear. Declarative artifact tests with v20260901preview.
  • Unit: Validation (create, update, clear, wrong type, same-MRG), admission version gate (4.17/4.21/4.22), backend permission validation (9 scenarios), OCM conversion round-trip, dispatch config, immutability enforcement for all platform fields.

Companion changes

@openshift-ci

openshift-ci Bot commented Jun 8, 2026

Copy link
Copy Markdown

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

@twolff-gh
Todd Wolff (twolff-gh) marked this pull request as ready for review June 8, 2026 21:43
Copilot AI lite review requested due to automatic review settings June 8, 2026 21:43

Copilot AI left a comment

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.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds support for configuring Azure Container Registry (ACR) authentication on HCP OpenShift clusters (2025-12-23-preview), wiring the new containerRegistry field through API schemas, internal types, conversions, and validation.

Changes:

  • Introduces containerRegistry into the platform profile schema (TypeSpec/OpenAPI) and updates example payloads.
  • Extends internal API types/enums plus external<->internal conversion logic to carry the new profile across versions.
  • Updates validation to allow platform.containerRegistry updates while keeping the rest of platform immutable.

Reviewed changes

Copilot reviewed 18 out of 22 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
internal/validation/validate_cluster.go Allows mutability for platform.containerRegistry and adds validation for the new profile.
internal/api/v20251223preview/hcpopenshiftclusters_methods.go Maps containerRegistry in model conversions and normalizes incoming payloads.
internal/api/v20240610preview/hcpopenshiftclusters_methods.go Preserves unknown containerRegistry field across older API version conversions.
internal/api/v20240610preview/conversion_fuzz_test.go Updates fuzz round-trip customizations for version-skewed fields, including containerRegistry.
internal/api/types_cluster.go Adds internal API structs for ContainerRegistryProfile and related types.
internal/api/enums.go Adds ContainerRegistryCredentialType enum and allowed set.
api/.../openapi.json Defines containerRegistry schemas and embeds it into PlatformProfile (and update variants).
api/.../examples/*.json Adds containerRegistry to generated example payloads for the 2025-12-23-preview API.
api/.../hcpCluster-models.tsp Adds TypeSpec models for containerRegistry and supporting shapes/enums.
Files not reviewed (1)
  • internal/api/zz_generated.deepcopy.go: Language not supported

Comment thread internal/api/v20251223preview/hcpopenshiftclusters_methods.go Outdated
Comment thread internal/api/v20251223preview/hcpopenshiftclusters_methods.go Outdated
Comment thread internal/api/v20251223preview/hcpopenshiftclusters_methods.go Outdated
Comment thread internal/validation/validate_cluster.go Outdated
Comment thread internal/validation/validate_cluster.go Outdated
Comment thread internal/api/v20251223preview/hcpopenshiftclusters_methods.go Outdated
Comment thread internal/api/v20251223preview/hcpopenshiftclusters_methods.go Outdated
@twolff-gh
Todd Wolff (twolff-gh) force-pushed the ARO-24037-acr-pull-identity-arm-update branch from 73dab29 to 2d82b53 Compare June 8, 2026 22:57

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

lgtm

Not adding the command as I'd defer that to SMEs

@bennerv

Copy link
Copy Markdown
Member

/hold

@mbarnes Matthew Barnes (mbarnes) left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested simplification for consistency with other places in the API that take a user-assigned managed identity resource ID.

Copilot AI review requested due to automatic review settings June 15, 2026 23:31
@twolff-gh
Todd Wolff (twolff-gh) force-pushed the ARO-24037-acr-pull-identity-arm-update branch from 2d82b53 to 70d4923 Compare June 15, 2026 23:31

Copilot AI left a comment

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@twolff-gh
Todd Wolff (twolff-gh) force-pushed the ARO-24037-acr-pull-identity-arm-update branch from 70d4923 to 2bee5be Compare June 15, 2026 23:35
Copilot AI review requested due to automatic review settings June 17, 2026 16:34
@twolff-gh
Todd Wolff (twolff-gh) force-pushed the ARO-24037-acr-pull-identity-arm-update branch from 2bee5be to 0bb66e9 Compare June 17, 2026 16:34

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 30 out of 42 changed files in this pull request and generated 2 comments.

Files not reviewed (1)
  • internal/api/zz_generated.deepcopy.go: Generated file
Comments suppressed due to low confidence (2)

internal/ocm/convert.go:1

  • On update, this only sends containerRegistry when it’s non-nil, which prevents users from clearing/removing an existing container registry config (nil becomes “no change”). If the CS API supports clearing, explicitly set the field to nil on updates when hcpCluster.CustomerProperties.Platform.ContainerRegistry is nil; otherwise document/validate that removal isn’t supported.
    internal/validation/validate_cluster.go:1
  • The required-field message here doesn’t match the conventions used elsewhere in this repo’s validation tests (which often assert "Required"). Consider using the same capitalization/wording as other field.Required usages (or centralizing the message) to keep error outputs consistent and reduce brittle test expectations.

Comment thread internal/api/v20251223preview/hcpopenshiftclusters_methods.go Outdated
Comment thread internal/api/v20251223preview/hcpopenshiftclusters_methods.go Outdated
@twolff-gh
Todd Wolff (twolff-gh) force-pushed the ARO-24037-acr-pull-identity-arm-update branch from 0bb66e9 to b2094f1 Compare June 30, 2026 21:25
Copilot AI review requested due to automatic review settings June 30, 2026 23:07

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 34 out of 48 changed files in this pull request and generated 2 comments.

Comment thread internal/ocm/convert.go Outdated
Comment thread internal/validation/validate_cluster.go Outdated
Copilot AI review requested due to automatic review settings July 6, 2026 21:03
@twolff-gh
Todd Wolff (twolff-gh) force-pushed the ARO-24037-acr-pull-identity-arm-update branch from 77397f5 to 1a47c94 Compare July 6, 2026 21:03

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 52 out of 64 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

internal/validation/validate_cluster.go:654

  • This comment appears to have an incorrect JSON tag reference (json:"containerRegistry,omitempty"). The internal field is ContainerRegistryPullManagedIdentity with JSON tag containerRegistryPullManagedIdentity, while the external ARM shape is platform.containerRegistry.managedIdentity. Keeping the comment accurate helps avoid confusion when maintaining validation vs. API surface mappings.

Comment thread test/e2e/cluster_container_registry.go Outdated

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 51 out of 64 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/zz_generated.deepcopy.go: Generated file
Suppressed comments (2)

backend/pkg/utils/validationutils/container_registry_pull_credentials_permission_validation.go:82

  • The user-assigned identities ARM client is always built with the cluster’s subscription ID, but both the containerRegistry pull MI and CAPZ MI resource IDs may be in a different subscription (the PR explicitly targets cross-subscription identities). This will cause false "not found or not accessible" failures whenever either identity lives outside the cluster subscription.

Build the UserAssignedIdentitiesClient using the subscription ID from the resource being queried (and create a second client if CAPZ and the pull MI are in different subscriptions).

	uaisClient, err := v.smiClientBuilder.UserAssignedIdentitiesClient(ctx, clusterIdentityURL, smiResourceID, cluster.ID.SubscriptionID)
	if err != nil {
		return utils.TrackError(fmt.Errorf("failed to get user assigned identities client: %w", err))
	}

	_, err = uaisClient.Get(ctx, containerRegistryPullMI.ResourceGroupName, containerRegistryPullMI.Name, nil)
	if err != nil {
		return utils.TrackError(fmt.Errorf("container registry pull managed identity %s not found or not accessible: %w", containerRegistryPullMI, err))
	}

	capzMI, err := uaisClient.Get(ctx, capzResourceID.ResourceGroupName, capzResourceID.Name, nil)
	if err != nil {
		return utils.TrackError(fmt.Errorf("failed to get CAPZ managed identity %s: %w", capzResourceID, err))
	}

internal/validation/validate_cluster.go:654

  • The field comment references an incorrect JSON tag (json:"containerRegistry,omitempty"). The internal field is containerRegistryPullManagedIdentity, and externally it maps to platform.containerRegistry.managedIdentity. This comment is likely to mislead future readers.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 44 out of 50 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (3)

internal/validation/validate_cluster_test.go:962

  • for i := range typ.NumField() does not compile because range can't iterate over an int. This test will fail to build as written.
    test/e2e/cluster_container_registry.go:204
  • Same formatting issue as above: Gomega won't apply %s formatting to this message, so the failure output is harder to read. Prefer fmt.Sprintf or string concatenation.
    test/e2e/cluster_container_registry.go:187
  • These assertion messages pass printf-style format strings to Gomega, but Gomega does not format them; it will print the args separated (e.g. "failed to create ACR %s" acrName). Use fmt.Sprintf (or build the string explicitly) so failures show the intended message.

This issue also appears on line 201 of the same file.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 44 out of 50 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (1)

backend/pkg/utils/validationutils/container_registry_pull_credentials_permission_validation.go:76

  • UserAssignedIdentitiesClient is created with cluster.ID.SubscriptionID, but the container registry pull managed identity is explicitly allowed to be cross-subscription. When containerRegistryPullMI.SubscriptionID != cluster.ID.SubscriptionID, the subsequent Get will query the wrong subscription and incorrectly fail validation. Build the MSI client for the managed identity’s subscription (and reuse the cluster-subscription client for CAPZ if desired).
	smiResourceID := cluster.CustomerProperties.Platform.OperatorsAuthentication.UserAssignedIdentities.ServiceManagedIdentity
	clusterIdentityURL := cluster.ServiceProviderProperties.ManagedIdentitiesDataPlaneIdentityURL

	uaisClient, err := v.smiClientBuilder.UserAssignedIdentitiesClient(ctx, clusterIdentityURL, smiResourceID, cluster.ID.SubscriptionID)
	if err != nil {
		return utils.TrackError(fmt.Errorf("failed to get user assigned identities client: %w", err))
	}

	_, err = uaisClient.Get(ctx, containerRegistryPullMI.ResourceGroupName, containerRegistryPullMI.Name, nil)
	if err != nil {
		return utils.TrackError(fmt.Errorf("container registry pull managed identity %s not found or not accessible: %w", containerRegistryPullMI, err))
	}

Comment thread internal/validation/validate_cluster_test.go Outdated

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 44 out of 50 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (1)

test/e2e/cluster_container_registry.go:190

  • acrResp.Properties.LoginServer is dereferenced without checking acrResp.Properties / LoginServer for nil, which can panic the E2E test if the ARM response is missing these fields. Add explicit assertions before dereferencing.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 44 out of 50 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (1)

internal/ocm/cluster_update_dispatch_config.go:31

  • The clearing sentinel contains a NUL byte ("\x00clear"). While it won't collide with real ARM resource IDs, using a control character makes the value harder to debug and can behave unexpectedly if it ever gets logged or surfaced in errors. A plain string sentinel that cannot be a real resource ID (e.g., anything not starting with "/") is sufficient here.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 44 out of 50 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (1)

internal/api/coreapi/types_cluster.go:242

  • This adds a new Cosmos-persisted field on the cluster model (ContainerRegistryPullManagedIdentity). Per repo guidance, changes to internal/api/types_*.go that affect stored fields should be reflected in docs/cosmos-data-flow.md (regenerate/update it so the documented Cosmos read/write flows stay accurate).
	// Written by: Frontend PUT Cluster (Create), Frontend PATCH Cluster (Update)
	ContainerRegistryPullManagedIdentity *azcorearm.ResourceID `json:"containerRegistryPullManagedIdentity,omitempty"`

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 44 out of 50 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file

Comment thread internal/validation/validate_cluster_test.go Outdated

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 50 out of 56 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (1)

test/e2e/cluster_container_registry.go:192

  • acrResp.Properties.LoginServer is dereferenced without checking that acrResp.Properties and LoginServer are non-nil. If the ARM response is missing these fields, this test will panic instead of failing with a clear assertion message.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 50 out of 56 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (1)

backend/pkg/controllers/cluster/validation/cluster_validation_controller.go:228

  • For InputKeyedClusterValidation, shouldProcess currently requires the stored condition Message to exactly equal InputKey(cluster). If a keyed validation ever wants to include a human-readable success message (or if existing data has extra text), this will force re-validation on every reconcile.

Consider parsing the key from the message (e.g. treat the first line as the key and ignore the rest) so keyed validations can safely append more context without breaking the gating logic.

	if keyed, ok := c.validation.(validationutils.InputKeyedClusterValidation); ok {
		return keyed.InputKey(cluster) != condition.Message
	}

@openshift-ci

openshift-ci Bot commented Aug 11, 2026

Copy link
Copy Markdown

[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 mbarnes, patriksuba 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

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 51 out of 57 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (1)

backend/pkg/utils/validationutils/cluster_validation.go:34

  • The InputKeyedClusterValidation doc says the controller stores the input key in the condition’s Message on success, but the controller currently only compares InputKey(cluster) against the existing condition’s Message (it doesn’t overwrite it). This mismatch is easy to follow incorrectly when implementing future keyed validations, and can lead to the validation re-running every reconcile if the PassedValidation user message isn’t set to the input key.
// InputKeyedClusterValidation is an optional extension of ClusterValidation
// for validations whose input can change on day-2 updates. The validation
// controller stores the key in the condition's Message field on success and
// re-validates when the key changes.

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 78 out of 84 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (9)

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/examples/HcpOpenShiftClusters_CreateOrUpdate_MaximumSet_Gen.json:151

  • This example places containerRegistry under properties.platform, but the OpenAPI schema defines properties.containerRegistry (sibling of platform). As-is, the example JSON does not conform to the schema.
            "issuerUrl": "https://oidc.contoso.com",
            "containerRegistry": {
              "managedIdentity": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myAcrPullIdentity"
            }

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/examples/HcpOpenShiftClusters_CreateOrUpdate_MaximumSet_Gen.json:272

  • This example places containerRegistry under properties.platform, but the OpenAPI schema defines properties.containerRegistry (sibling of platform). As-is, the example JSON does not conform to the schema.
            "issuerUrl": "https://oidc.contoso.com",
            "containerRegistry": {
              "managedIdentity": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myAcrPullIdentity"
            }

test/e2e/cluster_container_registry.go:182

  • Possible nil dereference: acrResp.Properties.LoginServer is dereferenced without checking acrResp.Properties and acrResp.Properties.LoginServer for nil, which would panic and obscure the test failure cause. Add explicit nil assertions before dereferencing.
    test/e2e/cluster_container_registry.go:108
  • Possible nil dereference: clusterParams.UserAssignedIdentitiesProfile is used without a nil check. If cluster params are ever constructed without calling CreateClusterCustomerResources..., this will panic rather than returning a useful error.
    api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/examples/HcpOpenShiftClusters_Update_MaximumSet_Gen.json:68
  • This example places containerRegistry under properties.platform, but the OpenAPI schema defines properties.containerRegistry (sibling of platform). As-is, the example JSON does not conform to the schema.
            "issuerUrl": "https://oidc.contoso.com",
            "containerRegistry": {
              "managedIdentity": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myAcrPullIdentity"
            }

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/examples/HcpOpenShiftClusters_Get_MaximumSet_Gen.json:57

  • This example places containerRegistry under properties.platform, but the OpenAPI schema defines properties.containerRegistry (sibling of platform). As-is, the example JSON does not conform to the schema.
            "issuerUrl": "https://oidc.contoso.com",
            "containerRegistry": {
              "managedIdentity": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myAcrPullIdentity"
            }

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/examples/HcpOpenShiftClusters_ListBySubscription_MaximumSet_Gen.json:57

  • This example places containerRegistry under properties.platform, but the OpenAPI schema defines properties.containerRegistry (sibling of platform). As-is, the example JSON does not conform to the schema.
                "issuerUrl": "https://oidc.contoso.com",
                "containerRegistry": {
                  "managedIdentity": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myAcrPullIdentity"
                }

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/examples/HcpOpenShiftClusters_ListByResourceGroup_MaximumSet_Gen.json:58

  • This example places containerRegistry under properties.platform, but the OpenAPI schema defines properties.containerRegistry (sibling of platform). As-is, the example JSON does not conform to the schema.
                "issuerUrl": "https://oidc.contoso.com",
                "containerRegistry": {
                  "managedIdentity": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myAcrPullIdentity"
                }

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/examples/HcpOpenShiftClusters_CreateOrUpdate_MaximumSet_Gen.json:51

  • This example places containerRegistry under properties.platform, but the OpenAPI schema defines properties.containerRegistry (sibling of platform). As-is, the example JSON does not conform to the schema.

This issue also appears in the following locations of the same file:

  • line 148
  • line 269
          "containerRegistry": {
            "managedIdentity": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myAcrPullIdentity"
          }

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 76 out of 82 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (3)

test/e2e/cluster_container_registry.go:182

  • acrResp.Properties.LoginServer is dereferenced without checking that acrResp.Properties and LoginServer are non-nil, which can panic the test if the SDK response is partially populated. Add explicit expectations before dereferencing.
    backend/pkg/utils/validationutils/cluster_validation.go:34
  • The InputKeyedClusterValidation docstring says the controller stores the key in the condition Message field on success, but the controller currently just writes ValidationResult.ToCondition(), which uses Outcome.Passed.UserMessage as Message. This makes the contract easy to misunderstand and can lead to keyed validations re-running every reconcile if they don’t set UserMessage to the key.
// InputKeyedClusterValidation is an optional extension of ClusterValidation
// for validations whose input can change on day-2 updates. The validation
// controller stores the key in the condition's Message field on success and
// re-validates when the key changes.

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/preview/2026-06-30-preview/openapi.json:2406

  • The PR description says this change adds platform.containerRegistry.managedIdentity, but the schema (and the rest of the implementation/tests) adds properties.containerRegistry.managedIdentity (sibling to platform). Please reconcile the description with the actual ARM shape (or move the field if the intended location really is under platform).
        "containerRegistry": {
          "$ref": "#/definitions/ContainerRegistryProfile",
          "description": "Azure Container Registry configuration for authenticating image pulls\non the cluster's worker nodes.",
          "x-ms-mutability": [
            "read",
            "update",
            "create"
          ]

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 64 out of 68 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (3)

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/hcpCluster-models.tsp:539

  • ContainerRegistryProfile itself is also marked as @added(Versions.v2026_09_01_preview), which prevents it (and containerRegistry) from being available in v2026_06_30_preview even though this PR updates v20260630preview clients/tests. Update the @added version to v2026_06_30_preview (and regenerate outputs).
@added(Versions.v2026_09_01_preview)

test/e2e/cluster_container_registry.go:181

  • Potential nil dereference: acrResp.Properties and/or acrResp.Properties.LoginServer can be nil, which would panic the test at *acrResp.Properties.LoginServer. Add assertions before dereferencing so failures are reported as test failures instead of panics.
    internal/validation/validate_cluster.go:266
  • Remove the stray commented-out struct-tag line. It looks like a copy/paste artifact and doesn’t add useful context; it also references omitempty even though the actual tag is omitzero on the internal type.
	// ContainerRegistry       ContainerRegistryProfile            `json:"containerRegistry,omitempty"`

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 65 out of 69 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (6)

internal/validation/validate_cluster.go:264

  • validateClusterCustomerProperties currently enforces platform immutability twice: a struct-level immutableByReflect on platform plus the per-field immutability checks inside validateCustomerPlatformProfile. This leads to duplicate immutability errors (one at platform, one at the specific field) and makes the per-field immutability coverage test less meaningful. Rely on validateCustomerPlatformProfile and drop the struct-level immutability check.
	// Platform                CustomerPlatformProfile             `json:"platform,omitempty"`
	errs = append(errs, immutableByReflect(ctx, op, fldPath.Child("platform"), &newObj.Platform, safe.Field(oldObj, toCustomerPlatform))...)
	errs = append(errs, validateCustomerPlatformProfile(ctx, op, fldPath.Child("platform"), &newObj.Platform, safe.Field(oldObj, toCustomerPlatform))...)

internal/azureapi/v20260630preview/conversion_fuzz_test.go:43

  • The PR description says containerRegistry.managedIdentity is added to v20260630preview, but this fuzz test explicitly says ContainerRegistry does not exist in v20260630preview and forces it to the zero value for round-tripping. Please reconcile the implementation with the PR description (either add ContainerRegistry to the v20260630preview API surface, or update the PR description/notes to reflect the actual versioning).
	fuzzer := coreapitesting.FuzzerFor(
		append(coreapitesting.CommonRoundTripFuzzFuncs(),
			// ContainerRegistry does not exist in v20260630preview.
			func(j *coreapi.HCPOpenShiftClusterCustomerProperties, c randfill.Continue) {
				c.FillNoCustom(j)
				j.ContainerRegistry = coreapi.ContainerRegistryProfile{}
			},

internal/azureapi/v20251223preview/conversion_fuzz_test.go:45

  • This comment says ContainerRegistry was added in v20260630preview, but the TypeSpec/OpenAPI changes add it in v2026_09_01_preview. The mismatch makes it harder to reason about why the fuzzer needs to zero this field for older API versions.
	fuzzer := coreapitesting.FuzzerFor(append(coreapitesting.CommonRoundTripFuzzFuncs(),
		// Ingress, CryptoRestrictions, and ContainerRegistry were added in v20260630preview and do not exist in v20251223preview.
		func(j *coreapi.HCPOpenShiftClusterCustomerProperties, c randfill.Continue) {
			c.FillNoCustom(j)
			j.Ingress = coreapi.CustomerIngressProfile{}
			j.CryptoRestrictions = metadataapi.CryptoRestrictionsNone
			j.ContainerRegistry = coreapi.ContainerRegistryProfile{}
		},

test/e2e/cluster_container_registry.go:399

  • The PR description claims the E2E scenario includes a day-2 clear via "containerRegistry": null PATCH, but this E2E test covers create + update only and never exercises clearing the field. Either update the PR description/testing section to match what’s actually covered here, or extend this test (or add a companion one) to validate the clear behavior end-to-end.
    internal/azureapi/v20240610preview/hcpopenshiftclusters_methods.go:456
  • Comment has the wrong API version: ContainerRegistry is introduced in the v2026_09_01_preview API (per the TypeSpec/OpenAPI changes), not v2026_06_30_preview.
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry

internal/azureapi/v20251223preview/hcpopenshiftclusters_methods.go:498

  • Comment has the wrong API version: ContainerRegistry is introduced in the v2026_09_01_preview API (per the TypeSpec/OpenAPI changes), not v2026_06_30_preview. Keeping this accurate helps explain why the field is preserved for this older API version.
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 65 out of 69 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (8)

internal/validation/validate_cluster_test.go:990

  • for i := range typ.NumField() does not compile because range can't be used over an int. Use a conventional indexed loop over NumField() so this test builds.
		typ := reflect.TypeOf(coreapi.CustomerPlatformProfile{})
		for i := range typ.NumField() {
			name := typ.Field(i).Name

api/redhatopenshift/resource-manager/Microsoft.RedHatOpenShift/hcpopenshiftclusters/hcpCluster-models.tsp:144

  • The PR metadata summary states this field is added in both v2026-06-30-preview and v2026-09-01-preview, but the TypeSpec marks it as @added(Versions.v2026_09_01_preview) only. If v2026_06_30_preview is intended to expose the field, it also needs schema/model/SDK updates for that API version; otherwise the PR description (and any internal comments referring to v2026_06_30_preview) should be updated to match the actual version boundary.
  /** Azure Container Registry configuration for authenticating image pulls
   * on the cluster's worker nodes. */
  @added(Versions.v2026_09_01_preview)
  @visibility(Lifecycle.Read, Lifecycle.Create, Lifecycle.Update)
  containerRegistry?: ContainerRegistryProfile;

internal/validation/validate_cluster.go:267

  • validateCustomerPlatformProfile already enforces per-field immutability for platform. Keeping immutableByReflect(..., &newObj.Platform, ...) here duplicates immutability checks and will also block any future platform fields intended to be day-2 mutable.
	errs = append(errs, immutableByReflect(ctx, op, fldPath.Child("platform"), &newObj.Platform, safe.Field(oldObj, toCustomerPlatform))...)
	errs = append(errs, validateCustomerPlatformProfile(ctx, op, fldPath.Child("platform"), &newObj.Platform, safe.Field(oldObj, toCustomerPlatform))...)

	// ContainerRegistry       ContainerRegistryProfile            `json:"containerRegistry,omitempty"`
	errs = append(errs, validateContainerRegistryPullCredentials(ctx, op, fldPath.Child("containerRegistry", "managedIdentity"), newObj.ContainerRegistry.PullManagedIdentity, safe.Field(oldObj, toCustomerPropertiesContainerRegistryPullMI), newObj.Platform.ManagedResourceGroup)...)

test/e2e/cluster_container_registry.go:176

  • acrResp.Properties.LoginServer is dereferenced without checking that Properties and LoginServer are non-nil. A nil value here will panic the test instead of producing a readable failure.
    test/e2e/cluster_container_registry.go:356
  • The PR description says the E2E covers day-2 clear via null PATCH, but this test currently stops after the day-2 update and never exercises containerRegistry: null / verify the field is cleared via GET. Adding that step would ensure the documented clear semantics keep working end-to-end.
    internal/azureapi/v20251223preview/hcpopenshiftclusters_methods.go:498
  • This comment says ContainerRegistry was added in v2026_06_30_preview, but the v20260630preview conversion fuzz test explicitly treats ContainerRegistry as non-existent in that version. Update the version in the comment to avoid misleading future maintainers.
	// CryptoRestrictions was added in v2026_06_30_preview
	to.CustomerProperties.CryptoRestrictions = from.CustomerProperties.CryptoRestrictions
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry

internal/azureapi/v20240610preview/hcpopenshiftclusters_methods.go:456

  • This comment says ContainerRegistry was added in v2026_06_30_preview, but the v20260630preview conversion fuzz test explicitly treats ContainerRegistry as non-existent in that version. Update the version in the comment to avoid misleading future maintainers.
	// CryptoRestrictions was added in v2026_06_30_preview
	to.CustomerProperties.CryptoRestrictions = from.CustomerProperties.CryptoRestrictions
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry

internal/azureapi/v20251223preview/conversion_fuzz_test.go:45

  • The comment claims ContainerRegistry was added in v20260630preview, but the v20260630preview conversion fuzz test and TypeSpec changes indicate it does not exist until later. Consider updating the version in this comment to match the actual API version boundary so the fuzz override rationale stays accurate.
	fuzzer := coreapitesting.FuzzerFor(append(coreapitesting.CommonRoundTripFuzzFuncs(),
		// Ingress, CryptoRestrictions, and ContainerRegistry were added in v20260630preview and do not exist in v20251223preview.
		func(j *coreapi.HCPOpenShiftClusterCustomerProperties, c randfill.Continue) {
			c.FillNoCustom(j)
			j.Ingress = coreapi.CustomerIngressProfile{}
			j.CryptoRestrictions = metadataapi.CryptoRestrictionsNone
			j.ContainerRegistry = coreapi.ContainerRegistryProfile{}
		},

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 65 out of 69 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (6)

test/e2e/cluster_container_registry.go:391

  • The second VerifyImagePulled(...) call can succeed because it checks any pod in the namespace. Since the first pod (acr-pull-test) remains and already has a populated ImageID, the post-update verification can pass even if the new pod never pulls successfully.

To make this assertion meaningful, remove the initial pod (and wait for deletion) before creating/verifying the post-update pod, so the verifier is forced to observe the new pull attempt.
test/e2e/cluster_container_registry.go:176

  • acrResp.Properties.LoginServer is dereferenced without checking acrResp.Properties/LoginServer for nil. If the ARM response omits these fields, the test will panic before reaching the assertions.

Add explicit nil checks before dereferencing so failures surface as clear test assertions.

This issue also appears on line 387 of the same file.
internal/azureapi/v20251223preview/hcpopenshiftclusters_methods.go:498

  • The comment says ContainerRegistry was added in v2026_06_30_preview, but this PR introduces containerRegistry in the v2026_09_01_preview ARM API. This comment should reflect the correct API version to avoid confusion when future versions add/rename fields.
	// CryptoRestrictions was added in v2026_06_30_preview
	to.CustomerProperties.CryptoRestrictions = from.CustomerProperties.CryptoRestrictions
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry

internal/azureapi/v20240610preview/hcpopenshiftclusters_methods.go:456

  • The comment says ContainerRegistry was added in v2026_06_30_preview, but this PR introduces containerRegistry in the v2026_09_01_preview ARM API. Update the comment to match the actual version where the field was introduced.
	// CryptoRestrictions was added in v2026_06_30_preview
	to.CustomerProperties.CryptoRestrictions = from.CustomerProperties.CryptoRestrictions
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry

internal/azureapi/v20251223preview/conversion_fuzz_test.go:45

  • This comment says ContainerRegistry was added in v20260630preview, but the new containerRegistry field is added in v20260901preview. Keeping this accurate matters because the fuzz test is specifically documenting fields that must be zeroed because they do not exist in this external API version.
	fuzzer := coreapitesting.FuzzerFor(append(coreapitesting.CommonRoundTripFuzzFuncs(),
		// Ingress, CryptoRestrictions, and ContainerRegistry were added in v20260630preview and do not exist in v20251223preview.
		func(j *coreapi.HCPOpenShiftClusterCustomerProperties, c randfill.Continue) {
			c.FillNoCustom(j)
			j.Ingress = coreapi.CustomerIngressProfile{}
			j.CryptoRestrictions = metadataapi.CryptoRestrictionsNone
			j.ContainerRegistry = coreapi.ContainerRegistryProfile{}
		},

backend/pkg/controllers/cluster/validation/cluster_validation_controller.go:229

  • shouldProcess uses condition.Message as the stored InputKey for keyed validations. Since condition.Message is also the user-facing condition message, keyed validations will end up showing opaque keys (e.g., a resource ID) instead of a meaningful success message.

Consider storing the input key in a dedicated field (e.g., a new status field alongside the condition) or encoding it in Message in a structured way (e.g., inputKey=<key>; <human message>) and parsing it here, so the condition message stays informative.

// shouldProcess returns true when the validation should run. This is the case when:
//   - the condition does not exist or previously failed, OR
//   - the validation implements InputKeyedClusterValidation and the input has changed
//     since the last successful validation.
func (c *clusterValidationSyncer) shouldProcess(serviceProviderCluster *coreapi.ServiceProviderCluster, cluster *coreapi.HCPOpenShiftCluster) bool {
	condition := meta.FindStatusCondition(serviceProviderCluster.Status.Validations, c.validation.Name())
	if condition == nil || condition.Status != metav1.ConditionTrue {
		return true
	}
	if keyed, ok := c.validation.(validationutils.InputKeyedClusterValidation); ok {
		return keyed.InputKey(cluster) != condition.Message
	}
	return false

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 65 out of 69 changed files in this pull request and generated no new comments.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (4)

backend/pkg/utils/validationutils/cluster_validation.go:34

  • The InputKeyedClusterValidation doc says the controller stores the key in the condition Message on success, but the controller actually just compares condition.Message to InputKey(...) and relies on the validation’s PassedValidation(UserMessage=...) to populate the message. This mismatch is easy to trip over when adding future keyed validations (they may keep a human-readable success message and unintentionally cause perpetual re-validation). Either update the doc to reflect the current contract, or change the controller to explicitly store the key when the outcome is Passed.
// InputKeyedClusterValidation is an optional extension of ClusterValidation
// for validations whose input can change on day-2 updates. The validation
// controller stores the key in the condition's Message field on success and
// re-validates when the key changes.

internal/azureapi/v20251223preview/hcpopenshiftclusters_methods.go:499

  • Comment says ContainerRegistry was added in v2026_06_30_preview, but this PR introduces it in v2026_09_01_preview. Since this function documents cross-version field preservation, the version marker should match the actual API introduction to avoid confusing future maintainers.
	// Ingress was added in v2026_06_30_preview.
	to.CustomerProperties.Ingress = from.CustomerProperties.Ingress
	// CryptoRestrictions was added in v2026_06_30_preview
	to.CustomerProperties.CryptoRestrictions = from.CustomerProperties.CryptoRestrictions
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry
}

internal/api/coreapi/types_cluster.go:93

  • A new Cosmos-persisted customer property (CustomerProperties.ContainerRegistry) is introduced here, but docs/cosmos-data-flow.md is not updated (no references to containerRegistry / pullManagedIdentity found). Per repo guidance, changes to internal/api/types_*.go that affect stored Cosmos fields should update that document so the data-flow inventory stays accurate.
	// Written by: Frontend PUT/PATCH Cluster
	Ingress CustomerIngressProfile `json:"ingress,omitempty"`
	// Written by: Frontend PUT/PATCH Cluster
	Platform CustomerPlatformProfile `json:"platform,omitempty"`
	// Written by: Frontend PUT/PATCH Cluster
	ContainerRegistry ContainerRegistryProfile `json:"containerRegistry,omitzero"`
	// Written by: Frontend PUT/PATCH Cluster
	Autoscaling ClusterAutoscalingProfile `json:"autoscaling,omitempty"`

internal/azureapi/v20240610preview/hcpopenshiftclusters_methods.go:456

  • Comment says ContainerRegistry was added in v2026_06_30_preview, but this PR adds it in v2026_09_01_preview. Updating the comment avoids implying the field exists in older API versions.
	// CryptoRestrictions was added in v2026_06_30_preview
	to.CustomerProperties.CryptoRestrictions = from.CustomerProperties.CryptoRestrictions
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.ContainerRegistry = from.CustomerProperties.ContainerRegistry

@twolff-gh

Copy link
Copy Markdown
Collaborator Author

/test e2e-parallel

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 67 out of 71 changed files in this pull request and generated 1 comment.

Files not reviewed (1)
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (7)

test/e2e/cluster_container_registry.go:384

  • Redundant assertion: this repeats the exact same nil check twice.
    internal/azureapi/v20251223preview/hcpopenshiftclusters_methods.go:498
  • The version annotation comment is incorrect: ContainerRegistry was added in v2026_09_01_preview, not v2026_06_30_preview.
	// Ingress was added in v2026_06_30_preview.
	to.CustomerProperties.Ingress = from.CustomerProperties.Ingress
	// CryptoRestrictions was added in v2026_06_30_preview
	to.CustomerProperties.CryptoRestrictions = from.CustomerProperties.CryptoRestrictions
	// ContainerRegistry was added in v2026_06_30_preview.
	to.CustomerProperties.Platform.ContainerRegistry = from.CustomerProperties.Platform.ContainerRegistry

internal/azureapi/v20251223preview/conversion_fuzz_test.go:45

  • This comment misstates when ContainerRegistry was introduced. Ingress/CryptoRestrictions were added in v20260630preview, but ContainerRegistry was added in v20260901preview.
	fuzzer := coreapitesting.FuzzerFor(append(coreapitesting.CommonRoundTripFuzzFuncs(),
		// Ingress, CryptoRestrictions, and ContainerRegistry were added in v20260630preview and do not exist in v20251223preview.
		func(j *coreapi.HCPOpenShiftClusterCustomerProperties, c randfill.Continue) {
			c.FillNoCustom(j)
			j.Ingress = coreapi.CustomerIngressProfile{}
			j.CryptoRestrictions = metadataapi.CryptoRestrictionsNone
			j.Platform.ContainerRegistry = coreapi.ContainerRegistryProfile{}
		},

internal/api/coreapi/types_cluster.go:233

  • The CustomerPlatformProfile visibility comment is now inaccurate: platform.containerRegistry is day-2 mutable (PATCH/update), so the platform struct is no longer entirely "read create".
// CustomerPlatformProfile represents the Azure platform configuration.
// Visibility for the entire struct is "read create".

test/e2e/cluster_container_registry.go:254

  • Redundant assertion: this repeats the exact same nil check twice.

This issue also appears on line 383 of the same file.
internal/azureapi/v20240610preview/hcpopenshiftclusters_methods.go:455

  • The version annotation comment is incorrect: ContainerRegistry was added in v2026_09_01_preview (see the TypeSpec/OpenAPI), not v2026_06_30_preview.
	// ContainerRegistry was added in v2026_06_30_preview.

internal/azureapi/v20240610preview/conversion_fuzz_test.go:45

  • This fuzz override now also clears Platform.ContainerRegistry (line 45), but the header comment for this override doesn't mention that ContainerRegistry doesn't exist in v20240610preview.
			j.Platform.ContainerRegistry = coreapi.ContainerRegistryProfile{}

Comment thread internal/azureapi/v20260630preview/conversion_fuzz_test.go

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 68 out of 73 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • backend/pkg/azure/client/mock_user_assigned_identities_client.go: Generated file
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (3)

backend/pkg/utils/validationutils/container_registry_pull_credentials_permission_validation.go:173

  • PassedValidation currently uses v.InputKey(cluster) as the user-facing message, so the condition Message becomes the (key) string instead of an explanatory success message. If the controller is using Message for staleness detection, one option is to make InputKey itself be the stable success message and use that message for both UserMessage and InternalMessage here.
			return PassedValidation(
				coreapi.ControllerConditionReasonAsExpected,
				v.InputKey(cluster),
				fmt.Sprintf("CAPZ identity has assign/action permission on container registry pull MI %s.", containerRegistryPullMI),
			)

internal/api/coreapi/types_cluster.go:233

  • The comment above CustomerPlatformProfile says the entire struct is "read create", but this PR introduces platform.containerRegistry as updateable/day-2 mutable. Update the comment to avoid misleading future readers about platform mutability.
// Visibility for the entire struct is "read create".

backend/pkg/utils/validationutils/container_registry_pull_credentials_permission_validation.go:57

  • InputKey is currently just the managed identity resource ID, which then gets surfaced as the condition Message on success (via PassedValidation). This makes the user-facing status condition message non-descriptive. Consider making InputKey return a stable, human-readable message (still keyed by the MI) so the stored condition remains actionable while still allowing day-2 change detection.

This issue also appears on line 169 of the same file.

	mi := cluster.CustomerProperties.Platform.ContainerRegistry.PullManagedIdentity
	if mi == nil {
		return ""
	}
	return mi.String()

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 68 out of 73 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • backend/pkg/azure/client/mock_user_assigned_identities_client.go: Generated file
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (3)

test/e2e/cluster_container_registry.go:163

  • ACR registry names must be lowercase and between 5–50 alphanumeric characters. acrName is derived from the resource group name without lowercasing or enforcing the minimum length, which can cause BeginCreate to fail for some generated RG names.
    test/e2e/cluster_container_registry.go:176
  • acrResp.Properties and acrResp.Properties.LoginServer are dereferenced without nil checks. If the ARM response ever omits these fields, this test will panic instead of producing a clear failure.
    internal/api/coreapi/types_cluster.go:234
  • The comment says the entire CustomerPlatformProfile is "read create", but this PR introduces a day-2 mutable field (containerRegistry). Update the comment so it doesn't mislead future changes/reviewers about mutability expectations.
// CustomerPlatformProfile represents the Azure platform configuration.
// Visibility for the entire struct is "read create".
type CustomerPlatformProfile struct {

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 69 out of 74 changed files in this pull request and generated no new comments.

Files not reviewed (2)
  • backend/pkg/azure/client/mock_user_assigned_identities_client.go: Generated file
  • internal/api/coreapi/zz_generated.deepcopy.go: Generated file
Suppressed comments (4)

internal/api/coreapi/types_cluster.go:234

  • The struct-level comment says CustomerPlatformProfile is entirely "read create", but this PR introduces a day-2 mutable field (containerRegistry). This comment is now inaccurate and can mislead future API/validation work.
// CustomerPlatformProfile represents the Azure platform configuration.
// Visibility for the entire struct is "read create".
type CustomerPlatformProfile struct {

internal/api/coreapi/types_cluster.go:249

  • Adding ContainerRegistry to CustomerPlatformProfile introduces a Cosmos-stored field. Per the repo's Cosmos data flow documentation rules, docs/cosmos-data-flow.md should be regenerated/updated when internal/api/types_*.go changes impact persisted fields.
	OperatorsAuthentication OperatorsAuthenticationProfile `json:"operatorsAuthentication,omitempty"`
	// Written by: Frontend PUT/PATCH Cluster
	ContainerRegistry ContainerRegistryProfile `json:"containerRegistry,omitzero"`
}

// ContainerRegistryProfile represents Azure Container Registry pull
// configuration for the cluster.
type ContainerRegistryProfile struct {
	// Written by: Frontend PUT Cluster (Create), Frontend PATCH Cluster (Update)
	PullManagedIdentity *azcorearm.ResourceID `json:"pullManagedIdentity,omitempty"`

backend/pkg/utils/validationutils/cluster_validation.go:34

  • The comment for InputKeyedClusterValidation says the controller stores the key in the condition Message field on success, but the controller always sets Condition.Message from ValidationResult.Outcome.*.UserMessage (via ToCondition). For keyed validations to work, PassedValidation must set UserMessage to the InputKey; consider clarifying this contract in the comment to avoid future keyed validations accidentally causing perpetual re-validation.
// InputKeyedClusterValidation is an optional extension of ClusterValidation
// for validations whose input can change on day-2 updates. The validation
// controller stores the key in the condition's Message field on success and
// re-validates when the key changes.

test/e2e/cluster_container_registry.go:176

  • acrResp.Properties.LoginServer is dereferenced without checking that Properties/LoginServer are non-nil. If the Azure SDK returns a response missing these pointers (transiently or due to a partial failure), the test will panic instead of failing with a clear assertion.

…ity (ARO-24037)

Add properties.platform.containerRegistry.managedIdentity to the v20260901preview
ARM API, allowing customers to configure a user-assigned managed identity
for worker node ACR image pulls — no imagePullSecrets, no per-pod setup.

ARM API:
- ContainerRegistryProfile on PlatformProfile (v20260901preview).
  Day-2 mutable via PATCH: set, change, or clear.
- TypeSpec gated to v2026_09_01_preview. Cross-version preservation
  for older API versions.

Validation and admission:
- OCP >= 4.22 version gate (CAPZ credential provider support).
- Resource type check, managed resource group rejection.
- Per-field platform immutability: ContainerRegistry is mutable;
  other platform fields retain their individual immutability checks.
- Immutability enforcement test verifies every classified platform
  field produces an error when mutated.

Backend:
- ContainerRegistryPullCredentialsPermissionValidation checks CAPZ
  has assign/action on the MI via CheckAccess V2.
- InputKeyedClusterValidation re-validates when MI changes on day-2.
- Dispatch config wiring for day-2 updates and clearing.
- Operation state convergence tracking.

Testing:
- E2E (v20260901preview): create cluster with MI, pull from private
  ACR, day-2 update to different MI, verify pull with new MI.
- Integration: PATCH to add containerRegistry, PATCH null to clear.
- Unit: validation, admission, backend permission validation, OCM
  conversion, dispatch config, immutability enforcement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment on lines +46 to +47
managedIdentityOperatorRoleID = "f1a07417-d97a-45cb-824c-7a7467783830"
acrPullRoleID = "7f951dda-4ed3-4680-a7ca-43fe172d538d"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Just mentioning: The docs team needs to capture these and the entire flow so that users will know what to do in regards to role assignment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

There should already be instructions for it, but Ill confirm 🙏

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Why this change?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Needs to be cleaned up. I had the PR originally for the previous preview release, but that has already been done. Ill clean this one up

return errs
}

func validateContainerRegistryPullCredentials(ctx context.Context, op operation.Operation, fldPath *field.Path, newObj *azcorearm.ResourceID, oldObj *azcorearm.ResourceID, managedResourceGroup string) field.ErrorList {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is this missing SameSubscription check?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

After our discussions, I thought we had landed on no check for the same sub due to that since it is an azure resource, it could be in any sub in the same tenant.

But if we want to just constrain it anyway, I can add that.

}

var clusterVersion semver.Version
switch op.Type {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does the operation type matter? can this be restructure in the way that it's agnostic to that?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, I can do that 👍

@twolff-gh

Copy link
Copy Markdown
Collaborator Author

/test images

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants