_________ ______ _____ _____ ______ /_____ ________ ___ /______ _____ _____(_)_______ ______ ___ /_______ ________ _ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __ \__ ___/ / /_/ / / __/__ /_/ /_ / / /_/ /_ /_/ / _ / _ / / // /_/ / / /_ / /_/ /_ / \__,_/ \___/ _ .___/ /_/ \____/ _\__, / /_/ /_/ /_/ \__,_/ \__/ \____/ /_/ /_/ /____/ Deploy with style!
Deployinator is a deployment framework extracted from Etsy. We've been using it since late 2009 / early 2010.
Here is a blog post explaining our rationale behind it and how it helps us out.
Deployments are grouped by "stacks". You might have a "web" and "search" stack.
Each of those stacks might have different deployment environments, such as "staging" or "production".
You can map a button to each of these environments, to create multi-stage pushes within each stack.
Deployinator is a standard Rack app (mostly Sinatra).
Point your rack-capable web server at the config.ru
, and it should mostly work. All dependencies are managed with bundler, so a bundle install
should get your gems set up.
It has been tested with ruby 1.8.6, 1.8.7 and 1.9.2. For local development, you can use Pow!. Assuming you are using OSX, you'd want to do the following:
curl get.pow.cx | sh
cd ~/.pow
ln -s /path/to/deployinator ./deployinator
cd /path/to/deployinator
bundle install
echo "export HTTP_X_USERNAME=$USER\nexport HTTP_X_GROUPS=foo" > .powenv
- visit deployinator.dev in your browser
If you are using RVM you may need to echo the ruby version like so:
echo "rvm ruby-1.9.3-head" > .rvmrc
You may want to tell Pow to restart before each request for development:
touch ./tmp/always_restart.txt
If you are not using Pow, you can of-course run it like a typical rack application.
bundle exec rackup
At Etsy, we use an internal SSO similar to GodAuth, which sets http headers that are checked in deployinator.
This code is abstractable, and will be made more generic soon.
There are a list of urls that don't require authentication, those are currently defined at the top of helpers.rb
but should be either set in a config, or maybe a sinatra plugin?
To create a new stack, run the rake task "new_stack"
STACK=my_blog rake new_stack
Note: a stack name must not begin with a capital letter
A stack can be customized so that you have flexibility over the different environments within it (which correspond to buttons) and the methods that correspond to each button press.
By default, you will see a button called "deploy stackname" where stackname is the STACK defined in the rake command above. In your stack file, you can add a function called stackname_environments that returns an array of hashes. Each hash will correspond to a new environment, or button. For example if your stack is called web, you can define a function like so to define qa and production environments within your web stack:
def web_environments
[
{
:name => "qa",
:method => "qa_rsync",
:current_version => qa_version,
:current_build => current_qa_build,
:next_build => next_qa_build
},
{
:name => "production",
:method => "prod_rsync",
:current_version => prod_version,
:current_build => current_prod_build,
:next_build => next_prod_build
}
]
end
The keys of each hash describe what you will be pushing for that environment:
- :name - name of the environment
- :method - method name (string) that gets invoked when you press the button
- :current_version - method that returns the version that is currently deployed in this environment
- :current_build - method that returns the build that is currently deployed (usually inferred from the version)
- :next_build - method that returns the next build that is about to be deployed
Configuration settings live in config/*.rb. For example, your config/development.rb might look like this:
Deployinator.log_file = Deployinator.root(["log", "development.log"])
Deployinator.domain = "awesomeco.com"
Deployinator.hostname = "deployinator.dev"
Deployinator.graphite_host = "localhost"
Deployinator.github_host = "github.com"
Deployinator.default_user = "joboblee"
Deployinator.devbot_url = "http://botserver/bot/announce"
Deployinator.new_relic_options = {
:apikey => "12345",
:appid => "4567"
}
Pony.options = {
:via => :smtp,
:from => "deployinator@#{Deployinator.domain}",
:headers => {"List-ID" => "deploy-announce"},
:to => "joboblee@#{Deployinator.domain}",
:via_options => {
:address => 'smtp.gmail.com',
:port => '587',
:enable_starttls_auto => true,
:user_name => 'gmailuser@gmail.com',
:password => 'gmail-password',
:authentication => :plain,
:domain => Deployinator.domain
}
}
There are a few helpers built in that you can use after creating a new stack to assist you
Shell out to run a command line program. Includes timing information streams and logs the output of the command.
For example you could wrap your capistrano deploy:
run_cmd %Q{cap deploy}
Output information to the log file, and the streaming output handler. The real time output console renders HTML so you should use markup here.
log_and_stream "starting deploy<br>"
Output an announcement message with build related information.
Also includes hooks for Email and IRC.
log_and_shout({
:old_build => old_build,
:build => build,
:send_email => true
});
The supported keys for log_and_shout are:
- :env - the environment that is being pushed
- :user - the user that pushed
- :start - the start time of the push (if provided the command will log timing output)
- :end - the end time of the push (defaults to "now")
- :old_build - the existing version to be replaced
- :build - the new version to be pushed
- :irc_channels - comma separated list of IRC channels
- :send_email - true if you want it to email the announcement (make sure to define settings in config)
The IRC bot works by Deployinator issuing a POST request with the announcement to your internal web page, which should in turn initiate the bot communication. The page must accept at least a message parameter which deployinator populates.
For example you should define devbot in the config to be a url
which accepts a POST request with the message key and channels key. The channels values are supplied from the :irc_channels parameter passed to log_and_shout. Its up to you to implement what happens with the message and channels POST request parameters, so you can plug in more than just IRC bot communication here.
Prerequisite: Disable mod_deflate/mod_gzip on Apache
Passenger versions < 3.0.11
- Standard configurations will work
Passenger versions >= 3.0.11
- Set PassengerBufferResponse to off. This is a change as of 3.0.11 and will break streaming of the output.
Once you've made your great commits (with tests!):
- Fork Deployinator
- Create a topic branch -
git checkout -b my_branch
- Push to your branch -
git push origin my_branch
- Create an Issue with a link to your branch
Come by #deployinator on Freenode. Mailing list coming soon!