Skip to content

Instantly share code, notes, and snippets.

@copiousfreetime
Last active October 9, 2022 21:49
Show Gist options
  • Save copiousfreetime/4e41bd115b63bc928aa8e5f8daf26d1b to your computer and use it in GitHub Desktop.
Save copiousfreetime/4e41bd115b63bc928aa8e5f8daf26d1b to your computer and use it in GitHub Desktop.
Replicating Heroku CI/CD + Review Apps

How to deal with missing Heroku CI/CD and Review Apps

The reason for this

In particular the update from 2022-04-26 23:54:00 UTC

For the protection of our customers, we will not be reconnecting to GitHub until we are certain that we can do so safely, which may take some time We recommend that customers use alternate methods rather than waiting for us to restore this integration. [emphasis mine].

Generate HEROKU_API_KEY

You'll need to take a heroku use for your team/group/organization and make sure they have all the permissions needed. Then go to that account page, scrolldown to the bottom under the API Key section. Take that API Key and put it in GitHub secrets for your repository as HEROKU_API_KEY.

This does mean that ALL of the items that happen in heroku as part of these workflows will appear to happen as the user that is associated with that api key.

Setup GitHub Secrets/Environment variables

In addition to the HEROKU_API_KEY above, you'll need to replicate any and all "secret" items that were in your Heroku config vars for your Test and Review environments to either GitHub secrets, or hardcode them into the workflows below.

This example uses a combination of both. If you did have the same environment variable in both Heroku Test and Heroku Review, but with different values, you'll have to take that into account when you do the GitHub secrets. I'd probably recomented doing something like THING_KEY_TEST and then THING_KEY_REVIEW as GitHub secrets.

And then in your workflows in the appropriate sections for CI or deploy use the env: section to setup the appropriate environment varable of THING_KEY baed upon THING_KEY_REVIEW or THING_KEY_TEST as appropriate.

General Migration steps for CI

  1. Deal with the environment variables - possible approach above.
  2. Create a continuous_integration job as a GitHub action and use the values that you did have in your app.json file for environments.test.scripts.test-setup and environments.test.scripts.test and make them individual steps in the workflow.
  3. Turn on this workflow as appropriate for your GitHub events. In this workflow in the continuous_integration job is happening on every push to main and on every push to a branch that is part of a pull request.
  4. Once this is in place, if you want to make the CI be required for branch merging, go to the Branch Protection rules of your repo and put this job in as a status check requirement. It should show up in the dropdown list if the job has run at least once in the past week.

General Migration steps for "Review" apps

  1. Deal with the environment variables - possible approach above.
  2. Create a review_deploy job as a GitHub action that manges a heroku application in your pipeline in the development stage.
  3. Update the Set Member Permissions step as appropriate for your situation. The old permissions that we done for the review apps where "ephemeral" and had specific rules around them in heroku along with the team member status in the pipeline. This does not apply for what we are dong now so we need to be explicity about the permissions of heroku users.
  4. Wire up GitHub Deployment status around the review app so the PR links are good
  5. Create a review_destroy job as a GitHub action that destorys the appropriate heroku app on pull request close.

General Migration steps for Autodeployment to other stages of your pipeline

  1. Deal with the environment variables - possible approacch above.
  2. Create a <stage>_deploy job as a GitHub action that does does a heroku deploy based upon the appropriate branch from your repo. The example in this workflow deploys from the main branch of the repo to the staging stage of a heroku pipeline. And it also only does this if the continuous_integration job is successful.
name: "CI/CD"
on:
push:
branches:
- 'main'
pull_request:
types: [opened, synchronize, reopened, closed]
env:
HEROKU_TEAM: "my-heroku-team"
HEROKU_STAGING_APP: "my-staging-pipeline-app"
STAGING_APP_URL: "https://url-to-staging-pipeline-app.invalid/"
HEROKU_REVIEW_APP_ADDONS: "--addons=heroku-postgresql:hobby-dev"
HEROKU_PIPELINE_NAME: "my-heroku-pipeline-name"
# All the other environment variables your test environment needs
# XYZ_TOKEN: ${{ secrets.XYZ_TOKEN }}}
# SOMETHING_URL: ${{ secrets.SOMETHING_URL }}
jobs:
# Continus Integration job that runs all the tests - similar to what was
# done with the Heroku CI Test. If you had an in-dyno database on your CI Test
# runs, then this should also work for you here with the postgres service.
continuous_integration:
if: github.event.action != 'closed'
env:
DATABASE_URL: postgresql://postgres:[email protected]/dbname_test
NODE_ENV: test
NOCK_DISABLE_NETWORK: on
runs-on: ubuntu-latest
services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: postgres
POSTGRES_USER: postgres
ports:
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: "Check out repository code"
uses: actions/checkout@v3
- name: Setup Node
uses: actions/setup-node@v3
with:
node-version-file: .nvmrc
cache: 'npm'
- name: Update to latest npm
run: npm install npm@latest -g
- name: Install Node packages
run: npm install
- name: Setup the Test environment
run: ./ci/test-setup # This was what you did have in app.json for test environment for `test-setup` script
- name: Run tests
run: ./ci/test # This was hwat you had in app.json for test environment `test` script
# Auto deploy to the staging stage of the application pipelin when a push
# happens on the main branch in the repo. This replicates auto deployment to a
# heroku pipeline stage from a known branch. If you used a different branch,
# change it here and in the `on` section at the top of the workflow. Feel free
# to add other `_deploy` items if you had other branches going to other stages
#
# This also enforces that the test above run before the deploy happens
staging_deploy:
if: github.ref_name == 'main'
name: Deploy to Heroku Staging
needs: [continuous_integration]
runs-on: ubuntu-latest
steps:
- name: "Check out repository code"
uses: actions/checkout@v3
- name: Get git output names
id: git
shell: bash
run: |
if [[ "${{ github.ref }}" != "refs/tags/"* ]]; then
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
echo "::set-output name=current_branch::$HEAD_REF"
else
echo "::set-output name=current_branch::$REF_BRANCH"
fi
else
REF=$(printf "%q" "${{ github.ref }}")
REF_BRANCH=${REF/refs\/tags\/${{ inputs.strip_tag_prefix }}/}
echo "::set-output name=current_branch::$(eval printf "%s" "$REF_BRANCH")"
fi
# Wire up github deployments so that these can fire so if other
# integrations depended on the github deployment notifications, they can
# also still happen.
- uses: chrnorm/deployment-action@releases/v1
name: Create GitHub deployment
id: deployment
with:
initial_status: "in_progress"
token: "${{ github.token }}"
target_url: ${{ env.STAGING_APP_URL }}
environment: staging
ref: ${{ steps.git.outputs.current_branch }}
- uses: akhileshns/[email protected]
name: Heroku Deploy
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_app_name: ${{ env.HEROKU_STAGING_APP }}
heroku_email: ${{ secrets.HEROKU_EMAIL }}
- name: Update deployment status (success)
if: ${{ success() }}
uses: chrnorm/deployment-status@releases/v1
with:
token: "${{ github.token }}"
target_url: ${{ env.STAGING_APP_URL }}
state: "success"
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
ref: ${{ steps.git.outputs.current_branch }}
- name: Update deployment status (failure)
if: ${{ failure() }}
uses: chrnorm/deployment-status@releases/v1
with:
token: "${{ github.token }}"
target_url: ${{ env.STAGING_APP_URL }}
state: "failure"
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
ref: ${{ steps.git.outputs.current_branch }}
# Create a review app on pull request open or reopen, and push to the review
# app on PR syncronize a.k.a 'push'
review_deploy:
name: "Deploy to Heroku Review"
if: github.event_name == 'pull_request' && github.event.action != 'closed'
runs-on: ubuntu-latest
env:
HEROKU_APP_NAME: my-app-slug-review-pr-${{ github.event.number }}
steps:
- name: "Check out repository code"
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
- name: Get git output names
id: git
shell: bash
run: |
if [[ "${{ github.ref }}" != "refs/tags/"* ]]; then
if [[ ${{ github.event_name }} == 'pull_request' ]]; then
echo "::set-output name=current_branch::$HEAD_REF"
else
echo "::set-output name=current_branch::$REF_BRANCH"
fi
else
REF=$(printf "%q" "${{ github.ref }}")
REF_BRANCH=${REF/refs\/tags\/${{ inputs.strip_tag_prefix }}/}
echo "::set-output name=current_branch::$(eval printf "%s" "$REF_BRANCH")"
fi
# Wire up the github deployment so the links are valid for the PR under
# `view deployment`
- name: Create GitHub deployment
uses: chrnorm/deployment-action@releases/v1
id: deployment
with:
initial_status: "in_progress"
token: "${{ github.token }}"
target_url: https://${{ env.HEROKU_APP_NAME}}.herokuapp.com/
environment: ${{ env.HEROKU_APP_NAME }}
ref: ${{ github.head_ref }}
- name: Login to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_email: ${{ secrets.HEROKU_EMAIL }}
heroku_app_name: ${{ env.HEROKU_APP_NAME }}
justlogin: true
- name: Create Heroku app
if: github.event.action == 'opened' || github.event.action == 'reopened'
run: heroku apps:create ${{ env.HEROKU_APP_NAME }} --team=${{ env.HEROKU_TEAM }} ${{ env.HEROKU_REVIEW_APP_ADDONS }}
- name: Add Heroku app to pipeline
if: github.event.action == 'opened' || github.event.action == 'reopened'
run: heroku pipelines:add ${{ env.HEROKU_PIPELINE_NAME }} --app=${{ env.HEROKU_APP_NAME }} --stage=development
# This is where you set the environment variables that were part of the
# original Heroku Config Vars for the Review App. Update this as
# appropriate
- name: Set the environment variables
if: github.event.action == 'opened' || github.event.action == 'reopened'
run: |
env | grep AUTH0 > .env
env | grep API_AUDIENCE >> .env
env | grep HEALTHCLOUD >> .env
env | grep SALESFORCE >> .env
cat .env | tr '\n' ' ' | xargs heroku config:set --app=${{ env.HEROKU_APP_NAME }}
rm -f .env
- name: Add Heroku remote
if: github.event.action != 'closed'
run: heroku git:remote --app=${{ env.HEROKU_APP_NAME }}
# Depending on how your heroku organization is setup, the step may not
# be necessary. Previously in heroku review apps, the members of the
# pipeline team would be automatically given ephemeral permissions on the
# review app. That doesn't happen so this needs to be replicated.
#
# This will cause a lot of email notifications.
- name: Set Member Permissions
if: github.event.action != 'closed'
shell: bash --noprofile --norc {0}
run: |
users=$(heroku members --team ${{ env.HEROKU_TEAM }} | grep "something appropriate" | awk '{ print $1 }')
for user in ${users}
do
heroku access:add ${user} --app=${{ env.HEROKU_APP_NAME }} --permissions deploy,operate,manage
heroku access:update ${user} --app=${{ env.HEROKU_APP_NAME }} --permissions deploy,operate,manage
done
exit 0
- name: Push to Heroku
if: github.event.action != 'closed'
run: git push heroku ${{ github.head_ref }}:main --force
# Update the github deployment with success and link to the environment
- name: Update deployment status
if: github.event.action != 'closed'
uses: chrnorm/deployment-status@releases/v1
with:
token: "${{ github.token }}"
target_url: https://${{ env.HEROKU_APP_NAME}}.herokuapp.com/
environment_url: https://${{ env.HEROKU_APP_NAME}}.herokuapp.com/
state: "success"
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
# Once the PR is closed, destroy the app in heroku.
#
review_destroy:
if: github.event_name == 'pull_request' && github.event.action == 'closed'
name: "Cleanup and Remove Heroku Review"
runs-on: ubuntu-latest
env:
HEROKU_APP_NAME: my-app-slug-review-pr-${{ github.event.number }}
steps:
- name: Login to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_email: ${{ secrets.HEROKU_EMAIL }}
heroku_app_name: ${{ env.HEROKU_APP_NAME }}
justlogin: true
- name: Destroy Heroku app
run: heroku apps:destroy --app=${{ env.HEROKU_APP_NAME }} --confirm=${{ env.HEROKU_APP_NAME }}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment