name: Pull Request env: # TODO: unhardcode this REGISTRY: iad.ocir.io/idyksih5sir9/cozystack on: pull_request: types: [opened, synchronize, reopened] paths-ignore: - 'docs/**/*' # Cancel in‑flight runs for the same PR when a new push arrives. concurrency: group: pr-${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: build: name: Build runs-on: [self-hosted] permissions: contents: read packages: write # Never run when the PR carries the "release" label. if: | !contains(github.event.pull_request.labels.*.name, 'release') steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Run unit tests run: make unit-tests - name: Set up Docker config run: | if [ -d ~/.docker ]; then cp -r ~/.docker "${{ runner.temp }}/.docker" fi - name: Login to GitHub Container Registry if: ${{ !github.event.pull_request.head.repo.fork }} uses: docker/login-action@v3 with: username: ${{ secrets.OCIR_USER}} password: ${{ secrets.OCIR_TOKEN }} registry: iad.ocir.io env: DOCKER_CONFIG: ${{ runner.temp }}/.docker - name: Build run: make build env: DOCKER_CONFIG: ${{ runner.temp }}/.docker - name: Build Talos image run: make -C packages/core/installer talos-nocloud - name: Save git diff as patch if: "!contains(github.event.pull_request.labels.*.name, 'release')" run: git diff HEAD > _out/assets/pr.patch - name: Upload git diff patch if: "!contains(github.event.pull_request.labels.*.name, 'release')" uses: actions/upload-artifact@v4 with: name: pr-patch path: _out/assets/pr.patch - name: Upload installer uses: actions/upload-artifact@v4 with: name: cozystack-installer path: _out/assets/cozystack-installer.yaml - name: Upload Talos image uses: actions/upload-artifact@v4 with: name: talos-image path: _out/assets/nocloud-amd64.raw.xz resolve_assets: name: "Resolve assets" runs-on: ubuntu-latest if: contains(github.event.pull_request.labels.*.name, 'release') outputs: installer_id: ${{ steps.fetch_assets.outputs.installer_id }} disk_id: ${{ steps.fetch_assets.outputs.disk_id }} steps: - name: Checkout code if: contains(github.event.pull_request.labels.*.name, 'release') uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Extract tag from PR branch (release PR) if: contains(github.event.pull_request.labels.*.name, 'release') id: get_tag uses: actions/github-script@v7 with: script: | const branch = context.payload.pull_request.head.ref; const m = branch.match(/^release-(\d+\.\d+\.\d+(?:[-\w\.]+)?)$/); if (!m) { core.setFailed(`❌ Branch '${branch}' does not match 'release-X.Y.Z[-suffix]'`); return; } core.setOutput('tag', `v${m[1]}`); - name: Find draft release & asset IDs (release PR) if: contains(github.event.pull_request.labels.*.name, 'release') id: fetch_assets uses: actions/github-script@v7 with: github-token: ${{ secrets.GH_PAT }} script: | const tag = '${{ steps.get_tag.outputs.tag }}'; const releases = await github.rest.repos.listReleases({ owner: context.repo.owner, repo: context.repo.repo, per_page: 100 }); const draft = releases.data.find(r => r.tag_name === tag && r.draft); if (!draft) { core.setFailed(`Draft release '${tag}' not found`); return; } const find = (n) => draft.assets.find(a => a.name === n)?.id; const installerId = find('cozystack-installer.yaml'); const diskId = find('nocloud-amd64.raw.xz'); if (!installerId || !diskId) { core.setFailed('Required assets missing in draft release'); return; } core.setOutput('installer_id', installerId); core.setOutput('disk_id', diskId); prepare_env: name: "Prepare environment" runs-on: [self-hosted] permissions: contents: read packages: read needs: ["build", "resolve_assets"] if: ${{ always() && (needs.build.result == 'success' || needs.resolve_assets.result == 'success') }} steps: # ▸ Checkout and prepare the codebase - name: Checkout code uses: actions/checkout@v4 # ▸ Regular PR path – download artefacts produced by the *build* job - name: "Download Talos image (regular PR)" if: "!contains(github.event.pull_request.labels.*.name, 'release')" uses: actions/download-artifact@v4 with: name: talos-image path: _out/assets - name: Download PR patch if: "!contains(github.event.pull_request.labels.*.name, 'release')" uses: actions/download-artifact@v4 with: name: pr-patch path: _out/assets - name: Apply patch if: "!contains(github.event.pull_request.labels.*.name, 'release')" run: | git apply _out/assets/pr.patch # ▸ Release PR path – fetch artefacts from the corresponding draft release - name: Download assets from draft release (release PR) if: contains(github.event.pull_request.labels.*.name, 'release') run: | mkdir -p _out/assets curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ -o _out/assets/nocloud-amd64.raw.xz \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.disk_id }}" env: GH_PAT: ${{ secrets.GH_PAT }} - name: Set sandbox ID run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV # ▸ Start actual job steps - name: Prepare workspace run: | rm -rf /tmp/$SANDBOX_NAME cp -r ${{ github.workspace }} /tmp/$SANDBOX_NAME - name: Prepare environment run: | cd /tmp/$SANDBOX_NAME attempt=0 until make SANDBOX_NAME=$SANDBOX_NAME prepare-env; do attempt=$((attempt + 1)) if [ $attempt -ge 3 ]; then echo "❌ Attempt $attempt failed, exiting..." exit 1 fi echo "❌ Attempt $attempt failed, retrying..." done echo "✅ The task completed successfully after $attempt attempts" install_cozystack: name: "Install Cozystack" runs-on: [self-hosted] permissions: contents: read packages: read needs: ["prepare_env", "resolve_assets"] if: ${{ always() && needs.prepare_env.result == 'success' }} steps: - name: Prepare _out/assets directory run: mkdir -p _out/assets # ▸ Regular PR path – download artefacts produced by the *build* job - name: "Download installer (regular PR)" if: "!contains(github.event.pull_request.labels.*.name, 'release')" uses: actions/download-artifact@v4 with: name: cozystack-installer path: _out/assets # ▸ Release PR path – fetch artefacts from the corresponding draft release - name: Download assets from draft release (release PR) if: contains(github.event.pull_request.labels.*.name, 'release') run: | mkdir -p _out/assets curl -sSL -H "Authorization: token ${GH_PAT}" -H "Accept: application/octet-stream" \ -o _out/assets/cozystack-installer.yaml \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/releases/assets/${{ needs.resolve_assets.outputs.installer_id }}" env: GH_PAT: ${{ secrets.GH_PAT }} # ▸ Start actual job steps - name: Set sandbox ID run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - name: Sync _out/assets directory run: | mkdir -p /tmp/$SANDBOX_NAME/_out/assets mv _out/assets/* /tmp/$SANDBOX_NAME/_out/assets/ - name: Install Cozystack into sandbox run: | cd /tmp/$SANDBOX_NAME attempt=0 until make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME install-cozystack; do attempt=$((attempt + 1)) if [ $attempt -ge 3 ]; then echo "❌ Attempt $attempt failed, exiting..." exit 1 fi echo "❌ Attempt $attempt failed, retrying..." done echo "✅ The task completed successfully after $attempt attempts." - name: Run OpenAPI tests run: | cd /tmp/$SANDBOX_NAME make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-openapi detect_test_matrix: name: "Detect e2e test matrix" runs-on: ubuntu-latest outputs: matrix: ${{ steps.set.outputs.matrix }} steps: - uses: actions/checkout@v4 - id: set run: | apps=$(ls hack/e2e-apps/*.bats | cut -f3 -d/ | cut -f1 -d. | jq -R | jq -cs) echo "matrix={\"app\":$apps}" >> "$GITHUB_OUTPUT" test_apps: strategy: fail-fast: false matrix: ${{ fromJson(needs.detect_test_matrix.outputs.matrix) }} name: Test ${{ matrix.app }} runs-on: [self-hosted] needs: [install_cozystack,detect_test_matrix] if: ${{ always() && (needs.install_cozystack.result == 'success' && needs.detect_test_matrix.result == 'success') }} steps: - name: Set sandbox ID run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - name: E2E Apps run: | cd /tmp/$SANDBOX_NAME attempt=0 until make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME test-apps-${{ matrix.app }}; do attempt=$((attempt + 1)) if [ $attempt -ge 3 ]; then echo "❌ Attempt $attempt failed, exiting..." exit 1 fi echo "❌ Attempt $attempt failed, retrying..." done echo "✅ The task completed successfully after $attempt attempts" collect_debug_information: name: Collect debug information runs-on: [self-hosted] needs: [test_apps] if: ${{ always() }} steps: - name: Checkout code uses: actions/checkout@v4 - name: Set sandbox ID run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - name: Collect report run: | cd /tmp/$SANDBOX_NAME make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-report - name: Upload cozyreport.tgz uses: actions/upload-artifact@v4 with: name: cozyreport path: /tmp/${{ env.SANDBOX_NAME }}/_out/cozyreport.tgz - name: Collect images list run: | cd /tmp/$SANDBOX_NAME make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME collect-images - name: Upload image list uses: actions/upload-artifact@v4 with: name: image-list path: /tmp/${{ env.SANDBOX_NAME }}/_out/images.txt cleanup: name: Tear down environment runs-on: [self-hosted] needs: [collect_debug_information] if: ${{ always() && needs.test_apps.result == 'success' }} steps: - name: Checkout code uses: actions/checkout@v4 with: fetch-depth: 0 fetch-tags: true - name: Set sandbox ID run: echo "SANDBOX_NAME=cozy-e2e-sandbox-$(echo "${GITHUB_REPOSITORY}:${GITHUB_WORKFLOW}:${GITHUB_REF}" | sha256sum | cut -c1-10)" >> $GITHUB_ENV - name: Tear down sandbox run: make -C packages/core/testing SANDBOX_NAME=$SANDBOX_NAME delete - name: Remove workspace run: rm -rf /tmp/$SANDBOX_NAME