Name: deployinator
Owner: Etsy, Inc.
Description: Deployinate!
Created: 2011-07-29 19:32:38.0
Updated: 2018-01-12 09:12:12.0
Pushed: 2017-09-21 15:43:42.0
Homepage: http://etsy.me/deployinator
Size: 1412
Language: Ruby
GitHub Committers
User | Most Recent Commit | # Commits |
---|
Other Committers
User | Most Recent Commit | # Commits |
---|
_________ ______ _____ _____ ______ /_____ ________ ___ /______ _____ _____(_)_______ ______ ___ /_______ ________ _ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __ \__ ___/ / /_/ / / __/__ /_/ /_ / / /_/ /_ /_/ / _ / _ / / // /_/ / / /_ / /_/ /_ / \__,_/ \___/ _ .___/ /_/ \____/ _\__, / /_/ /_/ /_/ \__,_/ \__/ \____/ /_/ /_/ /____/ Deploy with style!
Deployinator is a deployment framework extracted from Etsy. We've been using it since late 2009 / early 2010. This has been revamped into a ruby gem.
Table of Contents
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.
This demo assumes you are using bundler to install deployinator. If you aren't you can skip the bundler steps.
Create a directory for your project to hold your specific code (outside of the gem and this will be your own internal repository). mkdir test_stacks
Create a Gemfile for bundler:
source 'https://rubygems.org'
gem 'etsy-deployinator', :git => 'https://github.com/etsy/deployinator.git', :branch => 'master', :require => 'deployinator'
$ bundle install --path vendor/bundle
$ shopt -s xpg_echo
$ echo "require 'deployinator'\nload 'deployinator/tasks/initialize.rake' " > Rakefile
bundle binstub etsy-deployinator
$ bundle exec rake 'deployinator:init[Company]'
./bin/deployinator-tailer.rb &
$ bundle exec rake 'deployinator:new_stack[test_stack]'
gem 'puma'
bundle install --path vendor/bundle --no-deployment && bundle install --path vendor/bundle --deployment
-p
flag.$ bundle exec puma -b tcp://0.0.0.0:7777 config.ru
You will probably want a robust server like apache to handle production traffic.
The config/base.rb
file is the base config for the application. Replace all
occurences of test_stack with the name you chose above. Also the example
below uses a git repository of http://github.com/etsy/deployinator, feel free to
replace this with your specific repository
oyinator.app_context['test_stack_config'] = {
prod_host => "localhost",
checkout_path => "/tmp/deployinator_dev/"
oyinator.git_info_for_stack = {
:test_stack => {
:user => "etsy",
:repository => "deployinator"
}
ire 'helpers/test_stack'
le Deployinator
dule Stacks
class TestStackDeploy < Deployinator::Deploy
include Deployinator::Helpers::TestStackHelpers,
Deployinator::Helpers::GitHelpers
def test_stack_production(options={})
# save old version for announcement
old_version = test_stack_production_version
checkout_path = Deployinator.app_context['test_stack_config'][:checkout_path]
prod_host = Deployinator.app_context['test_stack_config'][:prod_host]
# Clone and update copy of git repository
git_freshen_or_clone(stack, "ssh #{prod_host}", checkout_path, "master")
# bump version
version = git_bump_version(stack, checkout_path, "ssh #{prod_host}", checkout_path)
# Write the sha1s of the different versions out to the logs for safe keeping.
log_and_stream "Updating application to #{version} from #{old_version}"
# log the deploy
log_and_shout :old_build => get_build(old_version), :build => get_build(version)
end
end
d
le Deployinator
dule Helpers
module TestStackHelpers
def test_stack_production_version
%x{ssh #{Deployinator.app_context["test_stack_config"][:prod_host]} cat #{Deployinator.app_context["test_stack_config"][:checkout_path]}/#{stack}/version.txt}
end
end
d
Create the directory that will contain the checkout if it doesn't exist already
(defined in config/base.rb
)
Load up deployinator and deploy your stack!
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. In your helpers 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 in helpers/web.rb to define qa and production environments within your web stack:
def web_environments
[
{
:name => "qa",
:method => "qa_rsync",
:current_version => proc{send(:qa_version)},
:current_build => proc{send(:current_qa_build)},
:next_build => proc{send(:next_qa_build)}
},
{
:name => "production",
:method => "prod_rsync",
:current_version => proc{send(:prod_version)},
:current_build => proc{send(:current_prod_build)},
:next_build => proc{send(:next_prod_build)}
}
]
end
The keys of each hash describe what you will be pushing for that environment:
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.
log_and_shout({
:old_build => old_build,
:build => build,
:send_email => true
});
The supported keys for log_and_shout are:
Deployinator provides various entry points to execute plugins without having to modify the gem code. Here is a list of current pluggable events:
integration)
To create a plugin simply create a new class (example from our code) that defined a run method taking event and state. event is a symbol from the table above and state is a hash of state data which varies from event to event
ire 'deployinator/plugin'
ire 'helpers/etsy'
ire 'deployinator/helpers'
le Deployinator
ass GraphitePlugin < Plugin
include Deployinator::Helpers::EtsyHelpers,
Deployinator::Helpers
def run(event, state)
case event
when :run_command_end
unless state[:timing_metric].nil?
graphite_timing "deploylong.#{state[:stack]}.#{state[:timing_metric]}", "#{state[:time]}", state[:start_time].to_i
end
when :timing_log
graphite_timing("deploylong.#{state[:stack]}.#{state[:type]}", "#{state[:duration]}", state[:timestamp])
end
return nil
end
d
Then simply require your plugin in lib/app.rb
and add it to your
config/base.rb
like this:
oyinator.global_plugins = []
oyinator.global_plugins << "GraphitePlugin"
You can also configure plugins to only apply to a single stack like this:
oyinator.stack_plugins = {}
oyinator.stack_plugins["test_stack"] << "TestStackPlugin"
Since the main layout page is contained within the gem, there are tags provided to allow you to add things to it in the header and body. List of points:
To set these simple override the methods in your view class. For example:
def additional_bottom_body_html
'<script src="/js/check_push_status.js"></script>'
end
This can be done on a global layout that extends the gem's default layout or on a stack by stack basis in their own view.
Deployinator has a setting for maintenance mode, which is mostly useful if you
have major changes that affect all stacks and you want to make sure no deploys
are going on while you make the change. In order to enable it, you have to set
Deployinator.maintenance_mode = true
. This will make all pages for anyone
not in Deployinator.admin_groups
go to /maintenance
. As a Deployinator
admin you can then still deploy stacks and use the app.
On the maintenance page Deployinator will show the value of
Deployinator.maintenance_contact
as the place to get help in case you need
any or are confused about maintenance mode.
If you find issues with the gem, or would like to play around with it, you can check it out from git and start hacking on it. First tell bundler to use your local copy instead by running:
$ bundle config local.deployinator /path/to/DeployinatorGem
Next, on every code change, you can install from the checked out gem by running (you will want to make commits to the gem to update the sha in the Gemfile.lock)
$ bundle install --no-deployment && bundle install --deployment
The /stats
page pulls from log/deployinator.log
to show a graph of deployments per day for each stack over time. By default, it shows all stacks. To blacklist or whitelist certain stacks, update config/base.rb
with:
ployinator.stats_included_stacks = ['my_whitelisted_stack', 'another_whitelisted_stack']
ployinator.stats_ignored_stacks = ['my_stack_to_ignore', 'another_stack_to_ignore']
ployinator.stats_extra_grep = 'Production deploy' # filter out log entries matching this string
Whitelisting stacks or applying a custom extra grep can help speed up graph rendering when you have a large log file.
If at some point you change the name of a stack, you can group the old log entries with the new by adding the following to config/base.rb
:
loyinator.stats_renamed_stacks = [
:previous_stack => {
:stack => [ "old_stack_name" ]
},
:new_name => "new_stack_name"
git checkout -b my-new-feature
)git commit -am 'Add some feature'
)git push origin my-new-feature
)