Run deploy to GCP #10
Workflow file for this run
This file contains 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
# The workflow processes GH secrets and variables managed by Terraform or manually, some of them for general usage in | |
# the github jobs, and some which component-relevant (cms, client, etc), used to build the .env files for the containers. | |
# These follow the naming convention: | |
# - TF_[PRODUCTION|<UPPER CASE BRANCH NAME>]_[CLIENT_ENV|CMS_ENV|]_* - managed by Terraform | |
# - [PRODUCTION|<UPPER CASE BRANCH NAME>]_[CLIENT_ENV|CMS_ENV|]_* - managed manually | |
name: Run deploy to GCP | |
on: | |
workflow_dispatch: | |
inputs: | |
ENVIRONMENT_NAME_OVERRIDE: | |
description: "Environment name to override the environment used in the workflow" | |
required: false | |
type: string | |
dry_run: | |
description: "Makes the workflow trigger in dry run mode (no components will be published or deployed to the actual environment infrastructure)" | |
required: false | |
default: false | |
type: boolean | |
push: | |
branches: | |
- main | |
- staging | |
paths: | |
- 'client/**' | |
- 'cms/**' | |
- '.github/workflows/*' | |
- 'infrastructure/**' | |
env: | |
PROJECT_ID: ${{ secrets.TF_GCP_PROJECT_ID }} | |
GAR_LOCATION: ${{ secrets.TF_GCP_REGION }} | |
REGION: ${{ secrets.TF_GCP_REGION }} | |
jobs: | |
deploy_client: | |
# Add 'id-token' with the intended permissions for workload identity federation | |
permissions: | |
contents: 'read' | |
id-token: 'write' | |
runs-on: ubuntu-latest | |
env: | |
APP_ENV_PREFIX: CLIENT_ENV | |
APP_ENV_PATH: client/.env.local | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- uses: dorny/paths-filter@v3 | |
id: client-changes | |
with: | |
filters: | | |
client: | |
- 'client/**' | |
- '.github/workflows/**' | |
- name: Applicable check | |
id: applicable_check | |
run: | | |
{ | |
echo "flag=${{ github.event_name == 'workflow_dispatch' || steps.client-changes.outputs.client == 'true' }}" | |
} >> $GITHUB_OUTPUT | |
- name: Extract branch name | |
if: ${{ steps.applicable_check.outputs.flag }} | |
run: | | |
{ | |
branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} | |
echo "branch=${branch}" | |
echo "branch_upper=${branch^^}" | |
} >> $GITHUB_OUTPUT | |
id: extract_branch | |
- name: Set environment name | |
if: ${{ steps.applicable_check.outputs.flag }} | |
id: environment_name | |
run: | | |
{ | |
echo "ENVIRONMENT=${{ inputs.ENVIRONMENT_NAME_OVERRIDE || steps.extract_branch.outputs.branch == 'main' && 'PRODUCTION' || steps.extract_branch.outputs.branch_upper }}" | |
} >> $GITHUB_ENV | |
- name: Output secrets and vars as JSON | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use GH Actions toJSON function to convert secrets and vars to JSON; in case no values present, output null (otherwise jq will fail) | |
run: | | |
{ | |
echo 'secrets<<EOF' | |
echo '${{ secrets != null && toJSON(secrets) || null }}' | |
echo 'EOF' | |
echo 'vars<<EOF' | |
echo '${{ vars != null && toJSON(vars) || null }}' | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_json | |
- name: Output secrets and vars as key=value entries | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use jq to convert JSON to key=value entries | |
# 1. to_entries converts JSON to array of key/value pairs | |
# 2. map(.key + "=" + .value) converts each key/value pair to key=value | |
# 3. .[] flattens array to key=value entries | |
run: | | |
{ | |
echo 'entries_all<<EOF' | |
echo '${{ steps.env_json.outputs.secrets }}' '${{ steps.env_json.outputs.vars }}' | jq -r 'to_entries | map(.key + "=" + .value) | .[]' | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_entries_all | |
- name: Filter secrets and vars for inclusion in .env file by environment and application prefixes | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use grep to filter client secrets & vars and save .env file (names starting with (TF_)((PRODUCTION|STAGING|SOMEBRANCH)_)[CLIENT_ENV|CMS_ENV]_ | |
run: | | |
{ | |
echo 'entries_filtered<<EOF' | |
echo '${{ steps.env_entries_all.outputs.entries_all }}' | grep -E "^(TF_)?(${ENVIRONMENT}_)?${APP_ENV_PREFIX}_" | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_entries_filtered | |
- name: Strip environment and application prefixes from secret and var names | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use sed to strip environment and application prefixes from secret and var names | |
run: | | |
{ | |
echo 'entries_stripped<<EOF' | |
echo '${{ steps.env_entries_filtered.outputs.entries_filtered }}' | sed -E "s/^(TF_)?("$ENVIRONMENT"_)?"$APP_ENV_PREFIX"_//" | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_entries_stripped | |
- name: Save .env file | |
if: ${{ steps.applicable_check.outputs.flag }} | |
run: | | |
echo '${{ steps.env_entries_stripped.outputs.entries_stripped }}' >> $APP_ENV_PATH | |
cat $APP_ENV_PATH | |
#- name: Google Auth authentication via credentials json | |
- name: Google Auth | |
if: ${{ steps.applicable_check.outputs.flag }} | |
id: auth | |
uses: 'google-github-actions/auth@v2' | |
with: | |
credentials_json: "${{ secrets[format('TF_{0}_GCP_SA_KEY', steps.environment_name.outputs.ENVIRONMENT)] }}" | |
# Authenticate Docker to Google Cloud Artifact Registry via credentials json | |
- name: Docker Auth | |
if: ${{ steps.applicable_check.outputs.flag }} | |
id: docker-auth | |
uses: 'docker/login-action@v3' | |
with: | |
registry: ${{ env.GAR_LOCATION }}-docker.pkg.dev | |
username: _json_key | |
password: ${{ secrets[format('TF_{0}_GCP_SA_KEY', steps.environment_name.outputs.ENVIRONMENT)] }} | |
- name: Build Container | |
if: ${{ steps.applicable_check.outputs.flag }} | |
env: | |
REPOSITORY: ${{ secrets[format('TF_{0}_CLIENT_REPOSITORY', steps.environment_name.outputs.ENVIRONMENT)] }} | |
SERVICE: ${{ secrets[format('TF_{0}_CLIENT_SERVICE', steps.environment_name.outputs.ENVIRONMENT)] }} | |
run: |- | |
docker build -f client/Dockerfile.prod -t "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }}" ./client | |
- name: Push Container | |
if: ${{ steps.applicable_check.outputs.flag && !inputs.dry_run }} | |
env: | |
REPOSITORY: ${{ secrets[format('TF_{0}_CLIENT_REPOSITORY', steps.environment_name.outputs.ENVIRONMENT)] }} | |
SERVICE: ${{ secrets[format('TF_{0}_CLIENT_SERVICE', steps.environment_name.outputs.ENVIRONMENT)] }} | |
run: |- | |
docker push "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }}" | |
# tag as "latest" | |
docker tag "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }}" "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:latest" | |
docker push "${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:latest" | |
- name: Deploy to Cloud Run | |
if: ${{ steps.applicable_check.outputs.flag && !inputs.dry_run }} | |
env: | |
REPOSITORY: ${{ secrets[format('TF_{0}_CLIENT_REPOSITORY', steps.environment_name.outputs.ENVIRONMENT)] }} | |
SERVICE: ${{ secrets[format('TF_{0}_CLIENT_SERVICE', steps.environment_name.outputs.ENVIRONMENT)] }} | |
id: deploy | |
uses: google-github-actions/deploy-cloudrun@v1 | |
with: | |
service: ${{ env.SERVICE }} | |
region: ${{ env.REGION }} | |
image: ${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/${{ env.REPOSITORY }}/${{ env.SERVICE }}:${{ github.sha }} | |
# NOTE: You can also set env variables here: | |
# env_vars: | | |
# NODE_ENV=production | |
# TOKEN_EXPIRE=6400 | |
# If required, use the Cloud Run url output in later steps | |
- name: Show Output | |
run: echo ${{ steps.deploy.outputs.url || 'No URL generated' }} | |
deploy_cms: | |
# Add 'id-token' with the intended permissions for workload identity federation | |
permissions: | |
contents: 'read' | |
id-token: 'write' | |
runs-on: ubuntu-latest | |
env: | |
APP_ENV_PREFIX: CMS_ENV | |
APP_ENV_PATH: cms/.env | |
steps: | |
- name: Checkout code | |
uses: actions/checkout@v4 | |
- uses: dorny/paths-filter@v3 | |
id: cms-changes | |
with: | |
filters: | | |
cms: | |
- 'cms/**' | |
- '.github/workflows/**' | |
- name: Extract branch name | |
if: ${{ github.event_name == 'workflow_dispatch' || steps.cms-changes.outputs.cms == 'true' }} | |
run: | | |
{ | |
branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} | |
echo "branch=${branch}" | |
echo "branch_upper=${branch^^}" | |
} >> $GITHUB_OUTPUT | |
id: extract_branch | |
- name: Applicable check | |
id: applicable_check | |
run: | | |
{ | |
echo "flag=${{ github.event_name == 'workflow_dispatch' || steps.cms-changes.outputs.cms == 'true' }}" | |
} >> $GITHUB_OUTPUT | |
- name: Set environment name | |
if: ${{ steps.applicable_check.outputs.flag }} | |
id: environment_name | |
run: | | |
{ | |
echo "ENVIRONMENT=${{ inputs.ENVIRONMENT_NAME_OVERRIDE ||steps.extract_branch.outputs.branch == 'main' && 'PRODUCTION' || steps.extract_branch.outputs.branch_upper }}" | |
} >> $GITHUB_ENV | |
- name: Output secrets and vars as JSON | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use GH Actions toJSON function to convert secrets and vars to JSON; in case no values present, output null (otherwise jq will fail) | |
run: | | |
{ | |
echo 'secrets<<EOF' | |
echo '${{ secrets != null && toJSON(secrets) || null }}' | |
echo 'EOF' | |
echo 'vars<<EOF' | |
echo '${{ vars != null && toJSON(vars) || null }}' | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_json | |
- name: Output secrets and vars as key=value entries | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use jq to convert JSON to key=value entries | |
# 1. to_entries converts JSON to array of key/value pairs | |
# 2. map(.key + "=" + .value) converts each key/value pair to key=value | |
# 3. .[] flattens array to key=value entries | |
run: | | |
{ | |
echo 'entries_all<<EOF' | |
echo '${{ steps.env_json.outputs.secrets }}' '${{ steps.env_json.outputs.vars }}' | jq -r 'to_entries | map(.key + "=" + .value) | .[]' | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_entries_all | |
- name: Filter secrets and vars for inclusion in .env file by environment and application prefixes | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use grep to filter client secrets & vars and save .env file (names starting with (TF_)((PRODUCTION|STAGING|SOMEBRANCH)_)[CLIENT_ENV|CMS_ENV]_ | |
run: | | |
{ | |
echo 'entries_filtered<<EOF' | |
echo '${{ steps.env_entries_all.outputs.entries_all }}' | grep -E "^(TF_)?(${ENVIRONMENT}_)?${APP_ENV_PREFIX}_" | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_entries_filtered | |
- name: Strip environment and application prefixes from secret and var names | |
if: ${{ steps.applicable_check.outputs.flag }} | |
# Use sed to strip environment and application prefixes from secret and var names | |
run: | | |
{ | |
echo 'entries_stripped<<EOF' | |
echo '${{ steps.env_entries_filtered.outputs.entries_filtered }}' | sed -E "s/^(TF_)?("$ENVIRONMENT"_)?"$APP_ENV_PREFIX"_//g" | |
echo 'EOF' | |
} >> $GITHUB_OUTPUT | |
id: env_entries_stripped | |
- name: Save .env file | |
if: ${{ steps.applicable_check.outputs.flag }} | |
run: | | |
echo '${{ steps.env_entries_stripped.outputs.entries_stripped }}' >> $APP_ENV_PATH | |
cat $APP_ENV_PATH | |
- name: Deploy and Push to Docker | |
if: ${{ steps.applicable_check.outputs.flag }} | |
id: deploy_and_push | |
uses: ./.github/actions/build-and-deploy-cloud-run | |
with: | |
GCP_SA_KEY: ${{ secrets[format('TF_{0}_GCP_SA_KEY', steps.environment_name.outputs.ENVIRONMENT)] }} | |
COMPONENT_PATH: "./cms" | |
ENVIRONMENT_NAME: ${{ steps.environment_name.outputs.ENVIRONMENT }} | |
GAR_LOCATION: ${{ env.GAR_LOCATION }} | |
PROJECT_ID: ${{ env.PROJECT_ID }} | |
REGION: ${{ env.REGION }} | |
REPOSITORY: ${{ secrets[format('TF_{0}_CMS_REPOSITORY', steps.environment_name.outputs.ENVIRONMENT)] }} | |
SERVICE: ${{ secrets[format('TF_{0}_CMS_SERVICE', steps.environment_name.outputs.ENVIRONMENT)] }} | |
DRY_RUN: ${{ inputs.dry_run }} | |
# If required, use the Cloud Run url output in later steps | |
- name: Show Output | |
if: ${{ steps.applicable_check.outputs.flag }} | |
run: echo ${{ steps.deploy_and_push.outputs.url || 'No URL generated' }} |