ci: add Linux installation QA workflow for .deb and .rpm packages #3
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Linux Install Tests | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| # PURPOSE: | |
| # Smoke-test Linux .deb and .rpm packages produced by the CI workflow. | |
| # Catches packaging regressions before release: missing files, broken | |
| # installs, service registration issues, configuration initialization | |
| # failures, and obvious startup problems. | |
| # | |
| # DESIGN: | |
| # - Separate reusable workflow (workflow_call + workflow_dispatch + schedule). | |
| # - Consumes the merged `devolutions-gateway` artifact from ci.yml. | |
| # - Runs package installation tests inside Docker containers on | |
| # GitHub-hosted runners (x86_64 only). | |
| # - DEB lane: Ubuntu 18.04 container (minimum supported distro; the binary | |
| # is built against an Ubuntu 18.04 sysroot / glibc 2.27). | |
| # - RPM lane: Rocky Linux 9 container (RHEL 9-compatible). | |
| # - Tests are split into: | |
| # 1. Mandatory package smoke tests (must pass). | |
| # 2. Best-effort systemd service tests (informational only). | |
| # | |
| # TRADEOFFS: | |
| # - Separate workflow: Keeps the main CI fast; install tests can run | |
| # independently or be chained into a release pipeline. | |
| # - GitHub-hosted runners: Sufficient for package QA; no self-hosted | |
| # infrastructure required. | |
| # - Container-based: Close enough to real distros for catching packaging | |
| # regressions, though not a perfect replica of production systems. | |
| # - RHEL 9-compatible (Rocky Linux 9): Free, widely-used RHEL 9 rebuild; | |
| # no exact RHEL version matching is required. | |
| # - x86_64 only: GitHub-hosted runners are x86_64; ARM64 packages would | |
| # require QEMU or ARM runners, not worth the complexity for v1. | |
| # - Service testing is best-effort: systemd inside Docker containers is | |
| # unreliable. Full service validation requires a real systemd environment. | |
| # ────────────────────────────────────────────────────────────────────────────── | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, reopened] | |
| # Callable from release pipelines (e.g. create-new-release.yml). | |
| # The caller may supply an explicit CI run ID; if omitted the shared run | |
| # context is used (all workflow_call children share the parent run ID). | |
| workflow_call: | |
| inputs: | |
| run: | |
| description: > | |
| CI workflow run ID whose package artifacts to test. | |
| Leave blank to use the current (shared) run context. | |
| type: string | |
| required: false | |
| # Allow ad-hoc manual triggers so engineers can test packages from any | |
| # previous CI run without triggering a full rebuild. | |
| workflow_dispatch: | |
| inputs: | |
| run: | |
| description: > | |
| CI workflow run ID whose package artifacts to test. | |
| Leave empty to use the latest successful CI run on the default branch. | |
| type: string | |
| required: false | |
| # Weekly scheduled run to catch regressions even when no release is in progress. | |
| schedule: | |
| - cron: "0 6 * * 1" # Monday 06:00 UTC | |
| permissions: | |
| contents: read | |
| actions: read | |
| jobs: | |
| # ─── Resolve which CI run to pull artifacts from, and read its version ────── | |
| # | |
| # Priority: | |
| # 1. Explicit input (workflow_dispatch or workflow_call with run supplied). | |
| # 2. Current run ID when called as workflow_call without explicit input | |
| # (all reusable workflows in a chain share the same top-level run ID, | |
| # so ci.yml artifacts are already in scope). | |
| # 3. Latest successful ci.yml run on the default branch (schedule or | |
| # workflow_dispatch without input). | |
| preflight: | |
| name: Preflight | |
| runs-on: ubuntu-latest | |
| outputs: | |
| run-id: ${{ steps.resolve.outputs.run-id }} | |
| version: ${{ steps.version.outputs.version }} | |
| steps: | |
| - name: Resolve CI run ID | |
| id: resolve | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| $explicitRun = "${{ inputs.run }}" | |
| if ($explicitRun) { | |
| $runId = $explicitRun | |
| Write-Host "Using provided run ID: $runId" | |
| } elseif ("${{ github.event_name }}" -eq 'workflow_call') { | |
| # All reusable workflows in a chain share the parent run ID. | |
| $runId = "${{ github.run_id }}" | |
| Write-Host "workflow_call: using shared run ID: $runId" | |
| } else { | |
| # schedule or workflow_dispatch without explicit input. | |
| Write-Host "Looking up latest successful ci.yml run on ${{ github.event.repository.default_branch }}..." | |
| $runId = gh run list ` | |
| --workflow ci.yml ` | |
| --status success ` | |
| --branch "${{ github.event.repository.default_branch }}" ` | |
| --limit 1 ` | |
| --json databaseId ` | |
| --jq '.[0].databaseId' ` | |
| --repo $env:GITHUB_REPOSITORY | |
| if (-not $runId -or $runId -eq 'null') { | |
| Write-Host "::error::No successful CI run found on ${{ github.event.repository.default_branch }}" | |
| exit 1 | |
| } | |
| Write-Host "Found latest successful CI run: $runId" | |
| } | |
| "run-id=$runId" >> $env:GITHUB_OUTPUT | |
| - name: Download version artifact | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh run download "${{ steps.resolve.outputs.run-id }}" ` | |
| -n version ` | |
| --repo $env:GITHUB_REPOSITORY | |
| - name: Read version | |
| id: version | |
| shell: pwsh | |
| run: | | |
| $version = Get-Content VERSION -First 1 | |
| "version=$version" >> $env:GITHUB_OUTPUT | |
| Write-Host "::notice::Testing packages for version $version from run ${{ steps.resolve.outputs.run-id }}" | |
| # ─── DEB and RPM install tests run in parallel via matrix ─────────────────── | |
| install-test: | |
| name: ${{ matrix.display-name }} | |
| needs: [preflight] | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| # DEB lane: Ubuntu 18.04 (minimum supported; built against Ubuntu 18.04 sysroot / glibc 2.27) | |
| - package-type: deb | |
| display-name: "DEB Install Test (Ubuntu 18.04)" | |
| container-image: "ubuntu:18.04" | |
| test-script: ".github/scripts/smoke-test-deb.sh" | |
| package-pattern: "*.deb" | |
| # RPM lane: Rocky Linux 9 (RHEL 9-compatible) | |
| - package-type: rpm | |
| display-name: "RPM Install Test (Rocky Linux 9)" | |
| container-image: "rockylinux:9" | |
| test-script: ".github/scripts/smoke-test-rpm.sh" | |
| package-pattern: "*.rpm" | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@v4 | |
| - name: Download devolutions-gateway artifact | |
| shell: pwsh | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| gh run download "${{ needs.preflight.outputs.run-id }}" ` | |
| -n devolutions-gateway ` | |
| -D gateway-artifacts ` | |
| --repo $env:GITHUB_REPOSITORY | |
| - name: Locate package file | |
| id: find-package | |
| shell: pwsh | |
| run: | | |
| $packageFile = Get-ChildItem -Path "gateway-artifacts/linux/x86_64" ` | |
| -Filter "${{ matrix.package-pattern }}" -File | Select-Object -First 1 | |
| if (-not $packageFile) { | |
| Write-Host "::error::No ${{ matrix.package-type }} package found in gateway-artifacts/linux/x86_64" | |
| Write-Host "Available files in gateway-artifacts:" | |
| Get-ChildItem -Path "gateway-artifacts" -Recurse -File | Sort-Object FullName | ForEach-Object { Write-Host $_.FullName } | |
| exit 1 | |
| } | |
| $packageFilePath = "gateway-artifacts/linux/x86_64/$($packageFile.Name)" | |
| Write-Host "Found package: $packageFilePath" | |
| "package-file=$packageFilePath" >> $env:GITHUB_OUTPUT | |
| "package-basename=$($packageFile.Name)" >> $env:GITHUB_OUTPUT | |
| - name: Run install tests in container | |
| shell: pwsh | |
| run: | | |
| chmod +x "${{ matrix.test-script }}" | |
| # Strip the gateway-artifacts/ prefix so the path is relative to | |
| # the /artifacts mount point inside the container. | |
| $packageRelPath = "${{ steps.find-package.outputs.package-file }}" | |
| $packageInContainer = "/artifacts/" + ($packageRelPath -replace '^gateway-artifacts/', '') | |
| docker run --rm ` | |
| -v "${{ github.workspace }}:/workspace:ro" ` | |
| -v "${{ github.workspace }}/gateway-artifacts:/artifacts:ro" ` | |
| -e "PACKAGE_FILE=$packageInContainer" ` | |
| -e "VERSION=${{ needs.preflight.outputs.version }}" ` | |
| -e "PACKAGE_NAME=devolutions-gateway" ` | |
| "${{ matrix.container-image }}" ` | |
| /workspace/${{ matrix.test-script }} | |
| - name: Write job summary | |
| if: always() | |
| shell: pwsh | |
| run: | | |
| @" | |
| ## ${{ matrix.display-name }} | |
| | Property | Value | | |
| |----------|-------| | |
| | Package | ``${{ steps.find-package.outputs.package-basename }}`` | | |
| | Version | ``${{ needs.preflight.outputs.version }}`` | | |
| | Container | ``${{ matrix.container-image }}`` | | |
| | CI Run | ``${{ needs.preflight.outputs.run-id }}`` | | |
| "@ | Add-Content -Path $env:GITHUB_STEP_SUMMARY |