add GitHub Actions workflow for testing and deployment #17
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |