Skip to content

Instantly share code, notes, and snippets.

@siberex
Last active June 11, 2021 15:18
Show Gist options
  • Save siberex/17321dff8b99ee363b2926f66e543150 to your computer and use it in GitHub Desktop.
Save siberex/17321dff8b99ee363b2926f66e543150 to your computer and use it in GitHub Desktop.
Script to setup service account for Google App Engine deployment (for GitHub Actions or any other CI)
#!/usr/bin/env bash
# Usage: _setup_gcloud.sh [PROJECT_ID]
set -euo pipefail
BASEDIR="$(
cd "$(dirname "$0")" || true
pwd -P
)"
GOOGLE_CLOUD_PROJECT=${1:-$(gcloud config list --format 'value(core.project)')}
ROLE_ID=appengine_deployer_role
SERVICE_ACCOUNT=ci-deployer
SERVICE_ACCOUNT_EMAIL=${SERVICE_ACCOUNT}@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com
bld='\033[1m'
clr='\033[0m'
if gcloud iam roles describe "${ROLE_ID}" --project="${GOOGLE_CLOUD_PROJECT}" >/dev/null 2>&1; then
echo -e "✓ Found Role $bld${ROLE_ID}$clr in Project $bld${GOOGLE_CLOUD_PROJECT}$clr"
else
echo -e "… Creating Role $bld${ROLE_ID}$clr in Project $bld${GOOGLE_CLOUD_PROJECT}$clr"
gcloud iam roles create "${ROLE_ID}" --file="$BASEDIR/appengine_deployer_role.yml" --project="${GOOGLE_CLOUD_PROJECT}"
fi
if gcloud iam service-accounts describe "${SERVICE_ACCOUNT_EMAIL}" --project="${GOOGLE_CLOUD_PROJECT}" >/dev/null 2>&1; then
echo -e "✓ Found Service Account $bld${SERVICE_ACCOUNT_EMAIL}$clr"
else
echo -e "… Creating Service Account $bld${SERVICE_ACCOUNT_EMAIL}$clr"
gcloud iam service-accounts create "${SERVICE_ACCOUNT}" --display-name="${SERVICE_ACCOUNT}" --description="Account to deploy via GitHub Actions" --project="${GOOGLE_CLOUD_PROJECT}"
fi
EXISTING_ROLE_BINDING=$(gcloud projects get-iam-policy "${GOOGLE_CLOUD_PROJECT}" --flatten="bindings[].members" --format="value(bindings.role)" --filter="bindings.role:roles/${ROLE_ID} AND bindings.members:serviceAccount:${SERVICE_ACCOUNT_EMAIL}")
if [ "${EXISTING_ROLE_BINDING}" = "projects/${GOOGLE_CLOUD_PROJECT}/roles/${ROLE_ID}" ]; then
echo -e "✓ Found IAM policy binding for $bld${SERVICE_ACCOUNT}$clr with Role $bld${ROLE_ID}$clr"
else
echo -e "… Creating IAM policy binding for $bld${SERVICE_ACCOUNT}$clr and Role $bld${ROLE_ID}$clr"
gcloud projects add-iam-policy-binding "${GOOGLE_CLOUD_PROJECT}" --member="serviceAccount:${SERVICE_ACCOUNT_EMAIL}" --role="projects/${GOOGLE_CLOUD_PROJECT}/roles/${ROLE_ID}" --condition=None
fi
echo -e "… Creating new private key for $bld${SERVICE_ACCOUNT}$clr"
TMP_OUTPUT_FILE="$(mktemp)"
gcloud iam service-accounts keys create "${TMP_OUTPUT_FILE}" --iam-account "${SERVICE_ACCOUNT_EMAIL}"
printf "\n${bld}GCLOUD_PROJECT_ID${clr}: %s\n" "${GOOGLE_CLOUD_PROJECT}"
printf "\n${bld}GCP_SA_EMAIL${clr}: %s\n" "${SERVICE_ACCOUNT_EMAIL}"
printf "\n${bld}GOOGLE_APPLICATION_CREDENTIALS${clr} (copy all lines):\n\n%s\n\n" "$(base64 "${TMP_OUTPUT_FILE}")"
rm "${TMP_OUTPUT_FILE}"
# https://cloud.google.com/appengine/docs/standard/nodejs/roles#predefined_roles
title: AppEngine Deployer GH Actions
description: Custom Role with required permissions for GAE deployment
stage: GA
includedPermissions:
- appengine.applications.get
- appengine.operations.get
- appengine.services.get
- appengine.services.list
- appengine.services.update
- appengine.versions.create
- appengine.versions.delete
- appengine.versions.get
- appengine.versions.list
- appengine.versions.update
- cloudbuild.builds.create
- cloudbuild.builds.get
- iam.serviceAccounts.actAs
- storage.buckets.get
- storage.objects.create
- storage.objects.delete
- storage.objects.get
- storage.objects.list
# Example GitHub Actions workflow
# .github/workflows/app-engine.yml
name: app-engine
env:
GCLOUD_PROJECT_ID: ${{ secrets.GCLOUD_PROJECT_ID }}
on:
push:
branches:
- main
pull_request:
branches:
- main
types: [opened, synchronize, reopened]
jobs:
cancel-previous:
name: Cancel Previous Jobs
# Skip workflow entirely for [skip ci] commits
if: |
false == contains(github.event.commits[0].message, '[skip ci]') &&
false == contains(github.event.commits[0].message, '[no ci]')
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.9.0
deploy-gae:
name: App Engine Deployment
environment: production
# Skip workflow entirely for [skip ci] commits
if: |
false == contains(github.event.commits[0].message, '[skip ci]') &&
false == contains(github.event.commits[0].message, '[no ci]')
runs-on: ubuntu-latest
needs: [lint]
# Map a step output to a job output
outputs:
version-url: ${{ steps.version-url.outputs.url }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Import Service Account key
run: echo "${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}" | base64 -d > /tmp/auth.json
- name: GCloud Auth
run: |
gcloud --quiet auth \
activate-service-account "${{ secrets.GCP_SA_EMAIL }}" \
--key-file=/tmp/auth.json \
--project="${GCLOUD_PROJECT_ID}"
- name: Deploy
run: |
gcloud --quiet app deploy app.yaml --no-promote --version "${GITHUB_ACTOR//[\[\]]/}-${GITHUB_SHA:0:7}"
- id: version-url
name: Get deployed app version URL
run: |
VERSION_URL=$(gcloud app versions describe "${GITHUB_ACTOR//[\[\]]/}-${GITHUB_SHA:0:7}" --service=default --format "value(versionUrl)")
echo $VERSION_URL
echo "::set-output name=url::$(echo $VERSION_URL)"
test:
name: Test deployed
runs-on: ubuntu-latest
# Run only after successful deployment
needs: [deploy-gae]
steps:
- name: Check deployed instance
run: curl --silent --fail --head --write-out "%{http_code}" --request GET "${{needs.deploy-gae.outputs.version-url}}"
production:
name: Update Production
environment: production
runs-on: ubuntu-latest
# Run only after successful deployment and test
needs: [deploy-gae, test]
# Run only on push to master
if: ${{ success() && github.ref == 'refs/heads/main' }}
steps:
- name: Import Service Account key
run: echo "${{ secrets.GOOGLE_APPLICATION_CREDENTIALS }}" | base64 -d > /tmp/auth.json
- name: GCloud Auth
run: |
gcloud --quiet auth \
activate-service-account "${{ secrets.GCP_SA_EMAIL }}" \
--key-file=/tmp/auth.json \
--project="${GCLOUD_PROJECT_ID}"
- name: Promote deployed version to production
run: |
gcloud --quiet app versions migrate "${GITHUB_ACTOR//[\[\]]/}-${GITHUB_SHA:0:7}" --service=default
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment