Skip to content

Merge pull request #463 from OperationCode/irving/202610-python-3.14.10 #52

Merge pull request #463 from OperationCode/irving/202610-python-3.14.10

Merge pull request #463 from OperationCode/irving/202610-python-3.14.10 #52

Workflow file for this run

name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
# Manual rebuild, e.g. to pick up Debian security updates when nothing in the repo changed
workflow_dispatch:
inputs:
no_cache:
description: "Build without layer cache (re-runs apt-get upgrade)"
type: boolean
default: true
env:
POETRY_VERSION: "2.5.1"
POETRY_VIRTUALENVS_IN_PROJECT: true
jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Python
id: setup-python
uses: actions/setup-python@v7
with:
python-version: "3.14"
# pipx is preinstalled on GitHub runners; avoids a third-party install action
- name: Install Poetry
run: pipx install "poetry==${{ env.POETRY_VERSION }}" --python "${{ steps.setup-python.outputs.python-path }}"
- name: Load cached venv
id: cached-venv
uses: actions/cache@v6
with:
path: .venv
key: venv-lint-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-lint-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-
- 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
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Python 3.14
id: setup-python
uses: actions/setup-python@v7
with:
python-version: "3.14"
# pipx is preinstalled on GitHub runners; avoids a third-party install action
- name: Install Poetry
run: pipx install "poetry==${{ env.POETRY_VERSION }}" --python "${{ steps.setup-python.outputs.python-path }}"
- name: Load cached venv
id: cached-venv
uses: actions/cache@v6
with:
path: .venv
key: venv-test-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-test-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-
- name: Install dependencies
if: steps.cached-venv.outputs.cache-hit != 'true'
run: poetry install --no-interaction
- name: Run tests with coverage
run: |
poetry run pytest \
--cov=pybot \
--cov-report=xml \
--cov-report=term-missing \
-v \
--tb=short
env:
SLACK_TOKEN: "xoxb-test-token"
SLACK_ADMIN_TOKEN: "xoxb-admin-test-token"
AIRTABLE_API_KEY: "test-key"
AIRTABLE_BASE_ID: "test-base"
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v7
with:
files: ./coverage.xml
fail_ci_if_error: false
verbose: true
security:
name: Security Scan
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v7
- name: Set up Python 3.14
id: setup-python
uses: actions/setup-python@v7
with:
python-version: "3.14"
# pipx is preinstalled on GitHub runners; avoids a third-party install action
- name: Install Poetry
run: pipx install "poetry==${{ env.POETRY_VERSION }}" --python "${{ steps.setup-python.outputs.python-path }}"
- name: Load cached venv
id: cached-venv
uses: actions/cache@v6
with:
path: .venv
key: venv-security-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('poetry.lock') }}
restore-keys: |
venv-security-${{ runner.os }}-py${{ steps.setup-python.outputs.python-version }}-
- 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 pybot -x pybot/_vendor --skip B101 -f json -o bandit-report.json || true
- name: Display Bandit results
run: poetry run bandit -r pybot -x pybot/_vendor --skip B101 -f txt || true
docker-build-push:
name: Build and Push Docker Image
runs-on: ubuntu-24.04-arm
# Run on push to master (build+push), manual dispatch (build+push) and on PRs (build only)
if: github.event_name == 'push' || github.event_name == 'pull_request' || github.event_name == 'workflow_dispatch'
# 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@v7
- name: Determine push eligibility
id: can-push
run: |
if [ "${{ github.event_name }}" == "push" ] || [ "${{ github.event_name }}" == "workflow_dispatch" ]; 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: Determine Docker tags
id: docker-tag
run: |
ECR=633607774026.dkr.ecr.us-east-2.amazonaws.com/pybot
if [ "${{ github.ref }}" == "refs/heads/master" ]; then
echo "images=${ECR}:prod,${ECR}:latest" >> "$GITHUB_OUTPUT"
echo "environment=Production" >> "$GITHUB_OUTPUT"
else
echo "images=${ECR}:staging" >> "$GITHUB_OUTPUT"
echo "environment=Staging" >> "$GITHUB_OUTPUT"
fi
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
with:
platforms: linux/arm64
- name: Configure AWS credentials
if: steps.can-push.outputs.push == 'true'
uses: aws-actions/configure-aws-credentials@v6
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@v7
with:
context: .
file: docker/Dockerfile
target: prod
platforms: linux/arm64
push: ${{ steps.can-push.outputs.push == 'true' }}
tags: ${{ steps.docker-tag.outputs.images }}
provenance: false
no-cache: ${{ inputs.no_cache == true }}
pull: ${{ inputs.no_cache == true }}
cache-from: type=gha,scope=arm64
cache-to: type=gha,mode=max,scope=arm64
- name: Output image URI
if: steps.can-push.outputs.push == 'true'
run: |
echo "Successfully pushed ${{ steps.docker-tag.outputs.environment }} images:"
echo "${{ steps.docker-tag.outputs.images }}"
# 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
run: |
if [[ "${{ needs.lint.result }}" == "skipped" ]]; then
echo "Lint job was skipped"
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"
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!"