mirror of
https://github.com/GoodOlClint/PSProxmoxVE.git
synced 2026-07-26 07:58:14 +00:00
ddecafee91
Workflow now provisions a throwaway nested PVE VM via Terraform, runs integration tests against it, then destroys it. Uses proxmox-auto-install-assistant to bake the answer file and a first-boot script (installs qemu-guest-agent) directly into the ISO. IP is discovered via the QEMU guest agent on the parent PVE, eliminating the need for static IP configuration. Supports both PVE 8.x and 9.x via workflow_dispatch version selector. Skip provisioning with skip_provision=true to test against a pre-existing PVE. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
254 lines
9.9 KiB
YAML
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@v4
|
|
|
|
# ── Provision nested PVE ───────────────────────────────────────────
|
|
|
|
- name: Select PVE ISO
|
|
if: inputs.skip_provision != true
|
|
id: iso
|
|
shell: bash
|
|
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
|
|
|
|
- name: Generate answer file
|
|
if: inputs.skip_provision != true
|
|
shell: bash
|
|
run: |
|
|
sed "s/\${root_password}/${PVE_PASSWORD}/" \
|
|
${INFRA_DIR}/answer.toml.tftpl > /tmp/answer.toml
|
|
echo "Generated answer file:"
|
|
cat /tmp/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 }}" \
|
|
/tmp/answer.toml \
|
|
${SCRIPTS_DIR}/first-boot.sh \
|
|
"/tmp/${{ steps.iso.outputs.filename }}"
|
|
|
|
- name: Upload ISO to PVE storage
|
|
if: inputs.skip_provision != true
|
|
id: upload
|
|
shell: bash
|
|
env:
|
|
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
|
run: |
|
|
bash ${SCRIPTS_DIR}/upload-to-pve.sh \
|
|
"$(echo '${{ secrets.PVE_ENDPOINT }}' | sed -E 's|https?://||;s|:.*||')" \
|
|
"${PVE_API_TOKEN}" \
|
|
"${{ secrets.PVE_TARGET_NODE }}" \
|
|
"local" \
|
|
"/tmp/${{ steps.iso.outputs.filename }}" \
|
|
"iso" | tee /tmp/upload-output.txt
|
|
ISO_VOLID=$(tail -1 /tmp/upload-output.txt)
|
|
echo "iso_volid=${ISO_VOLID}" >> "$GITHUB_OUTPUT"
|
|
|
|
- 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_file_id: ${{ steps.upload.outputs.iso_volid }}
|
|
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: |
|
|
PARENT_HOST=$(echo '${{ secrets.PVE_ENDPOINT }}' | sed -E 's|https?://||;s|:.*||')
|
|
OUTPUT=$(bash ${SCRIPTS_DIR}/create-api-token.sh \
|
|
"${PARENT_HOST}" \
|
|
"${PVE_API_TOKEN}" \
|
|
"${{ steps.terraform.outputs.vm_id }}" \
|
|
"${PVE_PASSWORD}" \
|
|
900)
|
|
echo "$OUTPUT"
|
|
VM_IP=$(echo "$OUTPUT" | grep "^IP=" | cut -d= -f2)
|
|
VM_TOKEN=$(echo "$OUTPUT" | grep "^TOKEN=" | cut -d= -f2-)
|
|
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=/tmp/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: /tmp/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_file_id: ${{ steps.upload.outputs.iso_volid }}
|
|
TF_VAR_test_vm_password: ${{ env.PVE_PASSWORD }}
|
|
run: terraform destroy -auto-approve -input=false
|
|
|
|
- name: Clean up uploaded ISO
|
|
if: always() && inputs.skip_provision != true
|
|
shell: bash
|
|
env:
|
|
PVE_API_TOKEN: ${{ secrets.PVE_API_TOKEN }}
|
|
run: |
|
|
PARENT_HOST=$(echo '${{ secrets.PVE_ENDPOINT }}' | sed -E 's|https?://||;s|:.*||')
|
|
bash ${SCRIPTS_DIR}/cleanup-pve-storage.sh \
|
|
"${PARENT_HOST}" \
|
|
"${PVE_API_TOKEN}" \
|
|
"${{ steps.upload.outputs.iso_volid }}" || true
|