Skip to content

NO-JIRA: rotation controller prototype#2237

Open
tjungblu wants to merge 1 commit into
openshift:masterfrom
tjungblu:rotation_controller
Open

NO-JIRA: rotation controller prototype#2237
tjungblu wants to merge 1 commit into
openshift:masterfrom
tjungblu:rotation_controller

Conversation

@tjungblu
Copy link
Copy Markdown
Contributor

@tjungblu tjungblu commented May 21, 2026

Summary by CodeRabbit

  • New Features

    • Added automated KMS key rotation controller to orchestrate rotation lifecycle, convergence detection, migration coordination, and operator status updates.
    • New status types and helpers to surface per-key health and rotation history.
  • Bug Fixes / Enhancements

    • Improved detection/tracking of migrated encrypted resources and safer rotation start/prune behavior.
    • Helper to reliably locate encryption key secrets.
  • Tests

    • Unit tests for convergence, rotation history, migration detection, and status round-trip.

@openshift-ci-robot openshift-ci-robot added the jira/valid-reference Indicates that this PR references a valid Jira ticket of any type. label May 21, 2026
@openshift-ci-robot
Copy link
Copy Markdown

@tjungblu: This pull request explicitly references no jira issue.

Details

In response to this:

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.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 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

This PR adds KMS KEK rotation orchestration: a new rotation controller, status types and helpers for convergence and rotation history, migration-completion checks from secret annotations, config/secrets helpers, operator-condition persistence, and unit tests.

Changes

KMS encryption key rotation

Layer / File(s) Summary
Controller registration
pkg/operator/encryption/controllers.go
Registers NewEncryptionRotationController into the operator controller list.
Rotation controller types & constructor
pkg/operator/encryption/controllers/encryption_rotation_controller.go
Defines the controller struct, KEK convergence delay constant, and NewEncryptionRotationController wiring with informers and resync.
Reconcile and orchestration logic
pkg/operator/encryption/controllers/encryption_rotation_controller.go
Reconcile loop loads encryption config/state and operator status, derives health reports, upserts open rotations, mirrors migration finish times from secrets, enforces post-discovery delay, prunes storage migration state, clears migration annotations, and persists rotation history to operator status.
Rotation actions (start/mirror/clear)
pkg/operator/encryption/controllers/encryption_rotation_controller.go
Implements startRotation, mirrorMigrationFinish, migrationFinishTimeFromSecret, and clearMigrationAnnotations with conflict-retry updates.
Config and secret helpers
pkg/operator/encryption/encryptiondata/config.go, pkg/operator/encryption/secrets/secrets.go
Adds Config.UsesKMS(), Config.EncryptedGroupResources(), and FindKeySecret() to locate managed encryption key secrets.
Encryption status types
pkg/operator/encryption/encryptionstatus/types.go
Introduces APIServerEncryptionStatus, KMSPluginHealthReport, KMSPluginRotationStatus, and related constants for status and conditions.
KEK convergence helpers and tests
pkg/operator/encryption/encryptionstatus/convergence.go, pkg/operator/encryption/encryptionstatus/convergence_test.go
KEKByKeyID filters healthy reports; ConvergedKEKForKeyID returns a unanimous KEK across healthy reports with unit tests.
Migration annotation parsing and check
pkg/operator/encryption/encryptionstatus/migration.go, pkg/operator/encryption/encryptionstatus/migration_test.go
Parses migrated-resources annotation from key secrets and implements AllEncryptedGRsMigrated with tests.
Operator status condition persistence
pkg/operator/encryption/encryptionstatus/operator.go, pkg/operator/encryption/encryptionstatus/operator_test.go
Reads health and rotation status from operator conditions and provides SetKeyRotationStatusCondition to persist rotation history into operator status (JSON message fallback for interim formats).
Rotation history helpers and tests
pkg/operator/encryption/encryptionstatus/rotation.go, pkg/operator/encryption/encryptionstatus/rotation_test.go
Provides lookup/upsert/setter utilities for rotation slices, caps history length, and tests for prepend/upsert/latest-completed behaviors.

Sequence Diagram(s)

sequenceDiagram
  participant RotationController
  participant OperatorStatus
  participant HealthReports
  participant EncryptionSecret
  participant Migrator
  RotationController->>OperatorStatus: load operator status
  RotationController->>HealthReports: extract health reports from conditions
  RotationController->>HealthReports: check KEK convergence per plugin
  RotationController->>RotationController: upsert open rotation entry
  RotationController->>EncryptionSecret: read migration finish annotation
  RotationController->>RotationController: mirror timestamp to rotation status
  RotationController->>RotationController: enforce post-discovery delay
  RotationController->>Migrator: prune storage migration state
  RotationController->>EncryptionSecret: clear migration annotations
  RotationController->>OperatorStatus: persist rotation status to conditions
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • dgrisonnet
  • ardaguclu
🚥 Pre-merge checks | ✅ 10 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.24% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Structure And Quality ❓ Inconclusive Custom check requires reviewing "Ginkgo test code," but all 8 new test files use standard Go testing (testing.T), not Ginkgo patterns (Describe/It/BeforeEach). Clarify if check applies to all Go tests or only Ginkgo tests. These are standard Go tests without Ginkgo framework.
✅ Passed checks (10 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main change: adding a new encryption rotation controller to the codebase. It accurately reflects the primary objective shown in the raw summary.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed PR contains only standard Go tests (not Ginkgo), so the Ginkgo test name stability check is not applicable. All 8 test names use static, descriptive identifiers with no dynamic content.
Microshift Test Compatibility ✅ Passed No Ginkgo e2e tests are added in this PR. The 4 newly added test files contain only standard Go unit tests using func TestX(t *testing.T) pattern. Check is not applicable.
Single Node Openshift (Sno) Test Compatibility ✅ Passed No Ginkgo e2e tests added in this PR. All test files are standard Go unit tests using testing.T, not Ginkgo patterns.
Topology-Aware Scheduling Compatibility ✅ Passed PR introduces only controller and helper code with no deployment manifests, pod specifications, or scheduling constraints. Not applicable to topology-aware scheduling check.
Ote Binary Stdout Contract ✅ Passed PR adds encryption rotation controller with no process-level stdout writes. All klog usage is within controller sync/reconcile functions, which don't violate the OTE Binary Stdout Contract.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PR adds only standard Go unit tests (func TestXxx), not Ginkgo e2e tests. Check for Ginkgo patterns (Describe/It/Context) is not applicable to this code.

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

✨ 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 openshift-ci Bot requested review from dgrisonnet and p0lyn0mial May 21, 2026 11:22
@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 21, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
pkg/operator/encryption/encryptionstatus/rotation_test.go (1)

23-35: ⚡ Quick win

Add a regression test for UpsertOpenRotation using a fixed now value.

Current tests miss the existing-open-entry path, which is where timestamp-source regressions occur.

Proposed test addition
+func TestUpsertOpenRotationUsesProvidedNowForMissingDiscoveryTime(t *testing.T) {
+	now := metav1.Now()
+	rotations := []KMSPluginRotationStatus{
+		{KeyID: "1", KEKID: "kek-a", DiscoveryTime: nil},
+	}
+
+	updated, idx := UpsertOpenRotation(rotations, "1", "kek-a", now)
+	if idx != 0 {
+		t.Fatalf("expected index 0, got %d", idx)
+	}
+	if updated[0].DiscoveryTime == nil || !updated[0].DiscoveryTime.Equal(&now) {
+		t.Fatalf("expected discoveryTime to equal provided now")
+	}
+}
🤖 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 `@pkg/operator/encryption/encryptionstatus/rotation_test.go` around lines 23 -
35, Add a regression test for UpsertOpenRotation that uses a fixed metav1.Time
value to ensure the code path that updates an existing open entry uses the
supplied now value as the timestamp source. In rotation_test.go create rotations
containing an open KMSPluginRotationStatus (MigrationFinishTime == nil) for a
given KeyID, call UpsertOpenRotation(rotations, <KeyID>, <KEKID>, now) where now
is a deterministic metav1.NewTime(fixedTime), and assert that the returned/open
KMSPluginRotationStatus for that KEK has its timestamp field set to exactly that
now (not a freshly generated time) and that the entry identity (KeyID/KEKID)
remains correct. Reference UpsertOpenRotation and KMSPluginRotationStatus to
locate where to add this test.
🤖 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 `@pkg/operator/encryption/controllers/encryption_rotation_controller.go`:
- Around line 254-257: The current code treats a failed time.Parse as a
successful completion by returning metav1.Now(); instead, when time.Parse(raw)
fails you must not signal completion—log or record the parse error and return a
zero metav1.Time (metav1.Time{}) and false (or propagate an error to requeue) so
the rotation is not marked finished; update the block around time.Parse(...) to
avoid using metav1.Now() on parse failure and reference the time.Parse/raw
parsing logic and the metav1.Now() return to locate and fix the code.

In `@pkg/operator/encryption/encryptiondata/config.go`:
- Around line 50-55: The loop in
pkg/operator/encryption/encryptiondata/config.go currently ignores entries where
resourceConfig.Resources has length != 1, dropping multi-resource entries and
underreporting encrypted GR coverage; update the loop over
c.Encryption.Resources to iterate over each entry in resourceConfig.Resources
(instead of only handling index 0 when len==1) and call
schema.ParseGroupResource for every resource string, appending each parsed value
to grs so all resources in resourceConfig.Resources are included; adjust any
variable names (resourceConfig, grs) accordingly to preserve existing semantics.

In `@pkg/operator/encryption/encryptionstatus/rotation.go`:
- Around line 35-42: The UpsertOpenRotation function inconsistently uses
metav1.Now() instead of the injected now parameter; locate UpsertOpenRotation
and replace the metav1.Now() call in the SetDiscoveryTime branch with the now
argument so the function uses the provided timestamp consistently (references:
UpsertOpenRotation, indexRotation, prependRotation, newOpenRotation,
SetDiscoveryTime).

---

Nitpick comments:
In `@pkg/operator/encryption/encryptionstatus/rotation_test.go`:
- Around line 23-35: Add a regression test for UpsertOpenRotation that uses a
fixed metav1.Time value to ensure the code path that updates an existing open
entry uses the supplied now value as the timestamp source. In rotation_test.go
create rotations containing an open KMSPluginRotationStatus (MigrationFinishTime
== nil) for a given KeyID, call UpsertOpenRotation(rotations, <KeyID>, <KEKID>,
now) where now is a deterministic metav1.NewTime(fixedTime), and assert that the
returned/open KMSPluginRotationStatus for that KEK has its timestamp field set
to exactly that now (not a freshly generated time) and that the entry identity
(KeyID/KEKID) remains correct. Reference UpsertOpenRotation and
KMSPluginRotationStatus to locate where to add this 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f8872af3-c7bd-42da-918f-d4f3c2c38997

📥 Commits

Reviewing files that changed from the base of the PR and between 83d6ad9 and 9958b19.

📒 Files selected for processing (13)
  • pkg/operator/encryption/controllers.go
  • pkg/operator/encryption/controllers/encryption_rotation_controller.go
  • pkg/operator/encryption/encryptiondata/config.go
  • pkg/operator/encryption/encryptionstatus/convergence.go
  • pkg/operator/encryption/encryptionstatus/convergence_test.go
  • pkg/operator/encryption/encryptionstatus/migration.go
  • pkg/operator/encryption/encryptionstatus/migration_test.go
  • pkg/operator/encryption/encryptionstatus/operator.go
  • pkg/operator/encryption/encryptionstatus/operator_test.go
  • pkg/operator/encryption/encryptionstatus/rotation.go
  • pkg/operator/encryption/encryptionstatus/rotation_test.go
  • pkg/operator/encryption/encryptionstatus/types.go
  • pkg/operator/encryption/secrets/secrets.go

Comment thread pkg/operator/encryption/encryptiondata/config.go
Comment thread pkg/operator/encryption/encryptionstatus/rotation.go Outdated
@tjungblu tjungblu force-pushed the rotation_controller branch from 9958b19 to b8c8866 Compare May 21, 2026 12:35
Copy link
Copy Markdown

@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

♻️ Duplicate comments (1)
pkg/operator/encryption/encryptiondata/config.go (1)

50-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not drop multi-resource encryption entries when deriving encrypted GRs.

Skipping entries where len(resourceConfig.Resources) != 1 can omit encrypted resources and make migration/rotation decisions incorrect.

✅ Include every listed resource
 	for _, resourceConfig := range c.Encryption.Resources {
-		if len(resourceConfig.Resources) != 1 {
-			continue
-		}
-		grs = append(grs, schema.ParseGroupResource(resourceConfig.Resources[0]))
+		for _, resource := range resourceConfig.Resources {
+			grs = append(grs, schema.ParseGroupResource(resource))
+		}
 	}
🤖 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 `@pkg/operator/encryption/encryptiondata/config.go` around lines 50 - 55, The
loop over c.Encryption.Resources currently skips entries with multiple resources
by checking len(resourceConfig.Resources) != 1; update the logic in the block
that builds grs so that for each resourceConfig in c.Encryption.Resources you
iterate over every entry in resourceConfig.Resources and call
schema.ParseGroupResource for each item (instead of only handling single-item
lists), appending each parsed GroupResource to grs; ensure you still handle
empty lists safely and preserve existing variable names
(resourceConfig.Resources, schema.ParseGroupResource, grs) so all listed
resources are included when deriving encrypted GRs.
🧹 Nitpick comments (1)
pkg/operator/encryption/encryptionstatus/migration_test.go (1)

30-30: ⚡ Quick win

Check marshal errors in test setup.

Ignoring the error here can mask setup failures and make assertions misleading.

Proposed fix
-	raw, _ = json.Marshal(partial)
+	raw, err = json.Marshal(partial)
+	if err != nil {
+		t.Fatal(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 `@pkg/operator/encryption/encryptionstatus/migration_test.go` at line 30, The
test currently ignores the error from json.Marshal(partial) when setting raw;
capture the error (err := json.Marshal(partial) or similar) and assert it
succeeded before proceeding (e.g., use require.NoError(t, err) or
t.Fatalf("marshal failed: %v", err)); ensure you still assign raw only after a
successful marshal so test setup failures are surfaced early (refer to the
variables raw and partial and the call to json.Marshal).
🤖 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 `@pkg/operator/encryption/controllers/encryption_rotation_controller.go`:
- Around line 150-160: UpsertOpenRotation is being called before we know whether
the converged kekID already matches the latest completed rotation, which causes
churn; call AllEncryptedGRsMigrated(...) first to get migrated, then inspect the
existing rotations slice to find the latest completed rotation's kekID (e.g.,
scan rotations for State==Completed and take its KekID) and only call
encryptionstatus.UpsertOpenRotation(...) if that latestCompletedKekID != kekID
or migration is not completed; ensure mirrorMigrationFinish and
maybeStartRotation still receive the correct rotations/idx values (create idx
only when you actually upsert) so we don't create new rotation entries during
steady-state convergence.

---

Duplicate comments:
In `@pkg/operator/encryption/encryptiondata/config.go`:
- Around line 50-55: The loop over c.Encryption.Resources currently skips
entries with multiple resources by checking len(resourceConfig.Resources) != 1;
update the logic in the block that builds grs so that for each resourceConfig in
c.Encryption.Resources you iterate over every entry in resourceConfig.Resources
and call schema.ParseGroupResource for each item (instead of only handling
single-item lists), appending each parsed GroupResource to grs; ensure you still
handle empty lists safely and preserve existing variable names
(resourceConfig.Resources, schema.ParseGroupResource, grs) so all listed
resources are included when deriving encrypted GRs.

---

Nitpick comments:
In `@pkg/operator/encryption/encryptionstatus/migration_test.go`:
- Line 30: The test currently ignores the error from json.Marshal(partial) when
setting raw; capture the error (err := json.Marshal(partial) or similar) and
assert it succeeded before proceeding (e.g., use require.NoError(t, err) or
t.Fatalf("marshal failed: %v", err)); ensure you still assign raw only after a
successful marshal so test setup failures are surfaced early (refer to the
variables raw and partial and the call to json.Marshal).
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3c07d496-feb3-4fd0-bfe1-149deb69b1ba

📥 Commits

Reviewing files that changed from the base of the PR and between 9958b19 and b8c8866.

📒 Files selected for processing (13)
  • pkg/operator/encryption/controllers.go
  • pkg/operator/encryption/controllers/encryption_rotation_controller.go
  • pkg/operator/encryption/encryptiondata/config.go
  • pkg/operator/encryption/encryptionstatus/convergence.go
  • pkg/operator/encryption/encryptionstatus/convergence_test.go
  • pkg/operator/encryption/encryptionstatus/migration.go
  • pkg/operator/encryption/encryptionstatus/migration_test.go
  • pkg/operator/encryption/encryptionstatus/operator.go
  • pkg/operator/encryption/encryptionstatus/operator_test.go
  • pkg/operator/encryption/encryptionstatus/rotation.go
  • pkg/operator/encryption/encryptionstatus/rotation_test.go
  • pkg/operator/encryption/encryptionstatus/types.go
  • pkg/operator/encryption/secrets/secrets.go

Comment thread pkg/operator/encryption/controllers/encryption_rotation_controller.go Outdated
@tjungblu tjungblu force-pushed the rotation_controller branch from b8c8866 to d0f58b3 Compare May 21, 2026 12:54
Copy link
Copy Markdown

@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: 2

🤖 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 `@pkg/operator/encryption/encryptionstatus/convergence.go`:
- Around line 9-17: KEKByKeyID currently only checks isHealthyReport but does
not skip reports with empty KeyID or KEKID as the contract requires; update
KEKByKeyID to additionally guard that report.KeyID != "" and report.KEKID != ""
before appending to result (keep the existing isHealthyReport(report) check), so
only reports with non-empty keyId and kekId are included and downstream callers
simplify.

In `@pkg/operator/encryption/encryptionstatus/migration_test.go`:
- Around line 30-31: The test setup currently ignores the error from
json.Marshal when creating raw from partial and assigning it to
secret.Annotations[secrets.EncryptionSecretMigratedResources]; update the setup
to check the error return from json.Marshal(partial), fail the test (or use
t.Fatalf/t.Helper with a clear message) when marshal returns an error, and
include a descriptive failure message that identifies the marshal of partial and
the target annotation key so test failures show the root cause.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 730cfbd0-b2d2-4d99-9d33-ec8cfb74a78f

📥 Commits

Reviewing files that changed from the base of the PR and between b8c8866 and d0f58b3.

📒 Files selected for processing (13)
  • pkg/operator/encryption/controllers.go
  • pkg/operator/encryption/controllers/encryption_rotation_controller.go
  • pkg/operator/encryption/encryptiondata/config.go
  • pkg/operator/encryption/encryptionstatus/convergence.go
  • pkg/operator/encryption/encryptionstatus/convergence_test.go
  • pkg/operator/encryption/encryptionstatus/migration.go
  • pkg/operator/encryption/encryptionstatus/migration_test.go
  • pkg/operator/encryption/encryptionstatus/operator.go
  • pkg/operator/encryption/encryptionstatus/operator_test.go
  • pkg/operator/encryption/encryptionstatus/rotation.go
  • pkg/operator/encryption/encryptionstatus/rotation_test.go
  • pkg/operator/encryption/encryptionstatus/types.go
  • pkg/operator/encryption/secrets/secrets.go

Comment on lines +9 to +17
func KEKByKeyID(reports []KMSPluginHealthReport) map[string][]string {
result := map[string][]string{}
for _, report := range reports {
if !isHealthyReport(report) {
continue
}
result[report.KeyID] = append(result[report.KeyID], report.KEKID)
}
return result
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Filter empty KeyID/KEKID to match documented contract.

The comment on line 8 states that only reports with non-empty keyId and kekId are included, but the code does not enforce this. Although ConvergedKEKForKeyID handles empty values downstream, filtering here would match the documented contract and simplify caller logic.

Proposed fix
 func KEKByKeyID(reports []KMSPluginHealthReport) map[string][]string {
 	result := map[string][]string{}
 	for _, report := range reports {
 		if !isHealthyReport(report) {
 			continue
 		}
+		if report.KeyID == "" || report.KEKID == "" {
+			continue
+		}
 		result[report.KeyID] = append(result[report.KeyID], report.KEKID)
 	}
 	return result
 }
🤖 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 `@pkg/operator/encryption/encryptionstatus/convergence.go` around lines 9 - 17,
KEKByKeyID currently only checks isHealthyReport but does not skip reports with
empty KeyID or KEKID as the contract requires; update KEKByKeyID to additionally
guard that report.KeyID != "" and report.KEKID != "" before appending to result
(keep the existing isHealthyReport(report) check), so only reports with
non-empty keyId and kekId are included and downstream callers simplify.

Comment on lines +30 to +31
raw, _ = json.Marshal(partial)
secret.Annotations[secrets.EncryptionSecretMigratedResources] = string(raw)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle marshal failure in the partial-case setup.

Ignoring the second json.Marshal error can hide setup failures behind assertion noise.

Proposed fix
 	partial := secrets.MigratedGroupResources{Resources: grs[:1]}
-	raw, _ = json.Marshal(partial)
+	raw, err = json.Marshal(partial)
+	if err != nil {
+		t.Fatal(err)
+	}
 	secret.Annotations[secrets.EncryptionSecretMigratedResources] = string(raw)

As per coding guidelines, "Assertions should include meaningful failure messages that help diagnose what went wrong."

🤖 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 `@pkg/operator/encryption/encryptionstatus/migration_test.go` around lines 30 -
31, The test setup currently ignores the error from json.Marshal when creating
raw from partial and assigning it to
secret.Annotations[secrets.EncryptionSecretMigratedResources]; update the setup
to check the error return from json.Marshal(partial), fail the test (or use
t.Fatalf/t.Helper with a clear message) when marshal returns an error, and
include a descriptive failure message that identifies the marshal of partial and
the target annotation key so test failures show the root cause.

@tjungblu tjungblu force-pushed the rotation_controller branch from d0f58b3 to e3b702a Compare May 21, 2026 13:38
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

Copy link
Copy Markdown

@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: 2

♻️ Duplicate comments (2)
pkg/operator/encryption/encryptionstatus/migration_test.go (1)

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

Handle marshal error in partial fixture setup.

Line 30 ignores json.Marshal(partial) error, which can hide setup breakage behind assertion failures. Please fail fast with a descriptive message.

Proposed fix
 	partial := secrets.MigratedGroupResources{Resources: grs[:1]}
-	raw, _ = json.Marshal(partial)
+	raw, err = json.Marshal(partial)
+	if err != nil {
+		t.Fatalf("failed to marshal partial migrated resources for annotation %q: %v", secrets.EncryptionSecretMigratedResources, err)
+	}
 	secret.Annotations[secrets.EncryptionSecretMigratedResources] = string(raw)
🤖 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 `@pkg/operator/encryption/encryptionstatus/migration_test.go` around lines 29 -
31, The test currently ignores the error returned by json.Marshal(partial);
update the fixture setup in migration_test.go to check the error from
json.Marshal(partial) and fail fast with a descriptive message (e.g., using
t.Fatalf or require.NoError) before assigning to
secret.Annotations[secrets.EncryptionSecretMigratedResources]; reference the
variables/functions partial, secrets.MigratedGroupResources, json.Marshal, and
secrets.EncryptionSecretMigratedResources so the change is applied where the
partial fixture is prepared.
pkg/operator/encryption/encryptionstatus/convergence.go (1)

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

Enforce the documented non-empty KeyID/KEKID filter in KEKByKeyID.

Line 8 says empty keyId/kekId are excluded, but Line 15 appends without that guard. Please skip reports where report.KeyID == "" || report.KEKID == "" before appending.

Proposed fix
 func KEKByKeyID(reports []KMSPluginHealthReport) map[string][]string {
 	result := map[string][]string{}
 	for _, report := range reports {
 		if !isHealthyReport(report) {
 			continue
 		}
+		if report.KeyID == "" || report.KEKID == "" {
+			continue
+		}
 		result[report.KeyID] = append(result[report.KeyID], report.KEKID)
 	}
 	return result
 }
🤖 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 `@pkg/operator/encryption/encryptionstatus/convergence.go` around lines 7 - 16,
KEKByKeyID currently appends report.KEKID even when KeyID or KEKID are empty;
update the loop in KEKByKeyID to, after confirming isHealthyReport(report), skip
the report if report.KeyID == "" || report.KEKID == "" so only healthy reports
with non-empty KeyID and KEKID are added; keep using the same map result and
append into result[report.KeyID] for valid reports.
🤖 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 `@pkg/operator/encryption/controllers/encryption_rotation_controller.go`:
- Around line 151-162: The controller is copying stale migration finish
annotations into a newly discovered KEK because migrated is read from
writeKeySecret before the secret is cleared; update the logic in the block that
creates/opens a rotation (where rotations, idx =
encryptionstatus.GetOrCreateOpenRotation(...) is called) so you only call
mirrorMigrationFinish(c.instanceName, rotations, idx, migrated, writeKeySecret)
when the migration annotations actually belong to the same KEK (i.e.,
hasCompleted is true and latest.KEKID == kekID), otherwise pass migrated=false
(or skip mirroring entirely). This ensures mirrorMigrationFinish is only applied
when the secret’s migration finish timestamp corresponds to the same
KEK/rotation.

In `@pkg/operator/encryption/encryptionstatus/operator.go`:
- Around line 29-33: The code treats an explicitly empty structured field as
absent by using len(encryptionStatus.HealthReports) > 0; change this to a nil
check so "present but empty" is preserved: replace the len(...) > 0 check with
encryptionStatus.HealthReports != nil and return that slice when non-nil,
otherwise call healthReportsFromConditions(status); apply the same nil-check fix
for the analogous block around lines 78-83 (same pattern) so other structured
report fields use presence (nil vs non-nil) rather than emptiness to decide
fallback.

---

Duplicate comments:
In `@pkg/operator/encryption/encryptionstatus/convergence.go`:
- Around line 7-16: KEKByKeyID currently appends report.KEKID even when KeyID or
KEKID are empty; update the loop in KEKByKeyID to, after confirming
isHealthyReport(report), skip the report if report.KeyID == "" || report.KEKID
== "" so only healthy reports with non-empty KeyID and KEKID are added; keep
using the same map result and append into result[report.KeyID] for valid
reports.

In `@pkg/operator/encryption/encryptionstatus/migration_test.go`:
- Around line 29-31: The test currently ignores the error returned by
json.Marshal(partial); update the fixture setup in migration_test.go to check
the error from json.Marshal(partial) and fail fast with a descriptive message
(e.g., using t.Fatalf or require.NoError) before assigning to
secret.Annotations[secrets.EncryptionSecretMigratedResources]; reference the
variables/functions partial, secrets.MigratedGroupResources, json.Marshal, and
secrets.EncryptionSecretMigratedResources so the change is applied where the
partial fixture is prepared.
🪄 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: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: e6b37a35-61e5-41cc-8dc2-6ba31fc8e0e4

📥 Commits

Reviewing files that changed from the base of the PR and between d0f58b3 and e3b702a.

📒 Files selected for processing (13)
  • pkg/operator/encryption/controllers.go
  • pkg/operator/encryption/controllers/encryption_rotation_controller.go
  • pkg/operator/encryption/encryptiondata/config.go
  • pkg/operator/encryption/encryptionstatus/convergence.go
  • pkg/operator/encryption/encryptionstatus/convergence_test.go
  • pkg/operator/encryption/encryptionstatus/migration.go
  • pkg/operator/encryption/encryptionstatus/migration_test.go
  • pkg/operator/encryption/encryptionstatus/operator.go
  • pkg/operator/encryption/encryptionstatus/operator_test.go
  • pkg/operator/encryption/encryptionstatus/rotation.go
  • pkg/operator/encryption/encryptionstatus/rotation_test.go
  • pkg/operator/encryption/encryptionstatus/types.go
  • pkg/operator/encryption/secrets/secrets.go

Comment thread pkg/operator/encryption/controllers/encryption_rotation_controller.go Outdated
Comment on lines +29 to +33
encryptionStatus := encryptionStatusFromOperator(status)
if len(encryptionStatus.HealthReports) > 0 {
return encryptionStatus.HealthReports
}
return healthReportsFromConditions(status)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor explicit empty structured status instead of falling back to interim conditions.

The current len(...) > 0 checks treat an explicitly empty structured field as “not present,” which can incorrectly fall back to old condition data. Prefer nil checks to preserve “present but empty” semantics.

Proposed fix
 func HealthReportsFromOperatorStatus(status *operatorv1.OperatorStatus) []KMSPluginHealthReport {
 	encryptionStatus := encryptionStatusFromOperator(status)
-	if len(encryptionStatus.HealthReports) > 0 {
+	if encryptionStatus.HealthReports != nil {
 		return encryptionStatus.HealthReports
 	}
 	return healthReportsFromConditions(status)
 }
@@
 func KeyRotationStatusFromOperatorStatus(status *operatorv1.OperatorStatus) ([]KMSPluginRotationStatus, error) {
 	encryptionStatus := encryptionStatusFromOperator(status)
-	if len(encryptionStatus.KeyRotationStatus) > 0 {
+	if encryptionStatus.KeyRotationStatus != nil {
 		return encryptionStatus.KeyRotationStatus, nil
 	}
 	return keyRotationStatusFromCondition(status)
 }

Also applies to: 78-83

🤖 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 `@pkg/operator/encryption/encryptionstatus/operator.go` around lines 29 - 33,
The code treats an explicitly empty structured field as absent by using
len(encryptionStatus.HealthReports) > 0; change this to a nil check so "present
but empty" is preserved: replace the len(...) > 0 check with
encryptionStatus.HealthReports != nil and return that slice when non-nil,
otherwise call healthReportsFromConditions(status); apply the same nil-check fix
for the analogous block around lines 78-83 (same pattern) so other structured
report fields use presence (nil vs non-nil) rather than emptiness to decide
fallback.

Signed-off-by: Thomas Jungblut <tjungblu@redhat.com>
@tjungblu tjungblu force-pushed the rotation_controller branch from e3b702a to 876547e Compare May 21, 2026 14:19
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (5)
pkg/operator/encryption/controllers/encryption_rotation_controller.go (1)

163-169: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't mirror the previous rotation's finish timestamp onto a newly discovered KEK.

Line 169 mirrors the write-secret migration timestamp before the new rotation has cleared the old annotations, so a fresh convergedKEKID can be recorded as finished before it has even started. That corrupts rotation history and can make status look complete too early.

💡 Guard mirroring until bootstrap or after this rotation has started
 	migrated, err := encryptionstatus.AllEncryptedGRsMigrated(writeKeySecret, encryptedGRs)
 	if err != nil {
 		return rotations, err
 	}
-	rotations = mirrorMigrationFinish(c.instanceName, rotations, openIdx, migrated, writeKeySecret)
+	if !hasCompleted || rotations[openIdx].MigrationStartTime != nil {
+		rotations = mirrorMigrationFinish(c.instanceName, rotations, openIdx, migrated, writeKeySecret)
+	}
🤖 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 `@pkg/operator/encryption/controllers/encryption_rotation_controller.go` around
lines 163 - 169, The code calls mirrorMigrationFinish(...) unconditionally and
can copy the previous rotation's migration-finished timestamp onto a newly
discovered KEK; change this by guarding the mirrorMigrationFinish call so it
only runs when this rotation has actually started or when bootstrapping is
allowed: after computing migrated via
encryptionstatus.AllEncryptedGRsMigrated(writeKeySecret, encryptedGRs), check
the rotation state in rotations[openIdx] (e.g., whether the rotation has a start
marker/flag or is a bootstrap rotation) and only invoke
mirrorMigrationFinish(c.instanceName, rotations, openIdx, migrated,
writeKeySecret) when that check confirms the rotation has begun (or bootstrap
path applies), otherwise skip mirroring to avoid tainting the new
convergedKEKID.
pkg/operator/encryption/encryptionstatus/migration_test.go (1)

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

Handle the json.Marshal error in test setup.

Ignoring the marshal error can hide setup failures and produce confusing assertion errors downstream if the struct becomes unmarshalable.

🧪 Proposed fix
 	partial := secrets.MigratedGroupResources{Resources: grs[:1]}
-	raw, _ = json.Marshal(partial)
+	raw, err = json.Marshal(partial)
+	if err != nil {
+		t.Fatal(err)
+	}
 	secret.Annotations[secrets.EncryptionSecretMigratedResources] = string(raw)

As per coding guidelines, "Assertions should include meaningful failure messages that help diagnose what went wrong."

🤖 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 `@pkg/operator/encryption/encryptionstatus/migration_test.go` around lines 30 -
31, The test currently ignores json.Marshal error when building the annotation
(raw, _ = json.Marshal(partial)) which can mask setup failures; change this to
capture the error (raw, err := json.Marshal(partial)) and fail the test on error
(e.g., require.NoError(t, err, "failed to marshal partial migration annotation")
or t.Fatalf("failed to marshal partial migration annotation: %v", err)), then
assign secret.Annotations[secrets.EncryptionSecretMigratedResources] =
string(raw) as before.
pkg/operator/encryption/encryptionstatus/convergence.go (1)

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

Filter empty KeyID/KEKID to match documented contract.

The past review comment remains valid: the function comment states "non-empty keyId and kekId are included," but the implementation only filters by health status without checking for empty identifiers.

🤖 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 `@pkg/operator/encryption/encryptionstatus/convergence.go` around lines 9 - 17,
KEKByKeyID currently only filters by health via isHealthyReport but must also
skip entries with empty KeyID or KEKID per its contract; update KEKByKeyID to
check report.KeyID != "" and report.KEKID != "" (in addition to
isHealthyReport(report)) before appending so only non-empty keyId and kekId
values are included, leaving the rest of the logic unchanged.
pkg/operator/encryption/encryptionstatus/operator.go (2)

29-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor explicit empty structured status instead of falling back to interim conditions.

The past review comment remains valid: using len(encryptionStatus.HealthReports) > 0 treats an explicitly empty slice as "not present" and falls back to condition parsing. A nil check would preserve "present but empty" semantics.

🤖 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 `@pkg/operator/encryption/encryptionstatus/operator.go` around lines 29 - 33,
The current check uses len(encryptionStatus.HealthReports) > 0 which treats an
explicitly present but empty slice as "absent" and incorrectly falls back to
healthReportsFromConditions; change the logic to detect presence (nil vs
non-nil) instead: use a nil-check on encryptionStatus.HealthReports (e.g.,
encryptionStatus.HealthReports != nil) to return the structured HealthReports
when it is present (even if empty), otherwise fall back to
healthReportsFromConditions(status); reference encryptionStatus,
encryptionStatusFromOperator, encryptionStatus.HealthReports, and
healthReportsFromConditions in the change.

78-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor explicit empty structured status instead of falling back to interim conditions.

The past review comment remains valid: using len(encryptionStatus.KeyRotationStatus) > 0 treats an explicitly empty slice as "not present." A nil check would correctly distinguish between absent and explicitly empty.

🤖 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 `@pkg/operator/encryption/encryptionstatus/operator.go` around lines 78 - 83,
Replace the current presence check that uses
len(encryptionStatus.KeyRotationStatus) > 0 with a nil check so an explicitly
empty slice is honored: call encryptionStatus :=
encryptionStatusFromOperator(status) and if encryptionStatus.KeyRotationStatus
!= nil { return encryptionStatus.KeyRotationStatus, nil } else return
keyRotationStatusFromCondition(status); update the branch around
encryptionStatusFromOperator and keyRotationStatusFromCondition to rely on nil
vs absent rather than length.
🧹 Nitpick comments (1)
pkg/operator/encryption/controllers/encryption_rotation_controller.go (1)

112-125: ⚡ Quick win

Sort plugin keys before reconciling.

Iterating currentConfig.KMSPlugins directly makes newly created rotation entries order-dependent on Go's randomized map iteration, which can churn the serialized operator status for the same logical state. Sorting the keyIDs before this loop keeps status updates stable.

♻️ Minimal change
-	for keyID := range currentConfig.KMSPlugins {
+	keyIDs := make([]string, 0, len(currentConfig.KMSPlugins))
+	for keyID := range currentConfig.KMSPlugins {
+		keyIDs = append(keyIDs, keyID)
+	}
+	sort.Strings(keyIDs)
+
+	for _, keyID := range keyIDs {
 		writeKeySecret := secrets.FindKeySecret(encryptionSecrets, c.instanceName, keyID)
 		if writeKeySecret == nil {
 			klog.Infof("%s: no write key secret for KMS plugin keyID %q, skipping", c.instanceName, keyID)
 			continue
 		}
🤖 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 `@pkg/operator/encryption/controllers/encryption_rotation_controller.go` around
lines 112 - 125, Iteration over currentConfig.KMSPlugins is non-deterministic
causing churn; collect the map keys into a slice, sort the slice, and iterate
the sorted keys instead of ranging the map directly so that calls to
c.reconcileKMSPlugin(ctx, keyID, ...) happen in a stable order; locate the loop
that ranges currentConfig.KMSPlugins and replace it with iterating over the
sorted keys slice (use the existing variable names keyID, rotations,
writeKeySecret and call reconcileKMSPlugin unchanged).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@pkg/operator/encryption/controllers/encryption_rotation_controller.go`:
- Around line 163-169: The code calls mirrorMigrationFinish(...) unconditionally
and can copy the previous rotation's migration-finished timestamp onto a newly
discovered KEK; change this by guarding the mirrorMigrationFinish call so it
only runs when this rotation has actually started or when bootstrapping is
allowed: after computing migrated via
encryptionstatus.AllEncryptedGRsMigrated(writeKeySecret, encryptedGRs), check
the rotation state in rotations[openIdx] (e.g., whether the rotation has a start
marker/flag or is a bootstrap rotation) and only invoke
mirrorMigrationFinish(c.instanceName, rotations, openIdx, migrated,
writeKeySecret) when that check confirms the rotation has begun (or bootstrap
path applies), otherwise skip mirroring to avoid tainting the new
convergedKEKID.

In `@pkg/operator/encryption/encryptionstatus/convergence.go`:
- Around line 9-17: KEKByKeyID currently only filters by health via
isHealthyReport but must also skip entries with empty KeyID or KEKID per its
contract; update KEKByKeyID to check report.KeyID != "" and report.KEKID != ""
(in addition to isHealthyReport(report)) before appending so only non-empty
keyId and kekId values are included, leaving the rest of the logic unchanged.

In `@pkg/operator/encryption/encryptionstatus/migration_test.go`:
- Around line 30-31: The test currently ignores json.Marshal error when building
the annotation (raw, _ = json.Marshal(partial)) which can mask setup failures;
change this to capture the error (raw, err := json.Marshal(partial)) and fail
the test on error (e.g., require.NoError(t, err, "failed to marshal partial
migration annotation") or t.Fatalf("failed to marshal partial migration
annotation: %v", err)), then assign
secret.Annotations[secrets.EncryptionSecretMigratedResources] = string(raw) as
before.

In `@pkg/operator/encryption/encryptionstatus/operator.go`:
- Around line 29-33: The current check uses len(encryptionStatus.HealthReports)
> 0 which treats an explicitly present but empty slice as "absent" and
incorrectly falls back to healthReportsFromConditions; change the logic to
detect presence (nil vs non-nil) instead: use a nil-check on
encryptionStatus.HealthReports (e.g., encryptionStatus.HealthReports != nil) to
return the structured HealthReports when it is present (even if empty),
otherwise fall back to healthReportsFromConditions(status); reference
encryptionStatus, encryptionStatusFromOperator, encryptionStatus.HealthReports,
and healthReportsFromConditions in the change.
- Around line 78-83: Replace the current presence check that uses
len(encryptionStatus.KeyRotationStatus) > 0 with a nil check so an explicitly
empty slice is honored: call encryptionStatus :=
encryptionStatusFromOperator(status) and if encryptionStatus.KeyRotationStatus
!= nil { return encryptionStatus.KeyRotationStatus, nil } else return
keyRotationStatusFromCondition(status); update the branch around
encryptionStatusFromOperator and keyRotationStatusFromCondition to rely on nil
vs absent rather than length.

---

Nitpick comments:
In `@pkg/operator/encryption/controllers/encryption_rotation_controller.go`:
- Around line 112-125: Iteration over currentConfig.KMSPlugins is
non-deterministic causing churn; collect the map keys into a slice, sort the
slice, and iterate the sorted keys instead of ranging the map directly so that
calls to c.reconcileKMSPlugin(ctx, keyID, ...) happen in a stable order; locate
the loop that ranges currentConfig.KMSPlugins and replace it with iterating over
the sorted keys slice (use the existing variable names keyID, rotations,
writeKeySecret and call reconcileKMSPlugin unchanged).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: openshift/coderabbit/.coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 84d39536-856c-47bc-a4e3-fe8a7b413d96

📥 Commits

Reviewing files that changed from the base of the PR and between e3b702a and 876547e.

📒 Files selected for processing (13)
  • pkg/operator/encryption/controllers.go
  • pkg/operator/encryption/controllers/encryption_rotation_controller.go
  • pkg/operator/encryption/encryptiondata/config.go
  • pkg/operator/encryption/encryptionstatus/convergence.go
  • pkg/operator/encryption/encryptionstatus/convergence_test.go
  • pkg/operator/encryption/encryptionstatus/migration.go
  • pkg/operator/encryption/encryptionstatus/migration_test.go
  • pkg/operator/encryption/encryptionstatus/operator.go
  • pkg/operator/encryption/encryptionstatus/operator_test.go
  • pkg/operator/encryption/encryptionstatus/rotation.go
  • pkg/operator/encryption/encryptionstatus/rotation_test.go
  • pkg/operator/encryption/encryptionstatus/types.go
  • pkg/operator/encryption/secrets/secrets.go

@openshift-ci
Copy link
Copy Markdown
Contributor

openshift-ci Bot commented May 21, 2026

@tjungblu: 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.

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

Labels

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