Skip to content

Instantly share code, notes, and snippets.

@hopsoft
Last active January 14, 2024 11:58
Show Gist options
  • Save hopsoft/ac014e0efc7bfc1dbdb19ff7a66c5274 to your computer and use it in GitHub Desktop.
Save hopsoft/ac014e0efc7bfc1dbdb19ff7a66c5274 to your computer and use it in GitHub Desktop.
Smart Heroku Review Apps managed by GitHub Actions

Smart Heroku Review Apps managed by GitHub Actions

This gist aims to provide a simple solution for managing Heroku Review Apps with GitHub Actions due to the security incident that continues to disrupt Heroku's GitHub integration. Watch the demo to learn more.

Demo Video

.github
├── workflows
│   ├── heroku_review_app_create.yml
│   └── heroku_review_app_destroy.yml

Triggers

Create/Update Review App

  • Pull Request opened - only when labeled with review-app
  • Pull Request reopened - only when labeled with review-app
  • Pull Request synchronize (push) - only when labeled with review-app
  • Pull Request labeled

Destroy Review App

  • Pull Request labeled - only when labeled with review-app

Dependencies

Create or Update Heroku Review App

.github/workflows/heroku_review_app_create.yml

Steps

  1. Login to Heroku
  2. Set Heroku Review App name
  3. Get Heroku Review App info
  4. Set exists ENV vars
  5. Check out PR code from GitHub
  6. Create GitHub deployment
  7. Create Heroku Review App
  8. Set Heroku Review App environment
  9. Add Heroku Review App to pipeline
  10. Add Heroku git remote
  11. Push PR branch to Heroku
  12. Initialize the Heroku Review App database
  13. Migrate and seed the Heroku Review App database
  14. Update deployment status on GitHub to success

Destroy Heroku Review App

.github/workflows/heroku_review_app_destroy.yml

Steps

  1. Login to Heroku
  2. Set Heroku Review App name
  3. Get Heroku Review App info
  4. Set exists ENV vars
  5. Destroy Heroku Review App
  6. Remove GitHub label
  7. Comment on Pull Request

GitHub Secrets

You'll need to configure your GitHub repo with the following Action secrets. NOTE: This is the minimal set.

  • HEROKU_API_KEY - Your Heroku user's API key
  • HEROKU_EMAIL - Your Heroku user's email address
  • RAILS_MASTER_KEY - [optional] The Rails master key used for encrypted credentials

⚠️ You'll need to setup environment variables that Heroku depends on as Action Secrets and copy these over in the Set Heroku Review App environment step.


This effort is sponsored by Orbit.love (mission control for your community).

# SEE: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
on:
pull_request:
types:
- labeled
- opened
- reopened
- synchronize
# SEE: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
contents: read
deployments: write
pull-requests: read
# Update these variables for your project
env:
GITHUB_DEPLOYMENT_ENVIRONMENT: heroku-review-app
GITHUB_LABEL: review-app
HEROKU_PIPELINE_NAME: review-apps
HEROKU_REVIEW_APP_ADDONS: --addons=heroku-postgresql:hobby-dev
jobs:
heroku_review_app_check:
name: Create or Update Heroku Review App
runs-on: ubuntu-latest
if: github.event.pull_request.state != 'closed'
steps:
- name: Login to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_email: ${{ secrets.HEROKU_EMAIL }}
justlogin: true
- name: Set Heroku Review App name
run: echo HEROKU_APP_NAME="${{ env.HEROKU_PIPELINE_NAME }}-review-app-${{ github.event.number }}" >> $GITHUB_ENV
- name: Get Heroku Review App info
id: heroku_apps_info
continue-on-error: true
run: heroku apps:info -a ${{ env.HEROKU_APP_NAME }}
- name: Set exists ENV vars
run: |
echo HEROKU_APP_EXISTS="${{ steps.heroku_apps_info.outcome == 'success' }}" >> $GITHUB_ENV
echo GITHUB_LABEL_EXISTS="${{ contains(github.event.pull_request.labels.*.name, env.GITHUB_LABEL) }}" >> $GITHUB_ENV
- name: Check out PR code from GitHub
if: env.GITHUB_LABEL_EXISTS == 'true'
uses: actions/checkout@v3
with:
fetch-depth: 0
ref: ${{ github.head_ref }}
# Wire up the github deployment so links to `view deployment` are valid on the PR
- name: Create GitHub deployment
id: deployment
if: env.GITHUB_LABEL_EXISTS == 'true'
uses: chrnorm/deployment-action@releases/v1
with:
initial_status: in_progress
token: ${{ github.token }}
target_url: https://${{ env.HEROKU_APP_NAME}}.herokuapp.com/
environment: ${{ env.GITHUB_DEPLOYMENT_ENVIRONMENT }}
ref: ${{ github.head_ref }}
- name: Create Heroku Review App
id: heroku_review_app_create
if: env.GITHUB_LABEL_EXISTS == 'true' && env.HEROKU_APP_EXISTS == 'false'
run: heroku apps:create ${{ env.HEROKU_APP_NAME }} ${{ env.HEROKU_REVIEW_APP_ADDONS }}
# Setup Heroku env vars (copy GitHub secrets etc...)
- name: Set Heroku Review App environment
id: heroku_review_app_env
if: env.GITHUB_LABEL_EXISTS == 'true'
run: >-
heroku config:set
--app=${{ env.HEROKU_APP_NAME }}
RAILS_MASTER_KEY='${{ secrets.RAILS_MASTER_KEY }}'
EXAMPLE='${{ secrets.example }}'
- name: Add Heroku Review App to pipeline
if: steps.heroku_review_app_create.outcome == 'success'
run: heroku pipelines:add ${{ env.HEROKU_PIPELINE_NAME }} --app=${{ env.HEROKU_APP_NAME }} --stage=development
- name: Add Heroku git remote
id: heroku_git_remote
if: env.GITHUB_LABEL_EXISTS == 'true'
run: heroku git:remote --app=${{ env.HEROKU_APP_NAME }}
- name: Push PR branch to Heroku
id: heroku_git_push
if: steps.heroku_git_remote.outcome == 'success'
run: git push heroku ${{ github.head_ref }}:main --force
- name: Initialize the Heroku Review App database
if: steps.heroku_review_app_create.outcome == 'success' && steps.heroku_git_push.outcome == 'success'
run: heroku run rails db:schema:load --app=${{ env.HEROKU_APP_NAME }}
# Migrations must work and seeds must be idempotent
- name: Migrate and seed the Heroku Review App database
if: steps.heroku_git_push.outcome == 'success'
run: heroku run rails db:migrate db:seed --app=${{ env.HEROKU_APP_NAME }}
# Updates the PR with a link to the Review App deployment
- name: Update deployment status on GitHub to success
if: steps.heroku_git_push.outcome == 'success'
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 }}
# SEE: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows
on:
pull_request:
types:
- closed
- unlabeled
# SEE: https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions
permissions:
pull-requests: write
# Update these variables for your project
env:
GITHUB_LABEL: review-app
HEROKU_PIPELINE_NAME: review-apps
jobs:
# Destroys the PR's Review App on Heroku
heroku_review_app_destroy:
name: Destroy Heroku Review App
runs-on: ubuntu-latest
steps:
- name: Login to Heroku
uses: akhileshns/[email protected]
with:
heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
heroku_email: ${{ secrets.HEROKU_EMAIL }}
justlogin: true
- name: Set Heroku Review App name
run: echo HEROKU_APP_NAME="${{ env.HEROKU_PIPELINE_NAME }}-review-app-${{ github.event.number }}" >> $GITHUB_ENV
- name: Get Heroku Review App info
id: heroku_apps_info
continue-on-error: true
run: heroku apps:info -a ${{ env.HEROKU_APP_NAME }}
- name: Set exists ENV vars
run: |
echo HEROKU_APP_EXISTS="${{ steps.heroku_apps_info.outcome == 'success' }}" >> $GITHUB_ENV
echo GITHUB_LABEL_EXISTS="${{ contains(github.event.pull_request.labels.*.name, env.GITHUB_LABEL) }}" >> $GITHUB_ENV
- name: Destroy Heroku Review App
if: env.HEROKU_APP_EXISTS == 'true'
run: heroku apps:destroy --app=${{ env.HEROKU_APP_NAME }} --confirm=${{ env.HEROKU_APP_NAME }}
- name: Remove GitHub label
uses: actions-ecosystem/action-remove-labels@v1
if: env.GITHUB_LABEL_EXISTS == 'true'
with:
labels: ${{ env.GITHUB_LABEL }}
- name: Comment on PR
uses: thollander/actions-comment-pull-request@v1
if: env.HEROKU_APP_EXISTS == 'true'
with:
message: ":warning: The Heroku Review App **${{ env.HEROKU_APP_NAME }}** has been destroyed."
GITHUB_TOKEN: ${{ github.token }}
@mfittko
Copy link

mfittko commented Sep 22, 2023

This is genius, exactly what we're missing with heroku, thanks so much! 🙏

Why heroku sucks so far:

  • it's not easy to re-create a review app => labels ftw!
  • any pull request is deployed to heroku => labels ftw!
  • secrets management is very intransparent => use GitHub actions
  • it's not easy to hook into the data setup => use GitHub actions
  • heroku UI is confusing (at least for the regular dev) => use GitHub actions
  • No real visibility of what is going on when releases fail unless logging into heroku => use GitHub actions
  • ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment