feat(cozyreport): collect Flux/cert-manager/host context + summary.txt - #2553
Conversation
`hack/cozyreport.sh` now collects materially more signal so a triage artifact can show what is broken rather than just dumping pod logs: - Flux controller logs (helm-controller, source-controller, notification-controller, kustomize-controller, last 2000 lines) - Flux source resources (HelmRepository, OCIRepository, GitRepository, ExternalArtifact) - Decoded Helm storage secrets for non-Ready HRs (`base64 -d | base64 -d | gzip -d`) - Cluster events (all + warning-only filter) - cert-manager Certificate / CertificateRequest / Order / Challenge resources + cert-manager logs - `cozystack-operator` deployment logs (current + previous) - `Application` / `ApplicationDefinition` / `Tenant` resources - Sandbox host context per node: df, free, ps, dmesg, talosctl logs/dmesg/kubelet/containerd New `hack/cozyreport-summary.sh` writes a `summary.txt` at the archive root listing what is broken right now — first thing to read when triaging an artifact. Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com> Assisted-By: Claude <noreply@anthropic.com>
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 improves the utility of the cozyreport diagnostic tool by broadening the scope of collected cluster state and adding an automated summary generation step. These changes aim to reduce the time required to triage E2E failures by providing a comprehensive snapshot of the cluster environment and a high-level overview of current issues directly within the diagnostic archive. Highlights
🧠 New Feature in Public Preview: You can now enable Memory 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 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 counter productive. 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
|
📝 WalkthroughWalkthroughTwo shell scripts extend cluster diagnostics: a new ChangesCluster Diagnostics Tooling
Sequence DiagramsequenceDiagram
participant Report as cozyreport.sh
participant Summary as cozyreport-summary.sh
participant K8s as Kubernetes API
participant Helm as Helm storage (Secrets)
participant Host as Host filesystem / talosctl
Report->>K8s: Collect Cozystack/operator logs & ConfigMaps
Report->>K8s: Collect Flux controller logs and source objects
Report->>K8s: Collect cert-manager resources and controller logs
Report->>K8s: Collect non-Normal events (warnings)
Report->>K8s: Query HelmReleases and locate Helm storage secrets
Report->>Helm: Decode & decompress latest release payload -> helm-release.json
Report->>K8s: Collect Cozystack app CRs (non-Ready)
Report->>Host: Gather df, free, ps, dmesg
alt /workspace/talosconfig exists
Report->>Host: talosctl -> fetch node dmesg, kubelet, containerd logs
end
Report->>Summary: Run summary script to query live cluster state for broken resources
Summary->>K8s: Query HelmReleases, pods, events, cert-manager, Flux sources, PVC/PV, nodes
Summary->>Host: Write summary.txt
Report->>Host: Create compressed archive
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~45 minutes
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 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. Review rate limit: 6/8 reviews remaining, refill in 9 minutes and 34 seconds.Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@hack/cozyreport-summary.sh`:
- Around line 31-34: The "## OOMKilled in last 30 min" heading is misleading
because the kubectl command (kubectl get events -A --field-selector
reason=OOMKilling --sort-by=.lastTimestamp) only limits count, not time; either
change the heading to "Recent OOMKilled events" or actually filter events to the
last 30 minutes: run kubectl get events -A -o json and use a JSON processor (jq
or python) to compare each event.metadata.creationTimestamp/.lastTimestamp
against (now - 30m), then sort by .lastTimestamp and print the tail as before;
update the echo line (echo "## OOMKilled in last 30 min") and replace the
kubectl pipeline accordingly so the output matches the label.
- Around line 53-56: The loop that prints non-ready Flux resources (for kind in
helmrepositories.source.toolkit.fluxcd.io
ocirepositories.source.toolkit.fluxcd.io
gitrepositories.source.toolkit.fluxcd.io) omits
externalartifacts.source.toolkit.fluxcd.io, so ExternalArtifact failures never
appear in summary.txt; update that loop to include
externalartifacts.source.toolkit.fluxcd.io (keeping the existing k extraction
${kind%%.*}) so ExternalArtifact resources are queried and printed the same way
as the other Flux kinds.
In `@hack/cozyreport.sh`:
- Around line 253-254: The call to hack/cozyreport-summary.sh fails unless the
CWD is the repo root; compute the script directory near the top (e.g., set
SCRIPT_DIR using: SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)) and
replace the invocation echo "Generating summary..." hack/cozyreport-summary.sh >
$REPORT_DIR/summary.txt 2>&1 || true with a resolved path using
"$SCRIPT_DIR/cozyreport-summary.sh" so the summary runs regardless of caller
CWD; keep REPORT_DIR usage the same.
- Around line 243-248: The script currently hardcodes the Talos node IPs
(192.168.123.11-13) so talosctl calls only capture those machines; replace the
fixed list in the for loop with a dynamic node enumeration using talosctl (e.g.,
capture the node list from a talosctl nodes/list command using --talosconfig
/workspace/talosconfig and iterate over that output), and add a safe fallback to
the original static list if the talosctl query fails; update the loop that calls
talosctl (the lines invoking talosctl --talosconfig /workspace/talosconfig -n
$node dmesg/logs) so it uses the dynamically discovered $node values and
preserves the existing DIR/talos-$node-*.txt naming and the "|| true" behavior.
🪄 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: bc4c7dd7-4486-456c-a6fb-4429c2b8e05c
📒 Files selected for processing (2)
hack/cozyreport-summary.shhack/cozyreport.sh
There was a problem hiding this comment.
Code Review
This pull request enhances the diagnostic capabilities of the cozyreport utility by adding modules for Flux controller state, cert-manager, Kubernetes events, Helm release secrets, and Cozystack applications, alongside a new sandbox-host diagnostic module. I have provided feedback regarding potential logic errors in the Helm and Flux resource readiness checks, the need for robust CRD existence verification to prevent script termination, and a recommendation to dynamically discover node IPs for the sandbox-host module rather than using hardcoded values.
| kubectl get hr -A --no-headers 2>/dev/null \ | ||
| | awk '$4 != "True" {printf " %s/%s — %s\n", $1, $2, $5}' \ | ||
| | head -40 |
There was a problem hiding this comment.
There are two issues here:
- Logic Error: In the output of
kubectl get hr -A, theREADYcolumn is typically the 3rd column ($3), and theSTATUSmessage is the 4th ($4). Checking$4 != "True"will incorrectly flag all releases as not ready because$4contains the status message string. - Reliability: Since the script uses
set -e(line 5), it will terminate immediately if theHelmReleaseCRD is not installed in the cluster. It is safer to check for the CRD's existence first.
| kubectl get hr -A --no-headers 2>/dev/null \ | |
| | awk '$4 != "True" {printf " %s/%s — %s\n", $1, $2, $5}' \ | |
| | head -40 | |
| if kubectl get crd helmreleases.helm.toolkit.fluxcd.io >/dev/null 2>&1; then | |
| kubectl get hr -A --no-headers 2>/dev/null \ | |
| | awk '$3 != "True" {printf " %s/%s — %s\n", $1, $2, $4}' \ | |
| | head -40 | |
| fi |
There was a problem hiding this comment.
Two parts:
Column position: I think the analysis is off here. Cozystack ships Flux v2.7 (packages/system/fluxcd/values.yaml → flux-instance.instance.distribution.version: 2.7.x), where helmreleases.helm.toolkit.fluxcd.io v2 printer columns are NAME AGE READY STATUS. With -A: NAMESPACE NAME AGE READY STATUS → $4 is READY, $5 is the status message. The same $4 != "True" pattern is in the pre-existing cozyreport.sh:101 and works in our E2E. Keeping $4.
CRD guard: Adopted — wrapped the HR block in if kubectl get crd helmreleases.helm.toolkit.fluxcd.io >/dev/null 2>&1; then ... fi to match the cert-manager block pattern at line 45.
Fixed in b40ab29.
| for kind in helmrepositories.source.toolkit.fluxcd.io ocirepositories.source.toolkit.fluxcd.io gitrepositories.source.toolkit.fluxcd.io; do | ||
| kubectl get $kind -A --no-headers 2>/dev/null \ | ||
| | awk -v k="${kind%%.*}" '$4 != "True" {printf " %s %s/%s — Ready=%s\n", k, $1, $2, $4}' | ||
| done |
There was a problem hiding this comment.
Similar to the HelmRelease check, if any of these Flux source CRDs are missing, the script will exit prematurely due to set -e. Adding a check for the CRD existence inside the loop ensures the script continues to other diagnostic sections.
| for kind in helmrepositories.source.toolkit.fluxcd.io ocirepositories.source.toolkit.fluxcd.io gitrepositories.source.toolkit.fluxcd.io; do | |
| kubectl get $kind -A --no-headers 2>/dev/null \ | |
| | awk -v k="${kind%%.*}" '$4 != "True" {printf " %s %s/%s — Ready=%s\n", k, $1, $2, $4}' | |
| done | |
| for kind in helmrepositories.source.toolkit.fluxcd.io ocirepositories.source.toolkit.fluxcd.io gitrepositories.source.toolkit.fluxcd.io; do | |
| kubectl get crd $kind >/dev/null 2>&1 || continue | |
| kubectl get $kind -A --no-headers 2>/dev/null \ | |
| | awk -v k="${kind%%.*}" '$4 != "True" {printf " %s %s/%s — Ready=%s\n", k, $1, $2, $4}' | |
| done |
There was a problem hiding this comment.
Adopted the CRD guard, plus two related changes the comment didn't surface:
- Added
externalartifacts.source.toolkit.fluxcd.ioto the loop (cozyreport.sh:37 already collects it; summary should match). - Switched the loop from column-based
awktojsonpathagainst.status.conditions[?(@.type=="Ready")].status. The reason: printer columns differ between source kinds — HelmRepository v1 isURL/AGE/READY/STATUSwhile OCIRepository v1beta2 has different ordering, so a single$Nfield index is wrong for at least one of them.jsonpathsidesteps that.
Fixed in b40ab29.
| short=${kind%%.*} | ||
| if kubectl get crd $kind >/dev/null 2>&1; then | ||
| kubectl get $kind -A > $DIR/$short.txt 2>&1 | ||
| kubectl get $kind -A --no-headers 2>/dev/null | awk 'NF >= 3 && $NF != "True" && $NF != "Ready"' | \ |
There was a problem hiding this comment.
The use of $NF (the last column) to check for readiness is problematic. In standard kubectl output, the last column is AGE (e.g., "10m"). Since "10m" is never "True" or "Ready", this filter will always evaluate to true, causing the script to collect detailed diagnostics for every single resource regardless of its actual state. For namespaced resources like Application, the READY column is typically $3.
| kubectl get $kind -A --no-headers 2>/dev/null | awk 'NF >= 3 && $NF != "True" && $NF != "Ready"' | \ | |
| kubectl get $kind -A --no-headers 2>/dev/null | awk '$3 != "True" && $3 != "Ready"' | \ |
There was a problem hiding this comment.
Real bug — the cozystack apps CRDs end with AGE, so $NF was never "True"/"Ready" and we were collecting full yaml + describe for every Application/ApplicationDefinition/Tenant on the cluster. Switched the filter to jsonpath against .status.conditions[?(@.type=="Ready")].status, so only non-Ready resources get the detailed treatment.
Fixed in b40ab29.
| ps auxww > $DIR/ps.txt 2>&1 | ||
| dmesg | tail -200 > $DIR/dmesg.txt 2>&1 || true | ||
| if [ -f /workspace/talosconfig ]; then | ||
| for node in 192.168.123.11 192.168.123.12 192.168.123.13; do |
There was a problem hiding this comment.
The node IPs are hardcoded to the specific range used in the E2E environment. This makes the sandbox-host module useless on clusters with different networking. It is better to dynamically discover the node IPs from the cluster.
| for node in 192.168.123.11 192.168.123.12 192.168.123.13; do | |
| NODES=$(kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="InternalIP")].address}' 2>/dev/null) | |
| for node in ${NODES:-192.168.123.11 192.168.123.12 192.168.123.13}; do |
There was a problem hiding this comment.
Same fix as in #discussion_r3176660510 — adopted dynamic kubectl get nodes discovery with the existing 192.168.123.11-13 list retained as fallback (${NODES:-...}) so the block still works if the apiserver is unreachable.
Fixed in b40ab29.
- Resolve hack/cozyreport-summary.sh via SCRIPT_DIR so cozyreport.sh
can be invoked from any working directory, not only the repo root.
- Discover Talos node IPs via `kubectl get nodes` (with the existing
192.168.123.11-13 list as fallback) instead of hardcoding the E2E
sandbox topology.
- Replace broken `awk '$NF != "True"'` filter on the cozystack-apps
loop with a jsonpath check against the Ready condition; the previous
filter compared AGE ("10m" etc.) against "True" and let every
resource through, defeating the non-Ready filter.
- Switch the Flux source CRD summary loop from column parsing to
jsonpath (column positions differ between HelmRepository and
OCIRepository printer columns) and add ExternalArtifact, matching
what cozyreport.sh already collects.
- Add `kubectl get crd` guards before HR/source CRD queries so the
summary script does not abort under `set -eu` when Flux v2 is not
installed.
- Relabel "OOMKilled in last 30 min" to "Recent OOMKilled events
(last 20)" — the kubectl call only limits count, not time.
Surfaced from coderabbit and gemini review on #2553.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
hack/cozyreport.sh (1)
38-41: ⚡ Quick winSkip missing Flux source CRDs before collecting artifacts.
This loop currently records
kubectlerrors intoflux/*.txtandflux/*.yamlwhen one of the source CRDs is absent.hack/cozyreport-summary.shalready guards the same kinds, so mirroring that check here keeps the report clean on clusters without the full Flux source set.Suggested fix
for kind in helmrepositories.source.toolkit.fluxcd.io ocirepositories.source.toolkit.fluxcd.io gitrepositories.source.toolkit.fluxcd.io externalartifacts.source.toolkit.fluxcd.io; do short=${kind%%.*} - kubectl get $kind -A > $REPORT_DIR/flux/$short.txt 2>&1 - kubectl get $kind -A -o yaml > $REPORT_DIR/flux/$short.yaml 2>&1 + kubectl get crd "$kind" >/dev/null 2>&1 || continue + kubectl get "$kind" -A > "$REPORT_DIR/flux/$short.txt" 2>&1 + kubectl get "$kind" -A -o yaml > "$REPORT_DIR/flux/$short.yaml" 2>&1 done🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@hack/cozyreport.sh` around lines 38 - 41, Before running kubectl get for each Flux source kind in the loop (the variable kind and short in hack/cozyreport.sh), first check whether the corresponding CRD exists and skip collection if it does not; implement this by querying the cluster for the CRD (e.g., via kubectl get crd $kind or an equivalent API-resources check) and only execute the two kubectl get commands that write to $REPORT_DIR/flux/$short.txt and $REPORT_DIR/flux/$short.yaml when the CRD is present, so absent Flux source CRDs do not produce error-filled report files.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@hack/cozyreport.sh`:
- Around line 38-41: Before running kubectl get for each Flux source kind in the
loop (the variable kind and short in hack/cozyreport.sh), first check whether
the corresponding CRD exists and skip collection if it does not; implement this
by querying the cluster for the CRD (e.g., via kubectl get crd $kind or an
equivalent API-resources check) and only execute the two kubectl get commands
that write to $REPORT_DIR/flux/$short.txt and $REPORT_DIR/flux/$short.yaml when
the CRD is present, so absent Flux source CRDs do not produce error-filled
report files.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 28bd0d90-e759-4403-be66-fff38b226b47
📒 Files selected for processing (2)
hack/cozyreport-summary.shhack/cozyreport.sh
Aleksei Sviridkin (lexfrei)
left a comment
There was a problem hiding this comment.
LGTM. Addresses the diagnostic-coverage gap surfaced from #2500 with a clean, additive change to two shell scripts. All bot findings from the first review pass are folded into b40ab29:
- "OOMKilled in last 30 min" heading relabeled to match the actual
--field-selector reason=OOMKillingsemantics (no time bound) — pragmatic call to avoid pulling jq/python into an otherwise pure-shscript. externalartifacts.source.toolkit.fluxcd.ionow in the summary loop, with the loop switched from column-basedawkto JSONPath so layout differences between source CRDs (HelmRepository's columns differ from OCIRepository) don't silently misclassify resources. Per-kindkubectl get crd … || continueguard keeps the script working on clusters without Flux v2.- Dynamic node IP discovery from
kubectl get nodeswith a fallback to the E2E sandbox IPs — the sandbox-host module now works on arbitrary clusters. cozyreport-summary.shinvoked via$SCRIPT_DIR(resolved throughCDPATH=+dirname $0) socozyreport.shworks from any CWD, not just repo root.kubectl get hr -A | awk '$4 != "True"'is correct against helm-controller v1.4.3 (api/v2/helmrelease_types.goexportsAge/Ready/Statusprintcolumns; with-Acolumns areNAMESPACE NAME AGE READY STATUS, so$4is Ready). The Gemini comment claiming$3is wrong — keep what's there.base64 -d | base64 -d | gzip -dfor Helm storage Secrets is correct: helm itself base64-encodes the gzipped JSON, then Kubernetes wraps the resulting string in Secret data which is base64-encoded again, so two decodes are required.
E2E Tests is currently red on this PR, but the failure is in Install Cozystack → Wait for Cluster-API provider deployments on all three retry attempts — well before collect-report runs, and unrelated to anything in this diff (the new code only executes at the end of an install, on report collection). Looks like a CAPI bootstrap flake on the oracle runners. A clean re-run should pass; please push an empty commit to trigger one before merge so the gate is actually green rather than waved through.
Non-blocking nits worth knowing about, none required for merge:
- Variable quoting in both scripts is sparse —
$NAMESPACE,$NAME,$REPORT_DIR, etc. unquoted across most loops. Kubernetes resource names can't contain spaces (they're DNS-label-validated) so this won't bite in practice, butset -euo pipefail+ consistent quoting would harden the scripts against future changes that pipe in less-trusted strings (e.g. message bodies from events). cozyreport.shdoesn'tset -e;cozyreport-summary.shdoes. Inconsistent — first is best-effort by design ("collect what you can, log errors into the file"), second is fail-fast wrapped in|| trueat the call site. Worth a one-line comment at the top ofcozyreport.shexplaining the choice so the next maintainer doesn't "fix" it.- Pipelines like
kubectl get … | awk …swallowkubectlexit codes (nopipefail). For a diagnostic script, recoverable failure is fine, but it's worth a one-lineset -o pipefailat the top ofcozyreport-summary.sh(alreadyset -eu) so failedkubectlcalls surface as missing sections rather than silently empty ones. - The
kubectl get events --field-selector reason=OOMKillingfilter catches kernel-OOM events emitted by kubelet, but not container exit-137 cases that don't escalate to a node-level OOMKilling event (e.g. cgroup-only kills with no kernel pressure). For a future iteration,kubectl get pod -A -o jsonpath='{range .items[*]}{range .status.containerStatuses[?(@.lastState.terminated.reason=="OOMKilled")]}{…}{end}{end}'catches the container-state path. Out of scope here.
What this PR does
Makes
cozyreport.tgzactually useful when triaging an E2E failure deeper than a single pod log. Adds:HelmRepository,OCIRepository,GitRepository,ExternalArtifact)base64 -d | base64 -d | gzip -d)Certificate/CertificateRequest/Order/Challengeresources + cert-manager logscozystack-operatordeployment logs (current + previous)Application/ApplicationDefinition/Tenantresourcesdf,free,ps,dmesg,talosctl logs/dmesg/kubelet/containerdNew executable
hack/cozyreport-summary.shwrites asummary.txtat the archive root listing what is broken right now — the first thing to read when downloading the artifact from a CI failure.Surfaced from #2500.
Release note
Summary by CodeRabbit