mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
9817d30a11
Provision two PVE nodes per version (a/b) for future cluster testing, plus Docker-based iSCSI target and NFS server for shared storage tests. Multi-node changes: - Each PVE version gets two nodes: 9a/9b and 8a/8b (4 VMs total) - Parameterized answer.toml FQDN for unique hostnames per node - Per-node auto-install ISOs with unique answer files - Node name discovered from FQDN and included in test config - API token creation handles pre-existing tokens (delete + recreate) - New test env vars: PVETEST_HOST_B, PVETEST_APITOKEN_B - Removed preflight cleanup from provision (use explicit cleanup instead) Docker storage services: - New docker-compose.storage.yml with iSCSI (tgt) and NFS containers - Host networking so PVE nodes can reach storage services - Docker socket mounted into dev-infra container for host Docker access - Docker CLI added to dev-infra Dockerfile stage - New test env vars: PVETEST_STORAGE_VM_IP, PVETEST_ISCSI_IQN, PVETEST_NFS_EXPORT Other fixes: - first-boot.sh installs open-iscsi on PVE nodes - preflight-cleanup.sh handles empty ISO filename gracefully - TMPDIR set to work dir to avoid /tmp overflow during ISO uploads Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
114 lines
4.0 KiB
Bash
Executable File
114 lines
4.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Wait for a fresh nested PVE instance to boot, discover its IP via the QEMU guest agent,
|
|
# then wait for the PVE API and create an API token.
|
|
#
|
|
# Usage: create-api-token.sh <parent-pve-endpoint> <parent-api-token> <vm-id> <root-password> [max-wait-seconds]
|
|
# parent-pve-endpoint: Full URL e.g. https://pve.example.com:8006
|
|
# Outputs two lines:
|
|
# IP=<discovered-ip>
|
|
# TOKEN=root@pam!integration=<secret>
|
|
set -euo pipefail
|
|
|
|
PARENT_ENDPOINT="${1%/}"
|
|
PARENT_TOKEN="$2"
|
|
VM_ID="$3"
|
|
ROOT_PASSWORD="$4"
|
|
MAX_WAIT="${5:-600}"
|
|
INTERVAL=10
|
|
PARENT_API="${PARENT_ENDPOINT}/api2/json"
|
|
NODES_JSON=$(curl -sk -H "Authorization: PVEAPIToken=${PARENT_TOKEN}" \
|
|
"${PARENT_API}/nodes")
|
|
PARENT_NODE=$(echo "$NODES_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['data'][0]['node'])")
|
|
|
|
# --- Phase 1: Discover IP via QEMU guest agent ---
|
|
echo "Waiting for guest agent on VM ${VM_ID} (node: ${PARENT_NODE})..."
|
|
VM_IP=""
|
|
elapsed=0
|
|
while [ $elapsed -lt $MAX_WAIT ]; do
|
|
AGENT_RESPONSE=$(curl -sk \
|
|
-H "Authorization: PVEAPIToken=${PARENT_TOKEN}" \
|
|
"${PARENT_API}/nodes/${PARENT_NODE}/qemu/${VM_ID}/agent/network-get-interfaces" 2>/dev/null || true)
|
|
|
|
VM_IP=$(echo "$AGENT_RESPONSE" | python3 -c "
|
|
import json, sys
|
|
try:
|
|
data = json.load(sys.stdin).get('data', {}).get('result', [])
|
|
for iface in data:
|
|
if iface.get('name') == 'lo':
|
|
continue
|
|
for addr in iface.get('ip-addresses', []):
|
|
if addr.get('ip-address-type') == 'ipv4' and not addr['ip-address'].startswith('127.'):
|
|
print(addr['ip-address'])
|
|
sys.exit(0)
|
|
except:
|
|
pass
|
|
" 2>/dev/null || true)
|
|
|
|
if [ -n "$VM_IP" ]; then
|
|
echo "Discovered VM IP: $VM_IP (after ${elapsed}s)"
|
|
break
|
|
fi
|
|
echo " Guest agent not ready yet (${elapsed}s elapsed)..."
|
|
sleep $INTERVAL
|
|
elapsed=$((elapsed + INTERVAL))
|
|
done
|
|
|
|
if [ -z "$VM_IP" ]; then
|
|
echo "ERROR: Could not discover VM IP via guest agent after ${MAX_WAIT}s" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Phase 2: Wait for PVE API on the nested instance ---
|
|
NESTED_API="https://${VM_IP}:8006/api2/json"
|
|
echo "Waiting for nested PVE API at ${NESTED_API}..."
|
|
while [ $elapsed -lt $MAX_WAIT ]; do
|
|
if curl -sk --connect-timeout 5 "${NESTED_API}/access/domains" 2>/dev/null | grep -q '"realm"'; then
|
|
echo "Nested PVE API is responsive after ${elapsed}s"
|
|
break
|
|
fi
|
|
echo " API not ready yet (${elapsed}s elapsed)..."
|
|
sleep $INTERVAL
|
|
elapsed=$((elapsed + INTERVAL))
|
|
done
|
|
|
|
if [ $elapsed -ge $MAX_WAIT ]; then
|
|
echo "ERROR: Nested PVE API not responsive after ${MAX_WAIT}s" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# --- Phase 3: Authenticate and create API token ---
|
|
echo "Authenticating as root@pam on nested PVE..."
|
|
AUTH_RESPONSE=$(curl -sk -d "username=root@pam&password=${ROOT_PASSWORD}" \
|
|
"${NESTED_API}/access/ticket")
|
|
TICKET=$(echo "$AUTH_RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['ticket'])" 2>/dev/null || true)
|
|
CSRF=$(echo "$AUTH_RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['CSRFPreventionToken'])" 2>/dev/null || true)
|
|
|
|
if [ -z "$TICKET" ] || [ -z "$CSRF" ]; then
|
|
echo "ERROR: Authentication failed. Response: $AUTH_RESPONSE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Delete existing token if present, then create fresh
|
|
echo "Creating API token root@pam!integration..."
|
|
curl -sk \
|
|
-b "PVEAuthCookie=${TICKET}" \
|
|
-H "CSRFPreventionToken: ${CSRF}" \
|
|
-X DELETE \
|
|
"${NESTED_API}/access/users/root@pam/token/integration" >/dev/null 2>&1 || true
|
|
|
|
TOKEN_RESPONSE=$(curl -sk \
|
|
-b "PVEAuthCookie=${TICKET}" \
|
|
-H "CSRFPreventionToken: ${CSRF}" \
|
|
-d "privsep=0" \
|
|
"${NESTED_API}/access/users/root@pam/token/integration")
|
|
|
|
TOKEN_VALUE=$(echo "$TOKEN_RESPONSE" | python3 -c "import json,sys; print(json.load(sys.stdin)['data']['value'])" 2>/dev/null || true)
|
|
|
|
if [ -z "$TOKEN_VALUE" ]; then
|
|
echo "ERROR: Token creation failed. Response: $TOKEN_RESPONSE" >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "IP=${VM_IP}"
|
|
echo "TOKEN=root@pam!integration=${TOKEN_VALUE}"
|