Last active
June 11, 2021 15:18
-
-
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)
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
#!/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}" |
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
# 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 |
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
# 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