name: CI on: pull_request: types: [opened, synchronize] push: branches: [master] workflow_dispatch: jobs: quality: runs-on: docker steps: - uses: actions/checkout@v4 - name: Set up environment run: make setup - name: Lint all run: | . .venv/bin/activate make lint-all - name: Unit tests with 100% coverage run: | . .venv/bin/activate make pytest-cov - name: Check unit test speed run: | . .venv/bin/activate python3 scripts/check_test_speed.py --max-seconds 10 - name: Documentation coverage check run: | . .venv/bin/activate PYTHONPATH=src python3 scripts/ci/doc_coverage.py release-dry-run: needs: [quality, detect-changes] if: needs.detect-changes.outputs.user-facing-changed == 'true' runs-on: docker steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up environment run: make setup - name: Install git-cliff run: | GIT_CLIFF_VERSION="2.13.0" URL="https://github.com/orhun/git-cliff/releases/download/v${GIT_CLIFF_VERSION}/git-cliff-${GIT_CLIFF_VERSION}-x86_64-unknown-linux-gnu.tar.gz" TMPDIR="$(mktemp -d)" curl -sL "$URL" | tar xz -C "$TMPDIR" mkdir -p "$HOME/.local/bin" mv "$TMPDIR/git-cliff-${GIT_CLIFF_VERSION}/git-cliff" "$HOME/.local/bin/git-cliff" chmod +x "$HOME/.local/bin/git-cliff" echo "$HOME/.local/bin" >> "$GITHUB_PATH" - name: Release dry-run validation run: | . .venv/bin/activate PYTHONPATH=. python3 scripts/ci/release.py --dry-run || true detect-changes: runs-on: docker outputs: ansible-changed: ${{ steps.detect.outputs.ansible-changed }} user-facing-changed: ${{ steps.detect.outputs.user-facing-changed }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Detect changed paths id: detect run: | if [ "${{ github.event_name }}" = "pull_request" ]; then BASE="origin/master" HEAD="${{ github.event.pull_request.head.sha }}" else BASE="HEAD~1" HEAD="HEAD" fi # Check if any Ansible-related files changed ANSIBLE_CHANGED=$(git diff --name-only "$BASE" "$HEAD" -- ansible/ .ansible-lint 2>/dev/null | head -1) if [ -n "$ANSIBLE_CHANGED" ]; then echo "ansible-changed=true" >> "$GITHUB_OUTPUT" echo "Ansible files changed — molecule tests will run." else echo "ansible-changed=false" >> "$GITHUB_OUTPUT" echo "No Ansible files changed — skipping molecule tests." fi # Check if any user-facing files changed (src/, ansible/, pyproject.toml) USER_FACING=$(git diff --name-only "$BASE" "$HEAD" -- src/gitea_runner_manager/ ansible/ pyproject.toml 2>/dev/null | head -1) if [ -n "$USER_FACING" ]; then echo "user-facing-changed=true" >> "$GITHUB_OUTPUT" echo "User-facing files changed — release dry-run will run." else echo "user-facing-changed=false" >> "$GITHUB_OUTPUT" echo "No user-facing files changed — skipping release dry-run." fi validate-merge: if: github.event_name == 'push' runs-on: docker steps: - uses: actions/checkout@v4 with: fetch-depth: 2 - name: Validate commit message format run: | set -euo pipefail MSG=$(git log -1 --pretty=%s) echo "Commit message: $MSG" # Allowed formats: # GRM-N : (squash-merge) # release: vX.Y.Z (release commits) # GRM-N : (#M) (squash-merge with PR ref) if echo "$MSG" | grep -qE '^GRM-[0-9]+ [a-z]+: .+'; then echo "OK: GRM-N format" elif echo "$MSG" | grep -qE '^release: v[0-9]+\.[0-9]+\.[0-9]+'; then echo "OK: release commit format" else echo "FAIL: commit message does not follow naming convention" echo "Expected: GRM-N : or release: vX.Y.Z" echo "Got: $MSG" exit 1 fi discover-runners: needs: [detect-changes] if: needs.detect-changes.outputs.ansible-changed == 'true' runs-on: docker outputs: runner-count: ${{ steps.discover.outputs.runner-count }} runner-indices: ${{ steps.discover.outputs.runner-indices }} steps: - uses: actions/checkout@v4 - name: Set up environment run: make setup - name: Discover available runners id: discover env: REPO_TOKEN: ${{ secrets.REPO_TOKEN }} MOLECULE_RUNNERS: ${{ vars.MOLECULE_RUNNERS }} PYTHONPATH: src run: | . .venv/bin/activate OUTPUT=$(python3 scripts/ci/discover_runners.py --owner "${{ github.repository_owner }}" --repo "${{ github.event.repository.name }}") echo "$OUTPUT" # Parse outputs RUNNER_COUNT=$(echo "$OUTPUT" | grep '^count=' | cut -d= -f2) RUNNER_INDICES=$(echo "$OUTPUT" | grep '^indices=' | cut -d= -f2) echo "runner-count=$RUNNER_COUNT" >> "$GITHUB_OUTPUT" echo "runner-indices=$RUNNER_INDICES" >> "$GITHUB_OUTPUT" molecule-tests: needs: [quality, detect-changes, discover-runners] if: needs.detect-changes.outputs.ansible-changed == 'true' runs-on: docker strategy: matrix: runner-index: ${{ fromJSON(needs.discover-runners.outputs.runner-indices) }} steps: - uses: actions/checkout@v4 - name: Set up environment run: make setup - name: Discover assigned test pairs run: | . .venv/bin/activate PAIRS=$(python3 scripts/ci/distribute_molecule.py --runner-index ${{ matrix.runner-index }} --max-runners ${{ needs.discover-runners.outputs.runner-count }}) echo "Assigned pairs: $PAIRS" echo "TEST_PAIRS=$PAIRS" >> $GITHUB_ENV - name: Run molecule tests run: | set -euo pipefail . .venv/bin/activate python3 scripts/ci/molecule_ci_guard.py $TEST_PAIRS env: GITEA_URL: ${{ github.server_url }} REPO_TOKEN: ${{ secrets.REPO_TOKEN }} RUN_ID: ${{ github.run_id }} JOB_NAME: ${{ github.job }} MATRIX_INDEX: ${{ matrix.runner-index }} GITEA_REPOSITORY: ${{ github.repository }} badges: needs: [quality] if: github.event_name == 'push' && github.ref == 'refs/heads/master' runs-on: docker steps: - uses: actions/checkout@v4 with: fetch-depth: 0 token: ${{ secrets.REPO_TOKEN }} - name: Set up environment run: make setup - name: Generate badge JSON files run: | . .venv/bin/activate python3 scripts/generate_badges.py --output-dir .badges/ - name: Push badges to badges branch run: | git config user.name "gitea-actions-bot" git config user.email "actions@oblachno.fyi" git checkout --orphan badges git rm -rf . cp -r .badges/* . git add *.json git commit -m "Update badges [skip ci]" git push origin badges --force