#!/bin/sh # Fake kubectl for hack/migration-54-redis-adopt.bats. It serves only the calls # migration 54 (and lib/cozystack-version.sh) make; behaviour is driven by # FAKE_* env and every invocation is appended to $FAKE_CMDLOG so the test can # assert on which RedisFailovers were patched and with what payload, and which # version was stamped. # # FAKE_CMDLOG file to append a one-line record of each call to # FAKE_CRD_ABSENT non-empty => the CRD probe prints nothing at rc 0, i.e. # the spotahome RedisFailover CRD is genuinely absent # (nothing to adopt). Mutually exclusive with the next. # FAKE_CRD_PROBE_FAIL non-empty => the CRD probe itself FAILS (rc != 0). A # real apiserver error is NOT the same answer as "absent": # the migration must abort under set -euo pipefail rather # than stamp past an un-probed fleet. # FAKE_RFS newline list of " " # — one per RedisFailover the -A fleet scan reports and the # per-CR read serves. A field of "-" (or empty) models an # UNSET image, so the migration's pin-where-empty branch is # exercised; any other value is a preset image that must be # left untouched. # FAKE_LABEL_FAIL non-empty => every `patch` exits 1 with this stderr. The # adoption label/pin is the whole point of the migration: # a failed patch must abort before stamping so the Job # retries rather than leaving the fleet invisible to the # new operator. # # Any call the migration makes that is not modelled below is drift between the # script and this fake and fails loudly, rather than letting the migration run # against a call the test never reasoned about. set -u log() { [ -n "${FAKE_CMDLOG:-}" ] && printf '%s\n' "$*" >> "$FAKE_CMDLOG"; } log "KUBECTL $*" args="$*" # arg_after KEY -- echoes the token following KEY in the argument vector. arg_after() { k="$1"; shift; p=""; for a in "$@"; do [ "$p" = "$k" ] && { printf '%s' "$a"; return; }; p="$a"; done; } # spec_image_obj VALUE -- a JSON object for a spec. node. "-" or empty # models an unset image (empty object, so `.spec..image // ""` is ""); any # other value is a preset image the migration must not overwrite. spec_image_obj() { case "${1:-}" in ""|"-") printf '{}' ;; *) printf '{"image":"%s"}' "$1" ;; esac } case "$args" in # CRD probe: `get crd --ignore-not-found -o name`. Empty + rc 0 => the # CRD is absent (nothing to adopt); a non-zero exit models a genuine apiserver # error, which under the migration's set -euo pipefail must abort — the exact # failure the --ignore-not-found form exists to keep distinct from "absent". *"get crd redisfailovers.databases.spotahome.com"*) if [ -n "${FAKE_CRD_PROBE_FAIL:-}" ]; then printf 'fake kubectl: the server could not find the requested resource\n' >&2 exit 1 fi if [ -n "${FAKE_CRD_ABSENT:-}" ]; then exit 0 fi printf 'customresourcedefinition.apiextensions.k8s.io/redisfailovers.databases.spotahome.com\n' exit 0 ;; # Fleet scan: every RedisFailover across all namespaces, as JSON. The # migration pipes this through jq for " ", so only those two fields # are consumed here; the per-CR read below serves the spec. *"get redisfailovers.databases.spotahome.com"*"--all-namespaces"*) printf '{"items":[' printf '%s\n' "${FAKE_RFS:-}" | { sep="" while read -r ns name ri si; do [ -n "$ns" ] || continue printf '%s{"metadata":{"namespace":"%s","name":"%s"}}' "$sep" "$ns" "$name" sep="," done } printf ']}\n' exit 0 ;; # Per-CR read: `-n get -o json`. Serves the spec so the # migration can decide, per role, whether the image is empty (pin) or preset # (leave). Reads the same FAKE_RFS row the fleet scan reported. *"get redisfailovers.databases.spotahome.com"*) ns=$(arg_after -n "$@") name=$(arg_after redisfailovers.databases.spotahome.com "$@") out=$(printf '%s\n' "${FAKE_RFS:-}" | { while read -r rns rname rri rsi; do [ "$rns" = "$ns" ] && [ "$rname" = "$name" ] || continue printf '{"metadata":{"namespace":"%s","name":"%s"},"spec":{"redis":%s,"sentinel":%s}}\n' \ "$rns" "$rname" "$(spec_image_obj "$rri")" "$(spec_image_obj "$rsi")" break done }) # A read with no matching FAKE_RFS row is a test-harness typo, not a cluster # state: serving empty at rc 0 would flow into the migration's pin-both # branch as a misleading green. Fail loudly, like every unmodelled call. if [ -z "$out" ]; then printf 'fake kubectl: no RedisFailover %s/%s in FAKE_RFS\n' "$ns" "$name" >&2 exit 1 fi printf '%s\n' "$out" exit 0 ;; # The adoption patch. --type=merge, carrying the operator-group label and, only # where empty, the engine pin. Logged verbatim so the test can inspect the # payload (label present on every CR; image pin present only where it should # be). FAKE_LABEL_FAIL models a patch the apiserver rejects. *"patch redisfailovers.databases.spotahome.com"*) if [ -n "${FAKE_LABEL_FAIL:-}" ]; then printf 'fake kubectl: admission webhook denied the patch\n' >&2 exit 1 fi ns=$(arg_after -n "$@") name=$(arg_after redisfailovers.databases.spotahome.com "$@") patch=$(arg_after -p "$@") log "PATCH $ns $name $patch" exit 0 ;; # Version stamp: kubectl apply --filename -. Record the number actually # stamped; a wrong number would loop run-migrations.sh forever and a bare # "STAMP" assertion would not notice. *"apply --filename -"*) manifest=$(cat) v=$(printf '%s\n' "$manifest" | sed -n 's/^ version: "\{0,1\}\([0-9]\{1,\}\)"\{0,1\}$/\1/p' | head -1) log "STAMP ${v:-unknown}" exit 0 ;; esac log "UNHANDLED $args" printf 'fake kubectl: unmodelled call: %s\n' "$args" >&2 exit 1