Skip to content

Instantly share code, notes, and snippets.

@theoephraim
Created December 22, 2015 15:50
Show Gist options
  • Save theoephraim/d2087563cfef8479d3fa to your computer and use it in GitHub Desktop.
Save theoephraim/d2087563cfef8479d3fa to your computer and use it in GitHub Desktop.
Capistrano style deploys using flightplan
plan = require 'flightplan'
moment = require 'moment-timezone'
_ = require 'lodash'
nconf = require 'nconf'
request = require 'superagent'
async = require 'async'
# TODO: move to /lib like the others
require './src/run-init'
USER = 'breather'
NUM_DEPLOYS = 5
SUBDOMAIN = nconf.get('BREATHER_SUBDOMAIN')
REPO = nconf.get('REPO_NAME')
REPO_URL = "https://github.com/breather/#{REPO}/"
NO_FRONTEND = !!nconf.get('NO_FRONTEND')
ssh_key = nconf.get('BREATHER_SSH_KEY') || "#{process.env.HOME}/.ssh/id_rsa"
deploy_branch = 'master'
# SERVERS
plan.target 'staging', _.map nconf.get('IP:staging').split(' '), (ip) ->
host: ip
username: USER
privateKey: ssh_key
plan.target 'production', _.map nconf.get('IP:production').split(' '), (ip) ->
host: ip
username: USER
privateKey: ssh_key
# TASKS
plan.local 'deploy', (local) ->
# confirm deployment to production
if plan.runtime.target == 'production'
input = local.prompt('Are you sure you want to deploy to production? [yes/no]')
if input.indexOf('yes') == -1
plan.abort('user canceled deploy')
if plan.runtime.options.branch
deploy_branch = plan.runtime.options.branch
local.log("Pushing master to #{SUBDOMAIN}.breather.com")
else
git_dirty = local.exec('git diff --shortstat')
if git_dirty.stdout
plan.abort('Please commit your local changes and push them to github to deploy')
git_local_commit = local.exec('git rev-parse HEAD').stdout
git_remote_commit = local.exec('git rev-parse @{u}').stdout
if git_local_commit != git_remote_commit
plan.abort('push local commits to github to deploy')
current_branch = local.exec("git rev-parse --abbrev-ref HEAD").stdout
deploy_branch = current_branch.replace("\n", '')
if plan.runtime.target != 'staging'
env_type = "#{plan.runtime.options.customTarget || plan.runtime.target}"
local.log("Pushing #{deploy_branch} to #{SUBDOMAIN}#{env_type || ''}.breatherdev.com")
# DEPLOY - deploys current branch for staging or master for production
plan.remote 'deploy', (remote) ->
# set nocache to not use the old node_modules and bower libs
no_dependency_cache = !!plan.runtime.options.nocache
deploy_name = moment.tz('America/New_York').format('YYYY-MM-DD-HHmmss')+'-'+deploy_branch.replace(/\//g, '-')
remote.log 'Deploying -- '+deploy_name
remote.log 'Checking out latest from github'
remote.exec "mkdir -p #{REPO}"
remote.exec "cd #{REPO} && git clone [email protected]:breather/#{REPO}.git -b #{deploy_branch} #{deploy_name}"
cd = "cd #{REPO}/#{deploy_name} &&"
if !no_dependency_cache
remote.log 'Using cached dependencies from last deploy (npm)'
remote.exec "cp -R #{REPO}/current/node_modules #{REPO}/#{deploy_name}/node_modules"
if !NO_FRONTEND
remote.exec "mkdir #{REPO}/#{deploy_name}/assets/lib-bower"
remote.exec "cp -R #{REPO}/current/assets/lib-bower #{REPO}/#{deploy_name}/assets/lib-bower"
remote.log 'Installing dependencies'
remote.exec "#{cd} npm install --production"
if !NO_FRONTEND
remote.log 'Building assets'
remote.exec "#{cd} ./node_modules/.bin/grunt build"
remote.log 'Deploying assets'
remote.exec "#{cd} ./node_modules/.bin/grunt deploy-assets"
remote.log 'Relinking new deployment'
remote.exec "ln -snf ~/#{REPO}/#{deploy_name} ~/#{REPO}/current"
remote.log 'Reloading application (zero-downtime)'
remote.exec "pm2 reload all"
remote.log "Deleting old deploys (last #{NUM_DEPLOYS} are saved)"
remote.exec "cd #{REPO} && rm -rf `ls -td 201* | awk 'NR>#{NUM_DEPLOYS}'`"
# ROLLBACK - rolls back and restarts
plan.remote 'rollback', (remote) ->
deploys = remote.exec("cd #{REPO} && ls -td 201*").stdout.replace(/\n$/, '')
deploys = deploys.split("\n")
current_deploy = remote.exec("readlink -f #{REPO}/current").stdout
current_deploy = current_deploy.substr(current_deploy.lastIndexOf('/')+1).replace("\n", '')
current_index = _.indexOf(deploys, current_deploy)
num_available = deploys.length - current_index - 1
if num_available == 0
plan.abort('No old deploys available to roll back to')
rollback_num = remote.prompt("How many deploys do you want to rollback? [default: 1, max: #{num_available}]")
if rollback_num == '' then rollback_num = 1
rollback_num = parseInt(rollback_num)
if rollback_num < 1 || rollback_num > num_available
plan.abort('Invalid # of deploys to roll back')
rollback_name = deploys[current_index + rollback_num]
remote.log "Relinking deployment #{rollback_num} back"
remote.exec "ln -snf ~/#{REPO}/#{rollback_name} ~/#{REPO}/current"
remote.log 'Reloading application (zero-downtime)'
remote.exec "pm2 reload all"
# RESTART - restarts pm2
plan.remote 'restart', (remote) ->
remote.log 'Restarting application'
if plan.runtime.options.force
remote.exec 'pm2 kill'
# spawn as many instances as we have cores on the machine
remote.exec "cd #{REPO} && pm2 start current/app.coffee -i 0 --name \"#{REPO}\""
else
remote.exec "pm2 reload all"
# STATUS - shows pm2 status
plan.remote 'status', (remote) ->
remote.exec "pm2 list"
# STATUS - shows pm2 status
plan.remote 'logs', (remote) ->
remote.exec "pm2 logs"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment