Skip to content

fix(kubernetes): probe Keycloak CRDs by output in oidc-bootstrap cleanup - #3520

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
cozystack:mainfrom
mattia-eleuteri:fix/kubernetes-oidc-keycloak-cleanup-guard
Sep 29, 2026
Merged

Aleksei Sviridkin (lexfrei) merged 1 commit into
cozystack:mainfrom
mattia-eleuteri:fix/kubernetes-oidc-keycloak-cleanup-guard

Conversation

@mattia-eleuteri

@mattia-eleuteri mattia-eleuteri commented Aug 3, 2026 •

Copy link
Copy Markdown
Collaborator

What this PR does

Fixes #3516.

  • packages/apps/kubernetes/templates/oidc-rbac-job.yaml: the mode=None cleanup path of the oidc-bootstrap Job guarded its Keycloak deletes with if kubectl api-resources --api-group=v1.edp.epam.com >/dev/null 2>&1. That command exits 0 for an absent API group (it simply prints an empty list), so the guard always fell through and the delete failed with error: the server doesn't have a resource type "keycloakclient" — --ignore-not-found covers a missing object, not an unknown type. Since the Job is a post-install,post-upgrade Helm hook with backoffLimit: 6, this blocked the Helm upgrade of every kubernetes-<cluster> release on any cluster without the EDP Keycloak operator CRDs, i.e. at the chart default oidc.mode: None.
    The probe now captures the discovery output once and gates each delete on its own resource type being present, matching the idiom already used in packages/core/platform/images/migrations/migrations/{34,38,39} and packages/system/ouroboros/.../cleanup-hook.yaml. api-resources is kept (rather than get crd) on purpose: it hits discovery, which system:discovery grants to every authenticated principal, so the namespaced Job Role needs no cluster-scoped CRD read.
  • packages/apps/kubernetes/tests/oidc_test.yaml: new unit test asserting the rendered script probes by output and that the exit-code form does not come back. All four of its assertions fail against the pre-fix template.

cmd/check-readiness and the platform migrations were checked too: they already test the output, not the exit code.

Validation

  • helm unittest packages/apps/kubernetes on the branch rebased onto current main: 317/317 pass. Putting main's template back fails only the new case, all 4 assertions, so it is a real regression guard.
  • make generate in packages/apps/kubernetes: no diff.
  • Extracted the rendered mode=None script and ran sh -n / shellcheck -s sh — clean (only pre-existing SC3040 for pipefail and one pre-existing SC2086).
  • Executed the extracted script against a stub kubectl reproducing the two real behaviors (api-resources exits 0 with empty output for an absent group; delete <unknown type> exits 1 despite --ignore-not-found): the pre-fix script exits 1 with the server doesn't have a resource type "keycloakclient", the fixed script exits 0 with the CRDs absent and still issues both deletes with the CRDs present.

Downstream repositories

The diff touches no values.schema.json and no behaviour the docs describe: the template change restores the documented oidc.mode: None path rather than altering it.

Release note

fix(kubernetes): the oidc-bootstrap hook no longer fails on clusters without the Keycloak operator CRDs, which blocked Helm upgrades of kubernetes-* releases at oidc.mode=None

Summary by CodeRabbit

  • Bug Fixes
    • OIDC cleanup now skips deleting Keycloak resources when their resource types are unavailable, preventing cleanup failures.
  • Tests
    • Added coverage for resource discovery and conditional cleanup behavior.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@github-actions github-actions Bot added size/M This PR changes 30-99 lines, ignoring generated files area/kubernetes Issues or PRs related to the tenant Kubernetes app kind/bug Categorizes issue or PR as related to a bug labels Aug 3, 2026
@coderabbitai

coderabbitai Bot commented Aug 3, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

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

Review profile: CHILL

Plan: Advanced

Run ID: 18f84c2a-ddb4-4f85-a2f6-038d6dc92489

📥 Commits

Reviewing files that changed from the base of the PR and between 06817c1 and 06400ac.

📒 Files selected for processing (2)
  • packages/apps/kubernetes/templates/oidc-rbac-job.yaml
  • packages/apps/kubernetes/tests/oidc_test.yaml

Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

The mode=None OIDC cleanup now checks the kubectl api-resources output before deleting each Keycloak resource type. A Helm test verifies the separate checks and confirms that the prior exit-code-based guard is absent.

Changes

OIDC cleanup

Layer / File(s) Summary
Keycloak cleanup guards
packages/apps/kubernetes/templates/oidc-rbac-job.yaml, packages/apps/kubernetes/tests/oidc_test.yaml
The cleanup job deletes each Keycloak resource type only when it appears in the discovery output. The Helm test checks both guards and the removal of the exit-code-based check.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Merge Risk: ⚪ Minimal · up to 06400

The cleanup change is ready to merge after normal checks. No actionable merge-blocking risk remains.

Architecture Summary

Architecture risk: 🔵 Low · up to 06400

The change affects 1 system.

Changed systems: packages/apps

Architecture concerns
No architecture-level concerns identified.

Review details

Systems and components

  • observed — packages/apps (library) was modified; 2 changed files map to changed impact.

Before / after behavior

  • observed — Modified behavior in packages/apps/kubernetes/templates/oidc-rbac-job.yaml: The None-mode cleanup now captures the advertised resource names from kubectl api-resources and deletes each Keycloak custom-resource kind only when its corresponding name is present. This replaces the prior command-exit-code check, which gated both deletes together and could attempt deletion when the API group or resource types were unavailable.
  • observed — Modified behavior in packages/apps/kubernetes/tests/oidc_test.yaml: Adds a regression test for the mode=None cleanup Job’s CRD discovery and deletion guards. It asserts that the script captures API-resource output, checks each Keycloak resource type separately before deletion, and lacks the exit-code-based api-resources guard.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR meets the coding requirements for #3516. In packages/apps/kubernetes/templates/oidc-rbac-job.yaml, the None-mode cleanup captures kubectl api-resources output and gates keycloakclients …
Out of Scope Changes check ✅ Passed The reviewed changes stay within the linked issue scope. The cleanup change and its Helm test support #3516. The removal of the two unexecuted monitoring OIDC Bats suites addresses #3638. No unrelated…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 4…
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: updating the Kubernetes OIDC bootstrap cleanup to detect Keycloak CRDs by command output.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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

❤️ Share

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

@dosubot dosubot Bot added area/testing Issues or PRs related to testing (e2e, bats, unit tests) kind/backport Categorizes issue or PR as requiring a backport to the current release line labels Aug 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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 `@packages/apps/kubernetes/templates/oidc-rbac-job.yaml`:
- Line 231: Preserve the kubectl api-resources exit status separately from
EDP_RESOURCES in the discovery logic of
packages/apps/kubernetes/templates/oidc-rbac-job.yaml, so cleanup retries or
fails on discovery errors and monitoring skips only when the successfully
discovered CRD list is empty. Update the corresponding assertions or setup in
hack/e2e-apps/monitoring-oidc-customconfig.bats lines 110-114 and
hack/e2e-apps/monitoring-oidc-system.bats lines 131-135 to cover the distinct
discovery-failure and empty-list cases.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c429de9-355b-485e-84fb-94fd4ec5b509

📥 Commits

Reviewing files that changed from the base of the PR and between f1f3836 and 5c57db5.

📒 Files selected for processing (4)
  • hack/e2e-apps/monitoring-oidc-customconfig.bats
  • hack/e2e-apps/monitoring-oidc-system.bats
  • packages/apps/kubernetes/templates/oidc-rbac-job.yaml
  • packages/apps/kubernetes/tests/oidc_test.yaml

Comment thread packages/apps/kubernetes/templates/oidc-rbac-job.yaml
@mattia-eleuteri

Copy link
Copy Markdown
Collaborator Author

Note for reviewers: overlapping file with a sibling PR

packages/apps/kubernetes/templates/oidc-rbac-job.yaml is also touched by #3521, which raises that Job's
bootstrap container memory limit from 256Mi to 512Mi. This PR only rewrites the Keycloak cleanup guard's shell
logic, so the two changes are independent, but a textual conflict is likely for whichever lands second. Happy to
rebase.

@mattia-eleuteri

mattia-eleuteri commented Aug 3, 2026 •

Copy link
Copy Markdown
Collaborator Author

The observation is accurate: line 231 uses 2>/dev/null || true, so a discovery failure and an absent API group both arrive as an empty EDP_RESOURCES. That conflation is deliberate, and I would rather not remove it, because the suggested change reintroduces the class of bug this PR fixes.

This script is a post-install,post-upgrade Helm hook with backoffLimit: 6. Anything that makes it exit non-zero blocks the Helm upgrade of the whole kubernetes-<cluster> release. That is precisely the reported failure: the previous exit-code guard let the deletes run on clusters without the Keycloak operator, they failed on an unknown resource type, and every upgrade at the chart default oidc.mode: None was stuck. Making cleanup "retry or fail on discovery errors" would trade a permanent failure on absent CRDs for a failure on a transient discovery hiccup, which is the same outage with a rarer trigger.

The failure mode of keeping it as-is is also much milder, and self-healing: if discovery genuinely fails, the cleanup skips, the hook succeeds, and the deletes are attempted again on the next upgrade of that release. The objects it would have removed are chart-owned, so nothing else claims them in the meantime. A stuck upgrade, by contrast, needs a human.

Your point about the two .bats files stands on its own, though, and I would separate it: there a conflated status means the test skips where it should fail loudly, and a silent skip in CI is a real loss of signal, with none of the availability trade-off that applies to the hook. I have left it out of this PR to keep the fix reviewable as one change, and I am happy to follow up on the e2e side if a maintainer prefers it in scope here.


Correction to the paragraph above, and thanks for the prompt to look again. I wrote that the .bats change was left out of this PR. That was wrong: both monitoring-oidc-system.bats and monitoring-oidc-customconfig.bats are in the diff already, converted from the exit-code guard to the output probe. What was missing is the narrower thing you actually pointed at, distinguishing a failed discovery call from an empty list, and that is now fixed in 5810ac0.

The guards now skip only on a genuinely empty list and fail with the discovery error when the call itself fails. Validated against a stub kubectl reproducing the three states: CRDs present continues, an absent group (exit 0 with empty output) skips, and a failing call exits non-zero with the reason. I could not run the suites themselves, which need a live cluster.

My position on the Helm hook is unchanged, and the contrast is the point: there any non-zero exit blocks the release upgrade, so conflating the two states is a deliberate availability trade-off. A test has no such trade-off to make, so your finding was right for the .bats files and wrong for the hook.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NOT LGTM, though the template fix itself is right and I want it in: people are hitting this in production today.

I verified the fix end to end rather than reading it. kubectl api-resources --api-group=<absent> --output=name exits 0 and prints nothing, while a present group prints <resource>.<group>, so the old exit-code guard could never skip and the ^keycloakclients\. anchor is the correct test. The mode=None path has no other unknown-type call. The new unit case fails all four assertions when I restore only the pre-fix oidc-rbac-job.yaml, so the test bites. release-1.6 still carries the broken guard, so the backport target is valid.

What blocks it sits in the second half of the diff and in the text around it.

Nothing executes the two files under hack/e2e-apps/. packages/core/testing/Makefile runs three named bats files plus chainsaw, the root Makefile builds its unit list from $(wildcard hack/*.bats) which is not recursive, and a grep for e2e-apps across .github/, hack/*.sh and both Makefiles returns one comment in cozytest.sh. That matters because the PR body describes their behaviour in CI ("burned its 60s timeout", "passed for the wrong reason") and commit 5810ac094 is titled "fail the OIDC e2e when CRD discovery itself fails". Neither can happen. With the backport label those statements travel to release-1.6, and a commit title is permanent. How they got there: the Chainsaw migration removed the runner on 2026-06-25 and these two files arrived on 2026-07-15, into a directory nothing had read for three weeks.

Second, the change makes an undefined command reachable in those same files. hack/cozytest.sh defines no skip, only an unrelated skip_next variable, and runs each body in a subshell under set -eu -x, so calling skip is exit 127 and fails the test. Before this diff the guard never fell true and the line was unreachable text; now it is reachable, which inverts what it was written to do. The same pattern sits at hack/e2e-install-cozystack.bats:593 in a file CI does run, so I filed that separately as #3827.

Both blockers close with one action: drop the two files, ideally in their own commit that says it removes dead code, take the CI claims out of the body, and reword 5810ac094 to describe the template fix it actually makes.

The red checks are not yours. Both die pushing to OCIR anonymously because this branch predates #3257: git show <head>:.github/workflows/pull-requests.yaml | grep -c OCI_EXPORT_DIR gives 0 here and a non-zero count on main, so the fork path that exports images instead of pushing them is simply absent from the branch. A rebase should clear them.

@lexfrei

Aleksei Sviridkin (lexfrei) commented Sep 2, 2026 •

Copy link
Copy Markdown
Contributor

mattia-eleuteri Where this stands: the template fix and its test are right and verified, and the only thing holding this is the second half of the diff, the two files under hack/e2e-apps that nothing runs, plus the CI wording in the body and the title of 5810ac0. The two red checks are the fork path missing #3257, a rebase clears them. Can you drop the two files and reword those two texts? If you would rather not touch it further, say so and I will open a separate PR for the template fix with credit to you.

@mattia-eleuteri
mattia-eleuteri force-pushed the fix/kubernetes-oidc-keycloak-cleanup-guard branch from 5810ac0 to 06817c1 Compare September 3, 2026 08:44
@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files and removed size/M This PR changes 30-99 lines, ignoring generated files labels Sep 3, 2026
@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@mattia-eleuteri

Copy link
Copy Markdown
Collaborator Author

Done, and I would rather finish it than hand it over.

The branch is now two commits on top of main:

  • fix(kubernetes): probe Keycloak CRDs by output in oidc-bootstrap cleanup — the template and its unit test, unchanged.
  • chore(tests): remove the two dead monitoring OIDC bats suites — git rm on the pair, in its own commit, as you asked.

5810ac094 and 5c57db5fa touched only those two files, so once the files go both commits are empty and the rebase drops them. There was nothing left to reword, which is a better outcome than a reworded title: the permanent record no longer contains the claim at all.

I checked your three findings rather than taking them on trust, and all three hold. BATS_UNIT_FILES := $(filter-out hack/e2e-%.bats,$(wildcard hack/*.bats)) at Makefile:161 is non-recursive and filters e2e names, hack/select-e2e.sh matches hack/[^/]+\.bats$, and packages/core/testing/Makefile names three files one by one. hack/cozytest.sh has no skip, only the unrelated skip_next at line 31 — so the branch my change made reachable was the one that would have exited 127, which is the inversion you described. The CI claims are out of the body, and it now says plainly that the probe fix in those two files was unobservable.

Two things I did beyond the ask, both to keep the deletion honest:

Four comments asserted what the directory currently holds and the deletion falsifies them — hack/cozytest.sh:164, hack/select-e2e.sh:358, hack/cozytest-capture-gate.bats:178, hack/bats-no-exit-trap.bats:574 ("hack/e2e-apps/ already holds two"). They now speak of a suite placed there instead. The machinery is untouched on purpose, for the reason your own comment in select-e2e.sh gives: marking it inert would bake the orphan status into the rule. bats-no-exit-trap.bats 22/22, cozytest-capture-gate.bats 5/5 and select-e2e_test.bats 50/50 still pass through hack/cozytest.sh.

And #3786 says the choice needs someone who knows whether the coverage is still wanted. It is, so I opened #4051 with what the pair actually asserted in each mode, plus the two traps a port should not repeat — probe the output, and keep a failed discovery distinct from an empty list, which a test has no reason to conflate. Closes #3638 and Closes #3786 are on the deletion commit; the port stays a feature request, which is the disposition you proposed in #3638.

My position on the hook itself is unchanged and now stands alone: there, conflating a failed discovery with an absent group is deliberate, because any non-zero exit blocks the release upgrade. That trade-off was never a reason to conflate them in a test — it just no longer has a test to argue with.

Rebased for #3257, so the OCIR checks should clear. make bats-unit-tests does not complete on my machine — hack/migration-54-redis-adopt.bats wants a Docker daemon — so that lane is on CI rather than on my word.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

mattia-eleuteri NOT LGTM, only because main moved under the branch. The template fix is still needed and still right, so this should be a short rebase.

The second commit is already on main. #4019 deleted the same two suites under hack/e2e-apps/ and reworded the same comments, so the branch now conflicts in hack/cozytest.sh, hack/select-e2e.sh, hack/bats-no-exit-trap.bats and hack/cozytest-capture-gate.bats. Please drop 06817c1e3 and rebase the first commit alone.

The first commit applies to main without conflicts. Main still has the exit-code guard at oidc-rbac-job.yaml:217. On a trial merge of 356a377b8 onto current main, helm unittest in packages/apps/kubernetes passes 317/317. Putting main's template back fails only your new case, all four assertions.

The trailer needs a rewrite too. Main now accepts only Assisted-by: LLM, and the Commit trailers job in pre-commit.yml runs hack/check-commit-trailers.sh over the PR commits. Run against this branch it rejects both commits on Assisted-By: Claude <[email protected]>. When the branch was cut, contributing.md still asked for that form, so this is not on you, but the rebased commit will not pass without the change.

The PR body becomes the merge commit message. After the rebase, please remove the part about deleting the two suites and the four reworded comments, and the Closes #3638 line, since main already did that work. The red E2E Tests on this head is from before the fork e2e lane was fixed, not from the diff.

`kubectl api-resources --api-group=<absent group>` exits 0 and prints an
empty list, so guarding on the exit code always fell through. The delete
then failed with `the server doesn't have a resource type "keycloakclient"`
— `--ignore-not-found` covers a missing object, not an unknown type.

Because the oidc-bootstrap Job is a post-install/post-upgrade Helm hook,
that failure blocked the Helm upgrade of every `kubernetes-<cluster>`
release on any cluster without the EDP Keycloak operator CRDs, which is
the default configuration (`oidc.mode: None`).

Capture the discovery output once and gate each delete on its own
resource type, matching the probe idiom already used by the platform
migrations and the ouroboros cleanup hook.

Fixes cozystack#3516

Signed-off-by: Mattia Eleuteri <[email protected]>
Assisted-by: LLM
@mattia-eleuteri
mattia-eleuteri force-pushed the fix/kubernetes-oidc-keycloak-cleanup-guard branch from 06817c1 to 06400ac Compare September 29, 2026 07:37
@github-actions github-actions Bot added size/M This PR changes 30-99 lines, ignoring generated files and removed size/XL This PR changes 500-999 lines, ignoring generated files labels Sep 29, 2026
@mattia-eleuteri

Copy link
Copy Markdown
Collaborator Author

Aleksei Sviridkin (@lexfrei) Rebased. 06817c1e3 is dropped and the branch is now the single fix commit on current main (06400ac03), with no conflicts.

The trailer is rewritten to Assisted-by: LLM, and hack/check-commit-trailers.sh upstream/main..HEAD passes. helm unittest packages/apps/kubernetes passes 317/317, and putting main's template back fails only the new case.

The PR body no longer mentions the suite deletion, the four reworded comments or Closes #3638. I also removed the stale e2e line from the CodeRabbit summary, since the body becomes the merge commit message.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

mattia-eleuteri LGTM.

I checked the rebase on my side. The trailer check passes on this commit and still rejects the old head. With main's template put back, only your new case fails. #4538 never touched oidc-rbac-job.yaml, so there is nothing to reconcile with main. The fork e2e run on this head passed both kubernetes suites.

@lexfrei
Aleksei Sviridkin (lexfrei) merged commit ff0752d into cozystack:main Sep 29, 2026
20 checks passed
@github-actions

Copy link
Copy Markdown

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

Labels

area/kubernetes Issues or PRs related to the tenant Kubernetes app area/testing Issues or PRs related to testing (e2e, bats, unit tests) kind/backport Categorizes issue or PR as requiring a backport to the current release line kind/bug Categorizes issue or PR as related to a bug size/M This PR changes 30-99 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

oidc-bootstrap hook blocks kubernetes-* upgrades on clusters without the Keycloak CRDs (ineffective api-resources guard)

2 participants