mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
Merge pull request #13 from GoodOlClint/refactor/integration-test-orchestration
refactor: extract integration test orchestration, add dev container
This commit is contained in:
@@ -5,15 +5,19 @@ name: Integration Tests
|
||||
# Architecture:
|
||||
# build → GitHub-hosted, dotnet publish → upload artifact
|
||||
# container-image → GitHub-hosted, build+push Docker image to GHCR
|
||||
# provision → self-hosted, provisions ALL nested PVE VMs in parallel via Terraform
|
||||
# test → self-hosted, runs Pester tests against pre-provisioned VMs
|
||||
# cleanup → self-hosted, tears down all VMs (always runs)
|
||||
# provision → self-hosted, provisions ALL nested PVE VMs via run-integration.sh
|
||||
# test → self-hosted, runs Pester tests via run-integration.sh
|
||||
# cleanup → self-hosted, tears down all VMs via run-integration.sh (always runs)
|
||||
# cleanup-images → GitHub-hosted, prunes old GHCR container images
|
||||
#
|
||||
# The provision/test/cleanup logic lives in tests/infrastructure/scripts/run-integration.sh
|
||||
# which is the single source of truth shared between CI and local dev containers.
|
||||
#
|
||||
# Required repository secrets (for provisioning):
|
||||
# PVE_ENDPOINT - Parent PVE API URL (e.g. https://pve.example.com:8006)
|
||||
# PVE_API_TOKEN - Parent PVE API token (for Terraform + uploads)
|
||||
# PVE_TARGET_NODE - Parent PVE node name
|
||||
# PVE_TEST_PASSWORD - Root password for nested PVE instances
|
||||
#
|
||||
# Optional secrets (for skip_provision mode):
|
||||
# PVETEST_HOST - Hostname or IP of a pre-existing nested PVE
|
||||
@@ -42,9 +46,7 @@ permissions:
|
||||
packages: write
|
||||
|
||||
env:
|
||||
INFRA_DIR: tests/infrastructure
|
||||
SCRIPTS_DIR: tests/infrastructure/scripts
|
||||
PVE_PASSWORD: ${{ secrets.PVE_TEST_PASSWORD }}
|
||||
TEST_IMAGE: ghcr.io/goodolclint/psproxmoxve-integration
|
||||
CACHE_DIR: /opt/pve-isos
|
||||
|
||||
@@ -110,181 +112,18 @@ jobs:
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
volumes:
|
||||
- /opt/pve-isos:/opt/pve-isos
|
||||
# Connection details are written to /opt/pve-isos/test-config.json
|
||||
# instead of job outputs, because GitHub Actions redacts masked/secret
|
||||
# values in outputs making them empty in downstream jobs.
|
||||
|
||||
steps:
|
||||
- name: Mask test password
|
||||
run: echo "::add-mask::${PVE_PASSWORD}"
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
# ── Cache: ensure base ISOs ──────────────────────────────────────
|
||||
|
||||
- name: Ensure PVE 9 base ISO
|
||||
shell: bash
|
||||
run: bash ${SCRIPTS_DIR}/ensure-base-iso.sh "proxmox-ve_9.1-1.iso" "${CACHE_DIR}"
|
||||
|
||||
- name: Ensure PVE 8 base ISO
|
||||
shell: bash
|
||||
run: bash ${SCRIPTS_DIR}/ensure-base-iso.sh "proxmox-ve_8.4-1.iso" "${CACHE_DIR}"
|
||||
|
||||
# ── Cache: ensure cloud images ───────────────────────────────────
|
||||
|
||||
- name: Ensure cloud images cached
|
||||
id: cloud_images
|
||||
shell: bash
|
||||
run: |
|
||||
OUTPUT=$(bash ${SCRIPTS_DIR}/ensure-cloud-images.sh "${CACHE_DIR}")
|
||||
CLOUD_IMAGE_PATH=$(echo "$OUTPUT" | grep "^CLOUD_IMAGE_PATH=" | cut -d= -f2)
|
||||
OVA_PATH=$(echo "$OUTPUT" | grep "^OVA_PATH=" | cut -d= -f2)
|
||||
echo "cloud_image_path=${CLOUD_IMAGE_PATH}" >> "$GITHUB_OUTPUT"
|
||||
echo "ova_path=${OVA_PATH}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# ── Pre-flight cleanup for ALL VMs ───────────────────────────────
|
||||
|
||||
- name: Pre-flight cleanup (PVE 9)
|
||||
- name: Provision PVE instances
|
||||
shell: bash
|
||||
env:
|
||||
PVE_ENDPOINT: ${{ secrets.PVE_ENDPOINT }}
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
|
||||
"${{ secrets.PVE_ENDPOINT }}" "${PVE_API_TOKEN}" \
|
||||
99909 "proxmox-ve_9.1-1-auto.iso" "${INFRA_DIR}"
|
||||
|
||||
- name: Pre-flight cleanup (PVE 8)
|
||||
shell: bash
|
||||
env:
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
|
||||
"${{ secrets.PVE_ENDPOINT }}" "${PVE_API_TOKEN}" \
|
||||
99908 "proxmox-ve_8.4-1-auto.iso" "${INFRA_DIR}"
|
||||
|
||||
# ── Generate answer files ────────────────────────────────────────
|
||||
|
||||
- name: Generate answer file
|
||||
shell: bash
|
||||
run: |
|
||||
sed "s/\${root_password}/${PVE_PASSWORD}/" \
|
||||
${INFRA_DIR}/answer.toml.tftpl > ${RUNNER_TEMP}/answer.toml
|
||||
|
||||
# ── Prepare auto-install ISOs (cached) ───────────────────────────
|
||||
|
||||
- name: Prepare PVE 9 auto-install ISO
|
||||
shell: bash
|
||||
run: |
|
||||
bash ${SCRIPTS_DIR}/prepare-auto-iso.sh \
|
||||
"${CACHE_DIR}/proxmox-ve_9.1-1.iso" \
|
||||
${RUNNER_TEMP}/answer.toml \
|
||||
${SCRIPTS_DIR}/first-boot.sh \
|
||||
"${RUNNER_TEMP}/proxmox-ve_9.1-1-auto.iso" \
|
||||
--cache-dir "${CACHE_DIR}"
|
||||
|
||||
- name: Prepare PVE 8 auto-install ISO
|
||||
shell: bash
|
||||
run: |
|
||||
bash ${SCRIPTS_DIR}/prepare-auto-iso.sh \
|
||||
"${CACHE_DIR}/proxmox-ve_8.4-1.iso" \
|
||||
${RUNNER_TEMP}/answer.toml \
|
||||
${SCRIPTS_DIR}/first-boot.sh \
|
||||
"${RUNNER_TEMP}/proxmox-ve_8.4-1-auto.iso" \
|
||||
--cache-dir "${CACHE_DIR}"
|
||||
|
||||
# ── Terraform: provision both VMs in parallel ────────────────────
|
||||
|
||||
- name: Terraform init
|
||||
shell: bash
|
||||
working-directory: ${{ env.INFRA_DIR }}
|
||||
run: terraform init -input=false
|
||||
|
||||
- name: Terraform apply (both VMs)
|
||||
shell: bash
|
||||
working-directory: ${{ env.INFRA_DIR }}
|
||||
env:
|
||||
TF_VAR_proxmox_endpoint: ${{ secrets.PVE_ENDPOINT }}
|
||||
TF_VAR_proxmox_api_token: ${{ secrets.PVE_API_TOKEN }}
|
||||
TF_VAR_target_node: ${{ secrets.PVE_TARGET_NODE }}
|
||||
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
|
||||
run: |
|
||||
cat > /tmp/instances.tfvars.json <<TFVARS
|
||||
{
|
||||
"pve_instances": {
|
||||
"pve9": {
|
||||
"iso_local_path": "${RUNNER_TEMP}/proxmox-ve_9.1-1-auto.iso",
|
||||
"vm_id": 99909,
|
||||
"vm_name": "pve-test-pve9"
|
||||
},
|
||||
"pve8": {
|
||||
"iso_local_path": "${RUNNER_TEMP}/proxmox-ve_8.4-1-auto.iso",
|
||||
"vm_id": 99908,
|
||||
"vm_name": "pve-test-pve8"
|
||||
}
|
||||
}
|
||||
}
|
||||
TFVARS
|
||||
terraform apply -auto-approve -input=false -var-file=/tmp/instances.tfvars.json
|
||||
|
||||
# ── Wait for both PVE instances and create API tokens ────────────
|
||||
|
||||
- name: Wait for PVE 9 and create API token
|
||||
id: wait_pve9
|
||||
shell: bash
|
||||
env:
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
OUTPUT=$(bash ${SCRIPTS_DIR}/create-api-token.sh \
|
||||
"${{ secrets.PVE_ENDPOINT }}" "${PVE_API_TOKEN}" \
|
||||
99909 "${PVE_PASSWORD}" 900)
|
||||
PVE9_IP=$(echo "$OUTPUT" | grep "^IP=" | cut -d= -f2)
|
||||
PVE9_TOKEN=$(echo "$OUTPUT" | grep "^TOKEN=" | cut -d= -f2-)
|
||||
echo "PVE 9 ready at ${PVE9_IP}"
|
||||
echo "{\"host\":\"${PVE9_IP}\",\"token\":\"${PVE9_TOKEN}\"}" > /tmp/pve9.json
|
||||
|
||||
- name: Wait for PVE 8 and create API token
|
||||
shell: bash
|
||||
env:
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
OUTPUT=$(bash ${SCRIPTS_DIR}/create-api-token.sh \
|
||||
"${{ secrets.PVE_ENDPOINT }}" "${PVE_API_TOKEN}" \
|
||||
99908 "${PVE_PASSWORD}" 900)
|
||||
PVE8_IP=$(echo "$OUTPUT" | grep "^IP=" | cut -d= -f2)
|
||||
PVE8_TOKEN=$(echo "$OUTPUT" | grep "^TOKEN=" | cut -d= -f2-)
|
||||
echo "PVE 8 ready at ${PVE8_IP}"
|
||||
echo "{\"host\":\"${PVE8_IP}\",\"token\":\"${PVE8_TOKEN}\"}" > /tmp/pve8.json
|
||||
|
||||
# ── Prepare test environments on both nested PVEs ────────────────
|
||||
|
||||
- name: Prepare PVE 9 test environment
|
||||
shell: bash
|
||||
run: |
|
||||
PVE9_IP=$(jq -r .host /tmp/pve9.json)
|
||||
bash ${SCRIPTS_DIR}/prepare-test-environment.sh "${PVE9_IP}" "${PVE_PASSWORD}"
|
||||
|
||||
- name: Prepare PVE 8 test environment
|
||||
shell: bash
|
||||
run: |
|
||||
PVE8_IP=$(jq -r .host /tmp/pve8.json)
|
||||
bash ${SCRIPTS_DIR}/prepare-test-environment.sh "${PVE8_IP}" "${PVE_PASSWORD}"
|
||||
|
||||
# ── Write test config to shared volume ───────────────────────────
|
||||
|
||||
- name: Write test config to shared volume
|
||||
shell: bash
|
||||
run: |
|
||||
CLOUD_IMG="${CACHE_DIR}/noble-server-cloudimg-amd64.qcow2"
|
||||
OVA="${CACHE_DIR}/ubuntu-24.04-server-cloudimg-amd64.ova"
|
||||
jq -n \
|
||||
--argjson pve9 "$(cat /tmp/pve9.json)" \
|
||||
--argjson pve8 "$(cat /tmp/pve8.json)" \
|
||||
--arg cloud_image "$CLOUD_IMG" \
|
||||
--arg ova "$OVA" \
|
||||
'{pve9: $pve9, pve8: $pve8, cloud_image_path: $cloud_image, ova_path: $ova}' \
|
||||
> "${CACHE_DIR}/test-config.json"
|
||||
echo "Test config written:"
|
||||
jq . "${CACHE_DIR}/test-config.json"
|
||||
PVE_TARGET_NODE: ${{ secrets.PVE_TARGET_NODE }}
|
||||
PVE_PASSWORD: ${{ secrets.PVE_TEST_PASSWORD }}
|
||||
run: bash ${SCRIPTS_DIR}/run-integration.sh provision
|
||||
|
||||
# ── Integration tests (self-hosted, per PVE version) ────────────────
|
||||
test:
|
||||
@@ -305,9 +144,6 @@ jobs:
|
||||
pve_version: ['9', '8']
|
||||
|
||||
steps:
|
||||
- name: Mask test password
|
||||
run: echo "::add-mask::${PVE_PASSWORD}"
|
||||
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Download module artifact
|
||||
@@ -316,86 +152,15 @@ jobs:
|
||||
name: module-integration
|
||||
path: ./publish/netstandard2.0/
|
||||
|
||||
# ── Load test config from shared volume ──────────────────────────
|
||||
|
||||
- name: Load test config
|
||||
id: target
|
||||
- name: Run integration tests (PVE ${{ matrix.pve_version }})
|
||||
shell: bash
|
||||
run: |
|
||||
CONFIG="${CACHE_DIR}/test-config.json"
|
||||
if [ "${{ inputs.skip_provision }}" = "true" ]; then
|
||||
echo "host=${{ secrets.PVETEST_HOST }}" >> "$GITHUB_OUTPUT"
|
||||
echo "port=${{ secrets.PVETEST_PORT || '8006' }}" >> "$GITHUB_OUTPUT"
|
||||
echo "token=${{ secrets.PVETEST_APITOKEN }}" >> "$GITHUB_OUTPUT"
|
||||
echo "node=${{ secrets.PVETEST_NODE || 'pve' }}" >> "$GITHUB_OUTPUT"
|
||||
echo "storage=${{ secrets.PVETEST_STORAGE || 'local' }}" >> "$GITHUB_OUTPUT"
|
||||
echo "cloud_image_path=" >> "$GITHUB_OUTPUT"
|
||||
echo "ova_path=" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
PVE_KEY="pve${{ matrix.pve_version }}"
|
||||
echo "host=$(jq -r ".${PVE_KEY}.host" "$CONFIG")" >> "$GITHUB_OUTPUT"
|
||||
echo "port=8006" >> "$GITHUB_OUTPUT"
|
||||
echo "token=$(jq -r ".${PVE_KEY}.token" "$CONFIG")" >> "$GITHUB_OUTPUT"
|
||||
echo "node=pve" >> "$GITHUB_OUTPUT"
|
||||
echo "storage=local" >> "$GITHUB_OUTPUT"
|
||||
echo "cloud_image_path=$(jq -r '.cloud_image_path' "$CONFIG")" >> "$GITHUB_OUTPUT"
|
||||
echo "ova_path=$(jq -r '.ova_path' "$CONFIG")" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Verify PVE API reachable
|
||||
shell: bash
|
||||
run: |
|
||||
HOST="${{ steps.target.outputs.host }}"
|
||||
PORT="${{ steps.target.outputs.port }}"
|
||||
TOKEN="${{ steps.target.outputs.token }}"
|
||||
echo "Testing connectivity to PVE ${{ matrix.pve_version }} API at ${HOST}:${PORT}..."
|
||||
if curl -sk --connect-timeout 10 \
|
||||
-H "Authorization: PVEAPIToken=${TOKEN}" \
|
||||
"https://${HOST}:${PORT}/api2/json/nodes" | grep -q '"node"'; then
|
||||
echo "PVE API is responsive"
|
||||
else
|
||||
echo "::error::Cannot reach PVE API at ${HOST}:${PORT}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── Install module ────────────────────────────────────────────────
|
||||
|
||||
- name: Copy module to module path
|
||||
shell: pwsh
|
||||
run: |
|
||||
$modulePath = "$HOME/.local/share/powershell/Modules/PSProxmoxVE"
|
||||
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null
|
||||
Copy-Item -Path ./publish/netstandard2.0/* -Destination $modulePath -Recurse -Force
|
||||
|
||||
# ── Run tests ─────────────────────────────────────────────────────
|
||||
|
||||
- name: Create test ISO
|
||||
shell: bash
|
||||
run: dd if=/dev/urandom of=${RUNNER_TEMP}/pvetest.iso bs=1M count=1 2>/dev/null
|
||||
|
||||
- name: Run integration tests
|
||||
shell: pwsh
|
||||
env:
|
||||
PVETEST_HOST: ${{ steps.target.outputs.host }}
|
||||
PVETEST_PORT: ${{ steps.target.outputs.port }}
|
||||
PVETEST_APITOKEN: ${{ steps.target.outputs.token }}
|
||||
PVETEST_NODE: ${{ steps.target.outputs.node }}
|
||||
PVETEST_STORAGE: ${{ steps.target.outputs.storage }}
|
||||
PVETEST_PVE_VERSION: ${{ matrix.pve_version }}
|
||||
PVETEST_PASSWORD: ${{ env.PVE_PASSWORD }}
|
||||
PVETEST_CLOUD_IMAGE_PATH: ${{ steps.target.outputs.cloud_image_path }}
|
||||
PVETEST_OVA_PATH: ${{ steps.target.outputs.ova_path }}
|
||||
run: |
|
||||
$env:PVETEST_ISO_PATH = Join-Path $env:RUNNER_TEMP "pvetest.iso"
|
||||
Import-Module Pester -MinimumVersion 5.0
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = "tests/PSProxmoxVE.Tests/Integration"
|
||||
$config.Filter.Tag = "Integration"
|
||||
$config.Output.Verbosity = "Detailed"
|
||||
$config.TestResult.Enabled = $true
|
||||
$config.TestResult.OutputFormat = "NUnitXml"
|
||||
$config.TestResult.OutputPath = "TestResults/integration-results.xml"
|
||||
Invoke-Pester -Configuration $config
|
||||
PVE_PASSWORD: ${{ secrets.PVE_TEST_PASSWORD }}
|
||||
MODULE_ARTIFACT: ./publish/netstandard2.0
|
||||
SKIP_PROVISION: ${{ inputs.skip_provision || 'false' }}
|
||||
PVETEST_HOST: ${{ secrets.PVETEST_HOST }}
|
||||
PVETEST_APITOKEN: ${{ secrets.PVETEST_APITOKEN }}
|
||||
run: bash ${SCRIPTS_DIR}/run-integration.sh test ${{ matrix.pve_version }}
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
@@ -421,23 +186,13 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: API-based cleanup (PVE 9)
|
||||
- name: Cleanup PVE instances
|
||||
shell: bash
|
||||
env:
|
||||
PVE_ENDPOINT: ${{ secrets.PVE_ENDPOINT }}
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
|
||||
"${{ secrets.PVE_ENDPOINT }}" "${PVE_API_TOKEN}" \
|
||||
99909 "proxmox-ve_9.1-1-auto.iso" "${INFRA_DIR}" || true
|
||||
|
||||
- name: API-based cleanup (PVE 8)
|
||||
shell: bash
|
||||
env:
|
||||
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
||||
run: |
|
||||
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
|
||||
"${{ secrets.PVE_ENDPOINT }}" "${PVE_API_TOKEN}" \
|
||||
99908 "proxmox-ve_8.4-1-auto.iso" "${INFRA_DIR}" || true
|
||||
PVE_TARGET_NODE: ${{ secrets.PVE_TARGET_NODE }}
|
||||
run: bash ${SCRIPTS_DIR}/run-integration.sh cleanup
|
||||
|
||||
# ── Clean up old container images from GHCR ──────────────────────────
|
||||
cleanup-images:
|
||||
|
||||
@@ -13,3 +13,5 @@ nupkg/
|
||||
BenchmarkDotNet.Artifacts/
|
||||
.env
|
||||
.env.*
|
||||
!.env.*.example
|
||||
tests/.env.test
|
||||
|
||||
@@ -8,7 +8,41 @@ C# binary PowerShell module for managing Proxmox VE (PVE) infrastructure. Two pr
|
||||
|
||||
Tests: xUnit (`tests/PSProxmoxVE.Core.Tests/`) and Pester 5 (`tests/PSProxmoxVE.Tests/`).
|
||||
|
||||
## Build & Test
|
||||
## Development Workflow
|
||||
|
||||
**All changes go through pull requests.** The `main` branch has branch protection enabled
|
||||
(required build checks, required review, admin enforced). Never push directly to main.
|
||||
|
||||
```bash
|
||||
# Create a feature branch
|
||||
git checkout -b feat/my-feature
|
||||
|
||||
# ... make changes ...
|
||||
|
||||
# Commit using conventional commits
|
||||
git commit -m "feat: add new cmdlet"
|
||||
|
||||
# Push and create PR
|
||||
git push -u origin feat/my-feature
|
||||
gh pr create
|
||||
```
|
||||
|
||||
### Dev container (recommended)
|
||||
|
||||
A Docker-based dev environment replicates the full CI setup locally. Works on ARM Macs
|
||||
(build + test) and x86 (full provisioning flow).
|
||||
|
||||
```bash
|
||||
./tests/dev.sh # Open pwsh shell in dev container
|
||||
./tests/dev.sh build # Build the module
|
||||
./tests/dev.sh test # Run unit tests
|
||||
./tests/dev.sh integration # Run integration tests against existing PVE
|
||||
./tests/dev.sh provision # Full CI flow: provision → test → cleanup (x86 only)
|
||||
```
|
||||
|
||||
Configure integration test targets by copying `tests/.env.test.example` to `tests/.env.test`.
|
||||
|
||||
### Build & test without container
|
||||
|
||||
```bash
|
||||
# Build
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# PSProxmoxVE integration test configuration.
|
||||
# Copy to .env.test and fill in values. This file is gitignored.
|
||||
|
||||
# ── For testing against a pre-existing PVE (./tests/dev.sh integration) ──
|
||||
PVETEST_HOST=pve.example.com
|
||||
PVETEST_PORT=8006
|
||||
PVETEST_APITOKEN=user@realm!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
PVETEST_NODE=pve1
|
||||
PVETEST_STORAGE=local
|
||||
PVETEST_PASSWORD=<your-test-password>
|
||||
|
||||
# ── For full provisioning (./tests/dev.sh provision, x86 only) ───────────
|
||||
# PVE_ENDPOINT=https://pve.example.com:8006
|
||||
# PVE_API_TOKEN=user@realm!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
|
||||
# PVE_TARGET_NODE=pve1
|
||||
# PVE_PASSWORD=<root-password-for-nested-pve>
|
||||
|
||||
# ── Optional overrides ──────────────────────────────────────────────────
|
||||
# CACHE_DIR=/opt/pve-isos
|
||||
# WORK_DIR=/tmp/pve-integration
|
||||
# PVE_VERSIONS=9 8
|
||||
+48
-31
@@ -1,36 +1,53 @@
|
||||
# Replicates the GitHub Actions CI environment for PS 7.x unit tests
|
||||
FROM mcr.microsoft.com/powershell:7.5-ubuntu-24.04
|
||||
# PSProxmoxVE development and integration testing container
|
||||
#
|
||||
# Two targets:
|
||||
# dev - .NET SDK + PowerShell + Pester (build & test, works on ARM/x86)
|
||||
# dev-infra - Adds Terraform + PVE provisioning tools (x86 only)
|
||||
#
|
||||
# Usage:
|
||||
# # For build + test (works on Mac M-series):
|
||||
# docker compose -f tests/docker-compose.test.yml up -d
|
||||
#
|
||||
# # For full CI flow including provisioning (x86 only):
|
||||
# docker compose -f tests/docker-compose.test.yml --profile infra up -d
|
||||
|
||||
# Install .NET SDK 9.0, build module, install Pester, copy to module path
|
||||
RUN apt-get update && apt-get install -y wget \
|
||||
&& wget https://dot.net/v1/dotnet-install.sh -O /tmp/dotnet-install.sh \
|
||||
&& chmod +x /tmp/dotnet-install.sh \
|
||||
&& /tmp/dotnet-install.sh --channel 9.0 --install-dir /usr/share/dotnet \
|
||||
&& ln -sf /usr/share/dotnet/dotnet /usr/local/bin/dotnet \
|
||||
&& rm /tmp/dotnet-install.sh \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
# ── Base: .NET SDK + PowerShell + Pester ────────────────────────────
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0-noble AS dev
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl jq openssh-client ca-certificates apt-transport-https gnupg \
|
||||
&& curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \
|
||||
| gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg \
|
||||
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/microsoft-archive-keyring.gpg] \
|
||||
https://packages.microsoft.com/ubuntu/24.04/prod noble main" \
|
||||
> /etc/apt/sources.list.d/microsoft-prod.list \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends powershell \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& pwsh -NoProfile -Command \
|
||||
'Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; \
|
||||
Install-Module -Name Pester -MinimumVersion 5.0 -Scope AllUsers -Force' \
|
||||
&& mkdir -p /usr/local/share/powershell/Modules/PSProxmoxVE
|
||||
|
||||
WORKDIR /repo
|
||||
COPY . .
|
||||
CMD ["pwsh", "-NoProfile"]
|
||||
|
||||
RUN dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj \
|
||||
--configuration Release --framework net9.0 --output ./publish/net9.0 \
|
||||
&& rm -f ./publish/net9.0/PSProxmoxVE.deps.json \
|
||||
&& pwsh -NoProfile -Command ' \
|
||||
Install-Module -Name Pester -MinimumVersion 5.0 -Force -Scope AllUsers; \
|
||||
$modulePath = "/usr/local/share/powershell/Modules/PSProxmoxVE"; \
|
||||
New-Item -ItemType Directory -Path $modulePath -Force | Out-Null; \
|
||||
Copy-Item -Path ./publish/net9.0/* -Destination $modulePath -Recurse -Force'
|
||||
# ── Full: adds Terraform + PVE provisioning tools (x86 only) ───────
|
||||
FROM dev AS dev-infra
|
||||
|
||||
# Unset DOTNET_ROOT so PS uses its own bundled runtime
|
||||
ENV DOTNET_ROOT=""
|
||||
ENV DOTNET_MULTILEVEL_LOOKUP=""
|
||||
|
||||
CMD ["pwsh", "-NoProfile", "-Command", " \
|
||||
Import-Module Pester -MinimumVersion 5.0; \
|
||||
$config = New-PesterConfiguration; \
|
||||
$config.Run.Path = 'tests/PSProxmoxVE.Tests'; \
|
||||
$config.Run.Exit = $true; \
|
||||
$config.Filter.ExcludeTag = @('Integration'); \
|
||||
$config.Output.Verbosity = 'Detailed'; \
|
||||
Invoke-Pester -Configuration $config"]
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
dosfstools mtools sshpass python3 xorriso gnupg \
|
||||
&& curl -fsSL https://apt.releases.hashicorp.com/gpg \
|
||||
| gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg \
|
||||
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] \
|
||||
https://apt.releases.hashicorp.com noble main" \
|
||||
> /etc/apt/sources.list.d/hashicorp.list \
|
||||
&& curl -fsSL https://enterprise.proxmox.com/debian/proxmox-release-bookworm.gpg \
|
||||
-o /usr/share/keyrings/proxmox-release-bookworm.gpg \
|
||||
&& echo "deb [signed-by=/usr/share/keyrings/proxmox-release-bookworm.gpg] \
|
||||
http://download.proxmox.com/debian/pve bookworm pve-no-subscription" \
|
||||
> /etc/apt/sources.list.d/proxmox-pve.list \
|
||||
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||
terraform proxmox-auto-install-assistant qemu-utils \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
Executable
+117
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env bash
|
||||
# Helper script for the dev/test containers.
|
||||
#
|
||||
# Usage:
|
||||
# ./tests/dev.sh # Start dev container and open pwsh shell
|
||||
# ./tests/dev.sh build # Build the module inside the container
|
||||
# ./tests/dev.sh test # Run unit tests
|
||||
# ./tests/dev.sh integration # Run integration tests against existing PVE (needs .env.test)
|
||||
# ./tests/dev.sh provision # Full CI flow: provision → test → cleanup (x86 only)
|
||||
# ./tests/dev.sh cleanup # Destroy provisioned VMs (x86 only)
|
||||
# ./tests/dev.sh stop # Stop all containers
|
||||
# ./tests/dev.sh rebuild # Rebuild container image(s)
|
||||
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
COMPOSE="docker compose -f tests/docker-compose.test.yml"
|
||||
DEV_CONTAINER="psproxmoxve-dev"
|
||||
INFRA_CONTAINER="psproxmoxve-dev-infra"
|
||||
MODULE_PATH="/usr/local/share/powershell/Modules/PSProxmoxVE"
|
||||
RUN_INTEGRATION="tests/infrastructure/scripts/run-integration.sh"
|
||||
|
||||
ensure_dev() {
|
||||
if ! docker inspect "$DEV_CONTAINER" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
|
||||
echo "Starting dev container..."
|
||||
$COMPOSE up -d dev
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_infra() {
|
||||
if ! docker inspect "$INFRA_CONTAINER" --format '{{.State.Running}}' 2>/dev/null | grep -q true; then
|
||||
echo "Starting infra container (x86 only)..."
|
||||
$COMPOSE --profile infra up -d dev-infra
|
||||
fi
|
||||
}
|
||||
|
||||
build_module() {
|
||||
local container="$1"
|
||||
docker exec "$container" bash -c "
|
||||
dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj \
|
||||
-c Release -f netstandard2.0 -o /tmp/publish 2>&1 | tail -1 && \
|
||||
cp -r /tmp/publish/* $MODULE_PATH/ && \
|
||||
echo "Module installed to $MODULE_PATH"
|
||||
"
|
||||
}
|
||||
|
||||
case "${1:-shell}" in
|
||||
shell)
|
||||
ensure_dev
|
||||
docker exec -it "$DEV_CONTAINER" pwsh -NoProfile
|
||||
;;
|
||||
|
||||
build)
|
||||
ensure_dev
|
||||
build_module "$DEV_CONTAINER"
|
||||
;;
|
||||
|
||||
test)
|
||||
ensure_dev
|
||||
build_module "$DEV_CONTAINER"
|
||||
docker exec "$DEV_CONTAINER" pwsh -NoProfile -Command "
|
||||
\$config = New-PesterConfiguration
|
||||
\$config.Run.Path = 'tests/PSProxmoxVE.Tests'
|
||||
\$config.Run.Exit = \$true
|
||||
\$config.Filter.ExcludeTag = @('Integration')
|
||||
\$config.Output.Verbosity = 'Detailed'
|
||||
Invoke-Pester -Configuration \$config
|
||||
"
|
||||
;;
|
||||
|
||||
integration)
|
||||
# Run integration tests against a pre-existing PVE (set via .env.test)
|
||||
ensure_dev
|
||||
build_module "$DEV_CONTAINER"
|
||||
docker exec "$DEV_CONTAINER" bash -c "
|
||||
SKIP_PROVISION=true bash $RUN_INTEGRATION test ${2:-all}
|
||||
"
|
||||
;;
|
||||
|
||||
provision)
|
||||
# Full CI lifecycle: provision → test → cleanup (x86 infra container)
|
||||
ensure_infra
|
||||
build_module "$INFRA_CONTAINER"
|
||||
docker exec "$INFRA_CONTAINER" bash "$RUN_INTEGRATION" all "${2:-all}"
|
||||
;;
|
||||
|
||||
cleanup)
|
||||
# Destroy provisioned VMs
|
||||
ensure_infra
|
||||
docker exec "$INFRA_CONTAINER" bash "$RUN_INTEGRATION" cleanup
|
||||
;;
|
||||
|
||||
stop)
|
||||
$COMPOSE --profile infra down
|
||||
;;
|
||||
|
||||
rebuild)
|
||||
$COMPOSE --profile infra down
|
||||
$COMPOSE build --no-cache dev
|
||||
$COMPOSE --profile infra build --no-cache dev-infra
|
||||
$COMPOSE up -d dev
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Usage: $0 {shell|build|test|integration|provision|cleanup|stop|rebuild}"
|
||||
echo ""
|
||||
echo " shell Open pwsh in the dev container"
|
||||
echo " build Build the module"
|
||||
echo " test Run unit tests"
|
||||
echo " integration Run integration tests against existing PVE (.env.test)"
|
||||
echo " provision Full CI flow: provision → test → cleanup (x86 only)"
|
||||
echo " cleanup Destroy provisioned VMs (x86 only)"
|
||||
echo " stop Stop all containers"
|
||||
echo " rebuild Rebuild container images"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,50 @@
|
||||
## PSProxmoxVE dev/test containers
|
||||
##
|
||||
## Default (works on ARM Macs + x86):
|
||||
## docker compose -f tests/docker-compose.test.yml up -d
|
||||
## docker exec -it psproxmoxve-dev pwsh
|
||||
##
|
||||
## Full CI infra (x86 only — adds Terraform + PVE provisioning tools):
|
||||
## docker compose -f tests/docker-compose.test.yml --profile infra up -d
|
||||
## docker exec -it psproxmoxve-dev-infra bash
|
||||
##
|
||||
## Stop:
|
||||
## docker compose -f tests/docker-compose.test.yml down
|
||||
|
||||
services:
|
||||
dev:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: tests/Dockerfile.test
|
||||
target: dev
|
||||
container_name: psproxmoxve-dev
|
||||
volumes:
|
||||
- ..:/repo
|
||||
working_dir: /repo
|
||||
stdin_open: true
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- path: .env.test
|
||||
required: false
|
||||
|
||||
dev-infra:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: tests/Dockerfile.test
|
||||
target: dev-infra
|
||||
container_name: psproxmoxve-dev-infra
|
||||
profiles: [infra]
|
||||
volumes:
|
||||
- ..:/repo
|
||||
- pve-isos:/opt/pve-isos
|
||||
working_dir: /repo
|
||||
stdin_open: true
|
||||
tty: true
|
||||
restart: unless-stopped
|
||||
env_file:
|
||||
- path: .env.test
|
||||
required: false
|
||||
|
||||
volumes:
|
||||
pve-isos:
|
||||
+351
@@ -0,0 +1,351 @@
|
||||
#!/usr/bin/env bash
|
||||
# PSProxmoxVE integration test orchestration script.
|
||||
#
|
||||
# Single source of truth for the provision → test → cleanup lifecycle.
|
||||
# Called by both the GitHub Actions workflow and the local dev container.
|
||||
#
|
||||
# Usage:
|
||||
# run-integration.sh provision Provision nested PVE VMs
|
||||
# run-integration.sh test [8|9|all] Run integration tests (default: all)
|
||||
# run-integration.sh cleanup Destroy provisioned VMs
|
||||
# run-integration.sh all [8|9|all] Full lifecycle: provision → test → cleanup
|
||||
#
|
||||
# Required env vars (provision/cleanup):
|
||||
# PVE_ENDPOINT Parent PVE API URL (e.g. https://pve.example.com:8006)
|
||||
# PVE_API_TOKEN Parent PVE API token
|
||||
# PVE_TARGET_NODE Parent PVE node name
|
||||
# PVE_PASSWORD Root password for nested PVE instances
|
||||
#
|
||||
# Required env vars (test with pre-existing PVE):
|
||||
# PVETEST_HOST PVE host IP
|
||||
# PVETEST_APITOKEN PVE API token
|
||||
# Set SKIP_PROVISION=true
|
||||
#
|
||||
# Optional env vars:
|
||||
# CACHE_DIR ISO/image cache (default: /opt/pve-isos)
|
||||
# WORK_DIR Temp dir for build artifacts (default: $RUNNER_TEMP or /tmp/pve-integration)
|
||||
# CONFIG_FILE Test config JSON path (default: $CACHE_DIR/test-config.json)
|
||||
# MODULE_ARTIFACT Path to built module DLLs (default: ./publish/netstandard2.0)
|
||||
# PVE_VERSIONS Space-separated versions to provision (default: "9 8")
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# ── Paths ───────────────────────────────────────────────────────────
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
INFRA_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
REPO_ROOT="$(cd "$INFRA_DIR/../.." && pwd)"
|
||||
|
||||
# ── Defaults ────────────────────────────────────────────────────────
|
||||
CACHE_DIR="${CACHE_DIR:-/opt/pve-isos}"
|
||||
WORK_DIR="${WORK_DIR:-${RUNNER_TEMP:-/tmp/pve-integration}}"
|
||||
CONFIG_FILE="${CONFIG_FILE:-$CACHE_DIR/test-config.json}"
|
||||
MODULE_ARTIFACT="${MODULE_ARTIFACT:-$REPO_ROOT/publish/netstandard2.0}"
|
||||
PVE_VERSIONS="${PVE_VERSIONS:-9 8}"
|
||||
SKIP_PROVISION="${SKIP_PROVISION:-false}"
|
||||
|
||||
# ── Version config ──────────────────────────────────────────────────
|
||||
pve_iso() { case "$1" in 9) echo "${PVE9_ISO:-proxmox-ve_9.1-1.iso}";; 8) echo "${PVE8_ISO:-proxmox-ve_8.4-1.iso}";; esac; }
|
||||
pve_vmid() { case "$1" in 9) echo "${PVE9_VMID:-99909}";; 8) echo "${PVE8_VMID:-99908}";; esac; }
|
||||
pve_vmname() { case "$1" in 9) echo "pve-test-pve9";; 8) echo "pve-test-pve8";; esac; }
|
||||
|
||||
# ── CI helpers ──────────────────────────────────────────────────────
|
||||
ci_mask() { [[ "${GITHUB_ACTIONS:-}" == "true" ]] && echo "::add-mask::$1" || true; }
|
||||
ci_error() { [[ "${GITHUB_ACTIONS:-}" == "true" ]] && echo "::error::$1" || echo "ERROR: $1" >&2; }
|
||||
|
||||
log() { echo "==> $*"; }
|
||||
|
||||
require_env() {
|
||||
local var="$1"
|
||||
if [[ -z "${!var:-}" ]]; then
|
||||
ci_error "Required environment variable $var is not set"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# ── Subcommands ─────────────────────────────────────────────────────
|
||||
|
||||
cmd_provision() {
|
||||
log "Starting provisioning..."
|
||||
require_env PVE_ENDPOINT
|
||||
require_env PVE_API_TOKEN
|
||||
require_env PVE_TARGET_NODE
|
||||
require_env PVE_PASSWORD
|
||||
|
||||
ci_mask "$PVE_PASSWORD"
|
||||
mkdir -p "$WORK_DIR" "$CACHE_DIR"
|
||||
|
||||
# Ensure base ISOs
|
||||
for v in $PVE_VERSIONS; do
|
||||
log "Ensuring base ISO for PVE $v..."
|
||||
bash "$SCRIPT_DIR/ensure-base-iso.sh" "$(pve_iso "$v")" "$CACHE_DIR"
|
||||
done
|
||||
|
||||
# Ensure cloud images
|
||||
log "Ensuring cloud images..."
|
||||
local cloud_output
|
||||
cloud_output=$(bash "$SCRIPT_DIR/ensure-cloud-images.sh" "$CACHE_DIR")
|
||||
CLOUD_IMAGE_PATH=$(echo "$cloud_output" | grep "^CLOUD_IMAGE_PATH=" | cut -d= -f2)
|
||||
OVA_PATH=$(echo "$cloud_output" | grep "^OVA_PATH=" | cut -d= -f2)
|
||||
|
||||
# Pre-flight cleanup
|
||||
for v in $PVE_VERSIONS; do
|
||||
local iso_name
|
||||
iso_name="$(pve_iso "$v")"
|
||||
log "Pre-flight cleanup for PVE $v (VMID $(pve_vmid "$v"))..."
|
||||
bash "$SCRIPT_DIR/preflight-cleanup.sh" \
|
||||
"$PVE_ENDPOINT" "$PVE_API_TOKEN" \
|
||||
"$(pve_vmid "$v")" "${iso_name%.iso}-auto.iso" "$INFRA_DIR"
|
||||
done
|
||||
|
||||
# Generate answer file
|
||||
log "Generating answer file..."
|
||||
local escaped_pve_password
|
||||
escaped_pve_password=$(printf '%s' "$PVE_PASSWORD" | sed 's/[\/&\\]/\\&/g')
|
||||
sed "s/\${root_password}/${escaped_pve_password}/" \
|
||||
"$INFRA_DIR/answer.toml.tftpl" > "$WORK_DIR/answer.toml"
|
||||
|
||||
# Prepare auto-install ISOs
|
||||
for v in $PVE_VERSIONS; do
|
||||
local iso_name
|
||||
iso_name="$(pve_iso "$v")"
|
||||
log "Preparing auto-install ISO for PVE $v..."
|
||||
bash "$SCRIPT_DIR/prepare-auto-iso.sh" \
|
||||
"$CACHE_DIR/$iso_name" \
|
||||
"$WORK_DIR/answer.toml" \
|
||||
"$SCRIPT_DIR/first-boot.sh" \
|
||||
"$WORK_DIR/${iso_name%.iso}-auto.iso" \
|
||||
--cache-dir "$CACHE_DIR"
|
||||
done
|
||||
|
||||
# Terraform
|
||||
log "Running Terraform init..."
|
||||
(cd "$INFRA_DIR" && terraform init -input=false)
|
||||
|
||||
log "Building Terraform vars..."
|
||||
local tfvars="$WORK_DIR/instances.tfvars.json"
|
||||
local instances='{}'
|
||||
for v in $PVE_VERSIONS; do
|
||||
local iso_name
|
||||
iso_name="$(pve_iso "$v")"
|
||||
local iso_path="$WORK_DIR/${iso_name%.iso}-auto.iso"
|
||||
local vm_id
|
||||
vm_id="$(pve_vmid "$v")"
|
||||
local vm_name
|
||||
vm_name="$(pve_vmname "$v")"
|
||||
instances="$(jq \
|
||||
--arg key "pve${v}" \
|
||||
--arg iso_local_path "$iso_path" \
|
||||
--arg vm_name "$vm_name" \
|
||||
--argjson vm_id "$vm_id" \
|
||||
'. + {($key): {iso_local_path: $iso_local_path, vm_id: $vm_id, vm_name: $vm_name}}' \
|
||||
<<<"$instances")"
|
||||
done
|
||||
jq -n --argjson pve_instances "$instances" '{pve_instances: $pve_instances}' > "$tfvars"
|
||||
|
||||
log "Running Terraform apply..."
|
||||
(cd "$INFRA_DIR" && \
|
||||
TF_VAR_proxmox_endpoint="$PVE_ENDPOINT" \
|
||||
TF_VAR_proxmox_api_token="$PVE_API_TOKEN" \
|
||||
TF_VAR_target_node="$PVE_TARGET_NODE" \
|
||||
TF_VAR_test_vm_password="$PVE_PASSWORD" \
|
||||
terraform apply -auto-approve -input=false -var-file="$tfvars")
|
||||
|
||||
# Wait for PVE instances and create API tokens
|
||||
for v in $PVE_VERSIONS; do
|
||||
log "Waiting for PVE $v to boot and creating API token..."
|
||||
local output
|
||||
output=$(bash "$SCRIPT_DIR/create-api-token.sh" \
|
||||
"$PVE_ENDPOINT" "$PVE_API_TOKEN" \
|
||||
"$(pve_vmid "$v")" "$PVE_PASSWORD" 900)
|
||||
local ip token
|
||||
ip=$(echo "$output" | grep "^IP=" | cut -d= -f2)
|
||||
token=$(echo "$output" | grep "^TOKEN=" | cut -d= -f2-)
|
||||
log "PVE $v ready at $ip"
|
||||
echo "{\"host\":\"$ip\",\"token\":\"$token\"}" > "$WORK_DIR/pve${v}.json"
|
||||
done
|
||||
|
||||
# Prepare test environments
|
||||
for v in $PVE_VERSIONS; do
|
||||
local ip
|
||||
ip=$(jq -r .host "$WORK_DIR/pve${v}.json")
|
||||
log "Preparing test environment on PVE $v ($ip)..."
|
||||
bash "$SCRIPT_DIR/prepare-test-environment.sh" "$ip" "$PVE_PASSWORD"
|
||||
done
|
||||
|
||||
# Write test config
|
||||
log "Writing test config to $CONFIG_FILE..."
|
||||
local jq_args=()
|
||||
for v in $PVE_VERSIONS; do
|
||||
jq_args+=(--argjson "pve${v}" "$(cat "$WORK_DIR/pve${v}.json")")
|
||||
done
|
||||
jq_args+=(--arg cloud_image "${CLOUD_IMAGE_PATH:-}")
|
||||
jq_args+=(--arg ova "${OVA_PATH:-}")
|
||||
|
||||
local jq_expr="{"
|
||||
local first=true
|
||||
for v in $PVE_VERSIONS; do
|
||||
$first || jq_expr+=","
|
||||
first=false
|
||||
jq_expr+="pve${v}: \$pve${v}"
|
||||
done
|
||||
jq_expr+=", cloud_image_path: \$cloud_image, ova_path: \$ova}"
|
||||
|
||||
jq -n "${jq_args[@]}" "$jq_expr" > "$CONFIG_FILE"
|
||||
log "Test config written to $CONFIG_FILE"
|
||||
log "Provisioning complete."
|
||||
}
|
||||
|
||||
cmd_test() {
|
||||
local requested="${1:-all}"
|
||||
local versions_to_test
|
||||
|
||||
if [[ "$requested" == "all" ]]; then
|
||||
versions_to_test="$PVE_VERSIONS"
|
||||
else
|
||||
versions_to_test="$requested"
|
||||
fi
|
||||
|
||||
# Install module
|
||||
local module_path="${MODULE_PATH:-$HOME/.local/share/powershell/Modules/PSProxmoxVE}"
|
||||
if [[ -d "$MODULE_ARTIFACT" ]] && ls "$MODULE_ARTIFACT"/*.dll >/dev/null 2>&1; then
|
||||
log "Installing module from $MODULE_ARTIFACT..."
|
||||
mkdir -p "$module_path"
|
||||
cp -r "$MODULE_ARTIFACT"/* "$module_path/"
|
||||
else
|
||||
# Try building it
|
||||
log "Module artifact not found at $MODULE_ARTIFACT, building..."
|
||||
(cd "$REPO_ROOT" && dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj \
|
||||
-c Release -f netstandard2.0 -o /tmp/pve-module-publish 2>&1 | tail -1)
|
||||
mkdir -p "$module_path"
|
||||
cp -r /tmp/pve-module-publish/* "$module_path/"
|
||||
fi
|
||||
|
||||
# Create test ISO
|
||||
local iso_path="$WORK_DIR/pvetest.iso"
|
||||
mkdir -p "$WORK_DIR"
|
||||
if [[ ! -f "$iso_path" ]]; then
|
||||
dd if=/dev/urandom of="$iso_path" bs=1M count=1 2>/dev/null
|
||||
fi
|
||||
|
||||
local overall_exit=0
|
||||
|
||||
for v in $versions_to_test; do
|
||||
log "Running integration tests for PVE $v..."
|
||||
|
||||
# Set env vars from config or from environment
|
||||
if [[ "$SKIP_PROVISION" == "true" ]]; then
|
||||
: "${PVETEST_HOST:?Set PVETEST_HOST when using SKIP_PROVISION}"
|
||||
: "${PVETEST_APITOKEN:?Set PVETEST_APITOKEN when using SKIP_PROVISION}"
|
||||
export PVETEST_PORT="${PVETEST_PORT:-8006}"
|
||||
export PVETEST_NODE="${PVETEST_NODE:-pve}"
|
||||
export PVETEST_STORAGE="${PVETEST_STORAGE:-local}"
|
||||
export PVETEST_CLOUD_IMAGE_PATH="${PVETEST_CLOUD_IMAGE_PATH:-}"
|
||||
export PVETEST_OVA_PATH="${PVETEST_OVA_PATH:-}"
|
||||
else
|
||||
if [[ ! -f "$CONFIG_FILE" ]]; then
|
||||
ci_error "No test config found at $CONFIG_FILE — run 'provision' first or set SKIP_PROVISION=true"
|
||||
exit 1
|
||||
fi
|
||||
export PVETEST_HOST=$(jq -r ".pve${v}.host" "$CONFIG_FILE")
|
||||
export PVETEST_APITOKEN=$(jq -r ".pve${v}.token" "$CONFIG_FILE")
|
||||
export PVETEST_PORT=8006
|
||||
export PVETEST_NODE=pve
|
||||
export PVETEST_STORAGE=local
|
||||
export PVETEST_CLOUD_IMAGE_PATH=$(jq -r '.cloud_image_path' "$CONFIG_FILE")
|
||||
export PVETEST_OVA_PATH=$(jq -r '.ova_path' "$CONFIG_FILE")
|
||||
fi
|
||||
|
||||
export PVETEST_ISO_PATH="$iso_path"
|
||||
export PVETEST_PVE_VERSION="$v"
|
||||
export PVETEST_PASSWORD="${PVETEST_PASSWORD:-${PVE_PASSWORD:-}}"
|
||||
|
||||
# Verify API reachable
|
||||
log "Verifying PVE $v API at $PVETEST_HOST:$PVETEST_PORT..."
|
||||
if ! curl -sk --connect-timeout 10 \
|
||||
-H "Authorization: PVEAPIToken=${PVETEST_APITOKEN}" \
|
||||
"https://${PVETEST_HOST}:${PVETEST_PORT}/api2/json/nodes" | grep -q '"node"'; then
|
||||
ci_error "Cannot reach PVE $v API at ${PVETEST_HOST}:${PVETEST_PORT}"
|
||||
overall_exit=3
|
||||
continue
|
||||
fi
|
||||
|
||||
# Run Pester
|
||||
local test_exit=0
|
||||
pwsh -NoProfile -Command '
|
||||
Import-Module Pester -MinimumVersion 5.0
|
||||
$config = New-PesterConfiguration
|
||||
$config.Run.Path = "tests/PSProxmoxVE.Tests/Integration"
|
||||
$config.Filter.Tag = "Integration"
|
||||
$config.Output.Verbosity = "Detailed"
|
||||
$config.TestResult.Enabled = $true
|
||||
$config.TestResult.OutputFormat = "NUnitXml"
|
||||
$config.TestResult.OutputPath = "TestResults/integration-results-pve'"$v"'.xml"
|
||||
Invoke-Pester -Configuration $config
|
||||
' || test_exit=$?
|
||||
|
||||
if [[ $test_exit -ne 0 ]]; then
|
||||
ci_error "PVE $v integration tests failed (exit code $test_exit)"
|
||||
overall_exit=3
|
||||
else
|
||||
log "PVE $v integration tests passed."
|
||||
fi
|
||||
done
|
||||
|
||||
return $overall_exit
|
||||
}
|
||||
|
||||
cmd_cleanup() {
|
||||
log "Starting cleanup..."
|
||||
for v in $PVE_VERSIONS; do
|
||||
local iso_name
|
||||
iso_name="$(pve_iso "$v")"
|
||||
log "Cleaning up PVE $v (VMID $(pve_vmid "$v"))..."
|
||||
bash "$SCRIPT_DIR/preflight-cleanup.sh" \
|
||||
"${PVE_ENDPOINT:-}" "${PVE_API_TOKEN:-}" \
|
||||
"$(pve_vmid "$v")" "${iso_name%.iso}-auto.iso" "$INFRA_DIR" \
|
||||
|| true
|
||||
done
|
||||
log "Cleanup complete."
|
||||
}
|
||||
|
||||
cmd_all() {
|
||||
local test_versions="${1:-all}"
|
||||
local test_exit=0
|
||||
|
||||
trap 'log "Running cleanup after test run..."; cmd_cleanup || true' EXIT
|
||||
|
||||
cmd_provision
|
||||
cmd_test "$test_versions" || test_exit=$?
|
||||
|
||||
if [[ $test_exit -ne 0 ]]; then
|
||||
log "Tests failed with exit code $test_exit. Cleanup will still run."
|
||||
fi
|
||||
|
||||
# Trap handles cleanup on exit
|
||||
return $test_exit
|
||||
}
|
||||
|
||||
# ── Main ────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
local cmd="${1:-}"
|
||||
shift || true
|
||||
|
||||
case "$cmd" in
|
||||
provision) cmd_provision "$@" ;;
|
||||
test) cmd_test "$@" ;;
|
||||
cleanup) cmd_cleanup "$@" ;;
|
||||
all) cmd_all "$@" ;;
|
||||
*)
|
||||
echo "Usage: $(basename "$0") {provision|test|cleanup|all} [8|9|all]"
|
||||
echo ""
|
||||
echo "Subcommands:"
|
||||
echo " provision Provision nested PVE VMs via Terraform"
|
||||
echo " test [8|9|all] Run integration tests (default: all versions)"
|
||||
echo " cleanup Destroy provisioned VMs"
|
||||
echo " all [8|9|all] Full lifecycle: provision → test → cleanup"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user