mirror of
https://github.com/ghndrx/github-actions-library.git
synced 2026-02-10 06:45:02 +00:00
feat: Add Python CI workflow with UV package manager
- Add setup-python-uv composite action for fast cached Python setup - Add python-ci.yml reusable workflow with: - Ruff linting and formatting - Pyright type checking - Matrix pytest with coverage enforcement - Bandit security scanning (SARIF upload) - Update README with comprehensive documentation - Based on 2025 best practices using astral-sh/setup-uv@v5
This commit is contained in:
71
.github/actions/setup-python-uv/action.yml
vendored
Normal file
71
.github/actions/setup-python-uv/action.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Composite action: Setup Python with UV package manager
|
||||||
|
# Fast, cached Python environment setup using Astral's UV
|
||||||
|
name: Setup Python with UV
|
||||||
|
description: Install UV, cache dependencies, and sync project
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
python-version:
|
||||||
|
description: 'Python version to install'
|
||||||
|
required: false
|
||||||
|
default: '3.12'
|
||||||
|
working-directory:
|
||||||
|
description: 'Directory containing pyproject.toml'
|
||||||
|
required: false
|
||||||
|
default: '.'
|
||||||
|
extras:
|
||||||
|
description: 'Extra dependency groups to install (comma-separated)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
dev:
|
||||||
|
description: 'Install dev dependencies'
|
||||||
|
required: false
|
||||||
|
default: 'true'
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
python-path:
|
||||||
|
description: 'Path to Python executable'
|
||||||
|
value: ${{ steps.setup.outputs.python-path }}
|
||||||
|
cache-hit:
|
||||||
|
description: 'Whether cache was hit'
|
||||||
|
value: ${{ steps.setup-uv.outputs.cache-hit }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Install UV
|
||||||
|
id: setup-uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
${{ inputs.working-directory }}/uv.lock
|
||||||
|
${{ inputs.working-directory }}/pyproject.toml
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
id: setup
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv python install ${{ inputs.python-version }}
|
||||||
|
echo "python-path=$(uv python find)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
SYNC_ARGS=""
|
||||||
|
|
||||||
|
# Add dev dependencies if requested
|
||||||
|
if [ "${{ inputs.dev }}" = "true" ]; then
|
||||||
|
SYNC_ARGS="$SYNC_ARGS --dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add extras if specified
|
||||||
|
if [ -n "${{ inputs.extras }}" ]; then
|
||||||
|
IFS=',' read -ra EXTRAS <<< "${{ inputs.extras }}"
|
||||||
|
for extra in "${EXTRAS[@]}"; do
|
||||||
|
SYNC_ARGS="$SYNC_ARGS --extra $(echo $extra | xargs)"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
uv sync $SYNC_ARGS
|
||||||
159
.github/workflows/python-ci.yml
vendored
Normal file
159
.github/workflows/python-ci.yml
vendored
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
# Reusable workflow: Python CI with UV
|
||||||
|
# Provides linting, type-checking, testing, and security scanning
|
||||||
|
name: Python CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_call:
|
||||||
|
inputs:
|
||||||
|
python-versions:
|
||||||
|
description: 'JSON array of Python versions to test'
|
||||||
|
type: string
|
||||||
|
default: '["3.12"]'
|
||||||
|
working-directory:
|
||||||
|
description: 'Directory containing the Python project'
|
||||||
|
type: string
|
||||||
|
default: '.'
|
||||||
|
run-lint:
|
||||||
|
description: 'Run Ruff linter'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
run-typecheck:
|
||||||
|
description: 'Run type checking with Pyright'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
run-tests:
|
||||||
|
description: 'Run pytest'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
run-security:
|
||||||
|
description: 'Run Bandit security scanner'
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
test-command:
|
||||||
|
description: 'Custom test command (default: pytest)'
|
||||||
|
type: string
|
||||||
|
default: 'pytest --cov --cov-report=xml'
|
||||||
|
coverage-threshold:
|
||||||
|
description: 'Minimum coverage percentage (0 to disable)'
|
||||||
|
type: number
|
||||||
|
default: 0
|
||||||
|
extras:
|
||||||
|
description: 'Extra dependency groups to install'
|
||||||
|
type: string
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
if: ${{ inputs.run-lint }}
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python with UV
|
||||||
|
uses: ./.github/actions/setup-python-uv
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
extras: ${{ inputs.extras }}
|
||||||
|
|
||||||
|
- name: Run Ruff linter
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv run ruff check . --output-format=github
|
||||||
|
|
||||||
|
- name: Run Ruff formatter check
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv run ruff format . --check --diff
|
||||||
|
|
||||||
|
typecheck:
|
||||||
|
if: ${{ inputs.run-typecheck }}
|
||||||
|
name: Type Check
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python with UV
|
||||||
|
uses: ./.github/actions/setup-python-uv
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
extras: ${{ inputs.extras }}
|
||||||
|
|
||||||
|
- name: Run Pyright
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv run pyright
|
||||||
|
|
||||||
|
test:
|
||||||
|
if: ${{ inputs.run-tests }}
|
||||||
|
name: Test (Python ${{ matrix.python-version }})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
python-version: ${{ fromJson(inputs.python-versions) }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python with UV
|
||||||
|
uses: ./.github/actions/setup-python-uv
|
||||||
|
with:
|
||||||
|
python-version: ${{ matrix.python-version }}
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
extras: ${{ inputs.extras }}
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv run ${{ inputs.test-command }}
|
||||||
|
|
||||||
|
- name: Check coverage threshold
|
||||||
|
if: ${{ inputs.coverage-threshold > 0 }}
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
COVERAGE=$(uv run coverage report --format=total 2>/dev/null || echo "0")
|
||||||
|
if [ "$COVERAGE" -lt "${{ inputs.coverage-threshold }}" ]; then
|
||||||
|
echo "::error::Coverage ${COVERAGE}% is below threshold ${{ inputs.coverage-threshold }}%"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Coverage: ${COVERAGE}% (threshold: ${{ inputs.coverage-threshold }}%)"
|
||||||
|
|
||||||
|
- name: Upload coverage
|
||||||
|
if: ${{ matrix.python-version == '3.12' }}
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
file: ${{ inputs.working-directory }}/coverage.xml
|
||||||
|
fail_ci_if_error: false
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
security:
|
||||||
|
if: ${{ inputs.run-security }}
|
||||||
|
name: Security Scan
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Setup Python with UV
|
||||||
|
uses: ./.github/actions/setup-python-uv
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
dev: 'true'
|
||||||
|
|
||||||
|
- name: Run Bandit security scanner
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv run bandit -r . -x ./tests -f sarif -o bandit-results.sarif || true
|
||||||
|
|
||||||
|
- name: Upload SARIF results
|
||||||
|
uses: github/codeql-action/upload-sarif@v3
|
||||||
|
with:
|
||||||
|
sarif_file: ${{ inputs.working-directory }}/bandit-results.sarif
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Check for high-severity issues
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv run bandit -r . -x ./tests -ll -f txt
|
||||||
113
README.md
113
README.md
@@ -7,43 +7,104 @@ Reusable GitHub Actions workflows and composite actions for CI/CD pipelines.
|
|||||||
|
|
||||||
## Workflows
|
## Workflows
|
||||||
|
|
||||||
```
|
| Workflow | Description |
|
||||||
.github/workflows/
|
|----------|-------------|
|
||||||
├── docker-build.yml # Build, scan, and push Docker images
|
| [`python-ci.yml`](.github/workflows/python-ci.yml) | Python CI with UV (lint, type-check, test, security) |
|
||||||
├── terraform-plan.yml # Terraform plan with cost estimation
|
|
||||||
├── k8s-deploy.yml # Kubernetes deployment with ArgoCD
|
|
||||||
├── security-scan.yml # SAST, DAST, dependency scanning
|
|
||||||
└── release.yml # Semantic release automation
|
|
||||||
```
|
|
||||||
|
|
||||||
## Composite Actions
|
## Composite Actions
|
||||||
|
|
||||||
```
|
| Action | Description |
|
||||||
actions/
|
|--------|-------------|
|
||||||
├── docker-build/ # Multi-arch Docker build
|
| [`setup-python-uv`](actions/setup-python-uv) | Fast Python setup with UV package manager |
|
||||||
├── terraform-plan/ # Terraform plan with PR comments
|
|
||||||
├── k8s-deploy/ # Kubernetes deployment
|
|
||||||
└── security-scan/ # Trivy, Grype, CodeQL
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
## Quick Start
|
||||||
|
|
||||||
|
### Python CI
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
# .github/workflows/ci.yml
|
||||||
|
name: CI
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
ci:
|
||||||
uses: ghndrx/github-actions-library/.github/workflows/docker-build.yml@main
|
uses: ghndrx/github-actions-library/.github/workflows/python-ci.yml@main
|
||||||
with:
|
with:
|
||||||
image-name: myapp
|
python-versions: '["3.11", "3.12", "3.13"]'
|
||||||
secrets: inherit
|
run-typecheck: true
|
||||||
|
coverage-threshold: 80
|
||||||
```
|
```
|
||||||
|
|
||||||
## Features
|
### Setup Python with UV (Composite Action)
|
||||||
|
|
||||||
- ✅ Reusable workflows (DRY)
|
```yaml
|
||||||
- ✅ Matrix builds
|
steps:
|
||||||
- ✅ Security scanning built-in
|
- uses: actions/checkout@v4
|
||||||
- ✅ Caching optimization
|
|
||||||
- ✅ OIDC authentication (no long-lived secrets)
|
- uses: ghndrx/github-actions-library/actions/setup-python-uv@main
|
||||||
|
with:
|
||||||
|
python-version: '3.12'
|
||||||
|
extras: 'dev,test'
|
||||||
|
|
||||||
|
- run: uv run pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Python CI Workflow Features
|
||||||
|
|
||||||
|
The `python-ci.yml` reusable workflow provides:
|
||||||
|
|
||||||
|
- **Ruff linting** - Fast Python linter with auto-fix suggestions
|
||||||
|
- **Pyright type checking** - Strict type validation
|
||||||
|
- **Matrix testing** - Test across multiple Python versions
|
||||||
|
- **Coverage enforcement** - Fail if coverage drops below threshold
|
||||||
|
- **Bandit security scanning** - Detect security vulnerabilities
|
||||||
|
- **UV caching** - 10-100x faster than pip installs
|
||||||
|
|
||||||
|
### Inputs
|
||||||
|
|
||||||
|
| Input | Type | Default | Description |
|
||||||
|
|-------|------|---------|-------------|
|
||||||
|
| `python-versions` | string | `'["3.12"]'` | JSON array of Python versions |
|
||||||
|
| `working-directory` | string | `.` | Project directory |
|
||||||
|
| `run-lint` | boolean | `true` | Run Ruff linter |
|
||||||
|
| `run-typecheck` | boolean | `true` | Run Pyright |
|
||||||
|
| `run-tests` | boolean | `true` | Run pytest |
|
||||||
|
| `run-security` | boolean | `true` | Run Bandit scanner |
|
||||||
|
| `test-command` | string | `pytest --cov --cov-report=xml` | Custom test command |
|
||||||
|
| `coverage-threshold` | number | `0` | Min coverage % (0 to disable) |
|
||||||
|
| `extras` | string | `''` | Extra dependency groups |
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Projects using the Python CI workflow should have:
|
||||||
|
|
||||||
|
- `pyproject.toml` with UV-compatible configuration
|
||||||
|
- Dev dependencies: `ruff`, `pyright`, `pytest`, `pytest-cov`, `bandit`
|
||||||
|
|
||||||
|
Example `pyproject.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[project]
|
||||||
|
name = "myproject"
|
||||||
|
requires-python = ">=3.11"
|
||||||
|
|
||||||
|
[tool.uv]
|
||||||
|
dev-dependencies = [
|
||||||
|
"ruff>=0.8",
|
||||||
|
"pyright>=1.1",
|
||||||
|
"pytest>=8.0",
|
||||||
|
"pytest-cov>=6.0",
|
||||||
|
"bandit>=1.8",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff]
|
||||||
|
line-length = 100
|
||||||
|
target-version = "py311"
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
pythonVersion = "3.12"
|
||||||
|
typeCheckingMode = "standard"
|
||||||
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
71
actions/setup-python-uv/action.yml
Normal file
71
actions/setup-python-uv/action.yml
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
# Composite action: Setup Python with UV package manager
|
||||||
|
# Fast, cached Python environment setup using Astral's UV
|
||||||
|
name: Setup Python with UV
|
||||||
|
description: Install UV, cache dependencies, and sync project
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
python-version:
|
||||||
|
description: 'Python version to install'
|
||||||
|
required: false
|
||||||
|
default: '3.12'
|
||||||
|
working-directory:
|
||||||
|
description: 'Directory containing pyproject.toml'
|
||||||
|
required: false
|
||||||
|
default: '.'
|
||||||
|
extras:
|
||||||
|
description: 'Extra dependency groups to install (comma-separated)'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
dev:
|
||||||
|
description: 'Install dev dependencies'
|
||||||
|
required: false
|
||||||
|
default: 'true'
|
||||||
|
|
||||||
|
outputs:
|
||||||
|
python-path:
|
||||||
|
description: 'Path to Python executable'
|
||||||
|
value: ${{ steps.setup.outputs.python-path }}
|
||||||
|
cache-hit:
|
||||||
|
description: 'Whether cache was hit'
|
||||||
|
value: ${{ steps.setup-uv.outputs.cache-hit }}
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: composite
|
||||||
|
steps:
|
||||||
|
- name: Install UV
|
||||||
|
id: setup-uv
|
||||||
|
uses: astral-sh/setup-uv@v5
|
||||||
|
with:
|
||||||
|
enable-cache: true
|
||||||
|
cache-dependency-glob: |
|
||||||
|
${{ inputs.working-directory }}/uv.lock
|
||||||
|
${{ inputs.working-directory }}/pyproject.toml
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
id: setup
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
uv python install ${{ inputs.python-version }}
|
||||||
|
echo "python-path=$(uv python find)" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
shell: bash
|
||||||
|
working-directory: ${{ inputs.working-directory }}
|
||||||
|
run: |
|
||||||
|
SYNC_ARGS=""
|
||||||
|
|
||||||
|
# Add dev dependencies if requested
|
||||||
|
if [ "${{ inputs.dev }}" = "true" ]; then
|
||||||
|
SYNC_ARGS="$SYNC_ARGS --dev"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Add extras if specified
|
||||||
|
if [ -n "${{ inputs.extras }}" ]; then
|
||||||
|
IFS=',' read -ra EXTRAS <<< "${{ inputs.extras }}"
|
||||||
|
for extra in "${EXTRAS[@]}"; do
|
||||||
|
SYNC_ARGS="$SYNC_ARGS --extra $(echo $extra | xargs)"
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
uv sync $SYNC_ARGS
|
||||||
Reference in New Issue
Block a user