mirror of
https://github.com/Sea-Haven-Industries/.github.git
synced 2026-09-30 07:03:11 +00:00
Some checks failed
ci / ci / ci (push) Has been cancelled
Add job-level concurrency (cancel-in-progress) to all four ci-python-app jobs, matching the ci-python-sam idiom. Per-job group keys include github.job so the parallel jobs in a single run do not share a group. Replace sam-deploy starter-template stack-name: $default-branch (which GitHub substitutes to the literal branch name main) with a REPLACE-ME-stack-name placeholder, and point cfn-role-arn at the real shared github-cfn-execution-role.
162 lines
5.4 KiB
YAML
162 lines
5.4 KiB
YAML
name: CI — Python (app)
|
|
|
|
# Reusable CI for plain Python apps / locally-run tooling that do NOT deploy via
|
|
# SAM or CDK (use ci-python-sam.yaml / ci-typescript-cdk.yaml for those). Beyond
|
|
# lint + format it adds two things such repos commonly need:
|
|
# * a collect-only import check for a root suite whose live run needs secrets
|
|
# (verifies every test module imports cleanly without running them), and
|
|
# * an isolated full pytest run for a self-contained subproject dir whose tests
|
|
# package collides with the root tests/ package (e.g. a `tests/` under a
|
|
# subdir) and so must run in its own working directory.
|
|
#
|
|
# Naming is load-bearing (see this repo's ci.yaml): the org ruleset matches the
|
|
# required `ci / ci` check against the JOB check-run name. A caller job keyed `ci`
|
|
# invoking this workflow reports each job here as `ci / <job>`, so the aggregator
|
|
# job below is keyed `ci` to emit exactly `ci / ci`. The aggregator gates on every
|
|
# other job, so the single required check fails if any sub-job fails.
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
python-version:
|
|
description: "Python version to use"
|
|
type: string
|
|
default: "3.12"
|
|
source-dirs:
|
|
description: "Space-separated directories for ruff (default: repo root)"
|
|
type: string
|
|
default: "."
|
|
requirements:
|
|
description: "Requirements file used for the pip cache key + install"
|
|
type: string
|
|
default: "requirements.txt"
|
|
collect-only:
|
|
description: "Run 'pytest --collect-only' at the repo root (imports resolve without secrets)"
|
|
type: boolean
|
|
default: true
|
|
subproject-dir:
|
|
description: "Optional self-contained subproject dir whose pytest suite runs in full"
|
|
type: string
|
|
default: ""
|
|
run-conventions-check:
|
|
description: "Run the lightweight conventions audit (README + .gitignore covers .env)"
|
|
type: boolean
|
|
default: true
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
concurrency:
|
|
group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-python@v6
|
|
with:
|
|
python-version: ${{ inputs.python-version }}
|
|
|
|
- name: Install ruff
|
|
run: pip install ruff
|
|
|
|
- name: Ruff check
|
|
run: ruff check ${{ inputs.source-dirs }}
|
|
|
|
- name: Ruff format check
|
|
run: ruff format --check ${{ inputs.source-dirs }}
|
|
|
|
- name: Conventions check
|
|
if: ${{ inputs.run-conventions-check }}
|
|
run: |
|
|
errors=0
|
|
fail() { echo "::error::$1"; errors=$((errors + 1)); }
|
|
|
|
# README must exist
|
|
if [[ ! -f README.md ]]; then
|
|
fail "Missing README.md"
|
|
fi
|
|
|
|
# .gitignore must cover .env
|
|
if [[ -f .gitignore ]]; then
|
|
if ! grep -qE '^\.env$|^\.env\b' .gitignore; then
|
|
fail ".gitignore does not include .env"
|
|
fi
|
|
else
|
|
fail "Missing .gitignore"
|
|
fi
|
|
|
|
if [[ $errors -gt 0 ]]; then
|
|
echo "Conventions check failed with $errors error(s)."
|
|
exit 1
|
|
fi
|
|
echo "Conventions check passed."
|
|
|
|
test-collect:
|
|
if: ${{ inputs.collect-only }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
concurrency:
|
|
group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-python@v6
|
|
with:
|
|
python-version: ${{ inputs.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: ${{ inputs.requirements }}
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
pip install -r "${{ inputs.requirements }}"
|
|
pip install pytest python-dotenv
|
|
|
|
- name: Pytest collect-only
|
|
run: pytest --collect-only -q
|
|
|
|
subproject-tests:
|
|
if: ${{ inputs.subproject-dir != '' }}
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
concurrency:
|
|
group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
|
|
- uses: actions/setup-python@v6
|
|
with:
|
|
python-version: ${{ inputs.python-version }}
|
|
cache: pip
|
|
cache-dependency-path: ${{ inputs.requirements }}
|
|
|
|
- name: Install dependencies
|
|
run: |
|
|
pip install -r "${{ inputs.requirements }}"
|
|
pip install pytest
|
|
|
|
- name: Run subproject suite
|
|
working-directory: ${{ inputs.subproject-dir }}
|
|
run: python -m pytest -q
|
|
|
|
ci:
|
|
# Aggregator — keyed `ci` so a caller job keyed `ci` reports `ci / ci`.
|
|
needs: [lint, test-collect, subproject-tests]
|
|
if: always()
|
|
runs-on: ubuntu-latest
|
|
concurrency:
|
|
group: ci-python-app-${{ github.workflow }}-${{ github.ref }}-${{ inputs.source-dirs }}-${{ github.job }}
|
|
cancel-in-progress: true
|
|
steps:
|
|
- name: Require all jobs to have succeeded
|
|
run: |
|
|
if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" = "true" ]; then
|
|
echo "A required CI job failed or was cancelled."
|
|
exit 1
|
|
fi
|
|
echo "All CI jobs passed."
|