Skip to content

fix: harden k8s deploy scripts against pipefail early exits#1079

Merged
ding113 merged 1 commit into
devfrom
fix/k8s-ip-review-fixes
Apr 23, 2026
Merged

fix: harden k8s deploy scripts against pipefail early exits#1079
ding113 merged 1 commit into
devfrom
fix/k8s-ip-review-fixes

Conversation

@ding113

@ding113 ding113 commented Apr 23, 2026

Copy link
Copy Markdown
Owner

Summary

  • Harden scripts/cch against pipefail early-exit cases in k3s digest resolution, doctor version probing, and fallback set image handling
  • Harden scripts/deploy-k8s.sh against the same class of early-exit issues in default StorageClass detection and random secret generation
  • Align k8s ingress / docs / schema comments around IP extraction expectations and add shell behavior tests for the deploy scripts

Related PRs:

Problem

Both scripts/cch and scripts/deploy-k8s.sh run under set -euo pipefail, but several pipelines used awk … {print …; exit} or head -1/head -c patterns that cause the upstream process to receive SIGPIPE (exit code 141) when it continues writing after the downstream consumer exits early. This silently kills the entire command substitution under pipefail, 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)

  • Extract k3s digest resolution into resolve_k3s_image_digest() and update_k3s_image_by_digest_or_restart(), replacing the inline awk … exit pipeline with a found flag pattern that avoids SIGPIPE
  • Replace kubectl version … | awk '/gitVersion/{print $2; exit}' with jsonpath extraction in cmd_doctor
  • Wrap fallback set image in proper error handling — failure now exits with explicit error message instead of silently continuing
  • Add CCH_SOURCE_ONLY guard to enable unit-testing by sourcing the script without triggering the dispatch block

Core Fixes (scripts/deploy-k8s.sh)

  • Replace | head -1 in StorageClass detection with bash-native ${default_sc%%$'\n'*} parameter expansion
  • Replace openssl rand … | head -c and tr … | head -c in generate_random() with a loop-based accumulator that guarantees exact-length output without broken pipes
  • Add DEPLOY_K8S_SOURCE_ONLY guard for test sourcing
  • Add has_command() helper to eliminate scattered command -v invocations

Docs & Schema Alignment

  • deploy/k8s/ingress/ingress.yaml: Document that configuration-snippet is not used by default because allow-snippet-annotations=false is the ingress-nginx default
  • deploy/k8s/ingress/traefik-ingressroute.yaml: Document X-Forwarded-For passthrough and forwardedHeaders.trustedIPs requirements
  • docs/k8s-deployment.md: Add client IP passthrough section; clarify cch update rollback behavior with digest fallback
  • deploy/k8s/README.md: Add IP passthrough section; clarify default image source
  • src/drizzle/schema.ts: Update ipExtractionConfig comment 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 probing
  • tests/unit/k8s-deploy-shell-helpers.test.ts (new): Tests generate_random() exact-length output and StorageClass detection without pipe-based head
  • tests/unit/k8s-deploy-assets-review-fixes.test.ts (updated): Expanded assertions covering all new patterns

Verification

  • bash -n scripts/cch scripts/deploy-k8s.sh
  • bunx 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.ts
  • bunx 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.tsx
  • bun run typecheck
  • bun run build

Notes

  • bun run lint / bun run lint:fix are still blocked locally by pre-existing repo baseline issues outside this diff (biome.json schema-version mismatch and existing Biome findings in unrelated files)
  • bun run test still 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 set

Description enhanced by Claude AI

Greptile Summary

This PR hardens scripts/cch and scripts/deploy-k8s.sh against pipefail/SIGPIPE early exits by refactoring the k3s digest resolution (found-flag awk pattern), StorageClass detection (parameter expansion instead of | head -1), ingress detection (--api-group filter), 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 -1 cleanly, and the loop-based generate_random produces exact-length output. Previously flagged concerns (${random:0:length} style, bash -lc in tests) remain P2-only and do not affect correctness. No new P1 issues were identified.

No files require special attention.

Important Files Changed

Filename Overview
scripts/cch Key SIGPIPE fix: k3s digest resolution refactored into found-flag awk helper; kubectl version switched to jsonpath; dispatch block guarded by CCH_SOURCE_ONLY; fallback set image now surfaces explicit errors.
scripts/deploy-k8s.sh Replaces all `
tests/unit/k8s-cch-update-flow.test.ts New test file covering k3s digest pinning, fallback-to-restart paths, double-@sha256 avoidance, fallback set-image failure propagation, and doctor version probing via jsonpath.
tests/unit/k8s-deploy-shell-helpers.test.ts New test file verifying exact-length output from generate_random fallback, first-class detection without head pipes, and runtime/ingress detection without grep pipes.
src/drizzle/schema.ts Comment-only change removing stale cf-connecting-ip from the documented default IP extraction chain; no schema or runtime change.
deploy/k8s/ingress/ingress.yaml Adds comments clarifying that configuration-snippet is not used by default due to allow-snippet-annotations=false and that IP passthrough requires controller-level configuration.
deploy/k8s/ingress/traefik-ingressroute.yaml Adds comments documenting Traefik X-Forwarded-For passthrough behaviour and forwardedHeaders.trustedIPs requirement for X-Real-Ip trust.
docs/k8s-deployment.md Adds client IP passthrough section, clarifies cch update rollout-undo semantics and digest-fallback limitations, removes stale 'automatic rollback' phrasing.
deploy/k8s/README.md Adds IP passthrough section and clarifies default image source (main:latest); purely documentation.
tests/unit/k8s-deploy-assets-review-fixes.test.ts Expanded assertions confirm absence of old pipe patterns, presence of new parameter-expansion forms, and documentation correctness for IP passthrough and default image reference.

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 --> L
Loading

Reviews (4): Last reviewed commit: "fix(k8s): harden deploy script pipefail ..." | Re-trigger Greptile

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

为 Kubernetes 部署文档和 Ingress 清单补充客户端 IP 透传与受信任代理说明,重构并提取 k3s 镜像 digest 解析/更新逻辑,增强 deploy 脚本的检测与随机生成保证,且新增/扩展多处 Vitest 单元测试覆盖这些变更。

Changes

Cohort / File(s) Summary
Kubernetes 部署文档与 Ingress 注释
deploy/k8s/README.md, deploy/k8s/ingress/ingress.yaml, deploy/k8s/ingress/traefik-ingressroute.yaml
添加注释与文档,明确客户端 IP 透传依赖 ingress-controller 的 forwarded-header/real-ip/受信任代理 配置,说明 Traefik/Ingress 对 X-Forwarded-For / X-Real-Ip 的信任语义及 app 端默认提取链;在 README 中标注 one-click deploy 默认 image tag。
部署文档与架构注释
docs/k8s-deployment.md, src/drizzle/schema.ts
docs 增补了端到端 client-IP forwarding、Traefik/nginx 信任设置及 cch update 的 rollout/回退语义说明;schema 注释调整 ipExtractionConfig 的默认头优先级描述为 x-real-ip → x-forwarded-for(仅注释变更)。
脚本重构与增强(cch & deploy)
scripts/cch, scripts/deploy-k8s.sh
提取并新增 k3s 镜像 digest 解析/构建与基于 digest 的 kubectl set image helper,digest 解析失败回退为 kubectl rollout restart;改进 kubelet/k3s/runtime/storage/ingress 检测逻辑(使用子串匹配、api-resources 检查等);generate_random() 循环保证返回长度;新增 has_command();顶层执行受环境变量门控以支持 source-only 模式。
单元测试(新增/扩展)
tests/unit/k8s-cch-update-flow.test.ts, tests/unit/k8s-deploy-assets-review-fixes.test.ts, tests/unit/k8s-deploy-shell-helpers.test.ts
新增与扩展 Vitest 测试:覆盖 k3s digest pinning 与回退场景、image@sha256: 构建与端口保留、deploy 脚本随机生成与 storageclass/runtime/ingress 检测路径、以及对 ingress 注释/README 文档中 forwarded-header/snippet/受信任代理 期望的断言。

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题清晰准确地概括了主要变更,即对k8s部署脚本的pipefail早期退出问题的加固。
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed PR description is directly related to the changeset, detailing specific fixes for pipefail/SIGPIPE issues in shell scripts and related documentation updates.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/k8s-ip-review-fixes

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

❤️ Share

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

@github-actions github-actions Bot added bug Something isn't working area:deployment size/M Medium PR (< 500 lines) labels Apr 23, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

Comment thread scripts/cch
Comment on lines +238 to +241
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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

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

Comment thread scripts/cch
Comment on lines +220 to +228
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
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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.

Suggested change
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.

Comment thread scripts/deploy-k8s.sh
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}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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}.

Suggested change
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!

Comment on lines +7 to +9
[
"-lc",
`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 1awk ... 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

📥 Commits

Reviewing files that changed from the base of the PR and between ffda33f and 7f4176a.

📒 Files selected for processing (10)
  • deploy/k8s/README.md
  • deploy/k8s/ingress/ingress.yaml
  • deploy/k8s/ingress/traefik-ingressroute.yaml
  • docs/k8s-deployment.md
  • scripts/cch
  • scripts/deploy-k8s.sh
  • src/drizzle/schema.ts
  • tests/unit/k8s-cch-update-flow.test.ts
  • tests/unit/k8s-deploy-assets-review-fixes.test.ts
  • tests/unit/k8s-deploy-shell-helpers.test.ts

Comment thread docs/k8s-deployment.md Outdated
Comment thread tests/unit/k8s-deploy-shell-helpers.test.ts
Comment thread scripts/deploy-k8s.sh Outdated
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')

@github-actions github-actions Bot Apr 23, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[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
fi

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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] dd failures are hidden by 2>/dev/null, which can make secret generation abort under set -euo pipefail without 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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

  • Identified PR #1079 and applied the size label size/M (422 lines changed across 10 files).
  • Posted 1 inline review comment on scripts/deploy-k8s.sh:569 about a potential silent-failure path in generate_random() due to dd ... 2>/dev/null under set -euo pipefail.
  • Submitted the required “Code Review Summary” via gh pr review --comment on PR #1079.

@ding113
ding113 force-pushed the fix/k8s-ip-review-fixes branch from 7f4176a to 4965166 Compare April 23, 2026 04:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ 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

📥 Commits

Reviewing files that changed from the base of the PR and between 7f4176a and 4965166.

📒 Files selected for processing (10)
  • deploy/k8s/README.md
  • deploy/k8s/ingress/ingress.yaml
  • deploy/k8s/ingress/traefik-ingressroute.yaml
  • docs/k8s-deployment.md
  • scripts/cch
  • scripts/deploy-k8s.sh
  • src/drizzle/schema.ts
  • tests/unit/k8s-cch-update-flow.test.ts
  • tests/unit/k8s-deploy-assets-review-fixes.test.ts
  • tests/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

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread scripts/deploy-k8s.sh Outdated
Comment on lines 484 to 485
if [[ "$api_resources" == *"ingresses"* ]] && [[ "$api_resources" == *"networking.k8s.io"* ]]; then
INGRESS_CLASS="${INGRESS_CLASS_ARG:-}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@ding113
ding113 force-pushed the fix/k8s-ip-review-fixes branch from 4965166 to a0aa34a Compare April 23, 2026 04:41

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against 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

📥 Commits

Reviewing files that changed from the base of the PR and between 4965166 and a0aa34a.

📒 Files selected for processing (10)
  • deploy/k8s/README.md
  • deploy/k8s/ingress/ingress.yaml
  • deploy/k8s/ingress/traefik-ingressroute.yaml
  • docs/k8s-deployment.md
  • scripts/cch
  • scripts/deploy-k8s.sh
  • src/drizzle/schema.ts
  • tests/unit/k8s-cch-update-flow.test.ts
  • tests/unit/k8s-deploy-assets-review-fixes.test.ts
  • tests/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

Comment thread scripts/deploy-k8s.sh Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread scripts/deploy-k8s.sh Outdated
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

@ding113
ding113 force-pushed the fix/k8s-ip-review-fixes branch from a0aa34a to 9d74e56 Compare April 23, 2026 05:44

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment thread scripts/cch
# 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)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge 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 👍 / 👎.

@ding113
ding113 merged commit c20f5d1 into dev Apr 23, 2026
11 of 13 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Apr 23, 2026
@ding113
ding113 deleted the fix/k8s-ip-review-fixes branch April 23, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:deployment bug Something isn't working size/M Medium PR (< 500 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant