mirror of
https://github.com/outbackdingo/cozystack.git
synced 2026-01-27 10:18:39 +00:00
The purpose of this change is to: 1. Provide more information to the developer who goes to look at CI results. 2. Help CI workflows complete faster, taking into account that we often restart these workflows. Default strategy is `fail-fast: true`, so when one of app test fails, all running jobs are canceled. This way we don't know if they would succeed or not. And when we restart them, all progress is lost. Reference: * https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/run-job-variations#handling-failures * https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#jobsjob_idstrategyfail-fast Signed-off-by: Nick Volynkin <nick.volynkin@gmail.com>
354 lines
12 KiB
YAML
354 lines
12 KiB
YAML
name: Pull Request
|
||
|
||
env:
|
||
REGISTRY: ${{ secrets.OCIR_REPO }}
|
||
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: Login to GitHub Container Registry
|
||
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."
|
||
|
||
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
|
||
|
||
|