Skip to content

Commit fe8503b

Browse files
committed
fix(backups): surface CLI stderr, own the demo pods, keep the nats password off argv
Addresses the review on the nats and kafka-metadata demos. - The nats verify read (stream_message_count) and the kafka verify reads (topic_meta, topic_partitions) dropped the CLI's stderr with 2>/dev/null, so a failed exec still surfaced as an empty value -- the very defect this PR claims to fix. Keep only jq's and the describe's own suppression; the exec's stderr now says why. New bats cases fail the exec and assert the message reaches the caller, so a re-added 2>/dev/null reddens the suite. - nats_cli passed NATS_PASSWORD in the --server URL, which the exec request carries into the apiserver audit log. Pass it in NATS_PASSWORD on stdin, as the redis demo does; a bats case asserts it stays out of argv. - The CLI pods were addressed by name alone. Label them cozystack.io/backup-demo=<demo>, reuse only a Ready pod the demo owns, replace a leftover in a terminal phase instead of waiting 5m on it, refuse a same-named pod the demo does not own, and reap by label with --grace-period=1 so cleanup does not wait the full grace period on a sleep-as-PID1 pod. - Move the kafka strategy-wait comment onto its own paragraph so the S3/CA comment sits with the if it describes. Assisted-by: LLM Signed-off-by: IvanHunters <[email protected]>
1 parent 28044aa commit fe8503b

7 files changed

Lines changed: 172 additions & 64 deletions

File tree

‎examples/backups/kafka-metadata/00-helpers.sh‎

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -203,12 +203,24 @@ kafka_wait_ready() {
203203
}
204204

205205
# Ensure the long-lived kafka-cli Pod exists and is Ready, so kafka_run can exec
206-
# into it. Idempotent: the Pod is reused across calls and across the numbered
207-
# demo scripts, and cleanup.sh removes it.
206+
# into it. Idempotent: a Ready Pod this demo owns is reused across calls and
207+
# across the numbered demo scripts; a leftover in a terminal phase (Succeeded /
208+
# Failed) is replaced rather than waited on; and a same-named Pod this demo does
209+
# not own is refused rather than hijacked or deleted. cleanup.sh removes it.
208210
kafka_cli_pod() {
209-
kubectl -n "$NAMESPACE" get pod "$KAFKA_CLI_POD" >/dev/null 2>&1 \
210-
|| kubectl -n "$NAMESPACE" run "$KAFKA_CLI_POD" --image="$KAFKA_IMAGE" \
211+
local phase owner
212+
phase=$(kubectl -n "$NAMESPACE" get pod "$KAFKA_CLI_POD" -o jsonpath='{.status.phase}' 2>/dev/null || true)
213+
owner=$(kubectl -n "$NAMESPACE" get pod "$KAFKA_CLI_POD" -o jsonpath='{.metadata.labels.cozystack\.io/backup-demo}' 2>/dev/null || true)
214+
if [ -n "$phase" ] && [ "$owner" != "kafka-metadata" ]; then
215+
log_error "Pod $NAMESPACE/$KAFKA_CLI_POD exists but this demo does not own it; refusing to use or delete it"
216+
return 1
217+
fi
218+
if [ "$phase" != "Running" ] && [ "$phase" != "Pending" ]; then
219+
kubectl -n "$NAMESPACE" delete pod "$KAFKA_CLI_POD" --grace-period=1 --ignore-not-found >/dev/null
220+
kubectl -n "$NAMESPACE" run "$KAFKA_CLI_POD" --image="$KAFKA_IMAGE" \
221+
--labels=cozystack.io/backup-demo=kafka-metadata \
211222
--restart=Never --command -- sleep infinity >/dev/null
223+
fi
212224
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$KAFKA_CLI_POD" \
213225
--timeout=5m >/dev/null
214226
}
@@ -280,7 +292,7 @@ topic_partitions() {
280292
line=$("$BIN"/kafka-topics.sh --bootstrap-server "$BOOT" --describe --topic "\Q$TOPIC\E" 2>/dev/null | head -1) || exit 0
281293
[ -n "$line" ] || exit 0
282294
printf "%s" "$line" | grep -oE "PartitionCount: [0-9]+" | awk "{print \$2}"
283-
' 2>/dev/null | tr -d '\r\n'
295+
' | tr -d '\r\n'
284296
}
285297

286298
# Print "<partitions> <retention.ms>" for the demo topic, or "" if absent.
@@ -292,5 +304,5 @@ topic_meta() {
292304
parts=$(printf "%s" "$line" | grep -oE "PartitionCount: [0-9]+" | awk "{print \$2}")
293305
ret=$(printf "%s" "$line" | grep -oE "retention.ms=[0-9]+" | head -1 | cut -d= -f2)
294306
printf "%s %s\n" "$parts" "$ret"
295-
' 2>/dev/null | tr -d '\r'
307+
' | tr -d '\r'
296308
}

‎examples/backups/kafka-metadata/cleanup.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,6 @@ kubectl -n "$NAMESPACE" delete kafka.apps.cozystack.io "$KAFKA_SRC_NAME" --ignor
1919
kubectl delete backupclass.backups.cozystack.io "$BACKUPCLASS_NAME" --ignore-not-found
2020
kubectl delete kafka.strategy.backups.cozystack.io "$STRATEGY_NAME" --ignore-not-found
2121
kubectl -n "$NAMESPACE" delete secret "$CA_SECRET" --ignore-not-found
22-
kubectl -n "$NAMESPACE" delete pod "$KAFKA_CLI_POD" --ignore-not-found
22+
kubectl -n "$NAMESPACE" delete pod -l cozystack.io/backup-demo=kafka-metadata --grace-period=1 --ignore-not-found
2323

2424
log_success "Cleanup complete."

‎examples/backups/kafka-metadata/run-all.sh‎

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,25 +37,26 @@ wait_hr_ready "kafka-${KAFKA_SRC_NAME}" 300
3737
kafka_wait_ready "$KAFKA_SRC_NAME" 600
3838

3939
print_header "Step 05a: Derive the demo Kafka strategy '${STRATEGY_NAME}' + BackupClass '${BACKUPCLASS_NAME}'"
40+
# Wait for the strategy rather than read it once. The shipped cozy-default-kafka
41+
# is rendered behind a lookup of the platform bucket, so it exists only once a
42+
# Helm upgrade has run after that bucket was provisioned. The controller forces
43+
# that upgrade, but helm-controller holds it while any release in its dependsOn
44+
# is not Ready, so a platform change made just before this runs can keep it
45+
# absent for minutes. Both the endpoint read below and provision_demo_strategy
46+
# derive from it, so wait once here; kubectl wait keeps polling through NotFound
47+
# and fails on any other error.
48+
log_substep "Waiting for the platform's cozy-default-kafka strategy..."
49+
kubectl wait --for=create kafka.strategy.backups.cozystack.io/cozy-default-kafka \
50+
--timeout=15m >/dev/null \
51+
|| { log_error "cozy-default-kafka strategy did not appear: enable the platform default backups (backupstrategy-controller) before running this demo"; exit 1; }
52+
4053
# The shipped cozy-default-kafka advertises the platform's EXTERNAL S3 ingress,
4154
# which in-cluster Pods cannot resolve or TLS-validate in CI. Derive a demo
4255
# strategy from it (so the driver script stays a single source of truth) pointed
4356
# at an in-cluster, verifiable endpoint. With an S3_ENDPOINT override, copy the
4457
# self-signed seaweedfs CA and mount it (CURL_CA_BUNDLE); with none, reuse the
4558
# shipped endpoint and skip the CA mount (its cert is publicly trusted, and
4659
# CURL_CA_BUNDLE would REPLACE the system bundle rather than extend it).
47-
# Wait for it rather than read it once. The shipped strategy is rendered behind a
48-
# lookup of the platform bucket, so it exists only once a Helm upgrade has run
49-
# after that bucket was provisioned. The controller forces that upgrade, but
50-
# helm-controller holds it while any release in its dependsOn is not Ready, so a
51-
# platform change made just before this runs can keep it absent for minutes. Both
52-
# the endpoint read below and provision_demo_strategy derive from it, so wait
53-
# once here; kubectl wait keeps polling through NotFound and fails on any other
54-
# error.
55-
log_substep "Waiting for the platform's cozy-default-kafka strategy..."
56-
kubectl wait --for=create kafka.strategy.backups.cozystack.io/cozy-default-kafka \
57-
--timeout=15m >/dev/null \
58-
|| { log_error "cozy-default-kafka strategy did not appear: enable the platform default backups (backupstrategy-controller) before running this demo"; exit 1; }
5960
if [[ -n "$S3_ENDPOINT" ]]; then
6061
CA_PRESENT=$(copy_s3_ca)
6162
else

‎examples/backups/nats/00-helpers.sh‎

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,33 @@ wait_for_field() {
8484
# names its client Service after the application (fullnameOverride=<name>) and
8585
# stores per-user passwords in the "<name>-credentials" Secret. The demo sets a
8686
# fixed password (NATS_PASSWORD) on both the source and restore-target apps, so
87-
# a single URL shape works everywhere.
87+
# a single URL shape works everywhere. The password is not in the URL: nats_cli
88+
# passes it to the CLI in NATS_PASSWORD on stdin, to keep it out of the exec
89+
# request URL the apiserver records in its audit log.
8890
nats_url() {
8991
local app="$1"
90-
echo "nats://${NATS_USER}:${NATS_PASSWORD}@${app}.${NAMESPACE}.svc:4222"
92+
echo "nats://${NATS_USER}@${app}.${NAMESPACE}.svc:4222"
9193
}
9294

9395
# Ensure the long-lived nats-box CLI Pod exists and is Ready, so nats_cli can
94-
# exec into it. Idempotent: the Pod is reused across calls and across the
95-
# numbered demo scripts, and cleanup.sh removes it.
96+
# exec into it. Idempotent: a Ready Pod this demo owns is reused across calls and
97+
# across the numbered demo scripts; a leftover in a terminal phase (Succeeded /
98+
# Failed) is replaced rather than waited on; and a same-named Pod this demo does
99+
# not own is refused rather than hijacked or deleted. cleanup.sh removes it.
96100
nats_cli_pod() {
97-
kubectl -n "$NAMESPACE" get pod "$NATS_CLI_POD" >/dev/null 2>&1 \
98-
|| kubectl -n "$NAMESPACE" run "$NATS_CLI_POD" --image="$NATS_BOX_IMAGE" \
101+
local phase owner
102+
phase=$(kubectl -n "$NAMESPACE" get pod "$NATS_CLI_POD" -o jsonpath='{.status.phase}' 2>/dev/null || true)
103+
owner=$(kubectl -n "$NAMESPACE" get pod "$NATS_CLI_POD" -o jsonpath='{.metadata.labels.cozystack\.io/backup-demo}' 2>/dev/null || true)
104+
if [ -n "$phase" ] && [ "$owner" != "nats" ]; then
105+
log_error "Pod $NAMESPACE/$NATS_CLI_POD exists but this demo does not own it; refusing to use or delete it"
106+
return 1
107+
fi
108+
if [ "$phase" != "Running" ] && [ "$phase" != "Pending" ]; then
109+
kubectl -n "$NAMESPACE" delete pod "$NATS_CLI_POD" --grace-period=1 --ignore-not-found >/dev/null
110+
kubectl -n "$NAMESPACE" run "$NATS_CLI_POD" --image="$NATS_BOX_IMAGE" \
111+
--labels=cozystack.io/backup-demo=nats \
99112
--restart=Never --command -- sleep infinity >/dev/null
113+
fi
100114
kubectl -n "$NAMESPACE" wait --for=condition=Ready "pod/$NATS_CLI_POD" \
101115
--timeout=5m >/dev/null
102116
}
@@ -110,19 +124,29 @@ nats_cli_pod() {
110124
# registers, so a CLI that finishes in between returns empty with exit 0. An
111125
# exec'd process owns its pipes, so its output cannot be missed that way, and
112126
# kubectl's and the CLI's stderr stay attached so a failed read says why.
127+
#
128+
# The password travels in NATS_PASSWORD on stdin rather than in the --server URL,
129+
# which the exec request carries into the apiserver's audit log. The nats CLI
130+
# reads NATS_PASSWORD from its environment.
113131
nats_cli() {
114132
local app="$1"; shift
115133
nats_cli_pod
116-
kubectl -n "$NAMESPACE" exec -i "$NATS_CLI_POD" -- \
117-
nats --server "$(nats_url "$app")" "$@"
134+
printf '%s\n' "$NATS_PASSWORD" | kubectl -n "$NAMESPACE" exec -i "$NATS_CLI_POD" -- sh -c '
135+
IFS= read -r NATS_PASSWORD || true
136+
export NATS_PASSWORD
137+
server=$1; shift
138+
exec nats --server "$server" "$@"
139+
' sh "$(nats_url "$app")" "$@"
118140
}
119141

120142
# Number of messages currently stored in a JetStream stream, or "" if the
121-
# stream does not exist.
143+
# stream does not exist. Only jq's stderr is dropped (a missing stream prints a
144+
# harmless "stream not found" there); nats_cli's own stderr stays, so an exec
145+
# that fails says why instead of surfacing as an empty count.
122146
stream_message_count() {
123147
local app="$1"
124148
local stream="$2"
125-
nats_cli "$app" stream info "$stream" --json 2>/dev/null \
149+
nats_cli "$app" stream info "$stream" --json \
126150
| jq -r '.state.messages // empty' 2>/dev/null | tr -d '[:space:]'
127151
}
128152

‎examples/backups/nats/cleanup.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ kubectl -n "$NAMESPACE" delete secret "${NATS_RESTORE_NAME}-backup-s3" --ignore-
1616
kubectl -n "$NAMESPACE" delete secret "${NATS_NAME}-backup-s3" --ignore-not-found
1717
kubectl -n "$NAMESPACE" delete nats "$NATS_RESTORE_NAME" --ignore-not-found
1818
kubectl -n "$NAMESPACE" delete nats "$NATS_NAME" --ignore-not-found
19-
kubectl -n "$NAMESPACE" delete pod "$NATS_CLI_POD" --ignore-not-found
19+
kubectl -n "$NAMESPACE" delete pod -l cozystack.io/backup-demo=nats --grace-period=1 --ignore-not-found
2020
kubectl -n "$NAMESPACE" delete bucket "$BUCKET_NAME" --ignore-not-found
2121
rm -f "$SCRIPT_DIR/.bucket-info.env"
2222
kubectl delete backupclass "$BACKUPCLASS_NAME" --ignore-not-found

‎hack/kafka-metadata-backup-demo-helpers.bats‎

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
# helper the kafka-3-metadata-roundtrip suite seeds and verifies topics through.
44
#
55
# kubectl and the Kafka CLI are stub executables on PATH. The kubectl stub
6-
# answers only the calls kafka_run is expected to make -- get/wait for the
7-
# long-lived CLI Pod and an `exec` into it -- and runs the exec'd snippet locally
8-
# against the kafka-topics.sh stub. A throwaway `kubectl run -i` Pod (the old
9-
# attach-racing path) makes the stub exit non-zero, so a test that gets the right
10-
# reply proves it came back through exec, not that path.
6+
# answers the calls kafka_run makes -- get the CLI Pod's phase and owner label,
7+
# create it when absent, wait for it, and `exec` into it -- and runs the exec'd
8+
# snippet locally against the kafka-topics.sh stub. STUB_PHASE / STUB_OWNER drive
9+
# the reuse guard, and STUB_EXEC_ERROR makes the exec fail so the "a failed read
10+
# says why" claim is pinned rather than asserted in prose.
1111
#
1212
# cozytest.sh's awk runner recognizes only @test blocks and a bare `}`; there is
1313
# no bats `run` or `$status`, and no setup/teardown, so each case calls
@@ -22,16 +22,21 @@ make_stubs() {
2222
printf '%s\n' "$*" >> "$STUB_DIR/kubectl.argv"
2323
case "$1 $2 $3" in
2424
"-n tenant-root get")
25-
[ -n "${STUB_NO_POD:-}" ] && exit 1
25+
case "$*" in
26+
*"jsonpath={.status.phase}"*) printf '%s' "${STUB_PHASE-Running}" ;;
27+
*"jsonpath={.metadata.labels"*) printf '%s' "${STUB_OWNER-kafka-metadata}" ;;
28+
esac
2629
exit 0 ;;
27-
"-n tenant-root wait")
30+
"-n tenant-root delete")
2831
exit 0 ;;
2932
"-n tenant-root run")
30-
# Only reachable when the CLI Pod was absent; otherwise kafka_run must never
31-
# start a Pod, and this exit fails the test if it does.
32-
[ -n "${STUB_NO_POD:-}" ] && exit 0
33-
echo "kafka_run started a throwaway run Pod instead of exec" >&2; exit 95 ;;
33+
# Reachable only when the Pod is absent; a reuse that starts a Pod is a bug.
34+
[ -z "${STUB_PHASE-Running}" ] && exit 0
35+
echo "kafka_run started a Pod though a Ready one it owns exists" >&2; exit 95 ;;
36+
"-n tenant-root wait")
37+
exit 0 ;;
3438
"-n tenant-root exec")
39+
[ -n "${STUB_EXEC_ERROR:-}" ] && { echo "$STUB_EXEC_ERROR" >&2; exit 1; }
3540
shift 6 # drop: -n tenant-root exec -i <pod> --
3641
if [ "$1" != "bash" ] || [ "$2" != "-c" ]; then
3742
echo "unexpected exec payload: $*" >&2; exit 96
@@ -53,25 +58,50 @@ EOF
5358
}
5459

5560
run_topic_meta() {
56-
STUB_DIR=$stub STUB_NO_POD="${STUB_NO_POD:-}" PATH="$stub:$PATH" NAMESPACE=tenant-root KAFKA_BIN=$stub TOPIC=orders bash -c '
61+
STUB_DIR=$stub STUB_PHASE="${STUB_PHASE-Running}" STUB_OWNER="${STUB_OWNER-kafka-metadata}" \
62+
STUB_EXEC_ERROR="${STUB_EXEC_ERROR:-}" PATH="$stub:$PATH" NAMESPACE=tenant-root \
63+
KAFKA_BIN=$stub TOPIC=orders bash -c '
5764
set -euo pipefail
5865
. examples/backups/kafka-metadata/00-helpers.sh
5966
topic_meta demo
6067
'
6168
}
6269

63-
@test "kafka_run reads the reply by exec into the long-lived CLI Pod" {
70+
@test "kafka_run reuses a Ready Pod it owns and reads the reply by exec" {
6471
make_stubs
6572
out=$(run_topic_meta)
6673
[ "$out" = "3 123456" ]
6774
grep -q 'exec -i kafka-cli -- bash -c' "$stub/kubectl.argv"
6875
}
6976

70-
@test "kafka_run starts the CLI Pod when it is absent, then execs into it" {
77+
@test "kafka_run creates the CLI Pod when it is absent, then execs into it" {
7178
make_stubs
72-
export STUB_NO_POD=1
79+
export STUB_PHASE=
7380
out=$(run_topic_meta)
7481
[ "$out" = "3 123456" ]
7582
grep -q 'run kafka-cli --image=' "$stub/kubectl.argv"
83+
grep -q 'labels=cozystack.io/backup-demo=kafka-metadata' "$stub/kubectl.argv"
7684
grep -q 'exec -i kafka-cli -- bash -c' "$stub/kubectl.argv"
7785
}
86+
87+
@test "kafka_run refuses a same-named Pod the demo does not own" {
88+
make_stubs
89+
export STUB_OWNER=someone-else
90+
err=$(mktemp)
91+
if run_topic_meta 2>"$err"; then
92+
echo "kafka_run used a Pod it does not own" >&2
93+
exit 1
94+
fi
95+
grep -q 'does not own it' "$err"
96+
}
97+
98+
@test "kafka_run fails with kubectl's own error when the exec fails" {
99+
make_stubs
100+
export STUB_EXEC_ERROR="error: unable to upgrade connection: container not found"
101+
err=$(mktemp)
102+
if run_topic_meta 2>"$err"; then
103+
echo "a failed exec read as a successful empty reply" >&2
104+
exit 1
105+
fi
106+
grep -q 'unable to upgrade connection: container not found' "$err"
107+
}

‎hack/nats-backup-demo-helpers.bats‎

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
# nats roundtrip suite seeds and verifies JetStream streams through.
44
#
55
# kubectl and the `nats` CLI are stub executables on PATH. The kubectl stub
6-
# answers only the calls nats_cli is expected to make -- get/wait for the
7-
# long-lived CLI Pod and an `exec` into it -- and runs the exec'd `nats`
8-
# invocation locally against the nats stub. A throwaway `kubectl run -i` Pod (the
9-
# old attach-racing path) makes the stub exit non-zero, so a test that gets the
10-
# right reply proves it came back through exec, not that path.
6+
# answers the calls nats_cli makes -- get the CLI Pod's phase and owner label,
7+
# create it when absent, wait for it, and `exec` into it -- and runs the exec'd
8+
# `nats` invocation locally against the nats stub. STUB_PHASE / STUB_OWNER drive
9+
# the reuse guard, and STUB_EXEC_ERROR makes the exec fail so the "a failed read
10+
# says why" claim is pinned. The password is piped on stdin, so it must not
11+
# appear in the recorded argv.
1112
#
1213
# cozytest.sh's awk runner recognizes only @test blocks and a bare `}`; there is
1314
# no bats `run` or `$status`, and no setup/teardown, so each case calls
@@ -22,18 +23,23 @@ make_stubs() {
2223
printf '%s\n' "$*" >> "$STUB_DIR/kubectl.argv"
2324
case "$1 $2 $3" in
2425
"-n tenant-root get")
25-
[ -n "${STUB_NO_POD:-}" ] && exit 1
26+
case "$*" in
27+
*"jsonpath={.status.phase}"*) printf '%s' "${STUB_PHASE-Running}" ;;
28+
*"jsonpath={.metadata.labels"*) printf '%s' "${STUB_OWNER-nats}" ;;
29+
esac
2630
exit 0 ;;
27-
"-n tenant-root wait")
31+
"-n tenant-root delete")
2832
exit 0 ;;
2933
"-n tenant-root run")
30-
# Only reachable when the CLI Pod was absent; otherwise nats_cli must never
31-
# start a Pod, and this exit fails the test if it does.
32-
[ -n "${STUB_NO_POD:-}" ] && exit 0
33-
echo "nats_cli started a throwaway run Pod instead of exec" >&2; exit 95 ;;
34+
# Reachable only when the Pod is absent; a reuse that starts a Pod is a bug.
35+
[ -z "${STUB_PHASE-Running}" ] && exit 0
36+
echo "nats_cli started a Pod though a Ready one it owns exists" >&2; exit 95 ;;
37+
"-n tenant-root wait")
38+
exit 0 ;;
3439
"-n tenant-root exec")
40+
[ -n "${STUB_EXEC_ERROR:-}" ] && { echo "$STUB_EXEC_ERROR" >&2; exit 1; }
3541
shift 6 # drop: -n tenant-root exec -i <pod> --
36-
exec "$@" ;;
42+
exec "$@" ;; # sh -c <script> sh <url> <args...>
3743
*)
3844
echo "unexpected kubectl call: $*" >&2; exit 97 ;;
3945
esac
@@ -52,26 +58,61 @@ EOF
5258
}
5359

5460
run_stream_count() {
55-
STUB_DIR=$stub STUB_NO_POD="${STUB_NO_POD:-}" PATH="$stub:$PATH" NAMESPACE=tenant-root \
56-
NATS_USER=demo NATS_PASSWORD=pw bash -c '
61+
STUB_DIR=$stub STUB_PHASE="${STUB_PHASE-Running}" STUB_OWNER="${STUB_OWNER-nats}" \
62+
STUB_EXEC_ERROR="${STUB_EXEC_ERROR:-}" PATH="$stub:$PATH" NAMESPACE=tenant-root \
63+
NATS_USER=demo NATS_PASSWORD=s3cret bash -c '
5764
set -euo pipefail
5865
. examples/backups/nats/00-helpers.sh
5966
stream_message_count demo orders
6067
'
6168
}
6269

63-
@test "nats_cli reads the reply by exec into the long-lived CLI Pod" {
70+
@test "nats_cli reuses a Ready Pod it owns and reads the reply by exec" {
71+
make_stubs
72+
out=$(run_stream_count)
73+
[ "$out" = "42" ]
74+
grep -q 'exec -i nats-cli -- sh -c' "$stub/kubectl.argv"
75+
}
76+
77+
@test "nats_cli passes the password on stdin, keeping it out of the exec argv" {
6478
make_stubs
6579
out=$(run_stream_count)
6680
[ "$out" = "42" ]
67-
grep -q 'exec -i nats-cli -- nats' "$stub/kubectl.argv"
81+
grep -q 'nats://[email protected]:4222' "$stub/kubectl.argv"
82+
if grep -q s3cret "$stub/kubectl.argv"; then
83+
echo "the password reached kubectl's argv" >&2
84+
exit 1
85+
fi
6886
}
6987

70-
@test "nats_cli starts the CLI Pod when it is absent, then execs into it" {
88+
@test "nats_cli creates the CLI Pod when it is absent, then execs into it" {
7189
make_stubs
72-
export STUB_NO_POD=1
90+
export STUB_PHASE=
7391
out=$(run_stream_count)
7492
[ "$out" = "42" ]
7593
grep -q 'run nats-cli --image=' "$stub/kubectl.argv"
76-
grep -q 'exec -i nats-cli -- nats' "$stub/kubectl.argv"
94+
grep -q 'labels=cozystack.io/backup-demo=nats' "$stub/kubectl.argv"
95+
grep -q 'exec -i nats-cli -- sh -c' "$stub/kubectl.argv"
96+
}
97+
98+
@test "nats_cli refuses a same-named Pod the demo does not own" {
99+
make_stubs
100+
export STUB_OWNER=someone-else
101+
err=$(mktemp)
102+
if run_stream_count 2>"$err"; then
103+
echo "nats_cli used a Pod it does not own" >&2
104+
exit 1
105+
fi
106+
grep -q 'does not own it' "$err"
107+
}
108+
109+
@test "nats_cli fails with kubectl's own error when the exec fails" {
110+
make_stubs
111+
export STUB_EXEC_ERROR="error: unable to upgrade connection: container not found"
112+
err=$(mktemp)
113+
if run_stream_count 2>"$err"; then
114+
echo "a failed exec read as a successful empty count" >&2
115+
exit 1
116+
fi
117+
grep -q 'unable to upgrade connection: container not found' "$err"
77118
}

0 commit comments

Comments
 (0)