fix: harden k8s deploy scripts against pipefail early exits#1079
Conversation
📝 WalkthroughWalkthrough为 Kubernetes 部署文档和 Ingress 清单补充客户端 IP 透传与受信任代理说明,重构并提取 k3s 镜像 digest 解析/更新逻辑,增强 deploy 脚本的检测与随机生成保证,且新增/扩展多处 Vitest 单元测试覆盖这些变更。 Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
🚥 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.
Code Review
This pull request enhances the Kubernetes deployment infrastructure by improving documentation for client IP passthrough and refactoring shell scripts for better reliability. Key changes include the introduction of robust image digest resolution for k3s, safer shell helpers for random string generation and storage class detection that avoid piping to head, and the addition of comprehensive unit tests for these deployment scripts. Feedback was provided regarding the image tag stripping logic in scripts/cch, which currently risks incorrectly removing port numbers from registry URLs; a more robust pattern-matching approach was suggested to handle these edge cases.
| if [[ -n "$image_digest" ]] && [[ "${image_digest#sha256:}" != "$image_digest" ]]; then | ||
| image_by_digest="${image%:*}@${image_digest}" | ||
| info " digest: $image_digest" | ||
| $KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$image_by_digest" >/dev/null |
There was a problem hiding this comment.
The logic for stripping the image tag using ${image%:*} is unreliable when the image reference includes a port number but no tag (e.g., localhost:5000/myimage). In such cases, it would incorrectly strip the port instead of the tag. A more robust approach is to only strip the suffix if it matches a tag pattern (a colon followed by characters not containing a slash).
if [[ -n "$image_digest" ]] && [[ "${image_digest#sha256:}" != "$image_digest" ]]; then
local repo="$image"
if [[ "$image" =~ :[^/]+$ ]]; then
repo="${image%:*}"
fi
image_by_digest="${repo}@${image_digest}"
info " digest: $image_digest"
$KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$image_by_digest" >/dev/null
| restart_k3s_rollout_with_image() { | ||
| local reason="$1" | ||
| warn "$reason" | ||
| if ! $KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$IMAGE" >/dev/null; then | ||
| err "set image 失败,未能应用目标镜像: $IMAGE" | ||
| return 1 | ||
| fi | ||
| $KUBECTL -n "$NAMESPACE" rollout restart deployment/claude-code-hub >/dev/null | ||
| } |
There was a problem hiding this comment.
restart_k3s_rollout_with_image references global $IMAGE instead of the passed image
update_k3s_image_by_digest_or_restart accepts $image as a local parameter, but restart_k3s_rollout_with_image hard-codes the global $IMAGE. In the current call-site they are identical, but if the helper is ever invoked with a different image than $IMAGE, the fallback will silently set the wrong image rather than the caller-supplied one.
| restart_k3s_rollout_with_image() { | |
| local reason="$1" | |
| warn "$reason" | |
| if ! $KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$IMAGE" >/dev/null; then | |
| err "set image 失败,未能应用目标镜像: $IMAGE" | |
| return 1 | |
| fi | |
| $KUBECTL -n "$NAMESPACE" rollout restart deployment/claude-code-hub >/dev/null | |
| } | |
| restart_k3s_rollout_with_image() { | |
| local reason="$1" | |
| local target_image="${2:-$IMAGE}" | |
| warn "$reason" | |
| if ! $KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$target_image" >/dev/null; then | |
| err "set image 失败,未能应用目标镜像: $target_image" | |
| return 1 | |
| fi | |
| $KUBECTL -n "$NAMESPACE" rollout restart deployment/claude-code-hub >/dev/null | |
| } |
Then call it from update_k3s_image_by_digest_or_restart as restart_k3s_rollout_with_image "reason" "$image" to close the coupling.
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/cch
Line: 220-228
Comment:
**`restart_k3s_rollout_with_image` references global `$IMAGE` instead of the passed image**
`update_k3s_image_by_digest_or_restart` accepts `$image` as a local parameter, but `restart_k3s_rollout_with_image` hard-codes the global `$IMAGE`. In the current call-site they are identical, but if the helper is ever invoked with a different image than `$IMAGE`, the fallback will silently set the wrong image rather than the caller-supplied one.
```suggestion
restart_k3s_rollout_with_image() {
local reason="$1"
local target_image="${2:-$IMAGE}"
warn "$reason"
if ! $KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$target_image" >/dev/null; then
err "set image 失败,未能应用目标镜像: $target_image"
return 1
fi
$KUBECTL -n "$NAMESPACE" rollout restart deployment/claude-code-hub >/dev/null
}
```
Then call it from `update_k3s_image_by_digest_or_restart` as `restart_k3s_rollout_with_image "reason" "$image"` to close the coupling.
How can I resolve this? If you propose a fix, please make it concise.| random+=$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none 2>/dev/null | tr -dc 'A-Za-z0-9') | ||
| done | ||
| fi | ||
| printf '%s' "${random:0:length}" |
There was a problem hiding this comment.
Unconventional bare variable in parameter expansion length position
${random:0:length} works because bash treats the length argument as an arithmetic expression where bare names resolve as variables — but it is unusual and easy to misread as a literal string "length". The more idiomatic form is ${random:0:$length}.
| printf '%s' "${random:0:length}" | |
| printf '%s' "${random:0:$length}" |
Prompt To Fix With AI
This is a comment left during a code review.
Path: scripts/deploy-k8s.sh
Line: 572
Comment:
**Unconventional bare variable in parameter expansion length position**
`${random:0:length}` works because bash treats the length argument as an arithmetic expression where bare names resolve as variables — but it is unusual and easy to misread as a literal string `"length"`. The more idiomatic form is `${random:0:$length}`.
```suggestion
printf '%s' "${random:0:$length}"
```
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| [ | ||
| "-lc", | ||
| ` |
There was a problem hiding this comment.
Login shell (
-l) sourcing user profiles may cause CI flakiness
bash -lc loads .bash_profile / .bashrc, which can add PATH entries, set umask, or alias builtins differently depending on the environment. If CI runners don't have the same login shell config as local machines, tests can pass locally and fail on CI. Consider using bash -c (non-login) and explicitly setting any needed PATH values in the env block instead.
Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/k8s-cch-update-flow.test.ts
Line: 7-9
Comment:
**Login shell (`-l`) sourcing user profiles may cause CI flakiness**
`bash -lc` loads `.bash_profile` / `.bashrc`, which can add PATH entries, set `umask`, or alias builtins differently depending on the environment. If CI runners don't have the same login shell config as local machines, tests can pass locally and fail on CI. Consider using `bash -c` (non-login) and explicitly setting any needed `PATH` values in the `env` block instead.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (2)
scripts/cch (1)
220-248: 回退路径也应使用传入的镜像参数。
update_k3s_image_by_digest_or_restart "$image"的 digest 路径使用参数,但 fallback helper 又读全局IMAGE;如果以后复用 helper 传入非全局镜像,会回退到错误 tag。建议调整
restart_k3s_rollout_with_image() { local reason="$1" + local image="$2" warn "$reason" - if ! $KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$IMAGE" >/dev/null; then - err "set image 失败,未能应用目标镜像: $IMAGE" + if ! $KUBECTL -n "$NAMESPACE" set image deployment/claude-code-hub app="$image" >/dev/null; then + err "set image 失败,未能应用目标镜像: $image" return 1 fi $KUBECTL -n "$NAMESPACE" rollout restart deployment/claude-code-hub >/dev/null } @@ else - restart_k3s_rollout_with_image "未解析到可用 digest,回落到 rollout restart" + restart_k3s_rollout_with_image "未解析到可用 digest,回落到 rollout restart" "$image" fi else - restart_k3s_rollout_with_image "k3s ctr images ls 失败,回落到 rollout restart" + restart_k3s_rollout_with_image "k3s ctr images ls 失败,回落到 rollout restart" "$image" fi }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/cch` around lines 220 - 248, The fallback path currently uses the global IMAGE inside restart_k3s_rollout_with_image, which breaks callers like update_k3s_image_by_digest_or_restart that pass a specific image; change the helper to accept and use the image parameter (or add a second parameter) so restart_k3s_rollout_with_image uses the passed image argument rather than the global IMAGE, and update_k3s_image_by_digest_or_restart should call restart_k3s_rollout_with_image with the local image variable when invoking the fallback.tests/unit/k8s-deploy-assets-review-fixes.test.ts (1)
60-64: 把 pipefail 回归测试改为断言不变量,而不是只匹配旧字符串。这些精确
toContain/not.toContain只能防住旧实现原文;等价回归如head -n 1、awk ... exit仍可能漏掉。建议对相关函数体做更宽但有边界的断言。建议修改
function readRepoFile(relativePath: string) { return readFileSync(resolve(process.cwd(), relativePath), "utf8"); } + +function shellFunctionBody(source: string, name: string) { + const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const match = source.match(new RegExp(`(?:^|\\n)${escapedName}\\(\\) \\{[\\s\\S]*?\\n\\}`)); + + expect(match, `${name} should exist`).not.toBeNull(); + return match?.[0] ?? ""; +}- expect(deployScript).not.toContain("| head -1"); - expect(deployScript).not.toContain('| head -c "$length"'); + expect(deployScript).not.toMatch(/\|\s*head\s+(?:-1|-n\s+1|-c\s+"\$length")/);- expect(cchScript).not.toContain( - "IMAGE_DIGEST=$(sudo k3s ctr images ls 2>/dev/null | awk -v img=\"$IMAGE\" '$1==img {print $3; exit}')" - ); + const digestResolver = shellFunctionBody(cchScript, "resolve_k3s_image_digest"); + + expect(digestResolver).not.toMatch(/\bexit\b/); expect(cchScript).toContain("load_config_file()"); expect(cchScript).toContain("resolve_k3s_image_digest()"); expect(cchScript).toContain("update_k3s_image_by_digest_or_restart"); @@ expect(cchScript).toContain('if [[ "${1:-}" =~ ^[0-9]+$ ]]; then'); expect(cchScript).toContain("if image_digest=$(resolve_k3s_image_digest"); - expect(cchScript).toContain("found==0 && $1==img { print $3; found=1 }"); + expect(digestResolver).toContain("found==0 && $1==img { print $3; found=1 }");Also applies to: 73-85
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/k8s-deploy-assets-review-fixes.test.ts` around lines 60 - 64, The tests currently assert exact substrings on deployScript (e.g., forbidding "| head -1" or '| head -c "$length"') which allows equivalent regressions (like "head -n 1" or awk-based truncation) to slip; change these to assert invariants instead: verify that the generated script contains the sanitization/truncation logic by checking for patterns or sequences (e.g., presence of default_sc assignment pattern "default_sc" and a newline-trim operation, the length-driven loop 'while [[ "${`#random`}" -lt "$length" ]]', and the conditional 'if [[ "${DEPLOY_K8S_SOURCE_ONLY' ) and assert that no unprotected pipelines are used for truncation by matching against broader regexes such as any use of "head" with -n/-1 or any inline awk truncation; update the tests around deployScript (and the similar block at 73-85) to use these pattern/reg-exp style assertions rather than exact string matches so equivalent implementations are caught.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/k8s-deployment.md`:
- Around line 272-275: Update the paragraph to clarify that a k3s digest parse
failure itself does not trigger a rollback; instead, when k3s digest parsing
fails cch update falls back to the same-tag "set image + rollout restart" path,
and only if a subsequent rollout or health-check fails will "rollout undo" be
executed and the replica count restored—so limit the digest fallback explanation
to the subsequent rollout/health-check failure scenario and mention the fallback
path ("set image + rollout restart") and the condition ("后续 rollout/健康检查失败 时才会执行
rollout undo").
In `@tests/unit/k8s-deploy-shell-helpers.test.ts`:
- Around line 46-65: The test claims to verify behavior "without head pipelines"
but still allows a real head binary to succeed; stub out the head command inside
the test script so any use of head fails—add a head() { return 1; } (or
equivalent) before calling detect_storage_class so the test forces the code path
that does not rely on piping into head; update the test inside the
kubectl_stub/detect_storage_class block to include this head stub to ensure
regressions using "| head -1" will fail the test.
---
Nitpick comments:
In `@scripts/cch`:
- Around line 220-248: The fallback path currently uses the global IMAGE inside
restart_k3s_rollout_with_image, which breaks callers like
update_k3s_image_by_digest_or_restart that pass a specific image; change the
helper to accept and use the image parameter (or add a second parameter) so
restart_k3s_rollout_with_image uses the passed image argument rather than the
global IMAGE, and update_k3s_image_by_digest_or_restart should call
restart_k3s_rollout_with_image with the local image variable when invoking the
fallback.
In `@tests/unit/k8s-deploy-assets-review-fixes.test.ts`:
- Around line 60-64: The tests currently assert exact substrings on deployScript
(e.g., forbidding "| head -1" or '| head -c "$length"') which allows equivalent
regressions (like "head -n 1" or awk-based truncation) to slip; change these to
assert invariants instead: verify that the generated script contains the
sanitization/truncation logic by checking for patterns or sequences (e.g.,
presence of default_sc assignment pattern "default_sc" and a newline-trim
operation, the length-driven loop 'while [[ "${`#random`}" -lt "$length" ]]', and
the conditional 'if [[ "${DEPLOY_K8S_SOURCE_ONLY' ) and assert that no
unprotected pipelines are used for truncation by matching against broader
regexes such as any use of "head" with -n/-1 or any inline awk truncation;
update the tests around deployScript (and the similar block at 73-85) to use
these pattern/reg-exp style assertions rather than exact string matches so
equivalent implementations are caught.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: cde983e1-d0fa-4579-aadc-7f132f88c24a
📒 Files selected for processing (10)
deploy/k8s/README.mddeploy/k8s/ingress/ingress.yamldeploy/k8s/ingress/traefik-ingressroute.yamldocs/k8s-deployment.mdscripts/cchscripts/deploy-k8s.shsrc/drizzle/schema.tstests/unit/k8s-cch-update-flow.test.tstests/unit/k8s-deploy-assets-review-fixes.test.tstests/unit/k8s-deploy-shell-helpers.test.ts
| else | ||
| tr -dc 'A-Za-z0-9' < /dev/urandom | head -c "$length" | ||
| while [[ "${#random}" -lt "$length" ]]; do | ||
| random+=$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none 2>/dev/null | tr -dc 'A-Za-z0-9') |
There was a problem hiding this comment.
[MEDIUM] [ERROR-SILENT] Random generation can exit with no visible error
Evidence: scripts/deploy-k8s.sh:569
random+=$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none 2>/dev/null | tr -dc 'A-Za-z0-9')
Why this is a problem: set -euo pipefail is enabled (scripts/deploy-k8s.sh:5). If dd fails (e.g., /dev/urandom unavailable) the script will abort, but 2>/dev/null hides the failure reason, making the deployment fail without actionable output.
Suggested fix (keep stderr visible, or log a clear error):
# Prefer: let dd errors surface
random+=$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none | tr -dc 'A-Za-z0-9')
# Or: handle failures explicitly
if chunk=$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none 2>/dev/null | tr -dc 'A-Za-z0-9'); then
random+=$chunk
else
log_error "读取 /dev/urandom 失败,无法生成随机凭据"
return 1
fiThere was a problem hiding this comment.
Code Review Summary
PR hardens the k8s/k3s deployment shell tooling against set -euo pipefail early-exit cases and updates the k8s deployment docs/assets accordingly. One remaining error-handling edge case in generate_random() can still fail without actionable output due to suppressed stderr.
PR Size: M
- Lines changed: 422
- Files changed: 10
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 1 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Critical Issues (Must Fix)
None.
High Priority Issues (Should Fix)
- (Confidence 86)
scripts/deploy-k8s.sh:569[MEDIUM] [ERROR-SILENT]ddfailures are hidden by2>/dev/null, which can make secret generation abort underset -euo pipefailwithout a usable error message.
Review Coverage
- Logic and correctness
- Security (OWASP Top 10)
- Error handling
- Type safety
- Documentation accuracy
- Test coverage
- Code clarity
Automated review by Codex AI
There was a problem hiding this comment.
- Identified PR
#1079and applied the size labelsize/M(422 lines changed across 10 files). - Posted 1 inline review comment on
scripts/deploy-k8s.sh:569about a potential silent-failure path ingenerate_random()due todd ... 2>/dev/nullunderset -euo pipefail. - Submitted the required “Code Review Summary” via
gh pr review --commenton PR#1079.
7f4176a to
4965166
Compare
There was a problem hiding this comment.
♻️ Duplicate comments (3)
docs/k8s-deployment.md (1)
272-275:⚠️ Potential issue | 🟡 Minor把 digest 回落说明限定到后续失败场景。
Line 274-275 仍容易让读者理解为 “k3s digest 解析失败” 本身会触发
rollout undo;实际应是先回落到同 tag 的set image + rollout restart,只有后续 rollout/健康检查失败才 undo。建议调整
> 说明: > - `cch update` 在 rollout / 健康检查失败时会自动执行 `rollout undo` 并恢复副本数。 -> - 但若 k3s digest 解析失败而回落到同 tag 的 `set image + rollout restart`, -> `rollout undo` 回退的是 Deployment 模板,不保证严格回到上一份镜像 digest。 +> - 当 k3s digest 解析失败时,会先回落到同 tag 的 `set image + rollout restart`。 +> 只有后续 rollout / 健康检查失败触发 `rollout undo` 时, +> 回退的才是 Deployment 模板,不保证严格回到上一份镜像 digest。🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/k8s-deployment.md` around lines 272 - 275, Clarify the sequence so readers know that a k3s digest parse failure does not itself trigger rollback: when `k3s` digest parsing fails the code falls back to using `set image` + `rollout restart`, and only if a subsequent `rollout` or health check then fails will `rollout undo` be executed to revert the Deployment template; update the lines referencing `cch update`, `k3s digest 解析失败`, `set image + rollout restart`, and `rollout undo` to state this explicit order of events.scripts/deploy-k8s.sh (1)
564-576:⚠️ Potential issue | 🟡 Minor
dd失败时仍会隐藏关键错误。Line 573 仍然把
/dev/urandom/dd的错误吞掉;在set -euo pipefail下脚本会退出,但用户看不到随机凭据生成失败的原因。建议调整
else while [[ "${`#random`}" -lt "$length" ]]; do - random+=$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none 2>/dev/null | tr -dc 'A-Za-z0-9') + local chunk + if ! chunk=$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none | tr -dc 'A-Za-z0-9'); then + log_error "读取 /dev/urandom 失败,无法生成随机凭据" + return 1 + fi + random+="$chunk" done fi🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@scripts/deploy-k8s.sh` around lines 564 - 576, The generate_random function currently discards dd errors (2>/dev/null) so failures are hidden; modify generate_random to capture dd/urandom stderr and check dd's exit status instead of swallowing it: remove the 2>/dev/null redirection (or redirect stderr to a variable), run dd in a subshell or command substitution, test its exit code (e.g., use a temporary var like chunk="$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none 2>&1 | tr -dc 'A-Za-z0-9')" ; rc=$? ), and if rc is non-zero write a clear diagnostic to stderr (e.g., >&2) and exit non-zero so failures are visible under set -euo pipefail; keep the existing loop logic and slicing (${random:0:length}) but ensure errors from dd propagate and are logged.tests/unit/k8s-deploy-shell-helpers.test.ts (1)
46-65:⚠️ Potential issue | 🟡 Minor让测试真正防住
head管道回归。当前用例标题说 “without head pipelines”,但如果实现退回
... | head -1,真实head仍会成功,测试不会失败;建议在脚本片段里显式禁用head。建议调整
log_info() { :; } + +head() { + printf 'unexpected head invocation\n' >&2 + return 99 +} kubectl_stub() {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@tests/unit/k8s-deploy-shell-helpers.test.ts` around lines 46 - 65, The test claims to verify detect_storage_class "without head pipelines" but doesn't prevent a real head from succeeding; update the test snippet to explicitly disable/override the head command so any implementation that falls back to using a pipe to head will fail the test; inside the test's shell fixture (where kubectl_stub, STORAGE_CLASS and STORAGE_CLASS_ARG are set) add an overriding head function/alias that returns failure to ensure detect_storage_class cannot rely on | head -1 and the test will catch regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@docs/k8s-deployment.md`:
- Around line 272-275: Clarify the sequence so readers know that a k3s digest
parse failure does not itself trigger rollback: when `k3s` digest parsing fails
the code falls back to using `set image` + `rollout restart`, and only if a
subsequent `rollout` or health check then fails will `rollout undo` be executed
to revert the Deployment template; update the lines referencing `cch update`,
`k3s digest 解析失败`, `set image + rollout restart`, and `rollout undo` to state
this explicit order of events.
In `@scripts/deploy-k8s.sh`:
- Around line 564-576: The generate_random function currently discards dd errors
(2>/dev/null) so failures are hidden; modify generate_random to capture
dd/urandom stderr and check dd's exit status instead of swallowing it: remove
the 2>/dev/null redirection (or redirect stderr to a variable), run dd in a
subshell or command substitution, test its exit code (e.g., use a temporary var
like chunk="$(LC_ALL=C dd if=/dev/urandom bs=256 count=1 status=none 2>&1 | tr
-dc 'A-Za-z0-9')" ; rc=$? ), and if rc is non-zero write a clear diagnostic to
stderr (e.g., >&2) and exit non-zero so failures are visible under set -euo
pipefail; keep the existing loop logic and slicing (${random:0:length}) but
ensure errors from dd propagate and are logged.
In `@tests/unit/k8s-deploy-shell-helpers.test.ts`:
- Around line 46-65: The test claims to verify detect_storage_class "without
head pipelines" but doesn't prevent a real head from succeeding; update the test
snippet to explicitly disable/override the head command so any implementation
that falls back to using a pipe to head will fail the test; inside the test's
shell fixture (where kubectl_stub, STORAGE_CLASS and STORAGE_CLASS_ARG are set)
add an overriding head function/alias that returns failure to ensure
detect_storage_class cannot rely on | head -1 and the test will catch
regressions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 6674c12b-a9ee-4c06-8d16-c9b6e426327b
📒 Files selected for processing (10)
deploy/k8s/README.mddeploy/k8s/ingress/ingress.yamldeploy/k8s/ingress/traefik-ingressroute.yamldocs/k8s-deployment.mdscripts/cchscripts/deploy-k8s.shsrc/drizzle/schema.tstests/unit/k8s-cch-update-flow.test.tstests/unit/k8s-deploy-assets-review-fixes.test.tstests/unit/k8s-deploy-shell-helpers.test.ts
✅ Files skipped from review due to trivial changes (5)
- deploy/k8s/ingress/ingress.yaml
- src/drizzle/schema.ts
- deploy/k8s/ingress/traefik-ingressroute.yaml
- deploy/k8s/README.md
- tests/unit/k8s-deploy-assets-review-fixes.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- scripts/cch
There was a problem hiding this comment.
Code Review Summary
This PR properly hardens k8s deploy scripts against SIGPIPE-induced early exits under set -euo pipefail. The found-flag awk pattern in resolve_k3s_image_digest, the loop-based generate_random, and the ${var%%$'\n'*} parameter expansion for StorageClass detection are all correct fixes. Error handling in the k3s update fallback path is improved with explicit error messages and proper exit propagation. The schema comment fix accurately reflects the actual default IP extraction chain.
PR Size: M
- Lines changed: 422 (381 additions, 41 deletions)
- Files changed: 10
Issues Found
| Category | Critical | High | Medium | Low |
|---|---|---|---|---|
| Logic/Bugs | 0 | 0 | 0 | 0 |
| Security | 0 | 0 | 0 | 0 |
| Error Handling | 0 | 0 | 0 | 0 |
| Types | 0 | 0 | 0 | 0 |
| Comments/Docs | 0 | 0 | 0 | 0 |
| Tests | 0 | 0 | 0 | 0 |
| Simplification | 0 | 0 | 0 | 0 |
Review Coverage
- Logic and correctness - Clean
- Security (OWASP Top 10) - Clean
- Error handling - Properly propagated with explicit messages in new fallback paths
- Type safety - Clean (comment-only change in schema.ts)
- Documentation accuracy - Verified against actual IP extraction chain; all comments match code behavior
- Test coverage - New tests cover k3s digest pinning, fallback paths, error propagation, and shell helper edge cases
- Code clarity - Well-structured extraction into focused helper functions
Automated review by Claude AI
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4965166931
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if [[ "$api_resources" == *"ingresses"* ]] && [[ "$api_resources" == *"networking.k8s.io"* ]]; then | ||
| INGRESS_CLASS="${INGRESS_CLASS_ARG:-}" |
There was a problem hiding this comment.
Match Ingress API group on the same api-resources row
This check now treats any output containing both ingresses and networking.k8s.io as support for standard Ingress, even when they come from different rows (for example ingresses in extensions/v1beta1 plus networkpolicies in networking.k8s.io/v1). In that case detect_ingress_variant selects standard, and later applying deploy/k8s/ingress/ingress.yaml (networking.k8s.io/v1) can fail instead of falling back to NodePort.
Useful? React with 👍 / 👎.
4965166 to
a0aa34a
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/deploy-k8s.sh`:
- Around line 482-484: The current check builds api_resources from kubectl and
tests for the substrings "ingresses" and "networking.k8s.io", which can
mis-detect resources; replace that with a targeted query using kubectl
api-resources --api-group=networking.k8s.io -o name and then test whether the
returned names include "ingresses.networking.k8s.io" (update the variable
api_resources and the subsequent if-condition that currently uses "$KUBECTL
api-resources" and the two substring tests to instead call the --api-group query
and check for the precise "ingresses.networking.k8s.io" entry).
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f4239a95-9d5e-4f4a-9be6-6386a5dd5ab2
📒 Files selected for processing (10)
deploy/k8s/README.mddeploy/k8s/ingress/ingress.yamldeploy/k8s/ingress/traefik-ingressroute.yamldocs/k8s-deployment.mdscripts/cchscripts/deploy-k8s.shsrc/drizzle/schema.tstests/unit/k8s-cch-update-flow.test.tstests/unit/k8s-deploy-assets-review-fixes.test.tstests/unit/k8s-deploy-shell-helpers.test.ts
✅ Files skipped from review due to trivial changes (4)
- src/drizzle/schema.ts
- deploy/k8s/ingress/ingress.yaml
- deploy/k8s/ingress/traefik-ingressroute.yaml
- deploy/k8s/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
- docs/k8s-deployment.md
- tests/unit/k8s-deploy-shell-helpers.test.ts
- tests/unit/k8s-deploy-assets-review-fixes.test.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a0aa34a63c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if $KUBECTL api-resources 2>/dev/null | grep -q '^ingresses.*networking.k8s.io'; then | ||
| local api_resources | ||
| api_resources="$($KUBECTL api-resources 2>/dev/null || echo "")" | ||
| if [[ "$api_resources" == *"ingresses"* ]] && [[ "$api_resources" == *"networking.k8s.io"* ]]; then |
There was a problem hiding this comment.
Require ingress and API group on the same row
detect_ingress_variant now checks api-resources output with two independent substring tests, so it can mis-detect standard Ingress support when ingresses and networking.k8s.io appear on different lines (for example, ingresses only in extensions/v1beta1 plus any other networking.k8s.io/* resource). In that case the script selects standard and applies deploy/k8s/ingress/ingress.yaml (networking.k8s.io/v1), which fails instead of correctly falling back to NodePort.
Useful? React with 👍 / 👎.
a0aa34a to
9d74e56
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9d74e5608f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| # kubectl | ||
| if command -v kubectl &>/dev/null; then | ||
| check_pass "kubectl installed: $(kubectl version --client -o yaml 2>/dev/null | awk '/gitVersion/{print $2; exit}')" | ||
| check_pass "kubectl installed: $(kubectl version --client -o jsonpath='{.clientVersion.gitVersion}' 2>/dev/null)" |
There was a problem hiding this comment.
Use supported output format for kubectl version
kubectl version does not accept JSONPath output (kubectl version --help lists -o, --output as only yaml or json), so this invocation returns non-zero on real kubectl clients. Because the call is inside a command substitution passed to check_pass, cch doctor still reports success but with an empty version string, which masks the probe failure and gives misleading diagnostics.
Useful? React with 👍 / 👎.
Summary
scripts/cchagainst pipefail early-exit cases in k3s digest resolution, doctor version probing, and fallbackset imagehandlingscripts/deploy-k8s.shagainst the same class of early-exit issues in default StorageClass detection and random secret generationRelated PRs:
cchCLI introduced by that PRipExtractionConfigschema comment to reflect the actual default IP extraction chain (removes stalecf-connecting-ipreference)Problem
Both
scripts/cchandscripts/deploy-k8s.shrun underset -euo pipefail, but several pipelines usedawk … {print …; exit}orhead -1/head -cpatterns that cause the upstream process to receiveSIGPIPE(exit code 141) when it continues writing after the downstream consumer exits early. This silently kills the entire command substitution underpipefail, aborting upgrade or deployment flows without error reporting.Additionally, ingress manifests and documentation did not clearly document the assumptions around client IP passthrough in k8s environments.
Changes
Core Fixes (
scripts/cch)resolve_k3s_image_digest()andupdate_k3s_image_by_digest_or_restart(), replacing the inlineawk … exitpipeline with afoundflag pattern that avoids SIGPIPEkubectl version … | awk '/gitVersion/{print $2; exit}'withjsonpathextraction incmd_doctorset imagein proper error handling — failure now exits with explicit error message instead of silently continuingCCH_SOURCE_ONLYguard to enable unit-testing by sourcing the script without triggering the dispatch blockCore Fixes (
scripts/deploy-k8s.sh)| head -1in StorageClass detection with bash-native${default_sc%%$'\n'*}parameter expansionopenssl rand … | head -candtr … | head -cingenerate_random()with a loop-based accumulator that guarantees exact-length output without broken pipesDEPLOY_K8S_SOURCE_ONLYguard for test sourcinghas_command()helper to eliminate scatteredcommand -vinvocationsDocs & Schema Alignment
deploy/k8s/ingress/ingress.yaml: Document thatconfiguration-snippetis not used by default becauseallow-snippet-annotations=falseis the ingress-nginx defaultdeploy/k8s/ingress/traefik-ingressroute.yaml: DocumentX-Forwarded-Forpassthrough andforwardedHeaders.trustedIPsrequirementsdocs/k8s-deployment.md: Add client IP passthrough section; clarifycch updaterollback behavior with digest fallbackdeploy/k8s/README.md: Add IP passthrough section; clarify default image sourcesrc/drizzle/schema.ts: UpdateipExtractionConfigcomment to match actual default chain (x-real-ip -> x-forwarded-for rightmost)Tests
tests/unit/k8s-cch-update-flow.test.ts(new): Tests k3s digest pinning, fallback to rollout restart, fallback failure propagation, and doctor version probingtests/unit/k8s-deploy-shell-helpers.test.ts(new): Testsgenerate_random()exact-length output and StorageClass detection without pipe-basedheadtests/unit/k8s-deploy-assets-review-fixes.test.ts(updated): Expanded assertions covering all new patternsVerification
bash -n scripts/cch scripts/deploy-k8s.shbunx vitest run tests/unit/k8s-cch-update-flow.test.ts tests/unit/k8s-deploy-shell-helpers.test.ts tests/unit/k8s-deploy-assets-review-fixes.test.tsbunx vitest run tests/unit/k8s-cch-update-flow.test.ts tests/unit/k8s-deploy-assets-review-fixes.test.ts src/lib/ip/extract-client-ip.test.ts src/lib/ip/index.test.ts tests/unit/settings/system-settings-form-ip-extraction.test.tsxbun run typecheckbun run buildNotes
bun run lint/bun run lint:fixare still blocked locally by pre-existing repo baseline issues outside this diff (biome.jsonschema-version mismatch and existing Biome findings in unrelated files)bun run teststill hits pre-existing local baseline issues outside this diff (existing public-status test failures / worker OOM), so targeted regressions were used as the binding proof for this change setDescription enhanced by Claude AI
Greptile Summary
This PR hardens
scripts/cchandscripts/deploy-k8s.shagainstpipefail/SIGPIPE early exits by refactoring the k3s digest resolution (found-flag awk pattern), StorageClass detection (parameter expansion instead of| head -1), ingress detection (--api-groupfilter), and random secret generation (loop-based accumulator instead of| head -c). Documentation and schema comments are updated to accurately reflect the IP extraction chain. New unit tests cover the k3s update fallback paths and the shell helpers.Confidence Score: 5/5
Safe to merge — all SIGPIPE/pipefail fixes are logically correct and covered by new unit tests; no P0/P1 issues found.
All core changes correctly address the stated pipefail/SIGPIPE problem. The awk
found-flag avoids early exit without introducing new failure modes, parameter expansion replaces| head -1cleanly, and the loop-basedgenerate_randomproduces exact-length output. Previously flagged concerns (${random:0:length}style,bash -lcin tests) remain P2-only and do not affect correctness. No new P1 issues were identified.No files require special attention.
Important Files Changed
found-flag awk helper;kubectl versionswitched to jsonpath; dispatch block guarded byCCH_SOURCE_ONLY; fallbackset imagenow surfaces explicit errors.@sha256avoidance, fallback set-image failure propagation, and doctor version probing via jsonpath.generate_randomfallback, first-class detection withoutheadpipes, and runtime/ingress detection without grep pipes.cf-connecting-ipfrom the documented default IP extraction chain; no schema or runtime change.configuration-snippetis not used by default due toallow-snippet-annotations=falseand that IP passthrough requires controller-level configuration.forwardedHeaders.trustedIPsrequirement for X-Real-Ip trust.cch updaterollout-undo semantics and digest-fallback limitations, removes stale 'automatic rollback' phrasing.main→:latest); purely documentation.Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[cmd_update: k3s path] --> B[update_k3s_image_by_digest_or_restart] B --> C[resolve_k3s_image_digest\nk3s ctr images ls - awk found-flag] C -->|non-zero exit| D[restart_k3s_rollout_with_image\nreason: ctr images ls failed] C -->|exit 0, empty or non-sha256| E[restart_k3s_rollout_with_image\nreason: no usable digest] C -->|exit 0, sha256 digest| F[build_image_ref_with_digest\nimage at sha256-xxx] F --> G[kubectl set image by digest] D --> H{set image ok?} E --> H H -->|yes| I[kubectl rollout restart] H -->|no| J[err: set image failed\nreturn 1] J --> K[restore_update_scaling + exit 1] G --> L[continue rollout and health-check] I --> LReviews (4): Last reviewed commit: "fix(k8s): harden deploy script pipefail ..." | Re-trigger Greptile