Files
freeCodeCamp/.github/workflows/deploy.yml
2025-02-17 15:54:58 +05:30

132 lines
4.8 KiB
YAML

name: CD -- Deploy - API
on:
workflow_dispatch:
inputs:
fcc_api_log_level:
description: 'Log level for the API'
type: choice
options:
- debug
- info
- warn
default: info
push:
branches:
- prod-*
jobs:
static:
name: Set Static Data
runs-on: ubuntu-24.04
outputs:
site_tld: ${{ steps.static_data.outputs.site_tld }}
environment: ${{ steps.static_data.outputs.environment }}
fcc_api_log_level: ${{ steps.static_data.outputs.fcc_api_log_level }}
steps:
- name: Set Static Data
id: static_data
run: |
if [ "${{ github.ref }}" == "refs/heads/prod-staging" ]; then
echo "site_tld=dev" >> $GITHUB_OUTPUT
echo "environment=stg" >> $GITHUB_OUTPUT
echo "fcc_api_log_level=${{ inputs.fcc_api_log_level || 'info' }}" >> $GITHUB_OUTPUT
elif [ "${{ github.ref }}" == "refs/heads/prod-current" ]; then
echo "site_tld=org" >> $GITHUB_OUTPUT
echo "environment=prd" >> $GITHUB_OUTPUT
echo "fcc_api_log_level=${{ inputs.fcc_api_log_level || 'info' }}" >> $GITHUB_OUTPUT
else
echo "site_tld=dev" >> $GITHUB_OUTPUT
echo "environment=stg" >> $GITHUB_OUTPUT
echo "fcc_api_log_level=${{ inputs.fcc_api_log_level || 'info' }}" >> $GITHUB_OUTPUT
fi
build:
name: Build & Push
needs: static
uses: ./.github/workflows/docker-docr.yml
with:
site_tld: ${{ needs.static.outputs.site_tld }}
app: api
secrets: inherit
deploy:
name: Deploy to Docker Swarm -- ${{ needs.static.outputs.environment }}
runs-on: ubuntu-24.04
needs: [static, build]
env:
TS_USERNAME: ${{ secrets.TS_USERNAME }}
TS_MACHINE_NAME: ${{ secrets.TS_MACHINE_NAME }}
permissions:
deployments: write
environment:
name: ${{ needs.static.outputs.environment }}
url: https://api.freecodecamp.${{ needs.static.outputs.site_tld }}/status/ping?version=${{ needs.build.outputs.tagname }}
steps:
- name: Setup and connect to Tailscale network
uses: tailscale/github-action@v3
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
tags: tag:ci
version: latest
- name: Configure SSH
# This is a workaround to avoid the SSH warning about known hosts & strict host key checking.
# It's not a problem for us, because we're using Tailscale to connect.
run: |
mkdir -p ~/.ssh
echo "Host *
UserKnownHostsFile=/dev/null
StrictHostKeyChecking no" > ~/.ssh/config
- name: Check connection
run: |
tailscale status | grep -q "$TS_MACHINE_NAME" || { echo "Error: Machine not found"; exit 1; }
ssh $TS_USERNAME@$TS_MACHINE_NAME "uptime"
- name: Deploy with Docker Stack
env:
# These are set in the "Environment" specifc secrets
AGE_ENCRYPTED_ASC_SECRETS: ${{ secrets.AGE_ENCRYPTED_ASC_SECRETS }}
AGE_SECRET_KEY: ${{ secrets.AGE_SECRET_KEY }}
# These are set in the static job above
STACK_NAME: ${{ needs.static.outputs.environment }}-api
DEPLOYMENT_VERSION: ${{ needs.build.outputs.tagname }}
FCC_API_LOG_LEVEL: ${{ needs.static.outputs.fcc_api_log_level }}
run: |
ssh $TS_USERNAME@$TS_MACHINE_NAME /bin/bash << EOF
set -e
cd /home/${TS_USERNAME}/docker-swarm-config/stacks/api || { echo "Error: Failed to change directory"; exit 1; }
which age > /dev/null || { echo "Error: age not installed"; exit 1; }
# Decrypt secrets
echo "${AGE_ENCRYPTED_ASC_SECRETS}" > secrets.age.asc
echo "${AGE_SECRET_KEY}" > age.key && chmod 600 age.key
age --identity age.key --decrypt secrets.age.asc > .env
rm -f age.key secrets.age.asc
# Add deployment variables
{
echo "DEPLOYMENT_VERSION=${DEPLOYMENT_VERSION}"
echo "FCC_API_LOG_LEVEL=${FCC_API_LOG_LEVEL}"
} >> .env
# Export environment variables with proper escaping
while IFS='=' read -r key value; do
if [[ -n \$key && ! \$key =~ ^# ]]; then
export "\${key}=\${value}"
fi
done < .env
rm -f .env
# Validate environment and config
env | grep -E 'DOMAIN|DEPLOYMENT' || { echo "Error: Required environment variables not found"; exit 1; }
docker stack config -c stack-api.yml > /dev/null || { echo "Error: Invalid stack configuration"; exit 1; }
# Deploy
docker stack deploy -c stack-api.yml --prune --with-registry-auth --detach=false ${STACK_NAME}
EOF
shell: bash