Skip to content

add GitHub Actions workflow for testing and deployment #17

add GitHub Actions workflow for testing and deployment

add GitHub Actions workflow for testing and deployment #17

Workflow file for this run

name: CI-CD
on:
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read
concurrency:
group: ci-cd-${{ github.ref }}
cancel-in-progress: true
env:
PYTHON_VERSION: "3.14"
DOCKER_REPOSITORY: fastapi
DOCKER_SERVER_TAG: myapp-wsl-api
jobs:
test:
runs-on: ubuntu-latest
environment:
name: tests
env:
ACCESS_TOKEN_EXPIRE_MINUTES: ${{ secrets.ACCESS_TOKEN_EXPIRE_MINUTES }}
ALGORITHM: ${{ secrets.ALGORITHM }}
DATABASE_HOSTNAME: localhost
DATABASE_PORT: "5432"
DATABASE_NAME: ${{ secrets.DATABASE_NAME }}
DATABASE_USERNAME: ${{ secrets.DATABASE_USERNAME }}
DATABASE_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
SECRET_KEY: ${{ secrets.SECRET_KEY }}
TESTING: "true"
services:
postgres:
image: postgres:16-bookworm
env:
POSTGRES_USER: ${{ secrets.DATABASE_USERNAME }}
POSTGRES_PASSWORD: ${{ secrets.DATABASE_PASSWORD }}
POSTGRES_DB: "${{ secrets.DATABASE_NAME }}_test"
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U $POSTGRES_USER -d $POSTGRES_DB"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
python -m pip install pytest
- name: Run Alembic migrations
run: alembic upgrade head
- name: Run tests
run: pytest --disable-warnings -v -s -x
validate_compose_dev:
needs: test
runs-on: ubuntu-latest
environment: tests
steps:
- uses: actions/checkout@v5
- name: Create temporary .env.docker from test environment secrets
run: |
cat > .env.docker <<EOF
DATABASE_HOSTNAME=postgres
DATABASE_PORT=5432
DATABASE_NAME=${{ secrets.DATABASE_NAME }}
DATABASE_USERNAME=${{ secrets.DATABASE_USERNAME }}
DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD }}
SECRET_KEY=${{ secrets.SECRET_KEY }}
ALGORITHM=${{ secrets.ALGORITHM }}
ACCESS_TOKEN_EXPIRE_MINUTES=${{ secrets.ACCESS_TOKEN_EXPIRE_MINUTES }}
POSTGRES_USER=${{ secrets.DATABASE_USERNAME }}
POSTGRES_PASSWORD=${{ secrets.DATABASE_PASSWORD }}
POSTGRES_DB=${{ secrets.DATABASE_NAME }}
EOF
- name: Validate docker-compose-dev.yml
run: docker compose -f docker-compose-dev.yml config
docker_wsl:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: ubuntu-server
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Extract Docker image metadata
id: meta
uses: docker/metadata-action@v6
with:
images: ${{ secrets.DOCKER_HUB_USERNAME }}/${{ env.DOCKER_REPOSITORY }}
tags: |
type=raw,value=${{ env.DOCKER_SERVER_TAG }}
type=sha,prefix=sha-
- name: Log in to Docker Hub
uses: docker/login-action@v4
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build and push Docker image
id: docker_build
uses: docker/build-push-action@v7
with:
context: .
pull: true
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: true
sbom: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Image digest
run: echo "digest=${{ steps.docker_build.outputs.digest }}"
deploy_render:
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment:
name: production
steps:
- name: Trigger Render deploy
run: curl -fsS -X POST "$RENDER_DEPLOY_HOOK_URL"
env:
RENDER_DEPLOY_HOOK_URL: ${{ secrets.RENDER_DEPLOY_HOOK_URL }}
deploy_ubuntu:
needs: docker_wsl
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: [self-hosted, linux, x64, ubuntu-wsl]
environment:
name: tests
steps:
- name: Checkout code
uses: actions/checkout@v5
- name: Create .env.docker for Ubuntu deploy
run: |
cat > .env.docker <<EOF
DATABASE_HOSTNAME=postgres
DATABASE_PORT=5432
DATABASE_NAME=${{ secrets.DATABASE_NAME }}
DATABASE_USERNAME=${{ secrets.DATABASE_USERNAME }}
DATABASE_PASSWORD=${{ secrets.DATABASE_PASSWORD }}
SECRET_KEY=${{ secrets.SECRET_KEY }}
ALGORITHM=${{ secrets.ALGORITHM }}
ACCESS_TOKEN_EXPIRE_MINUTES=${{ secrets.ACCESS_TOKEN_EXPIRE_MINUTES }}
POSTGRES_USER=${{ secrets.DATABASE_USERNAME }}
POSTGRES_PASSWORD=${{ secrets.DATABASE_PASSWORD }}
POSTGRES_DB=${{ secrets.DATABASE_NAME }}
EOF
- name: Deploy on Ubuntu machine
working-directory: ${{ github.workspace }}
run: |
set -euo pipefail
docker compose -f docker-compose-wsl.yml up -d postgres
docker compose -f docker-compose-wsl.yml pull api
docker compose -f docker-compose-wsl.yml run --rm --no-deps api alembic upgrade head
docker compose -f docker-compose-wsl.yml up -d --no-deps api
# deploy_ubuntu:
# needs: docker_wsl
# if: github.event_name == 'push' && github.ref == 'refs/heads/main'
# runs-on: ubuntu-latest
# environment:
# name: ubuntu-server
# steps:
# - name: Set up SSH
# run: |
# install -m 700 -d ~/.ssh
# printf '%s\n' "${{ secrets.PROD_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
# chmod 600 ~/.ssh/id_ed25519
# ssh-keyscan -p "${{ secrets.PROD_SSH_PORT }}" -H "${{ secrets.PROD_HOST }}" >> ~/.ssh/known_hosts
# - name: Deploy to Ubuntu server
# run: |
# ssh -p "${{ secrets.PROD_SSH_PORT }}" "${{ secrets.PROD_USERNAME }}@${{ secrets.PROD_HOST }}" <<'EOF'
# set -euo pipefail
# cd app/src
# git pull --ff-only origin main
# docker compose -f docker-compose-wsl.yml up -d postgres
# docker compose -f docker-compose-wsl.yml pull api
# docker compose -f docker-compose-wsl.yml run --rm --no-deps api alembic upgrade head
# docker compose -f docker-compose-wsl.yml up -d --no-deps api
# EOF