Skip to content

Instantly share code, notes, and snippets.

@jwaldrip
Last active March 9, 2017 07:23
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jwaldrip/01bfa48f03816a4df4ad to your computer and use it in GitHub Desktop.
Save jwaldrip/01bfa48f03816a4df4ad to your computer and use it in GitHub Desktop.
Full CI-CD with Heroku and Circle CI
machine:
services:
- 'postgresql'
- 'redis'
environment:
REDIS_URL: 'redis://localhost:6379/0'
dependencies:
pre:
- 'git config user.email deploy+$CIRCLE_USERNAME@brandfolder.com'
- 'git config user.name $CIRCLE_USERNAME'
- 'bundle config --global frozen 1'
cache_directories:
- 'vendor/bundle'
test:
override:
- 'bundle exec rspec --format progress --color spec'
deployment:
# Deploy the pre-release develop branch to staging
staging:
branch: develop
commands:
- 'WORKER_DYNOS=1 ./bin/heroku-deploy brandfolder-staging $CIRCLE_SHA1'
# Deploy the production apps, production and admin
production:
branch: master
commands:
- 'WORKER_DYNOS=2 ./bin/heroku-deploy brandfolder-production $CIRCLE_SHA1'
- 'heroku config -s -a brandfolder-production | xargs heroku config:set -a brandfolder-admin'
- 'WORKER_DYNOS=0 ./bin/heroku-deploy brandfolder-admin $CIRCLE_SHA1'
# Create a new app on each deploy of a branch in the feature namespace
feature:
branch: /feature\/.*/
commands:
- 'mkdir -p ./tmp'
- 'echo "bf-$CIRCLE_BRANCH" | sed "s/feature\///" | cut -c 1-30 > ./tmp/.appname'
- 'heroku apps:destroy -a `cat ./tmp/.appname` --confirm `cat ./tmp/.appname` || true'
- 'heroku create `cat ./tmp/.appname`'
- 'heroku addons:add -a `cat ./tmp/.appname` heroku-postgresql'
- 'heroku addons:add -a `cat ./tmp/.appname` sendgrid'
- 'heroku addons:add -a `cat ./tmp/.appname` rediscloud'
- 'heroku config:set -a `cat ./tmp/.appname` SECRET_KEY_BASE=`rake secret` LOCKUP_CODEWORD=cantshowthis'
- 'SKIP_MIGRATIONS=true WORKER_DYNOS=0 ./bin/heroku-deploy `cat ./tmp/.appname` $CIRCLE_SHA1'
- 'heroku pg:info --app `cat ./tmp/.appname` | grep --color=never -oE "HEROKU_POSTGRESQL[A-Z_]+" > ./tmp/.dbname'
- 'heroku pg:reset --app `cat ./tmp/.appname` --confirm `cat ./tmp/.appname` `cat ./tmp/.dbname`'
- 'bundle exec rake db:rebuild'
- 'heroku pg:push --app `cat ./tmp/.appname` circle_ruby_test `cat ./tmp/.dbname`'
- 'curl -f https://`cat ./tmp/.appname`.herokuapp.com'
# Deploy all things in the hotfix namespace to the hotfix app and Copy settings from prod
hotfix:
branch: /hotfix\/.*/
commands:
- 'heroku config -s -a brandfolder-production | xargs heroku config:set -a brandfolder-hotfix'
- 'heroku config:set -a brandfolder-production CACHE_DISABLED=false'
- 'WORKER_DYNOS=0 ./bin/heroku-deploy brandfolder-hotfix $CIRCLE_SHA1'
# Notify Kato of the Deploy
notify:
webhooks:
- url: 'https://api.kato.im/rooms/d14ec35cbb7cd962ab6e134be39614634bcf1fb2da856b821c8e2e4de1f98a3a/circleci'
#!/usr/bin/env ruby
class Deployer
LAST_MIGRATION_CMD = %{bundle exec rake db:migrate:status | grep "up" | grep -oE "[0-9]{8}[0-9]+" | tail -1}
def self.deploy!
new.deploy!
end
def initialize
@appname = ARGV[0] || raise("appname required")
@sha = ARGV[1] || raise("sha required")
@worker_dynos = ENV['WORKER_DYNOS'] || "1"
end
def local_sha
ENV['CIRCLE_SHA'] || `git rev-parse --verify HEAD`.strip
end
def remote_sha
`heroku releases -n 1 -a #{@appname} | awk '/Deploy/ {print $3}'`.strip
end
def remote
"git@heroku.com:#{@appname}.git"
end
def deploy!
puts "deploying `#{@sha}` to `#{@appname}`"
with_migrations do
output = `git push #{remote} #{@sha}:refs/heads/master`
!output.lines.last.to_s.include?("Aborting")
end
finish
end
private
def finish
system("heroku scale worker=#{@worker_dynos} --app #{@appname}")
system("heroku maintenance:off --app #{@appname}")
system('git stash pop', out: '/dev/null', err: '/dev/null')
unless local_sha.include? remote_sha
STDERR.puts("deploy invalid, remote: #{remote_sha} does not match #{local_sha}")
exit 1
end
end
def pending_migrations?
return false if ENV['SKIP_MIGRATIONS']
return true if ENV['FORCE_MIGRATIONS']
@pending_migrations ||= begin
`#{LAST_MIGRATION_CMD}`.strip !=
`heroku run --app #{@appname} #{LAST_MIGRATION_CMD}`.strip
end
end
def with_migrations(&block)
return instance_eval(&block) unless pending_migrations?
system("heroku maintenance:on --app #{@appname}") || exit(1)
system("heroku ps:stop worker --app #{@appname}") || finish
instance_eval(&block)
system("heroku run rake db:migrate --app #{@appname}")
end
end
Deployer.deploy!
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment