Skip to content

feat(clickhouse): let the backup sidecar trust a private S3 CA, unblocking the backup e2e round-trip - #3385

Merged
Andrey Kolkov (androndo) merged 12 commits into
mainfrom
test/clickhouse-backup-e2e-chainsaw
Aug 10, 2026
Merged

Andrey Kolkov (androndo) merged 12 commits into
mainfrom
test/clickhouse-backup-e2e-chainsaw

Conversation

@androndo

@androndo Andrey Kolkov (androndo) commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

What this PR does

Gives the ClickHouse clickhouse-backup sidecar a way to trust an S3 endpoint whose certificate is signed by a private CA, and uses that to make the ClickHouse backup/restore e2e round-trip actually run in CI. This actualizes #2600, whose feature code and example scripts have since landed on main independently, and whose remaining contribution — the e2e test — was still in the retired BATS format. #2600 can be closed once this merges.

1. backup.endpointCA on the ClickHouse chart

The sidecar had no TLS/CA surface at all: bucket coordinates and credentials arrive as S3_* env, and nothing let it verify an endpoint signed by a private CA. Cozystack's own in-cluster SeaweedFS is exactly that case — it serves :8333 behind the self-signed "SeaweedFS CA" — so a ClickHouse release could only ever back up to a publicly-trusted endpoint. MariaDB (storage.s3.tls.caSecretKeyRef) and Postgres (barman endpointCA) have had a CA field all along; this closes the gap for ClickHouse.

backup.endpointCA takes {name, key}. When set, the chart mounts that Secret into the sidecar and points SSL_CERT_DIR at it. Empty — the default — renders no env, no mount and no volume, so existing releases are byte-for-byte unchanged.

Why SSL_CERT_DIR and not AWS_CA_BUNDLE. Both are honoured by altinity/clickhouse-backup:2.7.4; I measured them against a self-signed endpoint and public AWS S3 in turn. Go reads SSL_CERT_DIR in addition to the system bundle, so both endpoints verify. AWS_CA_BUNDLE replaces the SDK's trust pool: the private endpoint verifies, and public S3 then fails x509: certificate signed by unknown authority — it would silently break a release that also talks to a public endpoint. S3_DISABLE_CERT_VERIFICATION was rejected outright, since it drops verification rather than trusting one more CA.

2. The e2e suite, and what it was really testing

Two Tests added to the existing hack/e2e-chainsaw/clickhouse/ suite (in name order, mirroring the etcd/mariadb/postgres shape — see the layout rationale below for why they live here rather than in a new directory):

  • clickhouse-1-backup-contracts (CI, tenant-test, ~98s): a backup-enabled ClickHouse asserted against the running cluster — the clickhouse-backup sidecar on the ch-backup-api :7171 port, and the <release>-backup-api-auth Secret whose keys the sidecar's API_USERNAME/API_PASSWORD actually resolve valueFrom. Each is a silent break if a chart bump renames it. Needs no object store.
  • clickhouse-2-backup-roundtrip (CI, tenant-root): Bucket → Altinity strategy + BackupClass → source ClickHouse with a sentinel row → BackupJob → in-place RestoreJob → to-copy RestoreJob into a second instance, driving examples/backups/clickhouse/run-all.sh so test and docs cannot drift.

The round-trip was originally gated behind CLICKHOUSE_E2E_S3_ROUNDTRIP and skipped in CI — its PASS (1.01s) meant the gate fired, not that a backup happened. backup.endpointCA removes the reason for the gate, so the suite now adopts the same three preconditions mariadb/postgres rely on: run in tenant-root (an isolated tenant's Cilium egress allowlist blocks its Pods from reaching tenant-root's SeaweedFS), target the in-cluster endpoint instead of the Bucket's external ingress URL (an unroutable placeholder in CI), and trust its certificate via the copied CA. It also pre-cleans with cleanup.sh, because tenant-root is shared and a leftover Succeeded BackupJob would satisfy the harness's wait without a backup ever running.

Both backup Tests live in the existing hack/e2e-chainsaw/clickhouse/ suite rather than a new suite directory, so no selector mapping is added: hack/select-e2e.sh already derives clickhouse both from packages/apps/clickhouse/** (the *-application rule) and from examples/backups/clickhouse/** (the harness path), and both paths therefore select the suite that actually runs the harness. That alignment is the reason for the layout — a separate clickhouse-backup/ directory would have made an edit to the harness select a suite that cannot exercise it. hack/select-e2e.sh and hack/select-install.sh are otherwise untouched (the only change is a comment recording that invariant), and the existing backup example harness edit selects its app suite case in hack/select-e2e_test.bats covers the mapping.

Verification

  • helm unittest — 17 pass, including 5 new endpointCA cases: the unset default renders nothing, the env/mount/volume wiring, a custom key still projected as ca.crt, composition with useSystemBucket, and backup.enabled=false renders nothing.
  • The CA-trust behaviour was measured against the real image, not assumed: without a CA knob the sidecar reports x509: certificate signed by unknown authority; with SSL_CERT_DIR the handshake succeeds.
  • hack/select-e2e_test.bats (16) and hack/select-install_test.bats (17) pass; select-install.sh --validate reports graph OK.
  • make generate is idempotent, so the pre-commit job's git diff --exit-code stays clean.
  • The contracts Test passed in CI on earlier runs of this branch. The round-trip only became CI-live with this work, so it still needs a green E2E run before merge — the branch has since been rebased onto current main to get one.

Screenshots

Not applicable — no UI changes.

Downstream repositories

Walked the trigger map against the diff, file by file. The load-bearing change is the new backup.endpointCA field in packages/apps/clickhouse/values.yaml / values.schema.json; everything else is hack/e2e-chainsaw/, examples/backups/, one docs paragraph, and regenerated artifacts, none of which appear in the map.

Not ticked, with reasons rather than silence: cozystack/website — the trigger is adding, renaming or removing a package, and this adds a field to an existing one. clickhouse is already in the website Makefile's app list and its reference page is generated from this package's README.md, so the regenerated backup.endpointCA rows are carried by the existing release-tag automation with no manual PR. cozystack/external-apps-example — vendors hack/package.mk and update-crd.sh, neither of which this PR touches. cozystack/ansible-cozystack, ccp, talm, cozyhr, cozy-proxy, cozystack-telemetry-server, examples — no installer values, hack/ layout, namespace, variant, node-prerequisite, annotation, label or metric in their trigger rows is touched here.

Release note

feat(clickhouse): add `backup.endpointCA` so the clickhouse-backup sidecar can verify an S3 endpoint whose certificate is signed by a private CA, such as Cozystack's in-cluster SeaweedFS. Leave it unset for publicly-trusted endpoints; existing releases are unaffected.

Summary by CodeRabbit

  • New Features

    • Added optional private S3 endpoint CA support for ClickHouse backup and restore sidecars through backup.endpointCA.
    • Configured CA bundles are mounted securely while public endpoints continue using the system trust store.
  • Documentation

    • Updated ClickHouse chart documentation, operational guidance, and backup/restore examples with CA configuration and cleanup instructions.
  • Tests

    • Added unit and end-to-end coverage for CA handling and ClickHouse backup/restore workflows.

@github-actions github-actions Bot added area/testing Issues or PRs related to testing (e2e, bats, unit tests) size/L This PR changes 100-499 lines, ignoring generated files labels Jul 21, 2026
@coderabbitai

coderabbitai Bot commented Jul 21, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds optional S3 endpoint CA support to ClickHouse backups. The change updates API and chart configuration, mounts a Secret in the backup sidecar, propagates CA settings through the example harness, improves waits and cleanup, and adds Chainsaw validation.

Changes

ClickHouse backup endpoint CA

Layer / File(s) Summary
CA contract and sidecar rendering
api/apps/v1alpha1/clickhouse/*, packages/apps/clickhouse/*, packages/system/clickhouse-rd/..., docs/operations/...
Adds backup.endpointCA, schema defaults, deepcopy methods, documentation, conditional Secret projection, SSL_CERT_DIR, and Helm-unittest coverage.
Example backup and restore harness
examples/backups/clickhouse/*
Adds CA discovery and propagation, readiness and deletion helpers, endpoint overrides, restore waits, and teardown handling.
Strategy client and test selection
examples/backups/clickhouse/01-create-strategy.sh, hack/e2e-chainsaw/.chainsaw.yaml, hack/select-e2e.sh
Uses the shipped strategy client image when available, bounds REST requests, and documents backup suite selection.
Contract and round-trip validation
hack/e2e-chainsaw/clickhouse/*
Adds backup sidecar contract checks and an example-driven backup and restore Chainsaw test with diagnostics and cleanup.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

  • cozystack/terraform-provider-cozystack#18 — Requests exposing backup.endpointCA in the Terraform provider schema and expand/flatten logic.

Suggested reviewers: kvaps, sircthulhu, lexfrei

Sequence Diagram(s)

sequenceDiagram
  participant Harness as ClickHouse example harness
  participant Kubernetes as Kubernetes Secret
  participant ClickHouse as ClickHouse resource
  participant Sidecar as clickhouse-backup sidecar
  participant S3 as S3 endpoint
  Harness->>Kubernetes: Copy endpoint CA bundle
  Harness->>ClickHouse: Set backup.endpointCA
  ClickHouse->>Sidecar: Mount CA Secret and set SSL_CERT_DIR
  Sidecar->>S3: Connect using extended trust store
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
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 Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: enabling the ClickHouse backup sidecar to trust private S3 endpoint CAs and support the backup e2e round trip.
✨ 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 test/clickhouse-backup-e2e-chainsaw

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.

@androndo
Andrey Kolkov (androndo) force-pushed the test/clickhouse-backup-e2e-chainsaw branch 2 times, most recently from 48d7300 to 0b7b444 Compare July 28, 2026 11:13
@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files and removed size/L This PR changes 100-499 lines, ignoring generated files labels Jul 30, 2026
@androndo Andrey Kolkov (androndo) changed the title test(e2e): add clickhouse altinity backup chainsaw suite feat(clickhouse): let the backup sidecar trust a private S3 CA, unblocking the backup e2e round-trip Jul 30, 2026
@github-actions github-actions Bot added area/database Issues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse) kind/feature Categorizes issue or PR as related to a new feature labels Jul 30, 2026
Andrey Kolkov (androndo) and others added 9 commits July 30, 2026 16:04
Port the intended ClickHouse Altinity backup/restore e2e coverage from
#2600 (never-merged hack/e2e-apps/backup-clickhouse.bats) to the
current Chainsaw approach, reworked on origin/main where the Altinity
backup feature and examples/backups/clickhouse already landed.

Two Tests, etcd-suite shaped:
- clickhouse-backup-1-contracts (CI): a backup-enabled ClickHouse, asserting
  the chart<->Altinity-strategy contract against the running cluster — the
  clickhouse-backup sidecar on the ch-backup-api :7171 port and the populated
  <release>-backup-api-auth Secret the strategy Pod authenticates with. Needs
  no reachable object store.
- clickhouse-backup-2-roundtrip: the full Bucket -> BackupJob -> Backup ->
  in-place + to-copy RestoreJob round-trip, driving
  examples/backups/clickhouse/run-all.sh. Gated out of CI
  (CLICKHOUSE_E2E_S3_ROUNDTRIP=1) because kind's self-signed SeaweedFS S3
  endpoint cannot be TLS-validated by the in-Pod sidecar.

Wire clickhouse-application -> "clickhouse clickhouse-backup" in
hack/select-e2e.sh so ClickHouse chart edits exercise the backup suite too.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
hack/select-install.sh --validate (run in the unit-test job) requires every
Chainsaw suite dir to resolve to a PackageSource via suite_to_source(); the
new clickhouse-backup suite has no <suite>-application fallback, so map it to
cozystack.clickhouse-application, in lockstep with the select-e2e.sh override.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
- Add a select-e2e_test.bats case asserting a packages/apps/clickhouse diff
  selects BOTH the clickhouse and clickhouse-backup suites (mirrors the
  kubernetes-application multi-suite test). Fails without the src_to_suites
  override that makes the backup contracts suite run on ClickHouse chart PRs.
- Correct the backup-api-auth Secret assertion's prose to match what it proves
  (present with both keys, not "populated").
- Capture the to-copy restore target's Pod logs in the round-trip catch so a
  failure isolated to step 07 keeps its diagnostics.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
Extend verify-backup-sidecar to assert the sidecar's API_USERNAME/API_PASSWORD
env resolve valueFrom the backup-api-auth Secret's username/password keys, not
just that the Secret and the :7171 port exist independently. A chart change
that repointed or dropped those secretKeyRefs while keeping both would slip
through and surface only as 401s at BackupJob time — the silent break this
suite exists to catch.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
The clickhouse-backup sidecar had no TLS/CA surface at all: bucket coordinates
and credentials arrive as S3_* env, and nothing let it verify an endpoint whose
certificate is signed by a private CA. Cozystack's own in-cluster SeaweedFS is
exactly that case — it serves :8333 behind the self-signed "SeaweedFS CA" — so a
ClickHouse release could only ever back up to a publicly-trusted endpoint, while
MariaDB (storage.s3.tls.caSecretKeyRef) and Postgres (barman endpointCA) have had
a CA field all along.

Add backup.endpointCA {name, key}. When set, the chart mounts that Secret into
the sidecar and points SSL_CERT_DIR at it. Empty (the default) renders no env, no
mount and no volume, so existing releases are untouched.

SSL_CERT_DIR rather than AWS_CA_BUNDLE, measured against
altinity/clickhouse-backup:2.7.4 with a self-signed endpoint and public AWS S3 in
turn: Go reads SSL_CERT_DIR in addition to the system bundle, so both verify;
AWS_CA_BUNDLE replaces the SDK trust pool, so the private endpoint verifies and
public S3 starts failing `x509: certificate signed by unknown authority`. That
would silently break a release that also talks to a public endpoint.
S3_DISABLE_CERT_VERIFICATION was rejected outright — it drops verification
instead of trusting one more CA.

Covered by helm-unittest: the unset default, the mount/env/volume wiring, a
custom key still projected as ca.crt, composition with useSystemBucket, and that
backup.enabled=false renders nothing.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
The round-trip Test was skipping: it exited 0 behind CLICKHOUSE_E2E_S3_ROUNDTRIP,
so "PASS (1.01s)" meant the gate fired, not that backup and restore worked. Only
the contracts Test ran, which proves the chart still exposes the sidecar and its
auth Secret — not that a byte ever reaches S3 and comes back.

The gate existed because the sidecar could not verify the in-cluster SeaweedFS
certificate. backup.endpointCA closes that, so adopt the shape mariadb and
postgres already use to run their round-trips un-gated:

- examples: resolve the S3 endpoint from S3_ENDPOINT when set (BucketInfo
  advertises the external ingress URL, an unroutable placeholder in CI), copy the
  seaweedfs CA (auto-discovered from its cert-manager Certificate when the
  fullname default is absent) into a per-release Secret, and reference it from
  backup.endpointCA on both the source and the to-copy target. NAMESPACE now
  defaults to tenant-root, matching the other backup demos.
- suite: drop the gate, run in tenant-root — an isolated tenant's Cilium egress
  allowlist blocks its Pods from reaching tenant-root's seaweedfs — and pre-clean
  with cleanup.sh, because tenant-root is shared and a leftover Succeeded
  BackupJob would satisfy the harness's wait without a backup running.

cleanup.sh also drops the copied CA Secret; the platform's own is never touched.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
Review findings on the previous two commits.

1. TIA no longer selected the suite that runs the harness. select-e2e.sh maps
   examples/backups/<app>/** to suite <app>, so with the round-trip living in a
   separate clickhouse-backup/ dir an edit to the harness selected `clickhouse`
   — which did not execute those scripts — while the suite that does was left
   out. Fixed the way postgres/mariadb/etcd already do it: both Tests move into
   hack/e2e-chainsaw/clickhouse/ as clickhouse-1-backup-contracts and
   clickhouse-2-backup-roundtrip. That also deletes every special case the
   earlier commits added (src_to_suites, suite_to_source, and the bats case that
   guarded them — both scripts are now byte-identical to main), and the suite
   list in hack/e2e-chainsaw/README.md stays correct because no dir is added.
   The bats case is replaced by one asserting the invariant that actually
   matters: an examples/backups/clickhouse edit selects the suite that runs it.

2. The docs claimed a fail-fast the harness did not have, and the budget was
   sized as if it did. wait_for_field polled to the full timeout with no
   terminal-value escape, and the HelmRelease/StatefulSet waits were bare
   `kubectl wait` with no existence backstop and no Stalled fail-fast. Ported
   wait_for_field's fail_value parameter and wait_hr_ready from the postgres
   helpers, added wait_sts_ready, and routed all eight waits through them.
   Verified against a fake kubectl: a Failed BackupJob now bails in 3s instead
   of 600s, a Stalled HelmRelease immediately. The inner budgets sum to 3900s,
   which the old 30m op timeout did not cover — a hang would have been SIGKILLed
   with no diagnostics, the failure mode .chainsaw.yaml calls strictly worse — so
   the timeout is now 65m with the arithmetic written out.

3. The strategy Pod ran `apk add --no-cache curl jq` on every backup and
   restore. Harmless while nothing in CI ran it; a per-PR dependency on the
   Alpine CDN once it does, and broken outright air-gapped — which is exactly
   why the shipped strategy bakes curl+jq into its image. Step 01 now reads that
   image off cozy-default-altinity and drops the apk add, falling back to
   alpine only when the platform strategy is absent. Also bounded the two
   polling curls with --max-time; a hung connection previously stalled the loop
   until the outer timeout.

4. 91-scenario-user-backup.md still documented the old tenant-test default.

5. .chainsaw.yaml claimed all app tests run in tenant-test, which the postgres
   and mariadb round-trips already contradicted before this branch.

The round-trip header now also states its scope plainly: it exercises the
example's strategy, not the shipped cozy-default-altinity.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
Second review pass on this branch.

1. The op timeout equalled the inner sum instead of exceeding it: the comment
   enumerated 3900s of inner waits and then set 65m, and 3900s IS 65m — so there
   was zero headroom for the applies and SQL calls between those waits, and the
   claim of "room" was false. Sized the inner budgets against what CI actually
   takes instead (the bucket suite completes in ~37s and a backup-enabled
   ClickHouse plus Keeper reaches Ready in ~98s, measured on run 30353894332):
   bucket claim/access 300->180 and the three job waits 600->480, summing to
   3300s, under a 60m op timeout. Shrinking the inner budgets rather than
   raising the ceiling also keeps this suite closer to its postgres and mariadb
   siblings on a 180-minute job cap.

2. The strategy Pod's securityContext comment still justified root with "Pod
   runs apk add at startup", which the previous commit made conditional — the
   default path now runs the platform's pre-baked image and installs nothing.
   Scoped the note to the alpine fallback and said explicitly that the field set
   matches cozy-default-altinity, runAsNonRoot deliberately unset in both.

3. clickhouse-backup.yaml still referenced clickhouse-backup-{1-contracts,
   2-roundtrip}: the file moved unchanged, so the Test rename never reached its
   own header and a reader following either name found nothing.

4. wait_sts_ready's timeout diagnostic could not print anything. It selected
   clickhouse.altinity.com/chi=${name#chi-}, which yields
   clickhouse-<app>-clickhouse-0-0 while the label carries the CHI name
   (clickhouse-<app>), and 2>/dev/null then swallowed kubectl's "No resources
   found". Ask for the StatefulSet's own first replica by name instead and let
   both streams through; verified it now surfaces the Pod events that explain
   why the replica never came up.

Also dropped the clickhouse bats case added in the previous commit: it asserted
what the generic examples/backups/<app> -> <app> mapping already guarantees, so
it did not lock the invariant its comment claimed. That invariant is now
recorded where it belongs — in the mapping's own comment in select-e2e.sh.

The dedicated ch-backup-client image this example now inherits is tracked in
#3420; noted there that the command: override is load-bearing in two files.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
Recommendations from the third review pass (which returned LGTM; none of these
blocked, all three are cheap and make the failure paths honest).

1. The round-trip's catch named nothing. An unnamed `describe HelmRelease` in
   tenant-root dumps every release on the root tenant of a live cluster and
   buries the two that matter, so name them (mariadb-2-backup-roundtrip names
   its release for the same reason). Raised podLogs tail 100 -> 200, since the
   sidecar logs every action and an S3 error sits well back from the tail by the
   time a step fails, and added the Keeper Pods: chkeeper.yaml labels them
   `app: <release>-keeper`, so the existing app.kubernetes.io/instance selectors
   missed them entirely — and a source ClickHouse that never reaches a ready
   replica is usually Keeper failing to form a quorum, which explains itself
   only there.

2. SSL_CERT_DIR is now `/etc/ssl/cozy-s3-ca:/etc/ssl/certs`. The single-directory
   form works — Go reads the default bundle FILE regardless, which is what the
   earlier measurement showed — but that left the public-CA half of the contract
   resting on a file this image happens to ship. Naming the system directory
   costs nothing and survives a base-image bump that moves the bundle; verified
   against public AWS S3 that both forms still verify (403 auth, no x509). The
   mount path stays the bare directory, and the unit test now pins both halves
   separately — it caught the over-broad edit that had briefly made mountPath a
   colon list.

3. cleanup.sh returned while the uninstalls it triggered were still running, so
   the next suite (etcd) could start with helm-controller still draining this
   one, and the pre-clean could hand step 04 an application whose predecessor
   was still going away. It now waits for both ClickHouse releases and the
   Bucket to actually disappear, and — since the script deliberately runs
   without `set -e` so one absent resource cannot abandon the rest — records
   those failures and exits non-zero, rather than reporting a teardown that did
   not settle as success. wait_deleted polls `get` instead of using
   `kubectl wait --for=delete`, which errors on an already-absent resource: the
   normal case here, and indistinguishable from a real failure.

Sized the two cleanup step budgets to 12m for the same reason the run step is
60m: cleanup.sh's waits now sum to 540s, which the previous 8m did not clear.

The Keeper-sized-from-the-server-resources bug the review found outside this
diff is filed as #3487.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
@dosubot dosubot Bot added the area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) label Jul 30, 2026
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/backups/clickhouse/00-helpers.sh`:
- Around line 46-47: The generated CA Secret currently uses a namespace-wide
static name, allowing collisions, overwrites, and unsafe cleanup. In
examples/backups/clickhouse/00-helpers.sh lines 46-47, derive the default
CH_CA_SECRET_NAME from CLICKHOUSE_NAME; in
examples/backups/clickhouse/03-create-bucket.sh lines 90-92, label the generated
Secret and refuse to overwrite an existing Secret without the demo ownership
label; in examples/backups/clickhouse/cleanup.sh lines 34-36, delete the Secret
only when that ownership label is present; update
examples/backups/clickhouse/README.md lines 30 and 60 to document and reference
the release-scoped generated name.
- Around line 188-215: The wait_deleted function must distinguish a missing
resource from kubectl retrieval failures. Update its polling logic to use
kubectl get with --ignore-not-found, treating only a successful empty result as
deletion and returning an error for RBAC, API-server, transport, or other
command failures; preserve the existing timeout and success logging 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 Plus

Run ID: 8286c769-d5b1-4639-abb2-3f7f030065de

📥 Commits

Reviewing files that changed from the base of the PR and between 10554f9 and 0c14d6c.

📒 Files selected for processing (23)
  • api/apps/v1alpha1/clickhouse/types.go
  • api/apps/v1alpha1/clickhouse/zz_generated.deepcopy.go
  • docs/operations/backup-classes.md
  • examples/backups/clickhouse/00-helpers.sh
  • examples/backups/clickhouse/01-create-strategy.sh
  • examples/backups/clickhouse/03-create-bucket.sh
  • examples/backups/clickhouse/04-create-clickhouse.sh
  • examples/backups/clickhouse/05-create-backupjob.sh
  • examples/backups/clickhouse/06-restore-in-place.sh
  • examples/backups/clickhouse/07-restore-to-copy.sh
  • examples/backups/clickhouse/91-scenario-user-backup.md
  • examples/backups/clickhouse/README.md
  • examples/backups/clickhouse/cleanup.sh
  • hack/e2e-chainsaw/.chainsaw.yaml
  • hack/e2e-chainsaw/clickhouse/chainsaw-test.yaml
  • hack/e2e-chainsaw/clickhouse/clickhouse-backup.yaml
  • hack/select-e2e.sh
  • packages/apps/clickhouse/README.md
  • packages/apps/clickhouse/templates/clickhouse.yaml
  • packages/apps/clickhouse/tests/backup_test.yaml
  • packages/apps/clickhouse/values.schema.json
  • packages/apps/clickhouse/values.yaml
  • packages/system/clickhouse-rd/cozyrds/clickhouse.yaml

Comment thread examples/backups/clickhouse/00-helpers.sh Outdated
Comment on lines +188 to +215
# Wait until a namespaced resource is really gone.
#
# `kubectl wait --for=delete` is not used: it errors out when the resource is
# already absent, which is the normal case on a clean namespace (cleanup.sh is
# idempotent and runs as a pre-clean too), and swallowing that error would also
# swallow a genuine failure. Polling `get` treats "already gone" and "gone now"
# as the same success.
wait_deleted() {
local resource_type="$1"
local resource_name="$2"
local timeout="${3:-300}"
local elapsed=0

while true; do
if ! kubectl -n "$NAMESPACE" get "$resource_type" "$resource_name" >/dev/null 2>&1; then
[[ $elapsed -gt 0 ]] && log_success "$resource_type/$resource_name is gone"
return 0
fi
if [[ $elapsed -ge $timeout ]]; then
log_error "Timeout waiting for $resource_type/$resource_name to be deleted; it is still present after ${timeout}s:"
kubectl -n "$NAMESPACE" get "$resource_type" "$resource_name" -o wide >&2 || true
return 1
fi
[[ $elapsed -eq 0 ]] && log_substep "Waiting for $resource_type/$resource_name to be deleted..."
sleep 5
elapsed=$((elapsed + 5))
done
}

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.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file line count and context =="
wc -l examples/backups/clickhouse/00-helpers.sh
sed -n '180,225p' examples/backups/clickhouse/00-helpers.sh

echo
echo "== wait_deleted usages =="
rg -n "wait_deleted|\$wait_deleted|wait_deleted\(" examples/backups/clickhouse -S

echo
echo "== log functions =="
rg -n "log_error|log_success|log_substep" examples/backups/clickhouse/00-helpers.sh -S

Repository: cozystack/cozystack

Length of output: 3963


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cleanup context =="
cat -n examples/backups/clickhouse/cleanup.sh | sed -n '1,60p'

echo
echo "== kubectl version/config if available =="
kubectl version --client --short 2>/dev/null || true
kubectl config current-context 2>/dev/null || true

Repository: cozystack/cozystack

Length of output: 3024


🌐 Web query:

kubectl get resource missing NotFound --ignore-not-found documentation

💡 Result:

The --ignore-not-found flag in kubectl get is used to suppress the "NotFound" error and return an exit code of 0 when a specific, requested object does not exist [1][2][3]. Key behaviors and details: - Purpose: When set to true, it prevents kubectl from returning an error if the specific resource you are trying to retrieve cannot be found [1][3]. - Scope: The flag specifically applies when requesting a specific resource by name [1][3]. It has no effect when querying collections of resources (e.g., listing all pods in a namespace or using label selectors), as these commands do not produce a "NotFound" error when no resources are returned; they simply return an empty list [1][3]. - Exit Codes: When used with a specific resource name, setting --ignore-not-found=true ensures the command exits with code 0 even if the resource is missing [2][4][5]. Conversely, if the flag is set to false (the default) and the resource is missing, the command will exit with a non-zero status and display a "NotFound" error [3][5]. For more detailed technical documentation, you can refer to the official Kubernetes kubectl get command reference [1].

Citations:


Do not treat every kubectl get error as successful deletion.

This returns success for RBAC, API-server, and transport failures, not just NotFound; the cleanup then settles on a false success and can leave half-uninstalled resources for later runs. Use --ignore-not-found for missing resources and fail when any other retrieval query fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backups/clickhouse/00-helpers.sh` around lines 188 - 215, The
wait_deleted function must distinguish a missing resource from kubectl retrieval
failures. Update its polling logic to use kubectl get with --ignore-not-found,
treating only a successful empty result as deletion and returning an error for
RBAC, API-server, transport, or other command failures; preserve the existing
timeout and success logging behavior.

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

LGTM with non-blocking notes

backup.endpointCA is opt-in and default-empty: the unset case renders no env/mount/volume (verified by render + a non-vacuous negative unit test), so existing releases are unchanged and no migration is needed. All schema-valid corners render admittable objects; generated artifacts (types.go, values.schema.json, README, cozyrds openAPISchema/keysOrder) are mutually consistent. Notes below are MINOR/observational and none blocks merge.

Findings

[MINOR] packages/apps/clickhouse/templates/clickhouse.yaml (the backup-s3-ca volume) — the projected CA Secret volume carries no optional: true, unlike the useSystemBucket credential env refs which are deliberately optional: true so the Pod starts before projection. If a tenant sets backup.endpointCA.name to a Secret that does not exist yet (or it is later deleted), the whole ClickHouse Pod (not just the sidecar) is stuck in ContainerCreating with MountVolume.SetUp failed ... secret not found on the next rollout. Fail-closed for trust material is a defensible choice and the failure is legible via kubectl describe, but the asymmetry with the adjacent credential design is undocumented — an inline comment stating the intent (or optional for symmetry) would prevent a surprised operator.

[MINOR] examples/backups/clickhouse/03-create-bucket.sh + examples/backups/clickhouse/README.md (step-order table) — the CA-copy probe reads the platform CA Secret cross-namespace in tenant-root with stderr swallowed, so an RBAC denial is misdiagnosed as "CA not found" and the script exits with advice ("set S3_CA_SECRET explicitly") that cannot fix a permissions problem. The README labels step 03 "tenant", but copying the CA requires reading seaweedfs-ca-cert in tenant-root, i.e. an admin/platform capability. Either relabel step 03 or distinguish NotFound from can't-ask in the probe.

Claim mismatches

[PARTIAL] PR body: "hack/select-e2e.sh maps clickhouse-application to clickhouse clickhouse-backup, and hack/select-install.sh maps the new suite to its PackageSource; both mappings have unit tests." The diff does not touch hack/select-install.sh, and the hack/select-e2e.sh change is comment-only. There is no separate clickhouse-backup suite dir — the two backup Tests (clickhouse-1-backup-contracts, clickhouse-2-backup-roundtrip) live in the existing hack/e2e-chainsaw/clickhouse/ suite, so the pre-existing examples/backups/<app>/ path derivation already selects them. The design is coherent; only the body text is stale.

Caveats

  • SSL_CERT_DIR additive-trust ("adds to, does not replace, the system bundle") rests on altinity/clickhouse-backup:2.7.4's Go runtime and is not re-verifiable in a hermetic review. Risk is low: opt-in, and the template appends /etc/ssl/certs explicitly.
  • Existing-customer upgrade verified by render only (unset → zero new fields on the running sidecar, so SSA field-ownership is not engaged); a live upgrade replay was out of scope for this static review.
  • On a cluster with a publicly-trusted S3 endpoint the demo now fails by default (S3_CA_SECRET defaults to seaweedfs-ca-cert, step 03 hard-exits when discovery is empty). The S3_CA_SECRET="" opt-out is documented, but a "no seaweedfs present → warn and skip" path would be safer for a legal input.
  • backup.endpointCA is wired only into the sidecar, not the legacy backup.schedule CronJob; combining schedule with a private-CA endpoint still fails TLS. The CronJob is deprecated, so note-level.

Recommended follow-ups

  • clickhouse-2-backup-roundtrip becomes CI-live only with this push and per the PR body has not had a green CI run. Confirm a green CI (or a cozystack-pr-test run) before merge — this gates the PR's own e2e, not customer clusters. Note 01-create-strategy.sh falls back to alpine:3.19 + apk add (needs internet) when cozy-default-altinity is absent — a possible constrained-CI flake.
  • Latent / pre-existing (not introduced here): packages/core/platform/sources/clickhouse-application.yaml dependsOn lists only clickhouse-operator + cozystack-engine, while the round-trip needs seaweedfs / bucket / backup-controller. Masked today because hack/select-install.sh is not wired into CI (full platform installed); the deferred "test-minimal" work would expose it for clickhouse and equally mariadb/postgres.

Review notes from IvanHunters on #3385 (LGTM with non-blocking findings).

1. The backup-s3-ca volume carries no `optional: true` while the adjacent
   useSystemBucket credential refs deliberately do, and the asymmetry was
   undocumented — an operator naming a Secret that does not exist gets the whole
   ClickHouse Pod stuck in ContainerCreating, not just the sidecar. Kept the
   fail-closed behaviour and wrote down why the two cases differ: credentials are
   projected by the platform after install, so a strict ref would wedge every
   fresh release until the first BackupJob, whereas this CA is named by the
   tenant, must already exist, and is trust material — optional would mount an
   empty dir, the Pod would look healthy, and the first backup would fail deep
   inside clickhouse-backup with an x509 error naming neither the Secret nor the
   chart. The comment also states the blast radius plainly.

2. The CA probe swallowed stderr, so an RBAC denial on the cross-namespace read
   was reported as "CA not found" followed by advice (set S3_CA_SECRET) that
   cannot fix a permissions problem. It now separates present / absent /
   not-allowed-to-look and says so, and the README no longer labels step 03
   simply "tenant": copying the CA reads a Secret in tenant-root, which is a
   platform capability. Added a section spelling out the three ways to satisfy
   that (admin credentials, a one-off admin copy, or skipping the CA entirely on
   a publicly-trusted endpoint).

3. Caveat, and a usability regression this branch introduced: on a cluster with
   a publicly-trusted endpoint and no seaweedfs, step 03 hard-exited on a default
   the user never chose. It now distinguishes an explicitly named Secret (still
   an error when missing — a typo should not be guessed past) from the unset
   default, where it warns and continues with backup.endpointCA unset. Verified
   all four paths against a fake kubectl: Forbidden, absent+default,
   absent+explicit, and present.

4. Caveat: endpointCA reaches the clickhouse-backup sidecar only. The legacy
   `schedule` CronJob writes to S3 through restic and does not consume it, so a
   private-CA endpoint combined with `schedule` still fails TLS. Documented on
   the field itself, so it lands in values.schema.json, the README and the
   cozyrds openAPISchema.

The PR body's claim about select-e2e.sh/select-install.sh mappings was stale
after the restructure that removed them; corrected there rather than in code.

Assisted-By: Claude <[email protected]>
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
@github-actions github-actions Bot added size/XXL This PR changes 1000+ lines, ignoring generated files and removed size/XL This PR changes 500-999 lines, ignoring generated files labels Aug 3, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/backups/clickhouse/README.md`:
- Line 72: Update the admin CA override instructions in the backup demo README
to specify copying the CA into <NAMESPACE>/<CH_CA_SECRET_NAME>, running with
S3_CA_SECRET="", and exporting CH_BACKUP_CA_SECRET="<application-secrets-name>"
before Step 04 and Step 07.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c26d4e99-e134-41b4-a279-482705d667a8

📥 Commits

Reviewing files that changed from the base of the PR and between 0c14d6c and 9379729.

📒 Files selected for processing (9)
  • api/apps/v1alpha1/clickhouse/types.go
  • examples/backups/clickhouse/00-helpers.sh
  • examples/backups/clickhouse/03-create-bucket.sh
  • examples/backups/clickhouse/README.md
  • packages/apps/clickhouse/README.md
  • packages/apps/clickhouse/templates/clickhouse.yaml
  • packages/apps/clickhouse/values.schema.json
  • packages/apps/clickhouse/values.yaml
  • packages/system/clickhouse-rd/cozyrds/clickhouse.yaml
🚧 Files skipped from review as they are similar to previous changes (8)
  • packages/apps/clickhouse/templates/clickhouse.yaml
  • packages/apps/clickhouse/values.schema.json
  • packages/apps/clickhouse/values.yaml
  • packages/system/clickhouse-rd/cozyrds/clickhouse.yaml
  • packages/apps/clickhouse/README.md
  • api/apps/v1alpha1/clickhouse/types.go
  • examples/backups/clickhouse/00-helpers.sh
  • examples/backups/clickhouse/03-create-bucket.sh

Comment thread examples/backups/clickhouse/README.md Outdated
@androndo

Copy link
Copy Markdown
Contributor Author

E2E: all ClickHouse tests pass ✅

The ClickHouse backup tests this PR adds/enables all passed in E2E — the backup/restore roundtrip that backup.endpointCA unblocks runs green end-to-end:

  • chainsaw/clickhouse-2-backup-roundtrip — PASS (restorejob/clickhouse-restore-to-copy reached 'Succeeded'; To-copy restore verified: 1 sentinel row)
  • chainsaw/clickhouse-1-backup-contracts — PASS
  • chainsaw/clickhouse — PASS

The only failures in that run were kubernetes-previous and kubernetes-latest — unrelated tenant-Kubernetes bring-up flakes (tenant cozy-cilium/cilium HelmRelease convergence timeout, cascading to the tenant control plane). Those are outside this PR's diff, which is entirely ClickHouse-scoped.

E2E run (ClickHouse tests green): https://github.com/cozystack/cozystack/actions/runs/30810480620/job/92274960158

IvanHunters
IvanHunters previously approved these changes Aug 5, 2026

@IvanHunters IvanHunters left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Verdict

LGTM with non-blocking notes.

Reviewed at 9379729 against merge-base 10554f9. Static review found nothing blocking: backup.endpointCA is opt-in and empty by default (existing releases render byte-identical), all five config corners render to admittable objects, the unit tests are non-vacuous, generated artifacts are in sync, and the shell harness is fail-closed. The remaining items are runtime facts a static review cannot settle.

Claim mismatches

[PARTIAL] PR body §2 describes a suite hack/e2e-chainsaw/clickhouse-backup/ with Tests clickhouse-backup-1-contracts / clickhouse-backup-2-roundtrip. The code folds both Tests into the existing hack/e2e-chainsaw/clickhouse/chainsaw-test.yaml with names clickhouse-1-backup-contracts (:119) / clickhouse-2-backup-roundtrip (:255). A later paragraph of the body corrects the directory to clickhouse/, so this is an internal wording inconsistency, not a shipped-artifact defect. Worth tidying the description before squash-merge.

[UNVERIFIABLE] The SSL_CERT_DIR-vs-AWS_CA_BUNDLE behaviour is documented as "measured against altinity/clickhouse-backup:2.7.4" (packages/apps/clickhouse/templates/clickhouse.yaml:319), but the chart ships ghcr.io/cozystack/cozystack/altinity-clickhouse-backup:v1.6.0 (templates/clickhouse.yaml:205). Whether that image honours SSL_CERT_DIR the same way at runtime cannot be checked statically. Risk is low (SSL_CERT_DIR-in-addition-to-the-system-store is a Go crypto/x509 property and clickhouse-backup is Go), but the load-bearing proof is the CI round-trip, not the code.

Caveats

  • Runtime CA trust (does the shipped v1.6.0 sidecar actually verify the SeaweedFS endpoint via the mounted bundle) is out of scope for a static review. Per the PR body the clickhouse-2-backup-roundtrip Test only became CI-live with this branch and still needs a green E2E run — that green run is the gating evidence.
  • Non-optional CA volume, blast radius (templates/clickhouse.yaml:411-417). The stated failure mode is accurate: a named-but-absent Secret holds the Pod in ContainerCreating with MountVolume.SetUp failed ... secret not found, and because the CA lives on the single Altinity CHI Pod the blast radius is the whole ClickHouse Pod, not just the sidecar. This is inherent to the single-Pod-with-sidecar CHI shape, it is opt-in and empty by default, and the fail-closed choice over optional: true is explicitly reasoned. Acceptable as designed; noted so operators know that deleting/renaming a referenced CA Secret will wedge the serving Pod on next (re)start.
  • Applying endpointCA to a running release is an additive volume+volumeMount+SSL_CERT_DIR change reconciled into the StatefulSet by the Altinity clickhouse-operator; the rolling recreation was not exercised here (additive on an opt-in toggle, low risk).

Verified negatives (so they are not re-litigated): corner endpointCA unset renders zero CA env/mount/volume; backup.enabled=false with endpointCA.name set renders no sidecar and no volume; cozyrds secrets/services.include and templates/dashboard-resourcemap.yaml untouched (no dashboard-RBAC drift); values.schema.json, keysOrder, types.go and zz_generated.deepcopy.go all consistent (make generate idempotent, go build/go vet clean); helm unittest 17/17 and mutation of the SSL_CERT_DIR gate reddens exactly the 3 endpointCA assertions (non-vacuous); hack/select-e2e.sh diff is comment-only; the CA-copy gate in examples/backups/clickhouse/03-create-bucket.sh is fail-closed (separates Forbidden from NotFound).

Recommended follow-ups

  • Land the green clickhouse-2-backup-roundtrip CI run (or a dev-cluster round-trip) before merge; it is the only thing that proves the shipped v1.6.0 sidecar honours SSL_CERT_DIR against SeaweedFS.
  • Track the already-opened terraform-provider-cozystack schema follow-up for the new nested endpointCA object.
  • Reconcile the PR description's suite-directory / Test-name wording with the code.

…SSL_CERT_DIR note

The SSL_CERT_DIR rationale said the behaviour was "verified against
altinity/clickhouse-backup:2.7.4" while the chart ships
altinity-clickhouse-backup:v1.6.0, which reads as a version mismatch.
They are the same binary: the cozystack image is built FROM upstream
altinity/clickhouse-backup:2.7.4. Say so, and point at the Dockerfile,
so the measurement provably applies to the shipped image.

Comment-only change inside a Go-template comment; render is unchanged
and helm unittest stays 17/17.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
IvanHunters
IvanHunters previously approved these changes Aug 5, 2026
Comment on lines +310 to +344
{{- /*
SSL_CERT_DIR points clickhouse-backup at the mounted CA
bundle so it can verify an S3 endpoint signed by a private
CA — Cozystack's in-cluster SeaweedFS
(seaweedfs-s3.<tenant>:8333) serves the self-signed
"SeaweedFS CA", which no system trust store carries.

SSL_CERT_DIR and NOT AWS_CA_BUNDLE, deliberately. Both are
honoured by this image — the shipped
altinity-clickhouse-backup:v1.6.0 is built FROM upstream
altinity/clickhouse-backup:2.7.4 (see
images/altinity-clickhouse-backup/Dockerfile), which is the
binary the behaviour below was measured against — but they
differ in one
way that matters: Go reads SSL_CERT_DIR *in addition to*
the default system bundle file, whereas AWS_CA_BUNDLE
REPLACES the SDK's trust pool. Measured against a private
CA and public AWS S3 in turn: with SSL_CERT_DIR both
verify; with AWS_CA_BUNDLE the private endpoint verifies
and public S3 fails `x509: certificate signed by unknown
authority`. So AWS_CA_BUNDLE would silently break a
release that also talks to a publicly-trusted endpoint.
S3_DISABLE_CERT_VERIFICATION is the third option and is
rejected on purpose: it drops verification entirely rather
than trusting one more CA.

/etc/ssl/certs is listed explicitly alongside our directory.
Go would still read the default bundle FILE
(/etc/ssl/certs/ca-certificates.crt) even if SSL_CERT_DIR
named only our dir — that is what the measurement above
showed — but that leaves the public-CA half of the contract
resting on a file this image happens to ship. Naming the
system directory costs nothing and survives a base-image
bump that moves the bundle.
*/}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Guys, a Helm template is not the right place for documentation.

Comment on lines +386 to +414
{{- /*
The CA bundle the sidecar's SSL_CERT_DIR points at. Projected
under the fixed filename ca.crt regardless of the key it occupies
in the Secret, because SSL_CERT_DIR reads whole directories and
the filename is what lands in the trust set.

Deliberately NOT `optional: true`, unlike the credential
secretKeyRefs above on the useSystemBucket path. The asymmetry is
intended and the two cases are not alike:

- credentials are projected by the platform AFTER the release
installs, so a strict ref would wedge every fresh
useSystemBucket release in CreateContainerConfigError until
the first BackupJob fired. Optional lets the Pod start and
surfaces the problem later, when a backup is actually run.
- this CA is named by the tenant, must already exist when they
name it, and is trust material. Optional would mount an empty
directory, the Pod would come up looking healthy, and the
first backup would fail deep inside clickhouse-backup with an
x509 error naming neither the Secret nor this chart.

So it fails closed: a missing Secret holds the Pod in
ContainerCreating with `MountVolume.SetUp failed ... secret not
found` on it, which names the exact missing object in
`kubectl describe pod`. Note the blast radius is the whole
ClickHouse Pod, not just the sidecar — that is the cost of the
legible failure, and the reason the field is opt-in and empty by
default.
*/}}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comments-as-docs issue here.

…emo CA secret

Review feedback on #3385:

- Move the endpointCA rationale out of the Helm template (lllamnyp): the
  SSL_CERT_DIR and CA-volume blocks were multi-paragraph essays. Trim each to
  a few lines stating only the non-obvious decision (SSL_CERT_DIR vs
  AWS_CA_BUNDLE; the deliberate non-optional mount) and point at the
  values.yaml endpointCA field for the full description.

- wait_deleted (00-helpers.sh): poll with 'get --ignore-not-found' and treat
  only an empty result as deletion, so an RBAC/API-server/transport failure is
  no longer read as success and cleanup can't settle on a false positive.

- Backup demo CA Secret: scope the default name to CLICKHOUSE_NAME, stamp an
  ownership label on the copy, refuse to overwrite a Secret the demo doesn't
  own, and delete on cleanup only when that label is present — so a foreign
  Secret sharing the name is never clobbered or removed.

- README: document CH_BACKUP_CA_SECRET and the release-scoped, demo-owned CA
  Secret; spell out the admin-copy path (export CH_BACKUP_CA_SECRET before
  steps 04 and 07).

Assisted-By: Claude <[email protected]>
Signed-off-by: Andrey Kolkov <[email protected]>
@github-actions github-actions Bot added size/XL This PR changes 500-999 lines, ignoring generated files and removed size/XXL This PR changes 1000+ lines, ignoring generated files labels Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/backups/clickhouse/00-helpers.sh (1)

219-225: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Bound every kubectl get in the deletion waiter.

kubectl --request-timeout defaults to 0, so requests do not time out. wait_deleted also updates elapsed only after kubectl returns, so a stalled API call can leave wait_deleted past its loop timeout. Add a bounded request timeout to both kubectl get invocations in this function, or wrap each call with an explicit process timeout.

Proposed fix
-        if got=$(kubectl -n "$NAMESPACE" get "$resource_type" "$resource_name" --ignore-not-found 2>/dev/null) && [[ -z "$got" ]]; then
+        if got=$(kubectl --request-timeout=30s -n "$NAMESPACE" get "$resource_type" "$resource_name" --ignore-not-found 2>/dev/null) && [[ -z "$got" ]]; then
...
-            kubectl -n "$NAMESPACE" get "$resource_type" "$resource_name" -o wide >&粗 or true
+            kubectl --request-timeout=30s -n "$NAMESPACE" get "$resource_type" "$resource_name" -o wide >&2 || true
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/backups/clickhouse/00-helpers.sh` around lines 219 - 225, Update the
kubectl invocations in wait_deleted, including the initial get used to assign
got and the timeout-diagnostic get, to enforce a bounded request duration via
--request-timeout or an equivalent process timeout. Keep the existing deletion
detection and timeout logging behavior unchanged.

Source: MCP tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@examples/backups/clickhouse/00-helpers.sh`:
- Around line 219-225: Update the kubectl invocations in wait_deleted, including
the initial get used to assign got and the timeout-diagnostic get, to enforce a
bounded request duration via --request-timeout or an equivalent process timeout.
Keep the existing deletion detection and timeout logging behavior unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: aff84ad3-400d-431a-9037-ef3ba26d41df

📥 Commits

Reviewing files that changed from the base of the PR and between 419c556 and 1b1a6b2.

📒 Files selected for processing (5)
  • examples/backups/clickhouse/00-helpers.sh
  • examples/backups/clickhouse/03-create-bucket.sh
  • examples/backups/clickhouse/README.md
  • examples/backups/clickhouse/cleanup.sh
  • packages/apps/clickhouse/templates/clickhouse.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • examples/backups/clickhouse/cleanup.sh
  • packages/apps/clickhouse/templates/clickhouse.yaml
  • examples/backups/clickhouse/03-create-bucket.sh
  • examples/backups/clickhouse/README.md

@androndo
Andrey Kolkov (androndo) merged commit d099029 into main Aug 10, 2026
106 of 109 checks passed
@androndo
Andrey Kolkov (androndo) deleted the test/clickhouse-backup-e2e-chainsaw branch August 10, 2026 17:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/database Issues or PRs related to managed databases (postgres, mariadb, redis, etcd, kafka, clickhouse) area/storage Issues or PRs related to storage (linstor, seaweedfs, bucket, velero, harbor) area/testing Issues or PRs related to testing (e2e, bats, unit tests) kind/feature Categorizes issue or PR as related to a new feature size/XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants