Files
PSProxmoxVE/.github/workflows/integration-tests.yml
T
Clint Branham b64c34c8cd fix(ci): align all projects on net9.0, upgrade actions to v5, enable VM tests
- Change test projects from net10.0 to net9.0 to match source projects
- Update build workflow from net8.0 to net9.0
- Upgrade checkout to v5, setup-dotnet to v5 across all workflows
- Remove outdated PS 7.2 test matrix entries
- Restructure integration tests: create test keeps VM alive so start/stop,
  snapshot, and cloud-init tests can use it instead of skipping. AfterAll
  handles cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:59:43 -05:00

254 lines
9.9 KiB
YAML

name: Integration Tests
# Real integration tests against a live Proxmox VE instance.
# By default, provisions a throwaway nested PVE VM via Terraform, runs tests,
# then destroys it. Can also run against a pre-existing PVE with skip_provision.
#
# 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
#
# Optional secrets (for skip_provision mode):
# PVETEST_HOST - Hostname or IP of a pre-existing nested PVE
# PVETEST_APITOKEN - API token for the pre-existing nested PVE
#
# WARNING: Integration tests create and destroy real VMs, upload files,
# and modify network configuration. Never run against production.
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
inputs:
pve_version:
description: 'PVE version to test (8 or 9)'
required: false
type: choice
options:
- '9'
- '8'
default: '9'
skip_provision:
description: 'Skip provisioning (use existing PVE from PVETEST_HOST secret)'
required: false
type: boolean
default: false
env:
INFRA_DIR: tests/infrastructure
SCRIPTS_DIR: tests/infrastructure/scripts
PVE_PASSWORD: "Testpass123!"
jobs:
integration:
runs-on: [self-hosted, proxmox, integration]
timeout-minutes: 45
steps:
- uses: actions/checkout@v5
# ── Pre-flight cleanup ─────────────────────────────────────────────
- name: Pre-flight cleanup
if: inputs.skip_provision != true
shell: bash
id: iso
env:
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
run: |
VERSION="${{ inputs.pve_version || '9' }}"
if [ "$VERSION" = "9" ]; then
echo "base=/opt/pve-isos/proxmox-ve_9.1-1.iso" >> "$GITHUB_OUTPUT"
echo "filename=proxmox-ve_9.1-1-auto.iso" >> "$GITHUB_OUTPUT"
else
echo "base=/opt/pve-isos/proxmox-ve_8.4-1.iso" >> "$GITHUB_OUTPUT"
echo "filename=proxmox-ve_8.4-1-auto.iso" >> "$GITHUB_OUTPUT"
fi
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
"${{ secrets.PVE_ENDPOINT }}" \
"${PVE_API_TOKEN}" \
99900 \
"auto.iso" \
"${INFRA_DIR}"
# ── Provision nested PVE ───────────────────────────────────────────
- name: Generate answer file
if: inputs.skip_provision != true
shell: bash
run: |
sed "s/\${root_password}/${PVE_PASSWORD}/" \
${INFRA_DIR}/answer.toml.tftpl > ${RUNNER_TEMP}/answer.toml
echo "Generated answer file:"
cat ${RUNNER_TEMP}/answer.toml
- name: Prepare auto-install ISO
if: inputs.skip_provision != true
shell: bash
run: |
bash ${SCRIPTS_DIR}/prepare-auto-iso.sh \
"${{ steps.iso.outputs.base }}" \
${RUNNER_TEMP}/answer.toml \
${SCRIPTS_DIR}/first-boot.sh \
"${{ runner.temp }}/${{ steps.iso.outputs.filename }}"
- name: Terraform init
if: inputs.skip_provision != true
shell: bash
working-directory: ${{ env.INFRA_DIR }}
run: terraform init -input=false
- name: Terraform apply
if: inputs.skip_provision != true
id: terraform
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_iso_local_path: ${{ runner.temp }}/${{ steps.iso.outputs.filename }}
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
run: |
terraform apply -auto-approve -input=false
echo "vm_id=$(terraform output -raw pve_test_vm_id)" >> "$GITHUB_OUTPUT"
- name: Wait for install and create API token
if: inputs.skip_provision != true
id: provision
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}" \
"${{ steps.terraform.outputs.vm_id }}" \
"${PVE_PASSWORD}" \
900)
VM_IP=$(echo "$OUTPUT" | grep "^IP=" | cut -d= -f2)
VM_TOKEN=$(echo "$OUTPUT" | grep "^TOKEN=" | cut -d= -f2-)
# Mask sensitive values before they appear in logs
echo "::add-mask::${VM_IP}"
echo "::add-mask::${VM_TOKEN}"
echo "Nested PVE ready at ${VM_IP}"
echo "host=${VM_IP}" >> "$GITHUB_OUTPUT"
echo "token=${VM_TOKEN}" >> "$GITHUB_OUTPUT"
# ── Resolve test target ────────────────────────────────────────────
- name: Set test target
id: target
shell: bash
run: |
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"
else
echo "host=${{ steps.provision.outputs.host }}" >> "$GITHUB_OUTPUT"
echo "port=8006" >> "$GITHUB_OUTPUT"
echo "token=${{ steps.provision.outputs.token }}" >> "$GITHUB_OUTPUT"
echo "node=pve" >> "$GITHUB_OUTPUT"
echo "storage=local" >> "$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 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
# ── Build and test ─────────────────────────────────────────────────
- name: Build module
run: dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj --configuration Release --framework net9.0 --output ./publish/net9.0
- name: Install Pester 5
shell: pwsh
run: Install-Module -Name Pester -MinimumVersion 5.0 -Force -Scope CurrentUser
- 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/net9.0/* -Destination $modulePath -Recurse -Force
- 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_ISO_PATH: ${{ runner.temp }}/pvetest.iso
run: |
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
- name: Upload test results
if: always()
uses: actions/upload-artifact@v5
with:
name: integration-test-results
path: TestResults/
# ── Teardown ───────────────────────────────────────────────────────
- name: Terraform destroy
if: always() && inputs.skip_provision != true
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_iso_local_path: ${{ runner.temp }}/${{ steps.iso.outputs.filename }}
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
run: |
terraform init -input=false
terraform destroy -auto-approve -input=false || true
rm -f terraform.tfstate terraform.tfstate.backup .terraform.tfstate.lock.info
- name: Final cleanup
if: always() && inputs.skip_provision != true
shell: bash
env:
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
run: |
bash ${SCRIPTS_DIR}/preflight-cleanup.sh \
"${{ secrets.PVE_ENDPOINT }}" \
"${PVE_API_TOKEN}" \
99900 \
"${{ steps.iso.outputs.filename }}" \
"${INFRA_DIR}" || true