mirror of
https://github.com/MarijnDoeve/TijdVoorDeTest.git
synced 2026-07-05 23:20:18 +02:00
5ea7a636b8
- Skip the dev image build job on tag pushes — it was wasted work since quality and tests are already skipped on tags - Remove the unnecessary `needs: build` from verify-prior-run; it ran independently of the dev image anyway - Make verify-prior-run poll (30s interval, 15 min max) so tagging immediately after a push to main waits for the CI run to finish rather than failing instantly - Replace the yes/no outcomes string in "Assert all checks passed" with per-step ::error:: annotations so GitHub highlights exactly which quality check failed
284 lines
10 KiB
YAML
284 lines
10 KiB
YAML
name: CI
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
tags:
|
|
- '*'
|
|
pull_request: ~
|
|
workflow_dispatch: ~
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
|
|
cancel-in-progress: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build:
|
|
name: Build Dev Image
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
if: "!startsWith(github.ref, 'refs/tags/')"
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
persist-credentials: false
|
|
- name: Lint Dockerfile
|
|
uses: hadolint/hadolint-action@2332a7b74a6de0dda2e2221d575162eba76ba5e5 # v3.3.0
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
|
- name: Build Docker images
|
|
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7
|
|
with:
|
|
pull: true
|
|
files: |
|
|
compose.yaml
|
|
compose.override.yaml
|
|
set: |
|
|
*.cache-from=type=gha,scope=${{github.ref}}-devbuild
|
|
*.cache-from=type=gha,scope=refs/heads/main-devbuild
|
|
*.cache-to=type=gha,scope=${{github.ref}}-devbuild,mode=${{ github.event_name == 'pull_request' && 'min' || 'max' }}
|
|
|
|
quality:
|
|
name: Code Quality
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
needs: build
|
|
if: "!startsWith(github.ref, 'refs/tags/')"
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
|
- name: Load Docker images
|
|
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7
|
|
with:
|
|
load: true
|
|
files: |
|
|
compose.yaml
|
|
compose.override.yaml
|
|
set: |
|
|
*.cache-from=type=gha,scope=${{github.ref}}-devbuild
|
|
- name: Start services
|
|
run: docker compose up php database --wait --no-build
|
|
- name: Warm up dev cache
|
|
run: docker compose exec -T php bin/console cache:warmup --env=dev
|
|
- name: Lint Twig templates
|
|
id: twig_lint
|
|
continue-on-error: true
|
|
run: docker compose exec -T php bin/console lint:twig --format=github templates
|
|
- name: Coding Style
|
|
id: cs
|
|
continue-on-error: true
|
|
run: docker compose exec -T php vendor/bin/php-cs-fixer check --diff --show-progress=none
|
|
- name: Twig Coding Style
|
|
id: twig_cs
|
|
continue-on-error: true
|
|
run: docker compose exec -T php vendor/bin/twig-cs-fixer check
|
|
- name: Static Analysis (PHPStan)
|
|
id: phpstan
|
|
continue-on-error: true
|
|
run: docker compose exec -T php vendor/bin/phpstan analyse --no-progress --no-ansi --error-format=github
|
|
- name: Rector
|
|
id: rector
|
|
continue-on-error: true
|
|
run: docker compose exec -T php vendor/bin/rector process --dry-run --no-progress-bar --output-format=github
|
|
- name: Check HTTP reachability
|
|
run: curl -v --fail-with-body http://localhost
|
|
- name: Assert all checks passed
|
|
if: always()
|
|
run: |
|
|
failed=0
|
|
check() {
|
|
local name="$1" outcome="$2"
|
|
if [[ "$outcome" == "failure" ]]; then
|
|
echo "::error::$name failed"
|
|
failed=1
|
|
fi
|
|
}
|
|
check "Twig Lint" "${{ steps.twig_lint.outcome }}"
|
|
check "Coding Style" "${{ steps.cs.outcome }}"
|
|
check "Twig Coding Style" "${{ steps.twig_cs.outcome }}"
|
|
check "PHPStan" "${{ steps.phpstan.outcome }}"
|
|
check "Rector" "${{ steps.rector.outcome }}"
|
|
exit $failed
|
|
|
|
tests:
|
|
name: Tests
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
needs: build
|
|
if: "!startsWith(github.ref, 'refs/tags/')"
|
|
permissions:
|
|
checks: write
|
|
pull-requests: write
|
|
contents: read
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
|
- name: Load Docker images
|
|
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7
|
|
with:
|
|
load: true
|
|
files: |
|
|
compose.yaml
|
|
compose.override.yaml
|
|
set: |
|
|
*.cache-from=type=gha,scope=${{github.ref}}-devbuild
|
|
- name: Start services
|
|
run: docker compose up php database --wait --no-build
|
|
- name: Create test database
|
|
run: docker compose exec -T php bin/console -e test doctrine:database:create
|
|
- name: Run migrations
|
|
run: docker compose exec -T php bin/console -e test doctrine:migrations:migrate --no-interaction
|
|
- name: Load fixtures
|
|
run: docker compose exec -T php bin/console -e test doctrine:fixtures:load --no-interaction --group=test
|
|
- name: Run PHPUnit
|
|
run: docker compose exec -T php vendor/bin/phpunit --log-junit var/phpunit/junit.xml
|
|
- name: Publish PHPUnit test results
|
|
if: always()
|
|
uses: mikepenz/action-junit-report@d9f48fc87bc235f7e214acf696ca5abc0a986f16 # v6
|
|
with:
|
|
report_paths: var/phpunit/junit.xml
|
|
check_name: PHPUnit
|
|
- name: Doctrine Schema Validator
|
|
run: docker compose exec -T php bin/console -e test doctrine:schema:validate
|
|
|
|
verify-prior-run:
|
|
name: Verify Prior CI Run
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
if: startsWith(github.ref, 'refs/tags/')
|
|
permissions:
|
|
actions: read
|
|
steps:
|
|
- name: Wait for and verify successful CI run on this commit
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
run: |
|
|
max_attempts=30
|
|
attempt=0
|
|
while [[ $attempt -lt $max_attempts ]]; do
|
|
attempt=$((attempt + 1))
|
|
|
|
success_count=$(gh api \
|
|
"repos/${{ github.repository }}/actions/workflows/ci.yml/runs?head_sha=${{ github.sha }}&status=success&per_page=5" \
|
|
--jq "[.workflow_runs[] | select(.id != ${{ github.run_id }})] | length")
|
|
|
|
if [[ "$success_count" -gt 0 ]]; then
|
|
echo "Found $success_count prior successful CI run(s) for ${{ github.sha }}."
|
|
exit 0
|
|
fi
|
|
|
|
in_progress_count=$(gh api \
|
|
"repos/${{ github.repository }}/actions/workflows/ci.yml/runs?head_sha=${{ github.sha }}&per_page=10" \
|
|
--jq "[.workflow_runs[] | select(.id != ${{ github.run_id }}) | select(.status == \"in_progress\" or .status == \"queued\" or .status == \"waiting\" or .status == \"requested\" or .status == \"pending\")] | length")
|
|
|
|
if [[ "$in_progress_count" -gt 0 ]]; then
|
|
echo "CI still in progress (attempt $attempt/$max_attempts), waiting 30s..."
|
|
sleep 30
|
|
else
|
|
echo "::error::No prior successful CI run found for ${{ github.sha }}. Only tag commits that have passed CI on main."
|
|
exit 1
|
|
fi
|
|
done
|
|
|
|
echo "::error::Timed out waiting for CI run to complete for ${{ github.sha }}."
|
|
exit 1
|
|
|
|
build-deploy:
|
|
name: Build and Deploy
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
environment:
|
|
name: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || 'acceptance' }}
|
|
url: ${{ vars.URL }}
|
|
needs: [quality, tests, verify-prior-run]
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
if: >-
|
|
always() && !cancelled() && !failure() &&
|
|
((github.ref == 'refs/heads/main' && false) || startsWith(github.ref, 'refs/tags/'))
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
|
- name: Log in to GitHub Container Registry
|
|
uses: docker/login-action@c99871dec2022cc055c062a10cc1a1310835ceb4 # v4
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Extract metadata
|
|
id: meta
|
|
run: |
|
|
REPO_LOWER=$(echo "${{ github.repository }}" | tr '[:upper:]' '[:lower:]')
|
|
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
|
|
TAG="${GITHUB_REF#refs/tags/}"
|
|
SENTRY_VERSION="${TAG#v}"
|
|
{
|
|
echo "tag=$TAG"
|
|
echo "sentry_version=$SENTRY_VERSION"
|
|
echo "full_name=ghcr.io/${REPO_LOWER}:$TAG"
|
|
} >> "$GITHUB_OUTPUT"
|
|
else
|
|
SHORT_SHA=$(git rev-parse --short HEAD)
|
|
{
|
|
echo "tag=$SHORT_SHA"
|
|
echo "sentry_version=$SHORT_SHA"
|
|
echo "full_name=ghcr.io/${REPO_LOWER}:$SHORT_SHA"
|
|
} >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Build and Push Docker images
|
|
uses: docker/bake-action@d3418bd7d0e9324001bca92fa8ba175ea7e6dc9b # v7
|
|
with:
|
|
pull: true
|
|
push: true
|
|
files: |
|
|
compose.yaml
|
|
compose.build.yaml
|
|
set: |
|
|
*.cache-from=type=gha,scope=${{github.ref}}
|
|
*.cache-from=type=gha,scope=refs/heads/main
|
|
*.cache-to=type=gha,scope=${{github.ref}},mode=max
|
|
*.tags=${{ steps.meta.outputs.full_name }}
|
|
|
|
- name: Create Sentry release
|
|
uses: getsentry/action-release@ff07929a6537bac57790c3451cf4d364aca38528 # v3
|
|
env:
|
|
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
|
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
|
with:
|
|
release: ${{steps.meta.outputs.sentry_version}}
|
|
environment: ${{ startsWith(github.ref, 'refs/tags/') && 'production' || 'acceptance' }}
|
|
|
|
- name: Trigger Portainer Deployment
|
|
shell: bash
|
|
env:
|
|
PORTAINER_WEBHOOK: ${{secrets.PORTAINER_WEBHOOK}}
|
|
IMAGE_TAG: ${{steps.meta.outputs.tag}}
|
|
SENTRY_RELEASE: ${{steps.meta.outputs.sentry_version}}
|
|
run: |
|
|
curl -v -X POST "${PORTAINER_WEBHOOK}?IMAGE_TAG=${IMAGE_TAG}&SENTRY_RELEASE=${SENTRY_RELEASE}" --fail-with-body
|