From db5201f5b2eca7c7418e04693d0581da1f824264 Mon Sep 17 00:00:00 2001 From: Mrugesh Mohapatra <1884376+raisedadead@users.noreply.github.com> Date: Sat, 15 Feb 2025 19:18:57 +0530 Subject: [PATCH] fix: update deployment workflows (#58817) --- .github/workflows/deploy.yml | 126 ++++++++++++++---------------- .github/workflows/docker-docr.yml | 2 +- 2 files changed, 59 insertions(+), 69 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a1e04774bde..c268d963c76 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -1,49 +1,63 @@ -name: CD -- Deploy - API (Docker Swarm) +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 jobs: static: + name: Set Static Data runs-on: ubuntu-24.04 outputs: site_tld: ${{ steps.static_data.outputs.site_tld }} - environment_long: ${{ steps.static_data.outputs.environment_long }} - environment_short: ${{ steps.static_data.outputs.environment_short }} - + environment: ${{ steps.static_data.outputs.environment }} + fcc_api_log_level: ${{ steps.static_data.outputs.fcc_api_log_level }} steps: - - name: Set site_tld + - name: Set Static Data id: static_data run: | if [ "${{ github.ref }}" == "refs/heads/prod-staging" ]; then echo "site_tld=dev" >> $GITHUB_OUTPUT - echo "environment_long=staging" >> $GITHUB_OUTPUT - echo "environment_short=stg" >> $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_long=production" >> $GITHUB_OUTPUT - echo "environment_short=prd" >> $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_long=staging" >> $GITHUB_OUTPUT - echo "environment_short=stg" >> $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 Docker Image + 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_long }} + name: ${{ needs.static.outputs.environment }} url: https://api.freecodecamp.${{ needs.static.outputs.site_tld }}/status/ping?version=${{ needs.build.outputs.tagname }} steps: @@ -66,73 +80,49 @@ jobs: - name: Check connection run: | - tailscale status | grep -q "$TS_MACHINE_NAME" || { echo "Machine not found"; exit 1; } + 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" secrets - API_LOCATION: ${{ secrets.API_LOCATION }} - AUTH0_CLIENT_ID: ${{ secrets.AUTH0_CLIENT_ID }} - AUTH0_CLIENT_SECRET: ${{ secrets.AUTH0_CLIENT_SECRET }} - AUTH0_DOMAIN: ${{ secrets.AUTH0_DOMAIN }} - COOKIE_DOMAIN: ${{ secrets.COOKIE_DOMAIN }} - COOKIE_SECRET: ${{ secrets.COOKIE_SECRET }} - DOCKER_REGISTRY: ${{ secrets.DOCKER_REGISTRY }} - FCC_API_LOG_LEVEL: ${{ secrets.FCC_API_LOG_LEVEL }} - GROWTHBOOK_FASTIFY_API_HOST: ${{ secrets.GROWTHBOOK_FASTIFY_API_HOST }} - GROWTHBOOK_FASTIFY_CLIENT_KEY: ${{ secrets.GROWTHBOOK_FASTIFY_CLIENT_KEY }} - HOME_LOCATION: ${{ secrets.HOME_LOCATION }} - JWT_SECRET: ${{ secrets.JWT_SECRET }} - MONGOHQ_URL: ${{ secrets.MONGOHQ_URL }} - SENTRY_DSN: ${{ secrets.SENTRY_DSN }} - SES_ID: ${{ secrets.SES_ID }} - SES_SECRET: ${{ secrets.SES_SECRET }} - STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} + # 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 - DEPLOYMENT_ENV: ${{ needs.static.outputs.site_tld }} + STACK_NAME: ${{ needs.static.outputs.environment }}-api DEPLOYMENT_VERSION: ${{ needs.build.outputs.tagname }} - SENTRY_ENVIRONMENT: api-${{ needs.static.outputs.site_tld }} - STACK_NAME: ${{ needs.static.outputs.environment_short }}-api + 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; } - cd /home/${TS_USERNAME}/docker-swarm-config/stacks/api || { echo "Failed to change directory"; exit 1; } - echo "Debug: Current directory: \$(pwd)" + # 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 - echo "API_LOCATION=${API_LOCATION}" >> .env.tmp - echo "AUTH0_CLIENT_ID=${AUTH0_CLIENT_ID}" >> .env.tmp - echo "AUTH0_CLIENT_SECRET=${AUTH0_CLIENT_SECRET}" >> .env.tmp - echo "AUTH0_DOMAIN=${AUTH0_DOMAIN}" >> .env.tmp - echo "COOKIE_DOMAIN=${COOKIE_DOMAIN}" >> .env.tmp - echo "COOKIE_SECRET=${COOKIE_SECRET}" >> .env.tmp - echo "DOCKER_REGISTRY=${DOCKER_REGISTRY}" >> .env.tmp - echo "FCC_API_LOG_LEVEL=${FCC_API_LOG_LEVEL}" >> .env.tmp - echo "GROWTHBOOK_FASTIFY_API_HOST=${GROWTHBOOK_FASTIFY_API_HOST}" >> .env.tmp - echo "GROWTHBOOK_FASTIFY_CLIENT_KEY=${GROWTHBOOK_FASTIFY_CLIENT_KEY}" >> .env.tmp - echo "HOME_LOCATION=${HOME_LOCATION}" >> .env.tmp - echo "JWT_SECRET=${JWT_SECRET}" >> .env.tmp - echo "MONGOHQ_URL=${MONGOHQ_URL}" >> .env.tmp - echo "SENTRY_DSN=${SENTRY_DSN}" >> .env.tmp - echo "SES_ID=${SES_ID}" >> .env.tmp - echo "SES_SECRET=${SES_SECRET}" >> .env.tmp - echo "STRIPE_SECRET_KEY=${STRIPE_SECRET_KEY}" >> .env.tmp + # Add deployment variables + { + echo "DEPLOYMENT_VERSION=${DEPLOYMENT_VERSION}" + echo "FCC_API_LOG_LEVEL=${FCC_API_LOG_LEVEL}" + } >> .env - echo "DEPLOYMENT_ENV=${DEPLOYMENT_ENV}" >> .env.tmp - echo "DEPLOYMENT_VERSION=${DEPLOYMENT_VERSION}" >> .env.tmp - echo "SENTRY_ENVIRONMENT=${SENTRY_ENVIRONMENT}" >> .env.tmp + # 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 - set -a - source .env.tmp || { echo "Failed to source .env.tmp"; exit 1; } - set +a - rm -f .env.tmp + # 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; } - echo "Debug: Sanity check variables: " - env | grep -E '^DEPLOYMENT' || { echo 'Vars not found'; exit 1; } - echo "Debug: Sanity check config: " - docker stack config -c stack-api.yml | rg 'DOMAIN' || { echo 'Invalid stack config'; exit 1; } - - # Dump the stack config to a file for debugging, this will be replaced with a deploy command - docker stack config -c stack-api.yml > /tmp/stack-api-${DEPLOYMENT_VERSION}.yml + # Deploy + docker stack deploy -c stack-api.yml --prune --with-registry-auth --detach=false ${STACK_NAME} EOF shell: bash diff --git a/.github/workflows/docker-docr.yml b/.github/workflows/docker-docr.yml index a3ebcca6a9b..9681c071304 100644 --- a/.github/workflows/docker-docr.yml +++ b/.github/workflows/docker-docr.yml @@ -33,7 +33,7 @@ on: jobs: build: - name: Build (Image) + name: Build & Push runs-on: ubuntu-24.04 permissions: contents: read