Skip to content

CNTRLPLANE-3070: Support KMS on self-managed Azure without affecting ARO HCP#8088

Draft
bryan-cox wants to merge 7 commits intoopenshift:mainfrom
bryan-cox:CNTRLPLANE-3070
Draft

CNTRLPLANE-3070: Support KMS on self-managed Azure without affecting ARO HCP#8088
bryan-cox wants to merge 7 commits intoopenshift:mainfrom
bryan-cox:CNTRLPLANE-3070

Conversation

@bryan-cox
Copy link
Copy Markdown
Member

@bryan-cox bryan-cox commented Mar 26, 2026

What this PR does / why we need it:

Enables Azure Key Vault KMS encryption (etcd encryption at rest) for self-managed Azure HyperShift clusters using workload identity federation, without breaking the existing ARO HCP (managed Azure) KMS path that uses managed identities with CSI secret store volumes.

Key Changes

API: Added SelfManagedKMS field (type SelfManagedAzureKMS) to AzureKMSSpec with a ClientID for the workload identity that has Key Vault Crypto User role on the Key Vault. CEL validation rules enforce mutual exclusivity between kms (managed) and selfManagedKMS (self-managed), and immutability once set.

Control Plane Operator:

  • Azure KMS provider now supports both ARO HCP and self-managed paths
  • ARO HCP: CSI secret store volume for managed identity (unchanged)
  • Self-managed: token-minter sidecar + emptyDir cloud token volume for workload identity federation
  • KMS cloud config secret sets federated identity fields (UseWorkloadIdentityExtension) for self-managed

HyperShift Operator:

  • KMS config secret reconciliation branches on managed vs self-managed, returning an error when neither credential is configured
  • Credential reconciliation for self-managed Azure workload identities

CLI:

  • create cluster azure: Only sets ManagedIdentity KMS creds for managed Azure; self-managed uses AzureKMSSpec.SelfManagedKMS
  • create iam azure: Creates KMS workload identity with federated credential for kms-provider service account

E2E: Updated TestCreateClusterCustomConfig to handle self-managed Azure KMS assertions. Added envtest coverage for CEL validation rules (mutual exclusivity, immutability).

Documentation: Added KMS encryption section to self-managed Azure cluster guide with Key Vault setup and workload identity federation instructions.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3070

Special notes for your reviewer:

The self-managed Azure KMS authentication pattern follows the same approach used by Cloud Controller Manager (CCM) and Azure CSI storage drivers, which already support self-managed Azure with workload identity federation.

The token-minter sidecar mints OIDC tokens for the kms-provider service account in kube-system namespace, matching the pattern used by AWS KMS.

kms and selfManagedKMS are mutually exclusive and immutable once set — switching between managed and self-managed KMS auth after cluster creation is not supported.

Checklist:

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

@openshift-ci-robot
Copy link
Copy Markdown

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-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label Mar 26, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Mar 26, 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-robot
Copy link
Copy Markdown

openshift-ci-robot commented Mar 26, 2026

@bryan-cox: This pull request references CNTRLPLANE-3070 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Enables Azure Key Vault KMS encryption (etcd encryption at rest) for self-managed Azure HyperShift clusters using workload identity federation, without breaking the existing ARO HCP (managed Azure) KMS path that uses managed identities with CSI secret store volumes.

Key Changes

API: Added optional KMS field to AzureWorkloadIdentities for specifying a workload identity client ID that has Key Vault Crypto User role on the Key Vault.

Control Plane Operator:

  • Azure KMS provider now supports both ARO HCP and self-managed paths
  • ARO HCP: CSI secret store volume for managed identity (unchanged)
  • Self-managed: token-minter sidecar + emptyDir cloud token volume for workload identity federation
  • KMS cloud config secret sets federated identity fields (UseFederatedWorkloadIdentityExtension, AADClientID, AADFederatedTokenFile) for self-managed

HyperShift Operator:

  • KMS config secret reconciliation branches on managed vs self-managed
  • HCP controller validates KMS config for self-managed (sets condition True with runtime validation note since CPO lacks KMS identity credentials)

CLI:

  • create cluster azure: Only sets ManagedIdentity KMS creds for managed Azure; self-managed uses WorkloadIdentities.KMS
  • create iam azure: Creates KMS workload identity with federated credential for kms-provider service account

E2E: Updated TestCreateClusterCustomConfig to handle self-managed Azure KMS assertions.

Documentation: Added KMS encryption section to self-managed Azure cluster guide with Key Vault setup instructions.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3070

Special notes for your reviewer:

The self-managed Azure KMS authentication pattern follows the same approach used by Cloud Controller Manager (CCM) and Azure CSI storage drivers, which already support self-managed Azure with workload identity federation.

The token-minter sidecar mints OIDC tokens for the kms-provider service account in kube-system namespace, matching the pattern used by AWS KMS.

Checklist:

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

🤖 Generated with Claude Code via /jira:solve [CNTRLPLANE-3070](https://redhat.atlassian.net/browse/CNTRLPLANE-3070)

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 openshift-eng/jira-lifecycle-plugin repository.

@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 Mar 26, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 26, 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

This pull request adds support for self-managed Azure KMS encryption at rest and wiring for KMS workload identities. Changes include making AzureKMSSpec.KMS optional and adding a mutually-exclusive SelfManagedKMS; adding --enable-kms to IAM creation and plumbing kmsClientID through IAM/infra outputs; conditionally including a kms workload identity; control-plane/operator changes to detect self-managed Azure and render token-minter/cloud-token or managed secret-store CSI volumes accordingly; centralizing the cloud token mount path with CloudTokenMountPath; and extensive tests and docs for both managed and self-managed Azure KMS flows.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant CLI as CLI/IAM Cmd
    participant WI as Workload Identity Mgr
    participant Infra as Infra Manager
    participant Secret as K8s Secrets

    User->>CLI: hypershift create iam azure --enable-kms
    CLI->>WI: CreateWorkloadIdentitiesFromIAMOptions(enableKMS=true)
    WI->>WI: GetWorkloadIdentityDefinitions(opts={IncludeKMS:true})
    WI->>Infra: Create Azure identities (includes kms)
    Infra->>Infra: Assign roles (if applicable)
    Infra-->>WI: Return identities + kmsClientID
    WI-->>CLI: Return IAM output with kmsClientID
    CLI->>Secret: Write workload-identities.json (includes kmsClientID)
Loading
sequenceDiagram
    participant User
    participant Cluster as Cluster Create
    participant Infra as Infra Extract
    participant CPO as Control Plane Operator
    participant KMS as KMS Provider

    User->>Infra: Load workload-identities.json
    Infra->>Infra: Extract kmsClientID from JSON
    Infra-->>Cluster: Pass infra output (kmsClientID)
    User->>Cluster: hypershift create cluster azure --encryption-key-id=<key>
    Cluster->>Cluster: Build AzureKMSSpec (SelfManagedKMS if kmsClientID present else KMS)
    Cluster-->>CPO: Apply SecretEncryption with AzureKMSSpec
    CPO->>CPO: Detect self-managed vs managed
    alt Self-Managed
        CPO->>KMS: Add token-minter container + cloud-token volume
        CPO->>KMS: Set AZURE_CLIENT_ID/AZURE_TENANT_ID and federated token file
        KMS->>KMS: Mint federated token at runtime and authenticate to Key Vault
    else Managed
        CPO->>KMS: Mount credentials secret via Secret Store CSI
        KMS->>KMS: Use credentials secret to authenticate to Key Vault
    end
Loading

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Stable And Deterministic Test Names ❌ Error Found violation: TestGenerateKMSPodConfig_LivenessProbe uses fmt.Sprintf to construct test name at line 536 of azure_test.go. Test names must be static, never dynamically constructed. Replace fmt.Sprintf("When %s is created...", tc.name) with static test names for each case.
Docstring Coverage ⚠️ Warning Docstring coverage is 17.65% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (10 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main objective: adding KMS support for self-managed Azure clusters while preserving ARO HCP functionality.
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.
Test Structure And Quality ✅ Passed Custom check reviews Ginkgo test code (Describe/Context/It blocks), but this PR contains only traditional Go testing.T tests. Check is not applicable.
Microshift Test Compatibility ✅ Passed No new Ginkgo e2e tests were added. PR contains only standard Go unit tests (testing.T) and modifications to existing standard Go e2e tests.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No new Ginkgo e2e tests were added in this PR. All new tests are unit tests using standard Go testing package, not Ginkgo patterns (It, Describe, Context, When).
Topology-Aware Scheduling Compatibility ✅ Passed PR adds Azure KMS encryption with sidecars and token-minter. No topology issues: no required anti-affinity, spread constraints, control-plane nodeSelectors, or problematic tolerations detected.
Ote Binary Stdout Contract ✅ Passed All modified test files use standard Go testing.T pattern. No process-level stdout writes detected. No init(), TestMain(), BeforeSuite(), or OTE suite patterns found.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed No new Ginkgo e2e tests (Describe/Context/It/When) were added in this PR. All test modifications are to standard Go testing.T tests. Custom check is not applicable.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented Mar 26, 2026

@bryan-cox: This pull request references CNTRLPLANE-3070 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "4.22.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Enables Azure Key Vault KMS encryption (etcd encryption at rest) for self-managed Azure HyperShift clusters using workload identity federation, without breaking the existing ARO HCP (managed Azure) KMS path that uses managed identities with CSI secret store volumes.

Key Changes

API: Added optional KMS field to AzureWorkloadIdentities for specifying a workload identity client ID that has Key Vault Crypto User role on the Key Vault.

Control Plane Operator:

  • Azure KMS provider now supports both ARO HCP and self-managed paths
  • ARO HCP: CSI secret store volume for managed identity (unchanged)
  • Self-managed: token-minter sidecar + emptyDir cloud token volume for workload identity federation
  • KMS cloud config secret sets federated identity fields (UseFederatedWorkloadIdentityExtension, AADClientID, AADFederatedTokenFile) for self-managed

HyperShift Operator:

  • KMS config secret reconciliation branches on managed vs self-managed
  • HCP controller validates KMS config for self-managed (sets condition True with runtime validation note since CPO lacks KMS identity credentials)

CLI:

  • create cluster azure: Only sets ManagedIdentity KMS creds for managed Azure; self-managed uses WorkloadIdentities.KMS
  • create iam azure: Creates KMS workload identity with federated credential for kms-provider service account

E2E: Updated TestCreateClusterCustomConfig to handle self-managed Azure KMS assertions.

Documentation: Added KMS encryption section to self-managed Azure cluster guide with Key Vault setup instructions.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3070

Special notes for your reviewer:

The self-managed Azure KMS authentication pattern follows the same approach used by Cloud Controller Manager (CCM) and Azure CSI storage drivers, which already support self-managed Azure with workload identity federation.

The token-minter sidecar mints OIDC tokens for the kms-provider service account in kube-system namespace, matching the pattern used by AWS KMS.

Checklist:

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

🤖 Generated with Claude Code via /jira:solve [CNTRLPLANE-3070](https://redhat.atlassian.net/browse/CNTRLPLANE-3070)

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 openshift-eng/jira-lifecycle-plugin repository.

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Mar 26, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: bryan-cox
Once this PR has been reviewed and has the lgtm label, please assign enxebre 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

@openshift-ci openshift-ci Bot added 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 area/testing Indicates the PR includes changes for e2e testing and removed do-not-merge/needs-area labels Mar 26, 2026
@bryan-cox bryan-cox force-pushed the CNTRLPLANE-3070 branch 2 times, most recently from 1890fdc to a3d1f65 Compare March 27, 2026 15:35
@openshift-ci openshift-ci Bot added needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. area/platform/aws PR/issue for AWS (AWSPlatform) platform labels Mar 27, 2026
@bryan-cox bryan-cox force-pushed the CNTRLPLANE-3070 branch 2 times, most recently from 21007e3 to 2d16bd3 Compare March 27, 2026 16:41
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Mar 27, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 31, 2026

Codecov Report

❌ Patch coverage is 61.86047% with 82 lines in your changes missing coverage. Please review.
✅ Project coverage is 37.68%. Comparing base (7ac2953) to head (781113e).

Files with missing lines Patch % Lines
cmd/cluster/azure/create.go 0.00% 23 Missing ⚠️
cmd/infra/azure/identities.go 44.11% 19 Missing ⚠️
...ostedcontrolplane/hostedcontrolplane_controller.go 0.00% 15 Missing ⚠️
cmd/infra/azure/create.go 0.00% 8 Missing ⚠️
...rator/controllers/hostedcontrolplane/v2/kas/kms.go 57.14% 5 Missing and 1 partial ⚠️
...ers/hostedcluster/internal/platform/azure/azure.go 68.75% 5 Missing ⚠️
cmd/infra/azure/create_iam.go 0.00% 4 Missing ⚠️
...or/controllers/hostedcontrolplane/manifests/kas.go 0.00% 1 Missing ⚠️
...ontrollers/hostedcontrolplane/v2/kas/deployment.go 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8088      +/-   ##
==========================================
+ Coverage   37.44%   37.68%   +0.24%     
==========================================
  Files         751      751              
  Lines       91969    92120     +151     
==========================================
+ Hits        34435    34719     +284     
+ Misses      54894    54757     -137     
- Partials     2640     2644       +4     
Files with missing lines Coverage Δ
...r/controllers/hostedcontrolplane/v2/kas/kms/aws.go 92.26% <100.00%> (ø)
...controllers/hostedcontrolplane/v2/kas/kms/azure.go 97.56% <100.00%> (+90.37%) ⬆️
support/azureutil/azureutil.go 44.23% <100.00%> (+1.37%) ⬆️
...t/controlplane-component/token-minter-container.go 89.09% <100.00%> (ø)
...or/controllers/hostedcontrolplane/manifests/kas.go 0.00% <0.00%> (ø)
...ontrollers/hostedcontrolplane/v2/kas/deployment.go 26.78% <0.00%> (ø)
cmd/infra/azure/create_iam.go 16.66% <0.00%> (-0.23%) ⬇️
...ers/hostedcluster/internal/platform/azure/azure.go 29.86% <68.75%> (+7.70%) ⬆️
...rator/controllers/hostedcontrolplane/v2/kas/kms.go 33.33% <57.14%> (+9.52%) ⬆️
cmd/infra/azure/create.go 8.08% <0.00%> (-0.25%) ⬇️
... and 3 more
Flag Coverage Δ
cmd-support 32.65% <29.87%> (+0.01%) ⬆️
cpo-hostedcontrolplane 37.69% <81.14%> (+1.21%) ⬆️
cpo-other 37.73% <ø> (ø)
hypershift-operator 48.05% <68.75%> (+0.11%) ⬆️
other 27.77% <ø> (ø)

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.

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Apr 1, 2026
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented Apr 17, 2026

@bryan-cox: all tests passed!

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.

@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 5, 2026
@openshift-ci-robot
Copy link
Copy Markdown

openshift-ci-robot commented May 5, 2026

@bryan-cox: This pull request references CNTRLPLANE-3070 which is a valid jira issue.

Warning: The referenced jira issue has an invalid target version for the target branch this PR targets: expected the story to target the "5.0.0" version, but no target version was set.

Details

In response to this:

What this PR does / why we need it:

Enables Azure Key Vault KMS encryption (etcd encryption at rest) for self-managed Azure HyperShift clusters using workload identity federation, without breaking the existing ARO HCP (managed Azure) KMS path that uses managed identities with CSI secret store volumes.

Key Changes

API: Added optional KMS field to AzureWorkloadIdentities for specifying a workload identity client ID that has Key Vault Crypto User role on the Key Vault.

Control Plane Operator:

  • Azure KMS provider now supports both ARO HCP and self-managed paths
  • ARO HCP: CSI secret store volume for managed identity (unchanged)
  • Self-managed: token-minter sidecar + emptyDir cloud token volume for workload identity federation
  • KMS cloud config secret sets federated identity fields (UseFederatedWorkloadIdentityExtension, AADClientID, AADFederatedTokenFile) for self-managed

HyperShift Operator:

  • KMS config secret reconciliation branches on managed vs self-managed
  • HCP controller validates KMS config for self-managed (sets condition True with runtime validation note since CPO lacks KMS identity credentials)

CLI:

  • create cluster azure: Only sets ManagedIdentity KMS creds for managed Azure; self-managed uses WorkloadIdentities.KMS
  • create iam azure: Creates KMS workload identity with federated credential for kms-provider service account

E2E: Updated TestCreateClusterCustomConfig to handle self-managed Azure KMS assertions.

Documentation: Added KMS encryption section to self-managed Azure cluster guide with Key Vault setup instructions.

Which issue(s) this PR fixes:

Fixes CNTRLPLANE-3070

Special notes for your reviewer:

The self-managed Azure KMS authentication pattern follows the same approach used by Cloud Controller Manager (CCM) and Azure CSI storage drivers, which already support self-managed Azure with workload identity federation.

The token-minter sidecar mints OIDC tokens for the kms-provider service account in kube-system namespace, matching the pattern used by AWS KMS.

Checklist:

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

🤖 Generated with Claude Code via /jira:solve [CNTRLPLANE-3070](https://redhat.atlassian.net/browse/CNTRLPLANE-3070)

Summary by CodeRabbit

Release Notes

  • New Features

  • Added support for self-managed Azure Key Vault encryption at rest (etcd encryption)

  • Introduced --enable-kms CLI flag for Azure workload identity creation and KMS configuration

  • Improvements

  • Enhanced KMS configuration validation with mutual exclusivity checks between managed and self-managed modes

  • Documentation

  • Added comprehensive guide for enabling Azure Key Vault encryption at rest

  • Updated Azure IAM setup documentation with KMS identity creation workflow and RBAC role assignment procedures

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 openshift-eng/jira-lifecycle-plugin repository.

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
test/e2e/create_cluster_test.go (1)

326-326: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use t.Fatalf instead of t.Fatal for formatted output.

t.Fatal does not support format verbs like %w. The error will be printed literally as %w instead of being formatted.

🐛 Proposed fix
-		t.Fatal("failed to get azure encryption key info: %w", err)
+		t.Fatalf("failed to get azure encryption key info: %v", err)
🤖 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 `@test/e2e/create_cluster_test.go` at line 326, Replace the t.Fatal call that
uses a format verb with t.Fatalf and use a proper verb for printing the error
(e.g., change t.Fatal("failed to get azure encryption key info: %w", err) to
t.Fatalf("failed to get azure encryption key info: %v", err)); locate the
t.Fatal in the test where the azure encryption key info is fetched and update
the call to t.Fatalf with %v (or %s) to ensure the error is formatted correctly.
🧹 Nitpick comments (2)
control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go (1)

167-191: ⚡ Quick win

Add a v1 existing-config test case for Azure parity.

At Lines 167-191, Azure coverage only asserts “no existing config” and “existing v2”. Adding an “existing v1 config preserves v1” case (as done in the AWS test) would close a regression gap in version-selection behavior.

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go`
around lines 167 - 191, Add a new testCase to the testCases slice that mirrors
the AWS "preserve v1" scenario: create an existing v1.EncryptionConfiguration
(TypeMeta APIVersion "apiserver.config.k8s.io/v1") whose Resources use
config.KMSEncryptedObjects() and a Provider with KMS APIVersion "v1", and set
expectedConfig to generateExpectedAzureEncryptionConfig(kmsAPIVersionV1) so the
test asserts the controller preserves v1 when an existing v1 KMS config is
present (follow the pattern used by the existing v2 case and the AWS test).
control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go (1)

194-194: 💤 Low value

t.Fatal captures outer test's t, not the subtest's.

The check function closure captures t from TestGenerateKMSPodConfig_SelfManaged, but when executed inside t.Run, the subtest shadows t. Using the outer t means failures may not be correctly associated with the subtest in test output.

Consider using Gomega's failure mechanism instead:

♻️ Suggested fix
-				t.Fatal("active KMS container not found")
+				g.Fail("active KMS container not found")

Apply the same fix to line 213 and line 294.

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go`
at line 194, In TestGenerateKMSPodConfig_SelfManaged the closure uses t.Fatal
which captures the outer test's t and misattributes failures; replace these
t.Fatal calls (e.g., the "active KMS container not found" check inside the check
closure and the similar checks later) with Gomega assertions: create a Gomega
instance with g := NewWithT(t) in the subtest (or inside the closure) and use
g.Expect(found).To(BeTrue(), "active KMS container not found") (and similar
messages for the other two occurrences). Apply the same replacement for the
other two t.Fatal usages referenced in the comment so failures are reported on
the subtest.
🤖 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/hostedcluster_types.go`:
- Line 530: The CEL XValidation currently references a non-existent field
workloadIdentities.kms; update the rule to check the correct KMS path for
WorkloadIdentities by replacing
has(self.platform.azure.azureAuthenticationConfig.workloadIdentities.kms) with
has(self.secretEncryption.kms.azure.selfManagedKMS). Keep the rest of the rule
logic the same so the full expression uses
self.secretEncryption.kms.azure.selfManagedKMS and the relevant symbols
AzureWorkloadIdentities, secretEncryption.kms.azure.selfManagedKMS, and
platform.azure.azureAuthenticationConfig.workloadIdentities to validate that
WorkloadIdentities auth requires the self-managed Azure KMS config.

In `@cmd/cluster/azure/create.go`:
- Around line 394-405: The current branch uses only o.infra.KMSClientID to
decide between SelfManagedKMS and ManagedIdentity, which can silently
mis-classify workload-identity clusters; change the logic to explicitly detect a
workload-identity Azure cluster (check the infra workload-identity flag on the
Azure platform spec, e.g. a field like o.infra.Platform.Azure.WorkloadIdentity
or o.infra.WorkloadIdentity) and if the cluster is workload-identity but
o.infra.KMSClientID is empty return an error rather than creating a
ManagedIdentity; otherwise keep the existing behavior (set
azureKMSSpec.SelfManagedKMS when KMSClientID is present, set azureKMSSpec.KMS
with o.KMSUserAssignedCredsSecretName and ObjectEncoding when not
workload-identity).

In `@cmd/infra/azure/identities.go`:
- Around line 371-373: GetWorkloadIdentityDefinitions is being called with a
WorkloadIdentityOptions that omits Topology, so controlPlaneOperator identities
are always included; update the call to pass the requested topology from the
incoming opts into WorkloadIdentityOptions.Topology (e.g., set Topology:
opts.Topology) alongside IncludeKMS so GetWorkloadIdentityDefinitions receives
the real cluster topology and selects identities correctly.

In
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go`:
- Around line 250-255: The helper generateExpectedAzureEncryptionConfig
currently discards the error from util.HashStruct; change its signature to
accept testing.TB (e.g., func generateExpectedAzureEncryptionConfig(t
testing.TB, apiVersion string) *v1.EncryptionConfiguration), call t.Helper(),
call util.HashStruct(...) and check the returned error, and call t.Fatalf(...)
(or t.Fatalf with the error) if hashing fails so tests fail fast; update every
call site in this test to pass the test variable (t) and add testing to imports
as needed.

In `@docs/content/how-to/azure/azure-workload-identity-setup.md`:
- Around line 67-76: The KMS example command for "hypershift create iam azure"
is missing the --location flag; update the snippet that calls hypershift create
iam azure (the KMS example that outputs workload-identities.json and uses
--enable-kms) to include --location $LOCATION (or the appropriate location
variable) so it matches the non-KMS example and avoids relying on defaults.

In `@docs/content/how-to/azure/create-self-managed-azure-cluster.md`:
- Around line 281-290: The Azure IAM creation example is missing the --location
flag; update the hypershift command invocation (the multi-line shell command
starting with "hypershift create iam azure") to include the --location
"$LOCATION" argument so it matches other examples and ensures location is
explicitly specified when creating the IAM resources.

---

Outside diff comments:
In `@test/e2e/create_cluster_test.go`:
- Line 326: Replace the t.Fatal call that uses a format verb with t.Fatalf and
use a proper verb for printing the error (e.g., change t.Fatal("failed to get
azure encryption key info: %w", err) to t.Fatalf("failed to get azure encryption
key info: %v", err)); locate the t.Fatal in the test where the azure encryption
key info is fetched and update the call to t.Fatalf with %v (or %s) to ensure
the error is formatted correctly.

---

Nitpick comments:
In
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go`:
- Line 194: In TestGenerateKMSPodConfig_SelfManaged the closure uses t.Fatal
which captures the outer test's t and misattributes failures; replace these
t.Fatal calls (e.g., the "active KMS container not found" check inside the check
closure and the similar checks later) with Gomega assertions: create a Gomega
instance with g := NewWithT(t) in the subtest (or inside the closure) and use
g.Expect(found).To(BeTrue(), "active KMS container not found") (and similar
messages for the other two occurrences). Apply the same replacement for the
other two t.Fatal usages referenced in the comment so failures are reported on
the subtest.

In
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go`:
- Around line 167-191: Add a new testCase to the testCases slice that mirrors
the AWS "preserve v1" scenario: create an existing v1.EncryptionConfiguration
(TypeMeta APIVersion "apiserver.config.k8s.io/v1") whose Resources use
config.KMSEncryptedObjects() and a Provider with KMS APIVersion "v1", and set
expectedConfig to generateExpectedAzureEncryptionConfig(kmsAPIVersionV1) so the
test asserts the controller preserves v1 when an existing v1 KMS config is
present (follow the pattern used by the existing v2 case and the AWS test).
🪄 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: 9a1a6096-d47a-46e4-91ce-fc49a46dbb2a

📥 Commits

Reviewing files that changed from the base of the PR and between 6b39d47 and 67b7e23.

⛔ 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/azurekmsspec.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/selfmanagedazurekms.go is excluded by !client/**
  • client/applyconfiguration/utils.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/featuregated.hostedclusters.kms.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/testdata/kube-apiserver/AROSwift/zz_fixture_TestControlPlaneComponents_managed_azure_kms_secretproviderclass.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/hostedcluster_types.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 (28)
  • api/hypershift/v1beta1/azure.go
  • api/hypershift/v1beta1/hostedcluster_types.go
  • cmd/cluster/azure/create.go
  • cmd/infra/azure/create.go
  • cmd/infra/azure/create_iam.go
  • cmd/infra/azure/identities.go
  • cmd/infra/azure/identities_test.go
  • cmd/infra/azure/types.go
  • cmd/util/azure_flag_descriptions.go
  • control-plane-operator/controllers/hostedcontrolplane/cloud/azure/providerconfig.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go
  • control-plane-operator/controllers/hostedcontrolplane/manifests/kas.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/aws.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go
  • docs/content/how-to/azure/azure-workload-identity-setup.md
  • docs/content/how-to/azure/create-self-managed-azure-cluster.md
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go
  • support/azureutil/azureutil.go
  • support/config/constants.go
  • support/controlplane-component/token-minter-container.go
  • support/controlplane-component/token-minter-container_test.go
  • test/e2e/create_cluster_test.go

Comment thread api/hypershift/v1beta1/hostedcluster_types.go Outdated
Comment thread cmd/cluster/azure/create.go Outdated
Comment thread cmd/infra/azure/identities.go
Comment thread docs/content/how-to/azure/azure-workload-identity-setup.md
Comment thread docs/content/how-to/azure/create-self-managed-azure-cluster.md
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

🧹 Nitpick comments (1)
control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go (1)

128-128: ⚡ Quick win

Use the subtest handle inside the table callbacks.

These check closures capture the parent t, so t.Fatal(...) here reports against the outer test instead of the current t.Run(...) case and gets fragile if these subtests are ever parallelized. Pass *testing.T into check, or stick to Gomega expectations inside the callback.

Also applies to: 202-203, 221-222, 255-255, 302-302

🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go`
at line 128, The table-driven tests define a closure type check func(g Gomega,
podConfig *KMSPodConfig) that captures the outer testing.T, so calls like
t.Fatal() inside those closures point at the parent test rather than the
subtest; change the signature to accept the subtest handle (e.g., check func(t
*testing.T, g Gomega, podConfig *KMSPodConfig)) and update each table callback
and its invocations to pass the local *testing.T from t.Run(...); alternatively,
remove direct t.Fatal usage from the closures and use only Gomega expectations
(g.Expect(...).To(...)) so failures are reported against the current
subtest—apply this change consistently to the check declarations and all places
that define or call check (the check closure and its table entries).
🤖 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go`:
- Around line 291-298: The test only asserts AZURE_CLIENT_ID is absent in the
managed KMS container; update the check inside the check func that iterates
KMSPodConfig.Containers (finding container Name "azure-kms-provider-active") to
assert that AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_FEDERATED_TOKEN_FILE are
all not present (use Gomega.Expect on e.Name for each of these three names) so
managed mode cannot accidentally leave any workload-identity env var.

---

Nitpick comments:
In
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go`:
- Line 128: The table-driven tests define a closure type check func(g Gomega,
podConfig *KMSPodConfig) that captures the outer testing.T, so calls like
t.Fatal() inside those closures point at the parent test rather than the
subtest; change the signature to accept the subtest handle (e.g., check func(t
*testing.T, g Gomega, podConfig *KMSPodConfig)) and update each table callback
and its invocations to pass the local *testing.T from t.Run(...); alternatively,
remove direct t.Fatal usage from the closures and use only Gomega expectations
(g.Expect(...).To(...)) so failures are reported against the current
subtest—apply this change consistently to the check declarations and all places
that define or call check (the check closure and its table entries).
🪄 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: a63c2d2c-0fb9-4dad-b3af-c06cdb5363b4

📥 Commits

Reviewing files that changed from the base of the PR and between 67b7e23 and c9809e2.

📒 Files selected for processing (1)
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go

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 (1)

849-857: 💤 Low value

Inconsistent JSON tag for required field.

ClientID is marked +required but uses json:"clientID,omitempty", which allows the field to be omitted during JSON marshaling when empty. For required fields, the JSON tag should typically not include omitempty to ensure the field is always present in serialized output.

📝 Proposed fix
 // SelfManagedAzureKMS contains the identity used to authenticate with Azure KMS on self-managed clusters.
 type SelfManagedAzureKMS struct {
 	// clientID is the client ID of a federated managed identity used for Azure Key Vault KMS encryption
 	// in self-managed Azure clusters. This identity must have "Key Vault Crypto User" role on the
 	// Key Vault containing the encryption key.
 	//
 	// +required
-	ClientID AzureClientID `json:"clientID,omitempty"`
+	ClientID AzureClientID `json:"clientID"`
 }
🤖 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 849 - 857, The ClientID field
on SelfManagedAzureKMS is declared required but its struct tag uses
json:"clientID,omitempty" which allows omission; update the json tag for
ClientID (type AzureClientID) to remove "omitempty" so it is serialized as
json:"clientID" to reflect the +required annotation and ensure the field is
always present in JSON output.
hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go (1)

487-495: 💤 Low value

Test case may represent invalid API state.

This test case uses baseHC() which has neither kms nor selfManagedKMS set in SecretEncryption.KMS.Azure. However, the API's XValidation rule at line 814 in azure.go requires "one of kms or selfManagedKMS must be set".

This test may be validating defensive behavior in reconcileKMSConfigSecret, but it's worth confirming this scenario is intentional and documenting it in the test name/comment (e.g., "When self-managed Azure without KMS identity configured (edge case)").

🤖 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/hostedcluster/internal/platform/azure/azure_test.go`
around lines 487 - 495, The test uses baseHC() which violates the XValidation in
azure.go (one of kms or selfManagedKMS must be set); either make the test input
valid by updating the HostCluster produced by baseHC() to set
SecretEncryption.KMS.Azure.selfManagedKMS or kms appropriately (so
reconcileKMSConfigSecret sees a valid KMS config), or explicitly mark the test
as an intentional invalid/edge-case by renaming the test ("...edge case" / add
comment) and keeping the existing input — reference baseHC(),
SecretEncryption.KMS.Azure, selfManagedKMS/kms, and reconcileKMSConfigSecret
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 849-857: The ClientID field on SelfManagedAzureKMS is declared
required but its struct tag uses json:"clientID,omitempty" which allows
omission; update the json tag for ClientID (type AzureClientID) to remove
"omitempty" so it is serialized as json:"clientID" to reflect the +required
annotation and ensure the field is always present in JSON output.

In
`@hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go`:
- Around line 487-495: The test uses baseHC() which violates the XValidation in
azure.go (one of kms or selfManagedKMS must be set); either make the test input
valid by updating the HostCluster produced by baseHC() to set
SecretEncryption.KMS.Azure.selfManagedKMS or kms appropriately (so
reconcileKMSConfigSecret sees a valid KMS config), or explicitly mark the test
as an intentional invalid/edge-case by renaming the test ("...edge case" / add
comment) and keeping the existing input — reference baseHC(),
SecretEncryption.KMS.Azure, selfManagedKMS/kms, and reconcileKMSConfigSecret
when making the change.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Enterprise

Run ID: 79106841-200b-43fc-99c6-3ebe3ad8d7d6

📥 Commits

Reviewing files that changed from the base of the PR and between c9809e2 and 559fe15.

⛔ 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/azurekmsspec.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/selfmanagedazurekms.go is excluded by !client/**
  • client/applyconfiguration/utils.go is excluded by !client/**
  • cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/featuregated.hostedclusters.kms.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/testdata/kube-apiserver/AROSwift/zz_fixture_TestControlPlaneComponents_managed_azure_kms_secretproviderclass.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/hostedcluster_types.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 (28)
  • api/hypershift/v1beta1/azure.go
  • api/hypershift/v1beta1/hostedcluster_types.go
  • cmd/cluster/azure/create.go
  • cmd/infra/azure/create.go
  • cmd/infra/azure/create_iam.go
  • cmd/infra/azure/identities.go
  • cmd/infra/azure/identities_test.go
  • cmd/infra/azure/types.go
  • cmd/util/azure_flag_descriptions.go
  • control-plane-operator/controllers/hostedcontrolplane/cloud/azure/providerconfig.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go
  • control-plane-operator/controllers/hostedcontrolplane/manifests/kas.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/aws.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go
  • docs/content/how-to/azure/azure-workload-identity-setup.md
  • docs/content/how-to/azure/create-self-managed-azure-cluster.md
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go
  • support/azureutil/azureutil.go
  • support/config/constants.go
  • support/controlplane-component/token-minter-container.go
  • support/controlplane-component/token-minter-container_test.go
  • test/e2e/create_cluster_test.go
✅ Files skipped from review due to trivial changes (10)
  • cmd/util/azure_flag_descriptions.go
  • control-plane-operator/controllers/hostedcontrolplane/cloud/azure/providerconfig.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go
  • support/controlplane-component/token-minter-container.go
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms.go
  • cmd/infra/azure/create_iam.go
  • cmd/cluster/azure/create.go
  • cmd/infra/azure/identities.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • cmd/infra/azure/create.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/aws.go
  • support/config/constants.go
  • support/azureutil/azureutil.go
  • cmd/infra/azure/types.go
  • api/hypershift/v1beta1/hostedcluster_types.go
  • control-plane-operator/controllers/hostedcontrolplane/manifests/kas.go
  • test/e2e/create_cluster_test.go
  • cmd/infra/azure/identities_test.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go

@openshift-ci openshift-ci Bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 5, 2026
@openshift-ci openshift-ci Bot removed the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label May 5, 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
`@control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go`:
- Around line 326-351: The test "When self-managed backup key is specified..."
is missing an assertion that the self-managed backup container includes the
workload identity env var; update the test that creates provider via
NewAzureKMSProvider(..., AzureKMSProviderOptions{IsSelfManaged: true, ...}) and
calls provider.GenerateKMSPodConfig() to also assert that the container named
"azure-kms-provider-backup" has an env var "AZURE_FEDERATED_TOKEN_FILE" (e.g.,
add g.Expect(envMap).To(HaveKey("AZURE_FEDERATED_TOKEN_FILE")) or equivalent)
alongside the existing AZURE_CLIENT_ID and AZURE_TENANT_ID checks.
🪄 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: 6f0668f7-58fc-46fd-b431-e87da9dc6e2a

📥 Commits

Reviewing files that changed from the base of the PR and between 559fe15 and 98156ae.

⛔ Files ignored due to path filters (18)
  • 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/**
  • cmd/install/assets/crds/hypershift-operator/tests/hostedclusters.hypershift.openshift.io/stable.hostedclusters.kms.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
  • docs/content/reference/aggregated-docs.md is excluded by !docs/content/reference/aggregated-docs.md
📒 Files selected for processing (6)
  • api/hypershift/v1beta1/azure.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go
  • docs/content/how-to/azure/azure-workload-identity-setup.md
  • docs/content/how-to/azure/create-self-managed-azure-cluster.md
  • test/e2e/create_cluster_test.go
✅ Files skipped from review due to trivial changes (2)
  • test/e2e/create_cluster_test.go
  • api/hypershift/v1beta1/azure.go

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: 3

🤖 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 `@control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go`:
- Around line 78-83: The constructor NewAzureKMSProvider currently validates
kmsSpec and client/tenant IDs for opts.IsSelfManaged but does not check
opts.TokenMinterImage; add a validation that when opts.IsSelfManaged is true
then opts.TokenMinterImage must be non-empty and return a clear error (e.g.,
"tokenMinterImage is required for self-managed Azure KMS") so the provider fails
fast instead of rendering a pod with an invalid sidecar image; update the
validation block that checks opts.IsSelfManaged (referencing
NewAzureKMSProvider, opts.IsSelfManaged, and opts.TokenMinterImage) to perform
this check and return an error when missing.

In `@docs/content/how-to/azure/create-self-managed-azure-cluster.md`:
- Around line 279-290: The snippet uses --infra-id "$INFRA_ID" but INFRA_ID is
never defined; add a preceding step that defines/exports INFRA_ID (the cluster
infra identifier used by Hypershift) before the hypershift create iam azure
command so copy-paste works—e.g., insert a short instruction to set INFRA_ID to
your cluster's infra ID and reference that variable before running hypershift
create iam azure (the command shown) so the generated ./workload-identities.json
and subsequent KMS identity lookup are accurate.

In
`@hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go`:
- Around line 488-494: Remove the table-driven test case whose name is "When
self-managed Azure without workload identities it should not set identity
fields" (the entry that sets hc: baseHC() and a validate func) because it
constructs an AzureKMSSpec with neither kms nor selfManagedKMS which is now
rejected by the API; delete that case from the test table and replace it with a
negative test that builds the same AzureKMSSpec and asserts validation/creation
fails (i.e., expect an error) or simply remove the entry entirely so tests only
exercise valid API shapes; reference AzureKMSSpec, baseHC(), and the validate
closure to locate the code to change.
🪄 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: a3872fcd-2af6-4cdb-8fb6-e22dbe00b3a1

📥 Commits

Reviewing files that changed from the base of the PR and between 98156ae and e10028b.

⛔ Files ignored due to path filters (42)
  • 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/azurekmsspec.go is excluded by !client/**
  • client/applyconfiguration/hypershift/v1beta1/selfmanagedazurekms.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.kms.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/testdata/kube-apiserver/AROSwift/zz_fixture_TestControlPlaneComponents_managed_azure_kms_secretproviderclass.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 (27)
  • api/hypershift/v1beta1/azure.go
  • cmd/cluster/azure/create.go
  • cmd/infra/azure/create.go
  • cmd/infra/azure/create_iam.go
  • cmd/infra/azure/identities.go
  • cmd/infra/azure/identities_test.go
  • cmd/infra/azure/types.go
  • cmd/util/azure_flag_descriptions.go
  • control-plane-operator/controllers/hostedcontrolplane/cloud/azure/providerconfig.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go
  • control-plane-operator/controllers/hostedcontrolplane/manifests/kas.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/aws.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go
  • docs/content/how-to/azure/azure-workload-identity-setup.md
  • docs/content/how-to/azure/create-self-managed-azure-cluster.md
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure_test.go
  • support/azureutil/azureutil.go
  • support/config/constants.go
  • support/controlplane-component/token-minter-container.go
  • support/controlplane-component/token-minter-container_test.go
  • test/e2e/create_cluster_test.go
✅ Files skipped from review due to trivial changes (12)
  • control-plane-operator/controllers/hostedcontrolplane/cloud/azure/providerconfig.go
  • support/config/constants.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/deployment.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/aws.go
  • cmd/cluster/azure/create.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller.go
  • support/controlplane-component/token-minter-container.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms.go
  • support/controlplane-component/token-minter-container_test.go
  • cmd/infra/azure/create_iam.go
  • hypershift-operator/controllers/hostedcluster/internal/platform/azure/azure.go
  • control-plane-operator/controllers/hostedcontrolplane/hostedcontrolplane_controller_test.go
🚧 Files skipped from review as they are similar to previous changes (8)
  • cmd/infra/azure/types.go
  • support/azureutil/azureutil.go
  • cmd/infra/azure/identities_test.go
  • cmd/infra/azure/identities.go
  • test/e2e/create_cluster_test.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/secretencryption_test.go
  • cmd/infra/azure/create.go
  • control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure_test.go

Comment thread control-plane-operator/controllers/hostedcontrolplane/v2/kas/kms/azure.go Outdated
Comment thread docs/content/how-to/azure/create-self-managed-azure-cluster.md Outdated
bryan-cox added 2 commits May 6, 2026 07:04
- Add SelfManagedAzureKMS type with ClientID for workload identity auth
- Add SelfManagedKMS field to AzureKMSSpec for self-managed clusters
- Add CEL validation: kms and selfManagedKMS are mutually exclusive
- Add CEL validation: kms is immutable (cannot switch between modes)
- Add UseWorkloadIdentityExtension field to AzureConfig for CPO

Signed-off-by: Bryan Cox <brcox@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
Signed-off-by: Bryan Cox <brcox@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
bryan-cox added 5 commits May 6, 2026 07:10
- Add --kms-key-id flag to parse Azure Key Vault URI into vault/key/version
- Add --kms-client-id flag for workload identity authentication
- Wire SelfManagedKMS into HostedCluster spec during cluster creation
- Add KMS credential helpers to create_iam infrastructure setup
- Add envtest cases for CEL validation of kms/selfManagedKMS mutual exclusivity
  and immutability

Signed-off-by: Bryan Cox <brcox@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
…ed clusters

- Add KMS config secret reconciliation for self-managed Azure
  with federated workload identity fields
- Add validation that one of kms or selfManagedKMS credentials
  must be configured, returning error when neither is set
- Add IsSelfManagedAzureWithWorkloadIdentity helper in azureutil
- Add token-minter sidecar container builder for Azure KMS
  workload identity token projection
- Add constants for Azure KMS service account and token paths
- Add tests for credential reconciliation, KMS config, and
  self-managed Azure workload identity detection

Signed-off-by: Bryan Cox <brcox@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
…naged clusters

- Add self-managed Azure KMS provider with token-minter sidecar
  for workload identity token projection
- Wire KMSClientID and TenantID into Azure KMS provider options
  when self-managed KMS is configured
- Add UseWorkloadIdentityExtension field to Azure cloud config
  for federated credential authentication
- Add KMS service account manifest for token projection
- Update KAS deployment to mount token-minter sidecar volumes
  and configure Azure KMS socket paths
- Add comprehensive tests for Azure KMS provider, encryption
  config reconciliation, and self-managed credential flow

Signed-off-by: Bryan Cox <brcox@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
- Add e2e test for Azure KMS encryption on self-managed clusters
  with workload identity authentication
- Fix t.Fatal format verb to use t.Fatalf with %v

Signed-off-by: Bryan Cox <brcox@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
- Add KMS encryption setup section to self-managed Azure cluster
  creation guide with key vault and key creation steps
- Add KMS workload identity setup instructions for federated
  credential configuration
- Update API reference with new SelfManagedAzureKMS type and
  UseWorkloadIdentityExtension field documentation

Signed-off-by: Bryan Cox <brcox@redhat.com>
Commit-Message-Assisted-by: Claude (via Claude Code)
@hypershift-jira-solve-ci
Copy link
Copy Markdown

hypershift-jira-solve-ci Bot commented May 7, 2026

I now have the complete root cause analysis. Let me produce the final report.

Test Failure Analysis Complete

Job Information

  • Prow Job: envtest-kube / Conclusion and envtest-ocp / Conclusion (GitHub Actions workflows, not Prow)
  • Build ID: Run 25431773640 (envtest-kube) and 25431773677 (envtest-ocp)
  • PR: #8088CNTRLPLANE-3070: Support KMS on self-managed Azure without affecting ARO HCP
  • Affected Jobs: All 5 envtest-kube matrix variants (K8s 1.31.0–1.35.0) and all 6 envtest-ocp matrix variants (K8s 1.30.3–1.35.1)

Test Failure Analysis

Error

Envtest jobs failed: cancelled
##[error]The operation was canceled.
##[error]Process completed with exit code 1.

Summary

All envtest jobs across both workflows (envtest-kube and envtest-ocp) timed out after exactly 15 minutes — the timeout-minutes: 15 limit configured in the GitHub Actions workflow. Each test binary (envtest.test) started executing but produced zero output for ~14 minutes before being killed. The Conclusion aggregation job then correctly reported the cancelled result as a failure (it only accepts success or skipped). The root cause is the PR's addition of three new CEL validation rules — including a costly oldSelf transition rule — to the HostedCluster and HostedControlPlane CRDs, which causes the embedded kube-apiserver to hang during CRD installation/schema compilation, exhausting the 15-minute job timeout.

Root Cause

The PR adds three new x-kubernetes-validations CEL rules to the AzureKMSSpec type, which is deeply nested inside the already very large HostedCluster CRD (thousands of lines of schema):

  1. !(has(self.kms) && has(self.selfManagedKMS)) — mutual exclusivity check
  2. has(self.kms) || has(self.selfManagedKMS) — at-least-one-set check
  3. !has(oldSelf) || (has(self.kms) == has(oldSelf.kms))transition rule using oldSelf

The third rule is the most expensive: transition rules with oldSelf require the API server to compile and maintain schemas for both the old and new versions of the object. On a CRD as large as HostedCluster, this triggers a combinatorial explosion in the CEL type-checking/compilation step inside the kube-apiserver's CRD validation controller.

Evidence of the hang pattern (identical across all 11 matrix jobs):

  • etcd and kube-apiserver processes start successfully (confirmed by orphan process cleanup)
  • The envtest.test process is running but produces zero Ginkgo test output
  • This means the BeforeSuite (which starts the envtest environment) or the first GenerateCRDInstallTest (which installs ALL CRDs including the modified ones) is blocked waiting for the CRD to become ready
  • The kube-apiserver's CRD validation controller is stuck compiling the CEL rules for the massive HostedCluster schema

The Makefile's test timeout (-timeout=30m) is 2x the GitHub Actions job timeout (timeout-minutes: 15), so the Go test's own timeout never fires — GitHub Actions kills the entire runner first. This is why no test failure output is produced.

These same envtest workflows pass consistently on main (5 recent runs all success), confirming this is caused by the PR's changes, not infrastructure.

Recommendations
  1. Increase the GitHub Actions timeout-minutes from 15 to 30 (matching the Go test timeout) — this is a quick fix that may allow the CEL compilation to complete, but it's a band-aid.

  2. Optimize the CEL validation rules — the oldSelf transition rule is the most expensive. Consider whether the immutability constraint can be enforced via a webhook instead of a CRD validation rule, avoiding the schema compilation cost.

  3. Split the CRD install test — the GenerateCRDInstallTest("Default") test installs ALL CRDs at once. If the HostedCluster CRD with transition rules is too expensive to compile under the embedded kube-apiserver, consider installing CRDs individually or excluding CRDs with transition rules from the bulk install test.

  4. Test locally first — run make test-envtest-kube ENVTEST_KUBE_VERSIONS="1.31.0" locally to measure actual compilation time and confirm the hang reproduces. If it takes >15 minutes but <30 minutes, option 1 alone may suffice.

  5. Consider using optionalOldSelf: true — if the kube-apiserver version supports it (1.29+), marking the transition rule with optionalOldSelf can reduce compilation cost by making the oldSelf binding optional rather than mandatory.

Evidence
Evidence Detail
Failure mode All envtest matrix jobs cancelled by 15-minute timeout, zero test output produced
envtest-kube 1.31.0 timeline Test started 11:16:03, cancelled 11:30:22 (14m19s of silence)
envtest-ocp 1.31.2 timeline Test started 11:15:38, cancelled 11:30:11 (14m33s of silence)
Orphan processes at cancellation envtest.test, etcd, kube-apiserver — envtest infra running, test blocked
Runner assigned Yes (arc-runner-set-zlf4h-runner-r864z) — not a runner queuing issue
Main branch status 5 most recent envtest-kube runs on main all success — not a flake
CRD changes 6 crd-manifests + 26 featuregated YAML files modified with new CEL rules
Critical CEL rule !has(oldSelf) || (has(self.kms) == has(oldSelf.kms)) — transition rule with oldSelf on deeply-nested field in HostedCluster CRD
Timeout mismatch Makefile: -timeout=30m, GitHub Actions: timeout-minutes: 15 — Go timeout never fires
Conclusion step logic Treats cancelled as failure (only accepts success or skipped) — correct behavior

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/aws PR/issue for AWS (AWSPlatform) platform area/platform/azure PR/issue for Azure (AzurePlatform) platform area/testing Indicates the PR includes changes for e2e testing do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. jira/valid-reference Indicates that this PR references a valid Jira ticket of any type.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants