diff --git a/.github/workflows/build-test-container.yml b/.github/workflows/build-test-container.yml new file mode 100644 index 0000000..7819756 --- /dev/null +++ b/.github/workflows/build-test-container.yml @@ -0,0 +1,34 @@ +name: Build Integration Test Container + +on: + push: + branches: [ main ] + paths: + - 'tests/infrastructure/Dockerfile' + - '.github/workflows/build-test-container.yml' + workflow_dispatch: + +permissions: + packages: write + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v5 + + - name: Log in to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: tests/infrastructure + file: tests/infrastructure/Dockerfile + push: true + tags: ghcr.io/${{ github.repository }}/integration-test:latest diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 9c1b7c8..a8290e4 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -5,6 +5,10 @@ name: Integration Tests # PVE 8 and PVE 9, runs tests against each, then destroys them. # Can also run against a pre-existing PVE with skip_provision. # +# Architecture: +# build job → GitHub-hosted runner, dotnet publish → upload artifact +# integration → self-hosted runner in Docker container, downloads artifact +# # 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) @@ -36,12 +40,43 @@ env: PVE_PASSWORD: "Testpass123!" jobs: + # ── Build module artifact (GitHub-hosted, no .NET SDK in container) ── + build: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v5 + + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: '9.0.x' + + - name: Build module + run: dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj --configuration Release --framework netstandard2.0 --output ./publish/netstandard2.0 + + - name: Clean publish output + run: rm -f ./publish/netstandard2.0/*.deps.json + + - name: Upload module artifact + uses: actions/upload-artifact@v7 + with: + name: module-integration + path: ./publish/netstandard2.0/ + + # ── Integration tests (self-hosted runner in Docker container) ─────── integration: + needs: build runs-on: [self-hosted, proxmox, integration] timeout-minutes: 45 + container: + image: ghcr.io/${{ github.repository }}/integration-test:latest + volumes: + - /opt/pve-isos:/opt/pve-isos + - /opt/pve-images:/opt/pve-images strategy: fail-fast: false - max-parallel: 1 # Single self-hosted runner — provision one at a time + max-parallel: 1 matrix: pve_version: ['9', '8'] include: @@ -59,7 +94,13 @@ jobs: steps: - uses: actions/checkout@v5 - # ── Pre-flight cleanup ───────────────────────────────────────────── + - name: Download module artifact + uses: actions/download-artifact@v8 + with: + name: module-integration + path: ./publish/netstandard2.0/ + + # ── Pre-flight cleanup ─────────────────────────────────────────── - name: Pre-flight cleanup if: inputs.skip_provision != true @@ -74,7 +115,7 @@ jobs: "${{ matrix.iso_filename }}" \ "${INFRA_DIR}" - # ── Provision nested PVE ─────────────────────────────────────────── + # ── Provision nested PVE ───────────────────────────────────────── - name: Generate answer file if: inputs.skip_provision != true @@ -133,14 +174,13 @@ jobs: 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 ${{ matrix.pve_version }} ready at ${VM_IP}" echo "host=${VM_IP}" >> "$GITHUB_OUTPUT" echo "token=${VM_TOKEN}" >> "$GITHUB_OUTPUT" - # ── Resolve test target ──────────────────────────────────────────── + # ── Resolve test target ────────────────────────────────────────── - name: Set test target id: target @@ -176,9 +216,9 @@ jobs: exit 1 fi - # ── Prepare test Linux VM ────────────────────────────────────────── + # ── Prepare test Linux VM ──────────────────────────────────────── - - name: Build Alpine test image (cached) + - name: Build Alpine test image (cached on host) if: inputs.skip_provision != true shell: bash run: | @@ -197,17 +237,7 @@ jobs: LINUX_VMID=$(echo "$OUTPUT" | grep "^LINUX_VMID=" | cut -d= -f2) echo "linux_vmid=${LINUX_VMID}" >> "$GITHUB_OUTPUT" - # ── Build and test ───────────────────────────────────────────────── - - - name: Build module - run: dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj --configuration Release --framework netstandard2.0 --output ./publish/netstandard2.0 - - - name: Clean publish output for PS module loading - run: rm -f ./publish/netstandard2.0/*.deps.json - - - name: Install Pester 5 - shell: pwsh - run: Install-Module -Name Pester -MinimumVersion 5.0 -Force -Scope CurrentUser + # ── Install module and run tests ───────────────────────────────── - name: Copy module to module path shell: pwsh @@ -249,7 +279,7 @@ jobs: name: integration-test-results-pve${{ matrix.pve_version }} path: TestResults/ - # ── Teardown ─────────────────────────────────────────────────────── + # ── Teardown ───────────────────────────────────────────────────── - name: Terraform destroy if: always() && inputs.skip_provision != true diff --git a/tests/infrastructure/Dockerfile b/tests/infrastructure/Dockerfile new file mode 100644 index 0000000..9aeb935 --- /dev/null +++ b/tests/infrastructure/Dockerfile @@ -0,0 +1,60 @@ +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive + +# ── Base packages ────────────────────────────────────────────────────── +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + jq \ + dosfstools \ + mtools \ + openssh-client \ + sshpass \ + apt-transport-https \ + gnupg \ + lsb-release \ + ca-certificates \ + python3 \ + libguestfs-tools \ + linux-image-generic \ + qemu-utils \ + && rm -rf /var/lib/apt/lists/* + +# ── Microsoft repo (PowerShell) ─────────────────────────────────────── +RUN curl -fsSL https://packages.microsoft.com/keys/microsoft.asc \ + | gpg --dearmor -o /usr/share/keyrings/microsoft-archive-keyring.gpg && \ + echo "deb [arch=amd64 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 + +# ── HashiCorp repo (Terraform) ──────────────────────────────────────── +RUN 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 + +# ── Proxmox repo (proxmox-auto-install-assistant) ───────────────────── +RUN 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 + +# ── Install tooling ─────────────────────────────────────────────────── +RUN apt-get update && apt-get install -y --no-install-recommends \ + powershell \ + terraform \ + proxmox-auto-install-assistant \ + && rm -rf /var/lib/apt/lists/* + +# ── Install Pester ──────────────────────────────────────────────────── +RUN pwsh -NoProfile -Command \ + 'Set-PSRepository -Name PSGallery -InstallationPolicy Trusted; \ + Install-Module -Name Pester -MinimumVersion 5.0 -Scope AllUsers -Force' + +# ── Verify installs ────────────────────────────────────────────────── +RUN pwsh --version && \ + terraform --version && \ + proxmox-auto-install-assistant --version && \ + sshpass -V | head -1