Files
PSProxmoxVE/.github/workflows/integration-tests.yml
T
Clint Branham 8d268aee61 fix(ci): remove PSProxmoxVE.deps.json to fix assembly loading in PS 7.x
The deps.json file causes .NET's AssemblyLoadContext to conflict with
PowerShell's own runtime assembly resolution, resulting in
FileNotFoundException for System.Runtime 9.0.0.0 on all PS 7.x
platforms. Binary PS modules should not include deps.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 08:36:49 -05:00

257 lines
10 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: Remove deps.json from publish output
run: rm -f ./publish/net9.0/PSProxmoxVE.deps.json
- 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@v4
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