Skip to content

Lab03

Lab03 #20

Workflow file for this run

# GitHub Actions CI/CD Pipeline for Python DevOps Info Service
# Triggers: push/PR to master/lab03 branches (only for app_python changes)
# Features: linting, testing, Docker build/push with CalVer versioning
name: Python CI
on:
push:
branches:
- master
- lab03
paths:
- "app_python/**"
- ".github/workflows/python-ci.yml"
pull_request:
branches:
- master
paths:
- "app_python/**"
- ".github/workflows/python-ci.yml"
# Permissions: read-only for security
permissions:
contents: read
# Cancel previous runs on same branch
concurrency:
group: python-ci-${{ github.ref }}
cancel-in-progress: true
env:
PIP_DISABLE_PIP_VERSION_CHECK: "1"
DOCKER_IMAGE: pepegx/devops-info-service
jobs:
# ========================================
# Job 1: Lint and Test (Matrix Build)
# ========================================
lint-test:
name: Lint & Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: true
matrix:
python-version: ["3.11", "3.12"]
defaults:
run:
working-directory: app_python
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
cache-dependency-path: app_python/requirements.txt
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Lint with ruff
run: python -m ruff check .
- name: Run unit tests with coverage
run: python -m pytest tests/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4
with:
file: app_python/coverage.xml
flags: unittests
name: codecov-${{ matrix.python-version }}
fail_ci_if_error: false
token: ${{ secrets.CODECOV_TOKEN }}
# ========================================
# Job 2: Security Scanning with Snyk
# ========================================
security:
name: Security Scan (Snyk)
runs-on: ubuntu-latest
needs: lint-test
defaults:
run:
working-directory: app_python
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
- name: Run Snyk security scan
uses: snyk/actions/python@master
continue-on-error: true
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
with:
args: --file=app_python/requirements.txt --severity-threshold=medium
# ========================================
# Job 3: Build and Push Docker Image
# ========================================
docker-build-push:
name: Build & Push Docker Image
runs-on: ubuntu-latest
needs: [lint-test, security]
# Only push on actual commits to master/lab03, not PRs
if: github.event_name == 'push'
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
steps:
- name: Check Docker Hub credentials
id: check-secrets
run: |
if [ -z "$DOCKERHUB_USERNAME" ] || [ -z "$DOCKERHUB_TOKEN" ]; then
echo "has_secrets=false" >> $GITHUB_OUTPUT
echo "⚠️ Docker Hub credentials not configured. Skipping Docker push."
echo "ℹ️ To enable Docker push, add DOCKERHUB_USERNAME and DOCKERHUB_TOKEN secrets."
else
echo "has_secrets=true" >> $GITHUB_OUTPUT
echo "✅ Docker Hub credentials found."
fi
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
if: steps.check-secrets.outputs.has_secrets == 'true'
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Generate CalVer version
id: version
run: |
# CalVer format: YYYY.MM.BUILD_NUMBER
CALVER=$(date +"%Y.%m")
VERSION="${CALVER}.${{ github.run_number }}"
echo "calver=${CALVER}" >> $GITHUB_OUTPUT
echo "version=${VERSION}" >> $GITHUB_OUTPUT
echo "Generated version: ${VERSION}"
- name: Build Docker image
uses: docker/build-push-action@v6
with:
context: app_python
file: app_python/Dockerfile
push: ${{ steps.check-secrets.outputs.has_secrets == 'true' }}
load: ${{ steps.check-secrets.outputs.has_secrets != 'true' }}
tags: |
${{ env.DOCKER_IMAGE }}:${{ steps.version.outputs.version }}
${{ env.DOCKER_IMAGE }}:${{ steps.version.outputs.calver }}
${{ env.DOCKER_IMAGE }}:latest
cache-from: type=gha
cache-to: type=gha,mode=max
labels: |
org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
org.opencontainers.image.revision=${{ github.sha }}
org.opencontainers.image.created=${{ github.event.head_commit.timestamp }}