Ctrl K
ComunidadGuías…

scripts/ci-qa-ssh.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bash
# QA V8: operator key, host-key fingerprint, live check=match, ssh login.
# Usage: scripts/ci-qa-ssh.sh <vm-id> [ipv4]
# Optional FIRECRAB_QA_SSH_KEY: keep the PEM at that path (caller deletes it).
set -euo pipefail

API=${FIRECRAB_API:-http://127.0.0.1:5523}
API=${API%/}
VM_ID=${1:?vm id required}
IPV4=${2-}

pass() { printf 'PASS %s\n' "$1"; }
warning() { printf 'WARNING %s\n' "$1"; }
fail() {
    printf 'FAILED %s: %s\n' "$1" "$2" >&2
    if [ -n "${BODY:-}" ]; then
        printf '%s\n' "$BODY" >&2
    fi
    exit 1
}

CODE=
BODY=
OWN_KEY=0
KEY=${FIRECRAB_QA_SSH_KEY-}
KNOWN_HOSTS=${FIRECRAB_QA_KNOWN_HOSTS-}
# Hosts with slow guest first boots (a nested lab) scale every guest wait.
WAIT_FACTOR=${FIRECRAB_QA_WAIT_FACTOR:-1}
OWN_KNOWN_HOSTS=0
HOST_PUBLIC_KEY=
SSH_TRANSPORT=()

cleanup() {
    if [ "$OWN_KEY" = 1 ] && [ -n "${KEY:-}" ]; then
        rm -f "$KEY"
    fi
    if [ "$OWN_KNOWN_HOSTS" = 1 ] && [ -n "${KNOWN_HOSTS:-}" ]; then
        rm -f "$KNOWN_HOSTS"
    fi
}
trap cleanup EXIT

http() {
    local method path out
    method=$1
    path=$2
    out=$(mktemp)
    CODE=$(curl -sS -o "$out" -w '%{http_code}' --connect-timeout 5 --max-time 45 \
        -X "$method" \
        -H "Origin: ${API}" \
        "${API}${path}") || CODE=000
    BODY=$(cat "$out")
    rm -f "$out"
}

json_get() {
    local expr=$1
    printf '%s' "$BODY" | python3 -c "import json,sys; d=json.load(sys.stdin); print($expr)"
}

configure_guest_transport() {
    local manager_host=${FIRECRAB_QA_MANAGER_HOST:-}
    local manager_key=${FIRECRAB_QA_MANAGER_KEY:-}
    local manager_known_hosts proxy
    local -a proxy_command

    if [ "$(uname -s)" = Linux ]; then
        return
    fi
    if [ -z "$manager_host" ] || [ -z "$manager_key" ]; then
        if [ "${FIRECRAB_QA_REQUIRE_LIVE_GUEST:-0}" = 1 ]; then
            fail V8d "management VM SSH proxy is required for live guest login"
        fi
        return
    fi
    [ -r "$manager_key" ] || fail V8d "management VM SSH key is not readable: ${manager_key}"
    manager_known_hosts="$(dirname -- "$manager_key")/known_hosts"
    proxy_command=(
        /usr/bin/ssh -i "$manager_key"
        -o BatchMode=yes
        -o ConnectTimeout=5
        -o StrictHostKeyChecking=accept-new
        -o "UserKnownHostsFile=$manager_known_hosts"
        "root@$manager_host"
    )
    printf -v proxy '%q ' "${proxy_command[@]}"
    proxy+='-W %h:%p'
    SSH_TRANSPORT=(-o "ProxyCommand=$proxy")
}

guest_ssh() {
    ssh "${SSH_TRANSPORT[@]}" -i "$KEY" \
        -o StrictHostKeyChecking=yes \
        -o UserKnownHostsFile="$KNOWN_HOSTS" \
        -o IdentitiesOnly=yes \
        -o ConnectTimeout=5 \
        -o BatchMode=yes \
        "root@${IPV4}" "$@"
}

if [ -z "$KEY" ]; then
    KEY=$(mktemp)
    chmod 600 "$KEY"
    OWN_KEY=1
fi

curl -sS --connect-timeout 5 --max-time 30 -o "$KEY" "${API}/api/vms/${VM_ID}/ssh-key" \
    || fail V8a "GET /ssh-key failed"
grep -q 'BEGIN OPENSSH PRIVATE KEY\|BEGIN.*PRIVATE KEY' "$KEY" \
    || fail V8a "ssh-key is not a PEM"
pass V8a

http GET "/api/vms/${VM_ID}/ssh-host-key"
[ "$CODE" = 200 ] || fail V8b "GET /ssh-host-key HTTP ${CODE}"
fp=$(json_get 'd.get("fingerprint") or ""')
[ -n "$fp" ] || fail V8b "missing fingerprint"
HOST_PUBLIC_KEY=$(json_get 'd.get("publicKey") or ""')
[ -n "$HOST_PUBLIC_KEY" ] || fail V8b "missing publicKey"
pass "V8b ${fp}"

check_ok=0
status=
for _ in $(seq 1 $((40 * WAIT_FACTOR))); do
    http GET "/api/vms/${VM_ID}/ssh-host-key/check"
    if [ "$CODE" = 200 ]; then
        status=$(json_get 'd.get("status") or ""')
        printf 'ssh-host-key/check status=%s\n' "$status"
        if [ "$status" = match ]; then
            check_ok=1
            break
        fi
    fi
    # The boot worker is finished when both markers are present. Waiting for
    # another SSH probe cannot recover a missing daemon until the next boot.
    check_body=$BODY
    check_code=$CODE
    http GET "/api/vms/${VM_ID}/log"
    if [ "$CODE" = 200 ] && printf '%s' "$BODY" | python3 -c '
import json, sys
log = json.load(sys.stdin).get("consoleLog") or ""
if "FIRECRAB_PACKAGES_FAILED" not in log or "FIRECRAB_SSHD skipped: no /usr/sbin/sshd" not in log:
    sys.exit(1)
# The caller removes its VM on exit; retain the failure before cleanup.
print("Guest console (last 80 lines, at most 16000 characters):", file=sys.stderr)
print("\n".join(log.splitlines()[-80:])[-16000:], file=sys.stderr)
'; then
        # Do not print the API's ssh-key response on this failure path.
        BODY=
        fail V8c "guest package installation failed and sshd is missing; inspect the guest console"
    fi
    BODY=$check_body
    CODE=$check_code
    sleep 3
done
[ "$check_ok" = 1 ] || fail V8c "GET /ssh-host-key/check expected match got ${status:-HTTP ${CODE}}"
pass V8c

if [ -z "$IPV4" ]; then
    http GET "/api/vms/${VM_ID}"
    [ "$CODE" = 200 ] || fail V8d "GET vm HTTP ${CODE}"
    IPV4=$(json_get 'd.get("ipv4") or ""')
fi
[ -n "$IPV4" ] || fail V8d "no ipv4 for ssh"

if [ -z "$KNOWN_HOSTS" ]; then
    KNOWN_HOSTS=$(mktemp)
    chmod 600 "$KNOWN_HOSTS"
    OWN_KNOWN_HOSTS=1
fi
printf '%s %s\n' "$IPV4" "$HOST_PUBLIC_KEY" > "$KNOWN_HOSTS"

configure_guest_transport
if [ "$(uname -s)" = Linux ] || [ ${#SSH_TRANSPORT[@]} -gt 0 ]; then
    ssh_ok=0
    for _ in $(seq 1 $((30 * WAIT_FACTOR))); do
        if guest_ssh true >/dev/null 2>&1; then
            ssh_ok=1
            break
        fi
        sleep 3
    done
    [ "$ssh_ok" = 1 ] || fail V8d "ssh root@${IPV4} never became ready"
    uname_out=$(guest_ssh uname -s)
    [ -n "$uname_out" ] || fail V8d "ssh uname empty"
    pass "V8d ssh root@${IPV4} ${uname_out}"
else
    warning "V8d skip live ssh on $(uname -s)"
fi