Repository navigation
feat(hack): improve check-readiness.sh — wait/core/parallel modes and broader coverage - #2740
myasnikovdaniil wants to merge 1 commit into
Conversation
… broader coverage
Replace the "True"-anywhere column heuristic with structured status-condition
parsing via jsonpath, and expand coverage to all Flux sources (GitRepository,
OCIRepository, HelmRepository, HelmChart, Bucket), Kustomizations, Nodes
(Ready), APIServices (Available), CRDs (Established), and PVCs (phase=Bound).
New modes:
--wait + --timeout block until ready (exit 0) or timeout (exit 1) — CI gate
--core limit to the 5 cozystack/Flux primitives that drive an
install; minimizes apiserver load during upgrades
--parallel opt-in concurrent fetches (default is sequential to
avoid bursts of N concurrent LISTs while reconcilers
are already hammering the apiserver mid-upgrade)
--verbose show condition reason+message and N/M-not-ready footer
-n / -l scope queries to a namespace or label selector
--no-color explicit toggle (auto-disabled on non-TTY)
Also:
- Detect .spec.suspend separately from failure (rendered as SUSPENDED, not
a hard error)
- Cluster-scoped kinds (Packages, Nodes, APIServices, CRDs) parse correctly
— previously empty leading namespace shifted every column
- Default watch interval 5s → 10s (less aggressive during long upgrades)
- "CRD not installed, skipping" instead of silently scanning empty output
Assisted-By: Claude <[email protected]>
Signed-off-by: Myasnikov Daniil <[email protected]>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly refactors the Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on Gemini (@gemini-code-assist) comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
📝 WalkthroughWalkthrough
ChangesCatalog-driven readiness monitoring
🎯 3 (Moderate) | ⏱️ ~25 minutes
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 `@hack/check-readiness.sh`:
- Around line 193-209: The script currently swallows kubectl failures (via
constructs like || true and ignoring empty caches) which causes unreachable API
server or forbidden LIST to be treated as "ready"; update
ensure_api_resources_cached to return a non-zero failure when the kubectl
api-resources call fails (don't use "|| true"), propagate that error to callers,
and change kind_exists so it distinguishes between an empty result and a real
kubectl failure (return non-zero on failure instead of assuming the kind
exists); likewise, in the per-kind get and the parallel wait logic (the other
places noted) stop discarding kubectl exit codes, check and propagate those
errors up so the script exits non-zero on API errors rather than printing "All
resources are ready."
- Around line 242-275: The jsonpath currently terminates rows with a literal
newline {"\n"}, which breaks rows when .status.conditions[].message contains
newlines; change the record terminator to a non-printing separator (e.g., ASCII
Record Separator \x1e) in the jsonpath construction inside fetch_rows (replace
{"\n"} with {"\x1e"}), then after the kubectl call convert that separator back
to newlines (pipe the KUBECTL get ... output through tr $'\x1e' '\n' or
equivalent) so multiline message fields stay contained in a single logical row;
update the jsonpath strings and the "${KUBECTL[@]}" get "$kind" ... invocation
accordingly.
🪄 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
Run ID: a601d6c4-b9a2-45d4-985f-9329f29a8c76
📒 Files selected for processing (1)
hack/check-readiness.sh
| ensure_api_resources_cached() { | ||
| if [[ $__API_RESOURCES_CACHED -eq 0 ]]; then | ||
| __API_RESOURCES_CACHE=$("${KUBECTL[@]}" api-resources --no-headers 2>/dev/null || true) | ||
| __API_RESOURCES_CACHED=1 | ||
| fi | ||
| } | ||
|
|
||
| # Does the API server know about this CRD/kind? | ||
| # `kind` may be the bare resource name ("nodes") or "name.group" ("packages.cozystack.io"). | ||
| # api-resources columns: NAME [SHORTNAMES] APIVERSION NAMESPACED KIND | ||
| # (SHORTNAMES is omitted entirely when there are none, so column index shifts.) | ||
| kind_exists() { | ||
| local kind="$1" | ||
| local header_printed=0 | ||
| ensure_api_resources_cached | ||
| # If we couldn't list resources at all (e.g. API server unreachable), assume the | ||
| # kind exists and let the actual fetch fail with a real error. | ||
| [[ -z "$__API_RESOURCES_CACHE" ]] && return 0 |
There was a problem hiding this comment.
Don’t treat kubectl failures as “ready.”
api-resources, per-kind get, and parallel wait errors are all swallowed here, so an unreachable apiserver or forbidden LIST can degrade into empty row files and a final All resources are ready. That’s a false green on the script’s main path.
Also applies to: 275-275, 367-367
🤖 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 `@hack/check-readiness.sh` around lines 193 - 209, The script currently
swallows kubectl failures (via constructs like || true and ignoring empty
caches) which causes unreachable API server or forbidden LIST to be treated as
"ready"; update ensure_api_resources_cached to return a non-zero failure when
the kubectl api-resources call fails (don't use "|| true"), propagate that error
to callers, and change kind_exists so it distinguishes between an empty result
and a real kubectl failure (return non-zero on failure instead of assuming the
kind exists); likewise, in the per-kind get and the parallel wait logic (the
other places noted) stop discarding kubectl exit codes, check and propagate
those errors up so the script exits non-zero on API errors rather than printing
"All resources are ready."
| # Returns one row per object, fields separated by ASCII Unit Separator (0x1f): | ||
| # namespace<US>name<US>status<US>reason<US>message<US>suspended | ||
| # | ||
| # Why \x1f and not \t: bash `read -d ... IFS=$'\t'` treats tab as whitespace IFS | ||
| # and silently strips leading empty fields — so a cluster-scoped row that starts | ||
| # with an empty namespace would shift every subsequent column. The Unit Separator | ||
| # is non-whitespace, so leading empty fields survive intact. | ||
| # | ||
| # `status` is the literal condition value ("True"/"False"/"Unknown"/"") or the | ||
| # phase value for PVCs. | ||
| fetch_rows() { | ||
| local kind="$1" scope="$2" cond_type="$3" supports_suspend="$4" | ||
| local args=() | ||
| build_kubectl_scope_args "$scope" args | ||
|
|
||
| local jsonpath | ||
| if [[ "$cond_type" == "_phase" ]]; then | ||
| jsonpath='{range .items[*]}{.metadata.namespace}{"\x1f"}{.metadata.name}{"\x1f"}{.status.phase}{"\x1f"}{""}{"\x1f"}{""}{"\x1f"}{"false"}{"\n"}{end}' | ||
| else | ||
| local suspend_field='{"false"}' | ||
| if [[ "$supports_suspend" == "1" ]]; then | ||
| suspend_field='{.spec.suspend}' | ||
| fi | ||
| if ! echo "$line" | awk '{for(i=1;i<=NF;i++) if($i=="True") exit 0; exit 1}'; then | ||
| if [[ $header_printed -eq 0 ]]; then | ||
| echo -e "${BOLD}${YELLOW}=== ${kind} (not ready) ===${RESET}" | ||
| header_printed=1 | ||
| jsonpath='{range .items[*]}' | ||
| jsonpath+='{.metadata.namespace}{"\x1f"}' | ||
| jsonpath+='{.metadata.name}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].status}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].reason}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].message}{"\x1f"}' | ||
| jsonpath+="${suspend_field}"'{"\n"}' | ||
| jsonpath+='{end}' | ||
| fi | ||
|
|
||
| "${KUBECTL[@]}" get "$kind" "${args[@]}" -o jsonpath="$jsonpath" 2>/dev/null || true |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rows="$(mktemp)"
printf 'default\037helmrelease-a\037False\037InstallFailed\037first line\nsecond line\037false\n' > "$rows"
while IFS=$'\x1f' read -r ns name status reason message suspended; do
printf 'ns=%q name=%q status=%q reason=%q message=%q suspended=%q\n' \
"$ns" "$name" "$status" "$reason" "$message" "$suspended"
done < "$rows"Repository: cozystack/cozystack
Length of output: 231
Fix row framing for multiline message values in hack/check-readiness.sh
fetch_rows currently terminates each row with a newline ({"\n"}) while .status.conditions[..].message is emitted verbatim; a multiline message will be split into multiple records and corrupt the parsed columns (e.g., first line becomes one row and second line shifts into subsequent fields).
Suggested change
-# Returns one row per object, fields separated by ASCII Unit Separator (0x1f):
-# namespace<US>name<US>status<US>reason<US>message<US>suspended
+# Returns one row per object, fields separated by ASCII Unit Separator (0x1f)
+# and records separated by ASCII Record Separator (0x1e):
+# namespace<US>name<US>status<US>reason<US>message<US>suspended<RS>
@@
- jsonpath='{range .items[*]}{.metadata.namespace}{"\x1f"}{.metadata.name}{"\x1f"}{.status.phase}{"\x1f"}{""}{"\x1f"}{""}{"\x1f"}{"false"}{"\n"}{end}'
+ jsonpath='{range .items[*]}{.metadata.namespace}{"\x1f"}{.metadata.name}{"\x1f"}{.status.phase}{"\x1f"}{""}{"\x1f"}{""}{"\x1f"}{"false"}{"\x1e"}{end}'
@@
- jsonpath+="${suspend_field}"'{"\n"}'
+ jsonpath+="${suspend_field}"'{"\x1e"}'
@@
- while IFS=$'\x1f' read -r ns name status reason message suspended; do
+ while IFS=$'\x1f' read -r -d $'\x1e' ns name status reason message suspended; do🤖 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 `@hack/check-readiness.sh` around lines 242 - 275, The jsonpath currently
terminates rows with a literal newline {"\n"}, which breaks rows when
.status.conditions[].message contains newlines; change the record terminator to
a non-printing separator (e.g., ASCII Record Separator \x1e) in the jsonpath
construction inside fetch_rows (replace {"\n"} with {"\x1e"}), then after the
kubectl call convert that separator back to newlines (pipe the KUBECTL get ...
output through tr $'\x1e' '\n' or equivalent) so multiline message fields stay
contained in a single logical row; update the jsonpath strings and the
"${KUBECTL[@]}" get "$kind" ... invocation accordingly.
There was a problem hiding this comment.
Code Review
This pull request significantly refactors and enhances the hack/check-readiness.sh script, introducing options for wait/watch modes, timeouts, parallel fetching, namespace/selector scoping, and verbose output, while parsing status conditions directly via JSONPath. The reviewer feedback focuses on improving the robustness of the script, including propagating errors from kubectl get to avoid false positives, returning explicit exit statuses from run_once instead of relying on fragile stdout grepping, dynamically parsing the help text in usage, ensuring the cursor is restored via an EXIT trap in watch mode, and handling potential tput failures gracefully.
| jsonpath+='{end}' | ||
| fi | ||
|
|
||
| "${KUBECTL[@]}" get "$kind" "${args[@]}" -o jsonpath="$jsonpath" 2>/dev/null || true |
There was a problem hiding this comment.
Appending `|| true` to the `kubectl get` command in `fetch_rows` silently ignores any actual errors (such as authentication failures, network timeouts, or API server unavailability). If `kubectl` fails, it outputs nothing, which causes `process_rows` to see an empty file and assume all resources of that kind are ready. This can lead to dangerous false positives where the script reports "All resources are ready" when the cluster is actually unreachable or experiencing issues. Since `kind_exists` is already checked beforehand, any failure here is a genuine error and should be propagated.
| "${KUBECTL[@]}" get "$kind" "${args[@]}" -o jsonpath="$jsonpath" 2>/dev/null || true | |
| "${KUBECTL[@]}" get "$kind" "${args[@]}" -o jsonpath="$jsonpath" 2>/dev/null |
| if [[ $found_issues -eq 0 ]]; then | ||
| echo -e "${GREEN}${BOLD}All resources are ready.${RESET}" | ||
| fi | ||
| } |
There was a problem hiding this comment.
Currently, `run_once` does not return an explicit exit status, meaning it always returns `0` (success) because the last evaluated statement (the `if` block when `found_issues` is non-zero) evaluates to `0` in Bash. This forces the `WAIT_MODE` loop to use a fragile `grep` on the stdout (`grep -q "All resources are ready."`). If any resource's status message or namespace happens to contain that string, the script will falsely report success and exit prematurely. Explicitly returning `0` or `1` from `run_once` allows us to check the exit status directly and safely.
| if [[ $found_issues -eq 0 ]]; then | |
| echo -e "${GREEN}${BOLD}All resources are ready.${RESET}" | |
| fi | |
| } | |
| if [[ $found_issues -eq 0 ]]; then | |
| echo -e "${GREEN}${BOLD}All resources are ready.${RESET}" | |
| return 0 | |
| fi | |
| return 1 | |
| } |
| output=$(run_once) | ||
| now=$(date +%s) | ||
| elapsed=$((now - start)) | ||
| if echo "$output" | grep -q "All resources are ready."; then | ||
| echo -e "$output" | ||
| echo -e "${GREEN}Ready after ${elapsed}s.${RESET}" | ||
| exit 0 | ||
| fi |
There was a problem hiding this comment.
Instead of grepping the stdout of `run_once` for a magic string (which is prone to false positives if any resource name, namespace, or error message contains `"All resources are ready."`), we should check the exit status of `run_once` directly. This is much more robust and idiomatic.
| output=$(run_once) | |
| now=$(date +%s) | |
| elapsed=$((now - start)) | |
| if echo "$output" | grep -q "All resources are ready."; then | |
| echo -e "$output" | |
| echo -e "${GREEN}Ready after ${elapsed}s.${RESET}" | |
| exit 0 | |
| fi | |
| now=$(date +%s) | |
| elapsed=$((now - start)) | |
| if output=$(run_once); then | |
| echo -e "$output" | |
| echo -e "${GREEN}Ready after ${elapsed}s.${RESET}" | |
| exit 0 | |
| fi |
| usage() { | ||
| sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//' | ||
| } |
There was a problem hiding this comment.
The `usage()` function relies on hardcoded line numbers (`sed -n '2,24p'`). This is fragile because any future changes to the script's header comments, adding/removing lines, or modifying the script's structure will silently break the help output. We can make this robust by using pattern matching to extract the help text dynamically.
| usage() { | |
| sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//' | |
| } | |
| usage() { | |
| sed -n '/^# Check readiness/,/^# -h, --help/p' "$0" | sed 's/^# \\{0,1\\}//' | |
| } |
| trap 'tput cnorm 2>/dev/null || true; exit 0' INT TERM | ||
| command -v tput >/dev/null 2>&1 && tput civis 2>/dev/null || true |
There was a problem hiding this comment.
In `WATCH` mode, the cursor is hidden using `tput civis`. If the script exits due to an error (e.g., a command fails and `set -e` triggers) or is terminated by another signal, the cursor will remain hidden in the user's terminal. Using an `EXIT` trap ensures that the cursor is always restored (`tput cnorm`) regardless of how the script exits.
| trap 'tput cnorm 2>/dev/null || true; exit 0' INT TERM | |
| command -v tput >/dev/null 2>&1 && tput civis 2>/dev/null || true | |
| trap 'tput cnorm 2>/dev/null || true' EXIT | |
| trap 'exit 0' INT TERM | |
| command -v tput >/dev/null 2>&1 && tput civis 2>/dev/null || true |
| clear_screen_smooth() { | ||
| if [[ $USE_COLOR -eq 1 ]] && command -v tput >/dev/null 2>&1; then | ||
| tput cup 0 0 | ||
| tput ed | ||
| else | ||
| clear | ||
| fi | ||
| } |
There was a problem hiding this comment.
If `tput` is installed but the terminal capability database is missing or `TERM` is invalid, `tput cup` or `tput ed` can fail and print error messages to stderr without falling back to `clear`. We should redirect stderr to `/dev/null` and fall back to `clear` if the `tput` commands fail.
| clear_screen_smooth() { | |
| if [[ $USE_COLOR -eq 1 ]] && command -v tput >/dev/null 2>&1; then | |
| tput cup 0 0 | |
| tput ed | |
| else | |
| clear | |
| fi | |
| } | |
| clear_screen_smooth() { | |
| if [[ $USE_COLOR -eq 1 ]] && command -v tput >/dev/null 2>&1; then | |
| tput cup 0 0 2>/dev/null && tput ed 2>/dev/null || clear | |
| else | |
| clear | |
| fi | |
| } |
There was a problem hiding this comment.
Code Review
This pull request significantly refactors the hack/check-readiness.sh script, adding support for advanced options such as wait mode with timeouts, parallel fetching, core-only checks, verbose logging, namespace/selector scoping, and color toggling. The review feedback highlights several critical robustness issues, including potential parsing failures caused by newlines in condition messages, silenced kubectl errors, a stale API resources cache during upgrades, script crashes under set -e if tput fails, and temporary directory leaks on interrupt or failure.
| fetch_rows() { | ||
| local kind="$1" scope="$2" cond_type="$3" supports_suspend="$4" | ||
| local args=() | ||
| build_kubectl_scope_args "$scope" args | ||
|
|
||
| local jsonpath | ||
| if [[ "$cond_type" == "_phase" ]]; then | ||
| jsonpath='{range .items[*]}{.metadata.namespace}{"\x1f"}{.metadata.name}{"\x1f"}{.status.phase}{"\x1f"}{""}{"\x1f"}{""}{"\x1f"}{"false"}{"\n"}{end}' | ||
| else | ||
| local suspend_field='{"false"}' | ||
| if [[ "$supports_suspend" == "1" ]]; then | ||
| suspend_field='{.spec.suspend}' | ||
| fi | ||
| if ! echo "$line" | awk '{for(i=1;i<=NF;i++) if($i=="True") exit 0; exit 1}'; then | ||
| if [[ $header_printed -eq 0 ]]; then | ||
| echo -e "${BOLD}${YELLOW}=== ${kind} (not ready) ===${RESET}" | ||
| header_printed=1 | ||
| jsonpath='{range .items[*]}' | ||
| jsonpath+='{.metadata.namespace}{"\x1f"}' | ||
| jsonpath+='{.metadata.name}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].status}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].reason}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].message}{"\x1f"}' | ||
| jsonpath+="${suspend_field}"'{"\n"}' | ||
| jsonpath+='{end}' | ||
| fi | ||
|
|
||
| "${KUBECTL[@]}" get "$kind" "${args[@]}" -o jsonpath="$jsonpath" 2>/dev/null || true | ||
| } |
There was a problem hiding this comment.
Newline in condition messages breaks parsing & Silenced kubectl errors
- Newline Issue: Kubernetes condition messages (especially for failing
HelmReleasesorKustomizations) frequently contain newlines. Sincefetch_rowsusesjsonpathto output raw fields andprocess_rowsreads line-by-line, any newline inside a message will break the row parsing. This leads to corrupted output (e.g., printing parts of the message as non-existent resource names with statusUnknown). - Silenced Errors: Appending
|| trueand redirecting stderr to/dev/nullinfetch_rowscauses any realkubectlerrors (such as authentication failure, network timeout, or permission denied) to be silently ignored. This results in empty output files, whichprocess_rowstreats as "no issues", ultimately leading the script to falsely report "All resources are ready." and exit with 0.
Solution
Using jq (which is already a project dependency) to parse the output of kubectl get -o json is much more robust. It allows us to safely replace newlines in messages with spaces using gsub("\n"; " "), handle missing fields gracefully, and propagate real connection/auth errors correctly.
fetch_rows() {
local kind="$1" scope="$2" cond_type="$3" supports_suspend="$4"
local args=()
build_kubectl_scope_args "$scope" args
if [[ "$cond_type" == "_phase" ]]; then
"${KUBECTL[@]}" get "$kind" "${args[@]}" -o json | jq -r '.items[]? | [
(.metadata.namespace // ""),
.metadata.name,
(.status.phase // ""),
"",
"",
"false"
] | join("\u001f")'
else
"${KUBECTL[@]}" get "$kind" "${args[@]}" -o json | jq -r --arg cond "$cond_type" --arg susp "$supports_suspend" '.items[]? | [
(.metadata.namespace // ""),
.metadata.name,
([.status.conditions[]? | select(.type == $cond)][0] | .status // ""),
([.status.conditions[]? | select(.type == $cond)][0] | .reason // ""),
([.status.conditions[]? | select(.type == $cond)][0] | .message // "" | gsub("\n"; " ")),
(if $susp == "1" then .spec.suspend // false else false end | tostring)
] | join("\u001f")'
fi
}| run_once() { | ||
| found_issues=0 | ||
| local tmpdir | ||
| tmpdir=$(mktemp -d -t check-readiness.XXXXXX) |
There was a problem hiding this comment.
Stale API Resources Cache during Upgrades
__API_RESOURCES_CACHED is a global variable and is never reset to 0. During a watch or wait loop, if a new CRD is installed (which is common during upgrades), the script will never detect it and will continue to skip it. Resetting __API_RESOURCES_CACHED=0 at the start of run_once ensures the cache is refreshed per iteration.
| run_once() { | |
| found_issues=0 | |
| local tmpdir | |
| tmpdir=$(mktemp -d -t check-readiness.XXXXXX) | |
| run_once() { | |
| found_issues=0 | |
| __API_RESOURCES_CACHED=0 | |
| local tmpdir | |
| tmpdir=$(mktemp -d -t check-readiness.XXXXXX) | |
| CURRENT_TMPDIR="$tmpdir" |
| clear_screen_smooth() { | ||
| if [[ $USE_COLOR -eq 1 ]] && command -v tput >/dev/null 2>&1; then | ||
| tput cup 0 0 | ||
| tput ed | ||
| else | ||
| clear | ||
| fi | ||
| } |
There was a problem hiding this comment.
tput failure under set -e crashes the script
Under set -e, if tput cup 0 0 or tput ed fails (which happens in environments with a dumb or missing TERM variable, such as some CI/CD runners or cron jobs), the script will immediately crash. Wrapping them in a safe && chain or appending || true / || clear prevents this.
| clear_screen_smooth() { | |
| if [[ $USE_COLOR -eq 1 ]] && command -v tput >/dev/null 2>&1; then | |
| tput cup 0 0 | |
| tput ed | |
| else | |
| clear | |
| fi | |
| } | |
| clear_screen_smooth() { | |
| if [[ $USE_COLOR -eq 1 ]] && command -v tput >/dev/null 2>&1 && tput cup 0 0 2>/dev/null; then | |
| tput ed 2>/dev/null || clear | |
| else | |
| clear | |
| fi | |
| } |
| SELECTOR="" | ||
| USE_COLOR=1 | ||
| PARALLEL=0 | ||
| CORE_ONLY=0 |
There was a problem hiding this comment.
Temporary directory leak on interrupt / failure
If the script is interrupted (e.g., via Ctrl+C in watch mode) or fails due to set -e while run_once is running, the temporary directory created by mktemp will be leaked in /tmp. Registering a global cleanup trap on EXIT and tracking the current temp directory ensures it is always cleaned up.
| CORE_ONLY=0 | |
| CORE_ONLY=0 | |
| CURRENT_TMPDIR="" | |
| cleanup() { | |
| if [[ -n "${CURRENT_TMPDIR:-}" ]]; then | |
| rm -rf "$CURRENT_TMPDIR" | |
| fi | |
| } | |
| trap cleanup EXIT |
| process_rows "$entry" "$tmpdir/$i" | ||
| done | ||
|
|
||
| rm -rf "$tmpdir" |
There was a problem hiding this comment.
Code Review
This pull request refactors hack/check-readiness.sh to robustly check the readiness of cozystack, Flux, and Kubernetes resources. It replaces column heuristics with direct JSONPath parsing of status conditions, introduces sequential fetching by default (with a --parallel option) to reduce API server load, and adds options for namespace/selector filtering, verbose output, and wait timeouts. The review feedback highlights several critical robustness improvements: using the ASCII Record Separator (\x1e) instead of newlines to prevent multiline status messages from breaking the parsing loop, dynamically extracting help comments in usage() rather than hardcoding line numbers, and utilizing EXIT traps to guarantee cleanup of temporary directories and restoration of the terminal cursor during early exits.
| if [[ "$cond_type" == "_phase" ]]; then | ||
| jsonpath='{range .items[*]}{.metadata.namespace}{"\x1f"}{.metadata.name}{"\x1f"}{.status.phase}{"\x1f"}{""}{"\x1f"}{""}{"\x1f"}{"false"}{"\n"}{end}' | ||
| else | ||
| local suspend_field='{"false"}' | ||
| if [[ "$supports_suspend" == "1" ]]; then | ||
| suspend_field='{.spec.suspend}' | ||
| fi | ||
| if ! echo "$line" | awk '{for(i=1;i<=NF;i++) if($i=="True") exit 0; exit 1}'; then | ||
| if [[ $header_printed -eq 0 ]]; then | ||
| echo -e "${BOLD}${YELLOW}=== ${kind} (not ready) ===${RESET}" | ||
| header_printed=1 | ||
| jsonpath='{range .items[*]}' | ||
| jsonpath+='{.metadata.namespace}{"\x1f"}' | ||
| jsonpath+='{.metadata.name}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].status}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].reason}{"\x1f"}' | ||
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].message}{"\x1f"}' | ||
| jsonpath+="${suspend_field}"'{"\n"}' | ||
| jsonpath+='{end}' | ||
| fi |
There was a problem hiding this comment.
In Kubernetes, status condition messages (especially for failing HelmReleases or Kustomizations) often contain newlines. Since the script currently uses \n as the record separator, any newline inside a condition message will prematurely terminate the read command, causing subsequent fields and rows to be misaligned or skipped entirely.
We can solve this robustly by using the ASCII Record Separator (\x1e) as the record delimiter instead of \n.
| if [[ "$cond_type" == "_phase" ]]; then | |
| jsonpath='{range .items[*]}{.metadata.namespace}{"\x1f"}{.metadata.name}{"\x1f"}{.status.phase}{"\x1f"}{""}{"\x1f"}{""}{"\x1f"}{"false"}{"\n"}{end}' | |
| else | |
| local suspend_field='{"false"}' | |
| if [[ "$supports_suspend" == "1" ]]; then | |
| suspend_field='{.spec.suspend}' | |
| fi | |
| if ! echo "$line" | awk '{for(i=1;i<=NF;i++) if($i=="True") exit 0; exit 1}'; then | |
| if [[ $header_printed -eq 0 ]]; then | |
| echo -e "${BOLD}${YELLOW}=== ${kind} (not ready) ===${RESET}" | |
| header_printed=1 | |
| jsonpath='{range .items[*]}' | |
| jsonpath+='{.metadata.namespace}{"\x1f"}' | |
| jsonpath+='{.metadata.name}{"\x1f"}' | |
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].status}{"\x1f"}' | |
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].reason}{"\x1f"}' | |
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].message}{"\x1f"}' | |
| jsonpath+="${suspend_field}"'{"\n"}' | |
| jsonpath+='{end}' | |
| fi | |
| if [[ "$cond_type" == "_phase" ]]; then | |
| jsonpath='{range .items[*]}{.metadata.namespace}{"\\x1f"}{.metadata.name}{"\\x1f"}{.status.phase}{"\\x1f"}{""}{"\\x1f"}{""}{"\\x1f"}{"false"}{"\\x1e"}{end}' | |
| else | |
| local suspend_field='{"false"}' | |
| if [[ "$supports_suspend" == "1" ]]; then | |
| suspend_field='{.spec.suspend}' | |
| fi | |
| jsonpath='{range .items[*]}' | |
| jsonpath+='{.metadata.namespace}{"\\x1f"}' | |
| jsonpath+='{.metadata.name}{"\\x1f"}' | |
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].status}{"\\x1f"}' | |
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].reason}{"\\x1f"}' | |
| jsonpath+='{.status.conditions[?(@.type=="'"$cond_type"'")].message}{"\\x1f"}' | |
| jsonpath+="${suspend_field}"'{"\\x1e"}' | |
| jsonpath+='{end}' | |
| fi |
| while IFS=$'\x1f' read -r ns name status reason message suspended; do | ||
| [[ -z "$name" ]] && continue |
There was a problem hiding this comment.
Use read -d $'\x1e' to read records delimited by the ASCII Record Separator (\x1e), ensuring that multiline status messages do not break the parsing loop.
| while IFS=$'\x1f' read -r ns name status reason message suspended; do | |
| [[ -z "$name" ]] && continue | |
| while IFS=$'\\x1f' read -d $'\\x1e' -r ns name status reason message suspended; do | |
| [[ -z "$name" ]] && continue |
| usage() { | ||
| sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//' | ||
| } |
There was a problem hiding this comment.
The usage() function hardcodes the line numbers (2,24p) to extract the help comment block. This is fragile and will break if any comments or lines are added or removed at the top of the file. We can make this dynamic and robust by using awk to print all comment lines starting from line 2 until the first non-comment line.
| usage() { | |
| sed -n '2,24p' "$0" | sed 's/^# \{0,1\}//' | |
| } | |
| usage() { | |
| awk 'NR>1 && /^#/ { sub(/^# ?/, ""); print; next } NR>1 { exit }' "$0" | |
| } |
| local tmpdir | ||
| tmpdir=$(mktemp -d -t check-readiness.XXXXXX) |
There was a problem hiding this comment.
The temporary directory tmpdir is created in run_once() but is only cleaned up at the very end of the function. If the script exits early (e.g., due to set -e on a command failure or user interruption), the temporary directory will be left behind in /tmp.
Using an EXIT trap with double quotes ensures that the temporary directory is always cleaned up when the shell/subshell exits, even if the local variable goes out of scope.
| local tmpdir | |
| tmpdir=$(mktemp -d -t check-readiness.XXXXXX) | |
| local tmpdir | |
| tmpdir=$(mktemp -d -t check-readiness.XXXXXX) | |
| trap "rm -rf '${tmpdir}'" EXIT |
| trap 'tput cnorm 2>/dev/null || true; exit 0' INT TERM | ||
| command -v tput >/dev/null 2>&1 && tput civis 2>/dev/null || true |
There was a problem hiding this comment.
In watch mode, the terminal cursor is hidden using tput civis. The current trap only restores the cursor (tput cnorm) on INT and TERM signals. If the script exits due to an error (e.g., a command failure under set -e), the cursor will remain hidden, which degrades the user's terminal experience.
Using an EXIT trap guarantees that the cursor is always restored when the script exits for any reason.
| trap 'tput cnorm 2>/dev/null || true; exit 0' INT TERM | |
| command -v tput >/dev/null 2>&1 && tput civis 2>/dev/null || true | |
| trap 'tput cnorm 2>/dev/null || true' EXIT | |
| trap 'exit 0' INT TERM | |
| command -v tput >/dev/null 2>&1 && tput civis 2>/dev/null || true |
…2755) ## What this PR does Replaces the `hack/check-readiness.sh` diagnostic with a Go command at `cmd/check-readiness`, integrated into the build the same way as `cozypkg`: - `make check-readiness` builds `_out/bin/check-readiness`, with `Version` injected via `-ldflags`. - The tool shells out to `kubectl` and reports only **non-ready** cozystack / Flux / Kubernetes resources, parsed from status conditions (not column heuristics). Flags: `--wait`/`--timeout`, `--watch`, `--core`, `--parallel`, `--verbose`, `-n`/`-l` scoping, `--no-color`, `--version`. Adds a black-box golden test suite under `test/check-readiness/`: - A mock `kubectl` serves canned fixtures; the suite diffs stdout/stderr/exit against golden files across 30 scenarios (arg parsing, condition/phase/suspend handling, CRD-not-installed, kubectl arg propagation, wait/timeout, edge cases) plus a PTY test for color output. - `make test-check-readiness` runs it; wired into `make unit-tests`. Supersedes #2740, which added the same feature set to the bash script. ### Release note ```release-note feat(hack): replace check-readiness.sh with a `check-readiness` Go command (build with `make check-readiness`); reports non-ready cozystack/Flux/Kubernetes resources with --wait/--watch/--core/--parallel/--verbose and namespace/label scoping. ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Release Notes * **New Features** * Added `check-readiness` command for verifying Kubernetes resource readiness status across namespaces * Supports multiple execution modes: one-shot, wait with timeout, and watch with configurable intervals * Includes namespace filtering, label selectors, parallel resource fetching, and color output control * **Tests** * Added comprehensive test suite validating command behavior across various scenarios <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/cozystack/cozystack/pull/2755?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
Replace hack/check-readiness.sh with a Go command at cmd/check-readiness, wired into the build like cozypkg (`make check-readiness`, Version via -ldflags). The tool shells out to kubectl and reports only non-ready cozystack / Flux / Kubernetes resources parsed from status conditions, supporting --wait/--timeout, --watch, --core, --parallel, --verbose, -n/-l scoping, --no-color and --version. Add a black-box golden test suite under test/check-readiness driven by a mock kubectl over fixtures; `make test-check-readiness` runs it and it is wired into `make unit-tests`. Supersedes cozystack#2740, which added the same feature set to the bash script. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
|
superseeded by #2755 |
What this PR does
Rewrite of
hack/check-readiness.shfor a more accurate readiness signal duringinstalls/upgrades, while being lighter on the apiserver.
Correctness fixes
.status.conditions[?(@.type=="Ready")].statusvia jsonpath instead ofgrepping for "True" anywhere in the row. Distinguishes
False,Unknown, andmissing conditions.
.spec.suspendseparately — suspended Flux resources show asSUSPENDED(cyan), not as failures.APIServices, CRDs) had every field misaligned because bash
read -d $'\t'strips leading empty fields. Switched to ASCII Unit Separator (
\x1f).--no-colorexplicit toggle.Expanded coverage
Original: Packages, ArtifactGenerators, ExternalArtifacts, HelmReleases.
Now adds: Kustomizations, GitRepository, OCIRepository, HelmRepository,
HelmChart, Bucket, Nodes (
Ready), APIServices (Available),CRDs (
Established), PersistentVolumeClaims (Bound).New modes
--wait+--timeout 30m--core--parallel-v/--verbosereason+ first-linemessage, plusN/M not readyfooter per kind-n/-lApiserver load
Default is now sequential fetch. This script is most often used while
watching long-running upgrades, when reconcilers are already hammering the
apiserver and a burst of 14 concurrent LISTs every interval is the wrong
shape. Sequential spreads the same requests over more wall-clock and avoids
the burst.
--parallelis opt-in for one-shot runs against a healthy cluster.Default watch interval bumped 5s → 10s for the same reason.
Measured on a populated dev cluster (~60 Packages, ~126 HRs, ~38 PVCs):
--core--core --parallel--parallelBackward compatibility
-w [INTERVAL]preserved (default interval bumped). No-arg invocation stillworks (covers more kinds, same exit-code semantics).
Release note
Summary by CodeRabbit