pkmntrade.club/.github/workflows/build_deploy.yml
badbl0cks 30ce126a07
feat(deploy): implement blue-green deployment strategy
This commit replaces the previous deployment mechanism with a blue-green strategy to lay the groundwork for zero-downtime deployments.
Key changes:
Introduces a deploy-blue-green.sh script to manage "blue" and "green" container sets, creating versioned releases.
Updates the Anubis gatekeeper template to dynamically route traffic based on the active deployment color, allowing for seamless traffic switching.
Modifies Docker Compose files to include color-specific labels and environment variables.
Adapts the GitHub Actions workflow to execute the new blue-green deployment process.
Removes the old, now-obsolete deployment and health check scripts.
Note: Automated rollback on health check failure is not yet implemented. Downgrades can be performed manually by switching the active color.
2025-06-12 16:58:55 -07:00

262 lines
No EOL
10 KiB
YAML

name: Build & Deploy
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
# Job 1: Build the Docker image
build:
runs-on: ubuntu-latest
outputs:
repo: ${{ steps.meta.outputs.REPO }}
repo-name: ${{ steps.meta.outputs.REPO_NAME_ONLY }}
repo-path: ${{ steps.meta.outputs.REPO_PROJECT_PATH }}
image-tar: ${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar
tags: ${{ steps.generated_docker_tags.outputs.tag }}
prod: ${{ steps.meta.outputs.prod }}
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Ensure scripts are executable
run: chmod +x scripts/*.sh
- name: Setup build metadata and environment
id: meta
run: |
echo "✅ Exit script on any error"
set -eu -o pipefail
# Parse repository name and set outputs
eval "$(./scripts/parse-repository-name.sh '${{ github.repository }}')"
echo "REPO=$REPO" >> $GITHUB_OUTPUT
echo "REPO_NAME_ONLY=$REPO_NAME_ONLY" >> $GITHUB_OUTPUT
echo "REPO_PROJECT_PATH=$REPO_PROJECT_PATH" >> $GITHUB_OUTPUT
# Determine PROD environment
prod_value=""
echo "🔍 Check if PROD is set via vars; if not, determine from github.ref"
if [ -z "${{ vars.PROD }}" ]; then
prod_value="${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-prerelease') }}"
echo "📦 PROD mode unset, determined from github.ref (starts with v and does not end with -prerelease?): ${prod_value}"
else
prod_value="${{ vars.PROD }}"
echo "📦 PROD mode already set to: ${prod_value}"
fi
echo "prod=${prod_value}" >> $GITHUB_OUTPUT
# Set environment variables for subsequent steps
echo "🖊️ Writing determined values to GITHUB_ENV:"
echo "PROD=${prod_value}" >> $GITHUB_ENV
echo "PROD=${prod_value} -> GITHUB_ENV"
echo "IMAGE_TAR_NAME=${REPO_NAME_ONLY}-${{ github.ref_name }}_${{ github.sha }}.tar" >> $GITHUB_ENV
echo "IMAGE_TAR_NAME=${REPO_NAME_ONLY}-${{ github.ref_name }}_${{ github.sha }}.tar -> GITHUB_ENV"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Generate tags
id: generated_docker_tags
run: |
echo "✅ Exit script on any error"
set -eu -o pipefail
# Use the script to generate tags
TAG_LIST=$(./scripts/generate-docker-tags.sh \
"${{ steps.meta.outputs.REPO }}" \
"${{ github.sha }}" \
"${{ github.ref }}" \
"$PROD")
echo "Final list of generated tags:"
echo "$TAG_LIST"
TAG_COUNT=$(echo "$TAG_LIST" | wc -l)
if [[ -z "${{ github.sha }}" || $TAG_COUNT -lt 4 ]]; then
echo "⚠️ No tags (or too few) were generated based on the logic. Need at least 4 tags. Generated: $TAG_COUNT"
exit 1
fi
# Output the tags for the docker build action
{
echo "tag<<EOF"
echo "$TAG_LIST"
echo "EOF"
} >> "$GITHUB_OUTPUT"
- name: Run prebuild tasks
run: ./scripts/prebuild.sh
- name: Cache Docker layers
uses: actions/cache@v4
with:
path: /tmp/.buildx-cache
key: ${{ runner.os }}-buildx-${{ github.sha }}
restore-keys: |
${{ runner.os }}-buildx-
- name: Extract version for Docker build
id: extract_version
run: |
pip uninstall setuptools
pip install setuptools-scm
VERSION=$(python -c "from setuptools_scm import get_version; print(get_version())")
echo "VERSION=${VERSION}" >> $GITHUB_ENV
- name: Build container
uses: docker/build-push-action@v6
with:
outputs: type=docker,dest=${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar
tags: ${{ steps.generated_docker_tags.outputs.tag }}
build-args: |
VERSION=${{ env.VERSION }}
context: .
cache-from: type=local,src=/tmp/.buildx-cache
cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max
- name: Rotate cache
run: |
rm -rf /tmp/.buildx-cache
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
- name: Upload container as artifact
uses: actions/upload-artifact@v4
with:
name: ${{ env.IMAGE_TAR_NAME }}
path: ${{ runner.temp }}/${{ steps.meta.outputs.REPO_NAME_ONLY }}-${{ github.ref_name }}_${{ github.sha }}.tar
if-no-files-found: error
retention-days: 1
# Job 2: Deploy (only runs on main branch or tags)
deploy:
#needs: build
runs-on: ubuntu-latest
#if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/'))
# Determine environment based on ref
environment: ${{ (startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-prerelease')) && 'production' || 'staging' }}
steps:
- name: Checkout the repo
uses: actions/checkout@v4
- name: Ensure scripts are executable
run: chmod +x scripts/*.sh
- name: Setup deployment metadata and environment
id: meta
run: |
echo "✅ Exit script on any error"
set -eu -o pipefail
# Parse repository name and set outputs
eval "$(./scripts/parse-repository-name.sh '${{ github.repository }}')"
echo "REPO=$REPO" >> $GITHUB_OUTPUT
echo "REPO_NAME_ONLY=$REPO_NAME_ONLY" >> $GITHUB_OUTPUT
echo "REPO_PROJECT_PATH=$REPO_PROJECT_PATH" >> $GITHUB_OUTPUT
# Determine PROD environment
prod_value=""
echo "🔍 Check if PROD is set via vars; if not, determine from github.ref"
if [ -z "${{ vars.PROD }}" ]; then
prod_value="${{ startsWith(github.ref, 'refs/tags/v') && !endsWith(github.ref, '-prerelease') }}"
echo "📦 PROD mode unset, determined from github.ref (starts with v and does not end with -prerelease?): ${prod_value}"
else
prod_value="${{ vars.PROD }}"
echo "📦 PROD mode already set to: ${prod_value}"
fi
echo "prod=${prod_value}" >> $GITHUB_OUTPUT
# Set all deployment environment variables
echo "📝 Setting deployment environment variables"
echo "REPO_PROJECT_PATH=${REPO_PROJECT_PATH}" >> $GITHUB_ENV
echo "REPO_NAME_ONLY=${REPO_NAME_ONLY}" >> $GITHUB_ENV
echo "REPO=${REPO}" >> $GITHUB_ENV
echo "IMAGE_TAR_NAME=${REPO_NAME_ONLY}-${{ github.ref_name }}_${{ github.sha }}.tar" >> $GITHUB_ENV
echo "PROD=${prod_value}" >> $GITHUB_ENV
echo "GIT_SHA=${{ github.sha }}" >> $GITHUB_ENV
echo "REPLICA_COUNT=${{ vars.REPLICA_COUNT }}" >> $GITHUB_ENV
echo "PRODUCTION_DOMAIN=${{ vars.PRODUCTION_DOMAIN }}" >> $GITHUB_ENV
echo "STAGING_DOMAIN=${{ vars.STAGING_DOMAIN }}" >> $GITHUB_ENV
- name: Download container artifact
uses: actions/download-artifact@v4
with:
name: ${{ env.IMAGE_TAR_NAME }}
path: ${{ runner.temp }}
- name: Get Deploy Secrets
uses: bitwarden/sm-action@v2
with:
access_token: ${{ secrets.BW_ACCESS_TOKEN }}
secrets: |
27a8da8d-5fb4-4f58-baaf-b2dd0032eca8 > DEPLOY_HOST
4cf9ab8d-1772-4ab0-9219-b2dd003315d4 > DEPLOY_KEY
9aefe34e-c2cf-442e-973c-b2dd0032b6cf > ENV_FILE_BASE64
d3bb47f8-bfc0-4a61-9cee-b2df0147a02a > CF_PEM_CERT
5f658ddf-aadd-4464-b501-b2df0147c338 > CF_PEM_CA
- name: Set up SSH
run: |
mkdir -p $HOME/.ssh
echo -e "${DEPLOY_KEY}" > $HOME/.ssh/deploy.key
chmod 700 $HOME/.ssh
chmod 600 $HOME/.ssh/deploy.key
cat >>$HOME/.ssh/config <<END
Host deploy
HostName ${DEPLOY_HOST}
Port ${{ vars.DEPLOY_PORT }}
User ${{ vars.DEPLOY_USER }}
IdentityFile $HOME/.ssh/deploy.key
UserKnownHostsFile /dev/null
StrictHostKeyChecking no
ControlMaster auto
ControlPath $HOME/.ssh/control-%C
ControlPersist yes
END
- name: Deploy to Server
env:
DOCKER_HOST: ssh://deploy
REPO_PROJECT_PATH: ${{ env.REPO_PROJECT_PATH }}
REPO: ${{ env.REPO }}
REPO_NAME_ONLY: ${{ env.REPO_NAME_ONLY }}
IMAGE_TAR: ${{ runner.temp }}/${{ env.IMAGE_TAR_NAME }}
PRODrequire_var: ${{ env.PROD }}
GIT_SHA: ${{ github.sha }}
REPLICA_COUNT: ${{ env.REPLICA_COUNT }}
PRODUCTION_DOMAIN: ${{ vars.PRODUCTION_DOMAIN }}
STAGING_DOMAIN: ${{ vars.STAGING_DOMAIN }}
run: |
echo "✅ Exit script on any error"
set -eu -o pipefail
./scripts/deploy-blue-green.sh
# - name: Health Check and Rollback
# run: |
# # Determine the correct URL based on environment
# if [ "${{ env.PROD }}" = "true" ]; then
# # Ensure PRODUCTION_DOMAIN is set
# if [ -z "${{ vars.PRODUCTION_DOMAIN }}" ]; then
# echo "Error: PRODUCTION_DOMAIN is not set"
# exit 1
# fi
# HEALTH_CHECK_URL="https://${{ vars.PRODUCTION_DOMAIN }}/health/"
# else
# # Ensure STAGING_DOMAIN is set
# if [ -z "${{ vars.STAGING_DOMAIN }}" ]; then
# echo "Error: STAGING_DOMAIN is not set"
# exit 1
# fi
# HEALTH_CHECK_URL="https://${{ vars.STAGING_DOMAIN }}/health/"
# fi
# # Copy script to remote and execute
# scp scripts/health-check-and-rollback.sh deploy:/tmp/
# ssh deploy "chmod +x /tmp/health-check-and-rollback.sh"
# ssh deploy "/tmp/health-check-and-rollback.sh '${{ env.REPO_PROJECT_PATH }}' '$HEALTH_CHECK_URL' 30"
# ssh deploy "rm -f /tmp/health-check-and-rollback.sh"