Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save stevenharman/98576bf49b050b9e59fb26626b7cceff to your computer and use it in GitHub Desktop.
Save stevenharman/98576bf49b050b9e59fb26626b7cceff to your computer and use it in GitHub Desktop.
Heroku Release Phase script for managing Rails DB migrations, and playing nice with Review Apps and postdeploy scripts

Heroku Release Phase + Review Apps + Rails

This is a simplified, but fairly thorough, set of scripts and configuration to enable Heroku Release Phase for Rails apps. Further, this particular set up plays nicely with Heroku Review Apps in that the release phase script will:

  1. Fail, loudly, if the DB does not yet exist.
  2. Load the DB schema if the current schema version (as determined by bin/rails db:version) is 0.
  3. Run DB migrations otherwise.

For a "normal" app that usually means it will run the DB migrations. For a Review App, on the first deploy the release phase will bin/rails db:schema:load. And then the postdeploy script will seed data. During subsequent deploys to the Review App, the release phase will bin/rails db:migrate.

{
"name": "your-app-name",
"description": "Configuration for per-Pull-Request Reviews Apps on Heroku.",
"scripts": {
"postdeploy": "LOG_LEVEL=INFO bin/rake review_app:seed"
},
"stack": "heroku-16",
"formation": [
{ "process": "web", "quantity": 1 },
{ "process": "worker", "quantity": 1 }
],
"addons": [
"heroku-postgresql:hobby-dev"
],
"buildpacks": [
{
"url": "heroku/ruby"
}
],
"env": {
"Lots of other Env Vars...": "as you need",
"HEROKU_APP_NAME": {
"required": true
},
"HEROKU_PARENT_APP_NAME": {
"required": true
}
}
}
#!/usr/bin/env bash
#
# Usage: bin/heroku_deploy
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
NO_COLOR='\033[0m'
set -euo pipefail
schema_version=$(bin/rails db:version | { grep "^Current version: [0-9]\\+$" || true; } | tr -s ' ' | cut -d ' ' -f3)
if [ -z "$schema_version" ]; then
printf "💀${RED} [Release Phase]: Database schema version could not be determined. Does the database exist?${NO_COLOR}\n"
exit 1
fi
if [ "$schema_version" -eq "0" ]; then
printf "\n⏳${YELLOW} [Release Phase]: Loading the database schema.${NO_COLOR}\n"
bin/rails db:schema:load
else
printf "\n⏳${YELLOW} [Release Phase]: Running database migrations.${NO_COLOR}\n"
bin/rails db:migrate
fi
printf "\n🎉${GREEN} [Release Phase]: Database is up to date.${NO_COLOR}\n"
web: bundle exec puma -C ./config/puma.rb
worker: DB_POOL_SIZE=${WORKER_CONCURRENCY:-20} bundle exec sidekiq -c ${WORKER_CONCURRENCY:-20} -t ${WORKER_TIMEOUT:-25} -q default,1 -q mailers,1
# === Heroku Release Phase, ignored by `heroku local`. See: https://devcenter.heroku.com/articles/release-phase
release: bin/heroku_release
# frozen_string_literal: true
namespace :review_app do
desc 'Ensure environment is one we shish to spread seed in'
task :ensure_review_app do
abort 'This is not a Heroku Review App' unless review_app?
end
desc 'Seeds a review app with a subset of realistic-looking data'
task :seed, [] => %w[
ensure_review_app
environment
db:seed
seed:administrator
seed:widgets
] do
Rails.logger.tagged('Seed App') { |l| l.info("Finished seeding new Review App: #{ENV['HEROKU_APP_NAME']}") }
end
def review_app?
!!ENV['HEROKU_PARENT_APP_NAME']
end
end
@thefotios
Copy link

Also, for those asking about running heroku restart. I'm not sure that's strictly necessary. I did run into problems when running tasks that relied on the new DB schema (eg, %w[db:migrate db:seed]). There's no problem if you run them as discrete rake commands, since the application code is reloaded, but in the same task, you'll need something like

  ##
  # This needs to be run after db:migrate if you plan on running any other tasks in the same rake process
  #  It will force AR to re-read the columns from the DB; otherwise you'll get errors trying to access new attributes
  task reset_column_information: :environment do
    ActiveRecord::Base.descendants.each(&:reset_column_information)
  end

@pebneter
Copy link

Interesting comment, @thefotios .
It's been a while since I have setup the bash script. Would it be worth migrating?

@softwaregravy
Copy link

@thefotios A side comment. In your review_app? method, I don't have a HEROKU_PARENT_APP_NAME available in my env. Maybe this is an older stack? However, I do something very similar, I just use the presence of HEROKU_PR_NUMBER.

if ENV.has_key? "HEROKU_PR_NUMBER"

@thefotios
Copy link

@pebneter It's up to you; they're functionally equivalent for this use case so it really all comes down to your use case and preference. We have a few more complicated things that run during our release and found it much easier to manage that in the Rakefile based approach for a few reasons

  1. Everything is in Ruby already, so it just felt more natural
  2. It was much easier to also tie it into our local development workflow (eg, something like rake local:setup can share the same steps)
  3. It was more easily testable

@softwaregravy Thanks for pointing that out. We don't actually use that logic, I was just trying to keep it consistent with the original gist. But I think you're right that some of that changed between the "old" and "new" review apps.

@stevenharman
Copy link
Author

@softwaregravy The HEROKU_PARENT_APP_NAME Config Var is defined as required in the app.json, meaning it inherited it from the parent app. So in the parent app (e.g., we always used our staging as the parent for Review Apps, so we'd set that Config Var there). As @thefotios said, all of this was build off of Review Apps v1 and I'm sure some things have changed in v2, but I've not updated this to reflect that.

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