Skip to content

Instantly share code, notes, and snippets.

@richardkmichael
Created October 18, 2012 14:11
Show Gist options
  • Save richardkmichael/3912072 to your computer and use it in GitHub Desktop.
Save richardkmichael/3912072 to your computer and use it in GitHub Desktop.
#!/bin/bash
# FIXME: Can't node write a PID file? This hopes the current user doesn't run more
# than one node process.
kill_node_server() {
kill -9 $(ps | grep -m 1 "node" | cut -c 1-5)
}
setup_heroku() {
gem install heroku -d --no-rdoc --no-ri
# export HEROKU_API_KEY=<api key>
heroku keys:clear
yes | heroku keys:add
sleep 3
}
# FIXME: Environments need a fail-consequence action, then the "else"
# can move inside this function too.
run_tests_in_environment() {
local environment=$1
NODE_ENV=$environment ./run_tests.sh $environment
}
print_banner(){
echo ""
echo ""
echo "---------- $1 ----------"
}
print_banner 'DEVELOPMENT'
# Node server removed:
# Do we really need to run a node server? run_tests.sh is just using cucumber.
if ! run_tests_in_environment development; then
echo "DEVELOPMENT tests fail."
exit 1
fi
print_banner 'STAGING'
# FIXME: The gem command is not the Unix-way(tm), so this won't work.
gem list heroku || setup_heroku
deploy_to_heroku_environment(){
local environment=$1
echo ""
echo "Started DEPLOYMENT to the ${environment} server"
git remote rm heroku >/dev/null 2>&1
yes | ruby travis-deployer.rb heroku-app-${environment}
git remote -v
yes | git push heroku master 2>&1 | grep -q "Everything up-to-date"
PUSH_TO_HEROKU_RESULT=$?
if [ $PUSH_TO_HEROKU_RESULT -eq 0 ]
then
# To really test for this, read the latest SHA1 from master and the remote;
# the 0 exit status is an indirect test.. ?
echo "${environment} server already has latest code deployed!"
echo "STOPPING, you should look into why this happened!"
echo ""
exit 0
fi
heroku config:set NODE_ENV=${environment} --app heroku-app-${environment}
}
echo "VERIFYING the STAGING server DEPLOYMENT"
if ! run_tests_in_environment staging; then
echo "STAGING tests fail, ROLLING BACK."
heroku releases:rollback --app heroku-app-staging
exit 1
fi
echo "DEPLOYMENT to STAGING was SUCCESSFUL"
print_banner 'PRODUCTION'
echo ""
echo "Started DEPLOYMENT to the PRODUCTION server"
git remote rm heroku >/dev/null 2>&1
yes | ruby travis-deployer.rb heroku-app-production
git remote -v
yes | git push heroku master 2>&1 | grep -q "Everything up-to-date"
RESULT=$?
if [ $RESULT -eq 0 ]
then
echo "PRODUCTION server already has latest code deployed!"
echo "STOPPING, you should look into why this happened!"
echo ""
exit 0
fi
heroku config:set NODE_ENV=production --app heroku-app-production
echo "VERIFYING the PRODUCTION server DEPLOYMENT"
if ! NODE_ENV=production ./run_tests.sh PRODUCTION
then
echo "DEPLOYMENT to PRODUCTION was NOT SUCCESSFUL"
echo "ROLLING-BACK PRODUCTION"
heroku releases:rollback --app heroku-app-production
echo "ROLLING-BACK STAGING"
git remote rm heroku >/dev/null 2>&1
yes | ruby travis-deployer.rb heroku-app-staging
heroku releases:rollback --app heroku-app-staging
exit 1
fi
echo "DEPLOYMENT to PRODUCTION was SUCCESSFUL"
echo "!!! AWESOME !!!"
exit 0
run_development() {
echo "Running All Features on DEVELOPMENT"
NODE_ENV=development ./node_modules/.bin/cucumber.js tests/cucumber/features
features=$?
exit $features
}
run_staging() {
echo "Running All Features on STAGING"
curl --silent --insecure --url "https://test.example.com/hirefire" # check if heroku is up
echo ""
NODE_ENV=staging ./node_modules/.bin/cucumber.js tests/cucumber/features
features=$?
exit $features
}
run_production() {
echo "Running @verify-against-production && ~@clean-mongo-db Features on PRODUCTION"
curl --silent --url "https://example.com/hirefire" # check if heroku is up
echo ""
NODE_ENV=production ./node_modules/.bin/cucumber.js tests/cucumber/features --tags @verify-against-production --tags ~@clean-mongo-db
features=$?
exit $features
}
TARGET_ENVIRONMENT=$1
run_${TARGET_ENVIRONMENT}
#!/usr/bin/env ruby
if ARGV.empty?
puts "Please provide the Heroku app name."
exit
end
heroku_app_name = ARGV[0]
File.open(".git/config", "a") do |git_config|
git_config.puts <<-EOF
[remote "heroku"]
url = [email protected]:#{heroku_app_name}.git
fetch = +refs/heads/*:refs/remotes/heroku/*
EOF
end
ssh_config = File.expand_path("~/.ssh/config")
File.open(ssh_config, "a") do |ssh_config|
ssh_config.puts <<-EOF
Host heroku.com
StrictHostKeyChecking no
CheckHostIP no
UserKnownHostsFile=/dev/null
EOF
end
@richardkmichael
Copy link
Author

deploy.sh

  • removed arg checking at beginning, it’s never used
  • refactored banner printing
  • removed running node development server - run_tests.sh uses cucumber (is a running server required for cucumber?)
  • refactor heroku setup (not finished: maybe an initialize() function: config multiple heroku remotes and test for/install the gem?)
  • need to finish refactoring heroku deployment
  • add a test for expected code version on a remote server

run_tests.sh

  • needs refactor (environments need a config block: a message / do curl test? / feature set to test)
  • move to deploy.sh? because, it is more or less the expression in “if ! NODE_ENV=… ./run_tests.sh ”

travis-deployer.rb

  • is this adapted from something else, because it has nothing to do with travis… ?
  • cleanup: known_hosts is really ssh_config, not a known hosts (also, it doesn’t need to run every time)
  • git config juggling can be removed if multiple heroku remotes (ex. “heroku-staging”, “heroku-production”)
  • this file can go away after heroku changes and ssh_config in deploy.sh:initialize() ?

Perhaps write it entirely in Ruby? But, it’s a Node project… so maybe only bash, and no Ruby? Although, the heroku gem introduces a Ruby dependency; so, use Ruby to make refactoring and config easier… ?

Give thought to exit status, where will the script be used?

@MarkNijhof
Copy link

Hi Richard,

Thanks for the feedback!

removed arg checking at beginning, it’s never used
I added this to make sure that I cannot accidentally call the deploy script locally after I committed some not ready to push yet code.

removed running node development server
This was of course not clear because you do not have the cucumber setup, but it would first run all tests locally against a local Node instance, MongoDB instance and RedisDB instance running the website. Then push to Heroku Staging and run all these tests again, I do this because running locally is a lot faster then against Heroku and the remote DB's. Then finally it goes to Heroku Production where only a subset of tests are being run.

refactor heroku setup
Yes this was one of my pain points as well, instead of doing things over and over again I should do it once and setup all different remotes at once.

add a test for expected code version on a remote server
This is interesting, how do you suggest we keep the history and update the version (I know Heroku has a deployment ID, so that could be stored somewhere. That I like a lot

config block
Here I don't exactly know what you mean by that?
move to deploy.sh?
I have this so that I can run the script locally on my machine, to ensure that the way I do stuff locally is how I do stuff on the CI as well.

is this adapted from something else, because it has nothing to do with travis
True I got this code from the TravisCI guys to use how to setup the Heroku remote for Git, I was lazy and just kept using that, would be nicer if I refactored that.

cleanup
Here I lack knowledge

git config juggling can be removed if multiple heroku remotes
Indeed that I am aware of, it would be good to pull this functionality into deploy.sh and setup everything in one go.

re Ruby vs Bash
I really don't care what language this is written in, I started in Bash because I execute a lot of commands and then I don't have to sh `` stuff from Ruby, but honestly performance in this script is not important at all :)

This is used in on the TravisCI server which will hook into GitHub so after each commit it will run (already does run now). so exit code basically 0 all is good anything else will fail the build and send me an e-mail after which I look at the logs.

https://travis-ci.org/ my project is a private repo so it is under a different url

Thanks again for all the feedback! Are you willing to keep helping me with this?

@richardkmichael
Copy link
Author

Re: the running Node server in development.

I did understand that you run tests locally, then staging, then production. But, I guessed the cucumber in the DEVELOPMENT case in run_tests.sh would handle the starting the development server, as cucumber in a Rails project doesn't require me to start the development Rails server separately.

In any case, if required, it would be nice to push the server-start down to hide it; it jumped out at me in the middle of the script - the other environments don't start a server (always running presumably, though, you do curl to test them -- that's a similar idea: "make sure test target server is available".).

@MarkNijhof
Copy link

The reason that my script doesn't do this is because in development I most of the time have the server running already and then it would fail to start on the same port, but I am open to suggestions on that one. Happy to kill the server before starting it again as well as this would mean all new code will be loaded.

The curl is also because Heroku has a long spinn up time and I don't want my first test to timeout because of that.

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