Skip to content

Change all references of master to main #15

Change all references of master to main

Change all references of master to main #15

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
POETRY_VERSION: "2.3.0"
POETRY_VIRTUALENVS_IN_PROJECT: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
# Only run on main branch pushes and PRs to main
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Load cached Poetry installation
id: cached-poetry
uses: actions/cache@v4
with:
path: ~/.local
key: poetry-${{ env.POETRY_VERSION }}-${{ runner.os }}
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Load cached venv
id: cached-venv
uses: actions/cache@v4
with:
path: .venv
key: venv-lint-${{ runner.os }}-py3.12-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-lint-${{ runner.os }}-py3.12-
- name: Install dependencies
if: steps.cached-venv.outputs.cache-hit != 'true'
run: poetry install --only dev --no-interaction
- name: Run Ruff linter
run: poetry run ruff check .
- name: Run Ruff formatter check
run: poetry run ruff format --check .
test:
name: Test
runs-on: ubuntu-latest
# Only run on main branch pushes and PRs to main
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Load cached Poetry installation
id: cached-poetry
uses: actions/cache@v4
with:
path: ~/.local
key: poetry-${{ env.POETRY_VERSION }}-${{ runner.os }}
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Load cached venv
id: cached-venv
uses: actions/cache@v4
with:
path: .venv
key: venv-test-${{ runner.os }}-py3.12-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-test-${{ runner.os }}-py3.12-
- name: Install dependencies
if: steps.cached-venv.outputs.cache-hit != 'true'
run: poetry install --no-interaction
- name: Run tests with coverage
working-directory: src
run: |
poetry run pytest \
--cov=. \
--cov-report=xml \
--cov-report=term-missing \
-v \
--tb=short
env:
DJANGO_ENV: testing
ENVIRONMENT: TEST
SECRET_KEY: test-secret-key-for-ci
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
with:
files: ./src/coverage.xml
fail_ci_if_error: false
verbose: true
security:
name: Security Scan
runs-on: ubuntu-latest
# Only run on main branch pushes and PRs to main
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Load cached Poetry installation
id: cached-poetry
uses: actions/cache@v4
with:
path: ~/.local
key: poetry-${{ env.POETRY_VERSION }}-${{ runner.os }}
- name: Install Poetry
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
with:
version: ${{ env.POETRY_VERSION }}
virtualenvs-create: true
virtualenvs-in-project: true
- name: Load cached venv
id: cached-venv
uses: actions/cache@v4
with:
path: .venv
key: venv-security-${{ runner.os }}-py3.12-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-security-${{ runner.os }}-py3.12-
- name: Install dependencies
if: steps.cached-venv.outputs.cache-hit != 'true'
run: poetry install --no-interaction
- name: Run Bandit security linter
run: poetry run bandit -r src --skip B101 --severity-level high -f json -o bandit-report.json || true
- name: Display Bandit results
run: poetry run bandit -r src --skip B101 --severity-level high -f txt || true
docker-build-push:
name: Build and Push Docker Image
runs-on: ubuntu-24.04-arm
# Run on push to main (build+push) and on PRs (build only)
if: github.event_name == 'push' || github.event_name == 'pull_request'
# For main/PR, wait for CI checks to pass
needs: [ci-success]
permissions:
id-token: write # Required for OIDC authentication
contents: read # Required to checkout code
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Determine push eligibility
id: can-push
run: |
if [ "${{ github.event_name }}" == "push" ]; then
echo "push=true" >> $GITHUB_OUTPUT
elif [ "${{ github.event.pull_request.head.repo.full_name }}" == "${{ github.repository }}" ]; then
echo "push=true" >> $GITHUB_OUTPUT
else
echo "push=false" >> $GITHUB_OUTPUT
fi
- name: Debug OIDC claims
if: steps.can-push.outputs.push == 'true'
run: |
echo "repo=${{ github.repository }}"
echo "ref=${{ github.ref }}"
echo "event=${{ github.event_name }}"
echo "head=${{ github.event.pull_request.head.repo.full_name }}"
if [ -z "$ACTIONS_ID_TOKEN_REQUEST_URL" ] || [ -z "$ACTIONS_ID_TOKEN_REQUEST_TOKEN" ]; then
echo "OIDC env missing"
exit 0
fi
token_json=$(curl -sS -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com" || true)
if [ -z "$token_json" ] || [ "$token_json" = "null" ]; then
echo "OIDC token missing"
exit 0
fi
OIDC_TOKEN_JSON="$token_json" python - <<'PY'
import base64,json,os,sys
token_json = os.environ.get("OIDC_TOKEN_JSON","")
if not token_json:
print("OIDC token missing")
sys.exit(0)
token = json.loads(token_json).get("value","")
if not token:
print("OIDC token missing")
sys.exit(0)
payload = token.split(".")[1]
payload += "=" * ((4 - len(payload) % 4) % 4)
data = json.loads(base64.urlsafe_b64decode(payload))
print(f"oidc.aud={data.get('aud')}")
print(f"oidc.sub={data.get('sub')}")
PY
- name: Determine Docker tag
id: docker-tag
run: |
if [ "${{ github.ref }}" == "refs/heads/main" ]; then
echo "image=633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end:prod" >> $GITHUB_OUTPUT
echo "environment=Production" >> $GITHUB_OUTPUT
else
echo "image=633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end:staging" >> $GITHUB_OUTPUT
echo "environment=Staging" >> $GITHUB_OUTPUT
fi
echo "Building for ${{ steps.docker-tag.outputs.environment }} with image: ${{ steps.docker-tag.outputs.image }}"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
with:
platforms: linux/arm64
- name: Configure AWS credentials
if: steps.can-push.outputs.push == 'true'
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
role-session-name: GitHubActions-DockerBuild-${{ steps.docker-tag.outputs.environment }}
aws-region: us-east-2
- name: Login to Amazon ECR
id: login-ecr
if: steps.can-push.outputs.push == 'true'
uses: aws-actions/amazon-ecr-login@v2
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
target: runtime
platforms: linux/arm64
push: ${{ steps.can-push.outputs.push == 'true' }}
tags: |
${{ steps.docker-tag.outputs.image }}
provenance: false
cache-from: |
type=gha,scope=arm64
type=registry,ref=633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end-cache:latest
cache-to: |
type=gha,mode=max,scope=arm64
type=registry,ref=633607774026.dkr.ecr.us-east-2.amazonaws.com/back-end-cache:latest,mode=max
- name: Output image URI
if: steps.can-push.outputs.push == 'true'
run: |
echo "Successfully pushed ${{ steps.docker-tag.outputs.environment }} image:"
echo "${{ steps.docker-tag.outputs.image }}"
# Final status check for branch protection
ci-success:
name: CI Success
needs: [lint, test, security]
runs-on: ubuntu-latest
# Always run to satisfy docker-build-push dependency
if: always()
steps:
- name: Check all jobs passed (main/PR only)
if: github.event_name == 'pull_request' || github.ref == 'refs/heads/main'
run: |
# Check if jobs were skipped (non-main) or failed
if [[ "${{ needs.lint.result }}" == "skipped" ]]; then
echo "Lint job was skipped - this should not happen on main/PR"
exit 1
fi
if [[ "${{ needs.lint.result }}" != "success" ]]; then
echo "Lint job failed"
exit 1
fi
if [[ "${{ needs.test.result }}" == "skipped" ]]; then
echo "Test job was skipped - this should not happen on main/PR"
exit 1
fi
if [[ "${{ needs.test.result }}" != "success" ]]; then
echo "Test job failed"
exit 1
fi
# Security is informational, doesn't fail CI
echo "All required jobs passed!"
- name: Pass through for non-main branches
if: github.event_name != 'pull_request' && github.ref != 'refs/heads/main'
run: |
echo "Skipping CI checks for non-main branch (staging build will proceed)"