Skip to content

fix: disable config cache, trigger reconcile if generation changes#649

Merged
raffis merged 5 commits into
masterfrom
fix-stale-reconcile
Apr 30, 2026
Merged

fix: disable config cache, trigger reconcile if generation changes#649
raffis merged 5 commits into
masterfrom
fix-stale-reconcile

Conversation

@raffis
Copy link
Copy Markdown
Member

@raffis raffis commented Apr 30, 2026

Current situation

keycloak-config-client by default caches the keycloakrealm. This is counter intuitive if something changes at keycloak and should be reverted by the reconciler. Before this change if this was not wanted one had to apply IMPORT_CACHE_ENABLED: false to the reconciler pod.

Moreover a reconciliation was not triggered by any change outside of keycloak specs. For instance changing spec.suspend to true and back to false did not trigger a reconciliation, it awaited spec.interval as well which is confusing for users.

Proposal

Remove checksum gate and disable "IMPORT_CACHE_ENABLED" by default.
Trigger a reconciliation if the keycloakrealm generation changes.

Summary by CodeRabbit

  • Improvements

    • Stricter realm reconciliation behavior for more efficient and reliable deployments
    • Disabled import cache during realm import to ensure fresh configuration application
  • Updates

    • Keycloak platform upgraded to a newer release (image/chart and default server version)
    • Test/default realm now relies on platform defaults for WebAuthn/passwordless settings
  • Chores

    • Updated CRD metadata version annotations

@raffis raffis requested a review from a team as a code owner April 30, 2026 08:23
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@raffis has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 30 minutes and 56 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a53408c6-614a-4183-9179-99e8cce7b821

📥 Commits

Reviewing files that changed from the base of the PR and between 96543e8 and 67191e7.

📒 Files selected for processing (2)
  • config/tests/cases/keycloak-v24/kustomization.yaml
  • internal/controllers/keycloakrealm_controller.go
📝 Walkthrough

Walkthrough

Bumps controller-gen annotation in CRD manifests, adjusts KeycloakRealm controller reconciliation and reconciler pod image/tag selection, sets IMPORT_CACHE_ENABLED="false" in keycloak-config-cli, updates related tests, advances test profile to keycloak-v26, and updates test kustomization and test realm YAML fields.

Changes

Cohort / File(s) Summary
CRD Manifests
chart/keycloak-controller/crds/keycloak.infra.doodle.com_keycloakclients.yaml, chart/keycloak-controller/crds/keycloak.infra.doodle.com_keycloakrealms.yaml, chart/keycloak-controller/crds/keycloak.infra.doodle.com_keycloakusers.yaml, config/base/crd/bases/keycloak.infra.doodle.com_keycloakclients.yaml, config/base/crd/bases/keycloak.infra.doodle.com_keycloakrealms.yaml, config/base/crd/bases/keycloak.infra.doodle.com_keycloakusers.yaml
Updated metadata.annotations["controller-gen.kubebuilder.io/version"] from v0.18.0 to v0.20.0 in all Keycloak CRD YAMLs; no schema changes.
KeycloakRealm controller & tests
internal/controllers/keycloakrealm_controller.go, internal/controllers/keycloakrealm_controller_test.go
Tightened reconciliation skip to require Generation == Status.ObservedGeneration; changed reconciler container image tag logic (default "latest", detect major when spec empty, use realm.Spec.Version when set); added env IMPORT_CACHE_ENABLED="false" to keycloak-config-cli; updated tests to expect new image tag format and new env var.
Makefile
Makefile
Updated TEST_PROFILE from keycloak-v20 to keycloak-v26.
Test realm YAML
config/tests/base/keycloakrealm.yaml
Removed explicit WebAuthn/passwordless fields (webAuthnPolicyExtraOrigins, webAuthnPolicyPasswordlessExtraOrigins, webAuthnPolicyPasswordlessPasskeysEnabled).
Test case kustomization
config/tests/cases/keycloak-v26/kustomization.yaml
Upgraded Helm chart version and Keycloak image tag (helmCharts.version 2.0.0 → 7.1.11, image.tag 18.0.2 → 26.6.1), changed strategic merge patch spec.version to latest-26, and removed --auto-build and --hostname-strict-https=false startup flags.

Sequence Diagram(s)

sequenceDiagram
  participant Controller as Controller (reconciler)
  participant K8sAPI as Kubernetes API
  participant Keycloak as Keycloak server
  Controller->>K8sAPI: Fetch KeycloakRealm
  alt ObservedGeneration matches and skip conditions met
    Controller->>Controller: Skip reconciliation
  else Proceed with reconcile
    Controller->>Keycloak: (optional) detect server version
    Keycloak-->>Controller: Return version (major)
    Controller->>Controller: Compute image tag (realm.Spec.Version or "latest" or "latest-<major>")
    Controller->>K8sAPI: Create/Update reconciler Pod (ENV IMPORT_CACHE_ENABLED="false", image tag)
    K8sAPI-->>Controller: Pod created/updated
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
Title check ✅ Passed The PR title accurately summarizes the two main fixes: disabling config cache (IMPORT_CACHE_ENABLED=false) and triggering reconciliation when generation changes. Both changes are clearly present in the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-stale-reconcile

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

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 30 minutes and 56 seconds.

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

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.

Caution

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

⚠️ Outside diff range comments (1)
internal/controllers/keycloakrealm_controller.go (1)

337-346: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Rate limiter now blocks legitimate drift reconciliations.

On Line 337, removing the checksum guard makes the controller skip while Ready=True purely based on interval timing (or always when interval is nil). That can suppress reconciles after watched resource changes when no reconciler pod is active, leaving drift unapplied.

💡 Suggested fix
- if readyCondition != nil && readyCondition.Status == metav1.ConditionTrue && (realm.Spec.Interval == nil || time.Since(readyCondition.LastTransitionTime.Time) < realm.Spec.Interval.Duration) {
+ if readyCondition != nil && readyCondition.Status == metav1.ConditionTrue &&
+    (realm.Spec.Interval == nil || time.Since(readyCondition.LastTransitionTime.Time) < realm.Spec.Interval.Duration) &&
+    checksum == realm.Status.ObservedSHA256 {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controllers/keycloakrealm_controller.go` around lines 337 - 346, The
skip logic around readyCondition and realm.Spec.Interval is too broad and
suppresses reconciles after watched changes; modify the condition in the
Reconcile (KeycloakRealmReconciler) so you only skip when the realm's readiness
and interval criteria AND the resource has not changed since last apply — e.g.,
require that realm.Status.LastAppliedChecksum (or
realm.Status.ObservedGeneration) equals the current calculated checksum (or
realm.GetGeneration()) in addition to readyCondition.Status == True and the
Interval timing check; if the checksum/observedGeneration differs, fall through
and perform reconciliation instead of returning early.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/controllers/keycloakrealm_controller.go`:
- Around line 337-346: The skip logic around readyCondition and
realm.Spec.Interval is too broad and suppresses reconciles after watched
changes; modify the condition in the Reconcile (KeycloakRealmReconciler) so you
only skip when the realm's readiness and interval criteria AND the resource has
not changed since last apply — e.g., require that
realm.Status.LastAppliedChecksum (or realm.Status.ObservedGeneration) equals the
current calculated checksum (or realm.GetGeneration()) in addition to
readyCondition.Status == True and the Interval timing check; if the
checksum/observedGeneration differs, fall through and perform reconciliation
instead of returning early.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 57685705-b499-4cbb-9039-8ab29acd02cb

📥 Commits

Reviewing files that changed from the base of the PR and between 8709ce8 and 503d29f.

📒 Files selected for processing (8)
  • chart/keycloak-controller/crds/keycloak.infra.doodle.com_keycloakclients.yaml
  • chart/keycloak-controller/crds/keycloak.infra.doodle.com_keycloakrealms.yaml
  • chart/keycloak-controller/crds/keycloak.infra.doodle.com_keycloakusers.yaml
  • config/base/crd/bases/keycloak.infra.doodle.com_keycloakclients.yaml
  • config/base/crd/bases/keycloak.infra.doodle.com_keycloakrealms.yaml
  • config/base/crd/bases/keycloak.infra.doodle.com_keycloakusers.yaml
  • internal/controllers/keycloakrealm_controller.go
  • internal/controllers/keycloakrealm_controller_test.go

@raffis raffis changed the title fix: do not compare checksums for reconcile trigger, disable config cache fix: disable config cache, trigger reconcile if generation changes Apr 30, 2026
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.

Caution

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

⚠️ Outside diff range comments (2)
internal/controllers/keycloakrealm_controller.go (2)

457-472: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove the dead tag default or make it an actual fallback.

tag := "latest" is overwritten on both branches, so the current change still fails the reported ineffassign check. If the intent was to fall back to latest, that fallback is not reachable because the version-detection error path returns immediately.

Suggested fix
-	tag := "latest"
+	var tag string
 	username, password, err := getSecret(ctx, r.Client, realm, usernameField, passwordField)
 	if err != nil {
 		return realm, ctrl.Result{}, err
 	}

 	if realm.Spec.Version == "" {
 		version, err := r.getKeycloakVersion(ctx, realm, username, password)
 		if err != nil {
 			return realm, reconcile.Result{}, err
 		}

 		tag = fmt.Sprintf("latest-%s", getMajor(version))
 		logger.Info("keycloak version detected", "version", version, "tag", tag)
 	} else {
 		tag = realm.Spec.Version
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controllers/keycloakrealm_controller.go` around lines 457 - 472, The
local variable tag is initialized to "latest" but that value is never used
because both branches overwrite it (and the getKeycloakVersion error path
returns), triggering the ineffassign warning; fix by either removing the dead
initialization and declare var tag string then set tag in the two branches (use
realm.Spec.Version when non-empty else set tag = fmt.Sprintf("latest-%s",
getMajor(version)) after successful getKeycloakVersion), or if you want a real
fallback, change the getKeycloakVersion error handling to not return immediately
and instead log the error and leave tag as "latest" so the fallback is used;
update the code around tag, getKeycloakVersion, and realm.Spec.Version
accordingly.

337-345: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the generation/checksum guards outside the spec.interval == nil fast path.

Lines 337-345 still skip every reconcile once the realm is Ready=True and spec.interval is nil, because realm.Spec.Interval == nil short-circuits the rest of the expression. That means a later generation-only update can still be ignored after the first successful reconcile, which misses the PR’s trigger-on-generation-change goal.

Suggested fix
-	if readyCondition != nil && readyCondition.Status == metav1.ConditionTrue && (realm.Spec.Interval == nil || time.Since(readyCondition.LastTransitionTime.Time) < realm.Spec.Interval.Duration && checksum == realm.Status.ObservedSHA256 && realm.Generation == realm.Status.ObservedGeneration) {
+	skipDueToInterval := realm.Spec.Interval == nil || time.Since(readyCondition.LastTransitionTime.Time) < realm.Spec.Interval.Duration
+	if readyCondition != nil &&
+		readyCondition.Status == metav1.ConditionTrue &&
+		skipDueToInterval &&
+		checksum == realm.Status.ObservedSHA256 &&
+		realm.Generation == realm.Status.ObservedGeneration {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/controllers/keycloakrealm_controller.go` around lines 337 - 345, The
current fast-path combines readyCondition, realm.Spec.Interval == nil and the
generation/checksum guards into one conditional, so when realm.Spec.Interval is
nil it short-circuits and skips reconciliation even if checksum or generation
changed; modify the logic in the reconciliation block around readyCondition to
first check Ready==True and then separately evaluate the generation/checksum
guards (checksum vs realm.Status.ObservedSHA256 and realm.Generation vs
realm.Status.ObservedGeneration) before deciding to return early, keeping the
realm.Spec.Interval == nil fast-path only for the interval timing (requeue
behavior via realm.Spec.Interval.Duration) and ensuring logger.V(1).Info and the
return paths only run when both interval timing and checksum/generation
conditions are satisfied.
🧹 Nitpick comments (1)
config/tests/cases/keycloak-v26/kustomization.yaml (1)

47-47: Pin to an immutable tag instead of latest-26 for test determinism.

latest-26 is a moving tag that tracks the latest keycloak-config-cli release built for Keycloak 26, which can cause CI drift over time. Use an immutable alternative such as latest-26.5.4, 6.5.0-26, or pin by digest (e.g., sha256:45fe580854ef20f6fbfe31d809265c3530248e75aa5f5843f2e66702cab06f18 for amd64) to keep test outcomes reproducible.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@config/tests/cases/keycloak-v26/kustomization.yaml` at line 47, The
kustomization.yaml currently uses a moving tag value "version: latest-26";
change this to an immutable tag or digest to ensure deterministic tests by
replacing the version field's value (the "version" key in kustomization.yaml)
with a specific release like "latest-26.5.4" or "6.5.0-26" or a pinned image
digest (e.g., the provided sha256) so CI runs use a fixed Keycloak
image/version.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@internal/controllers/keycloakrealm_controller.go`:
- Around line 457-472: The local variable tag is initialized to "latest" but
that value is never used because both branches overwrite it (and the
getKeycloakVersion error path returns), triggering the ineffassign warning; fix
by either removing the dead initialization and declare var tag string then set
tag in the two branches (use realm.Spec.Version when non-empty else set tag =
fmt.Sprintf("latest-%s", getMajor(version)) after successful
getKeycloakVersion), or if you want a real fallback, change the
getKeycloakVersion error handling to not return immediately and instead log the
error and leave tag as "latest" so the fallback is used; update the code around
tag, getKeycloakVersion, and realm.Spec.Version accordingly.
- Around line 337-345: The current fast-path combines readyCondition,
realm.Spec.Interval == nil and the generation/checksum guards into one
conditional, so when realm.Spec.Interval is nil it short-circuits and skips
reconciliation even if checksum or generation changed; modify the logic in the
reconciliation block around readyCondition to first check Ready==True and then
separately evaluate the generation/checksum guards (checksum vs
realm.Status.ObservedSHA256 and realm.Generation vs
realm.Status.ObservedGeneration) before deciding to return early, keeping the
realm.Spec.Interval == nil fast-path only for the interval timing (requeue
behavior via realm.Spec.Interval.Duration) and ensuring logger.V(1).Info and the
return paths only run when both interval timing and checksum/generation
conditions are satisfied.

---

Nitpick comments:
In `@config/tests/cases/keycloak-v26/kustomization.yaml`:
- Line 47: The kustomization.yaml currently uses a moving tag value "version:
latest-26"; change this to an immutable tag or digest to ensure deterministic
tests by replacing the version field's value (the "version" key in
kustomization.yaml) with a specific release like "latest-26.5.4" or "6.5.0-26"
or a pinned image digest (e.g., the provided sha256) so CI runs use a fixed
Keycloak image/version.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 05160fe9-fff7-402b-b5e7-d99495930386

📥 Commits

Reviewing files that changed from the base of the PR and between 503d29f and 96543e8.

📒 Files selected for processing (5)
  • Makefile
  • config/tests/base/keycloakrealm.yaml
  • config/tests/cases/keycloak-v26/kustomization.yaml
  • internal/controllers/keycloakrealm_controller.go
  • internal/controllers/keycloakrealm_controller_test.go
💤 Files with no reviewable changes (1)
  • config/tests/base/keycloakrealm.yaml
✅ Files skipped from review due to trivial changes (1)
  • Makefile

@github-actions github-actions Bot added size/L and removed size/M labels Apr 30, 2026
@raffis raffis merged commit 9789905 into master Apr 30, 2026
18 checks passed
@raffis raffis deleted the fix-stale-reconcile branch April 30, 2026 10:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants