etsy-deployinator 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +22 -0
  4. data/README.md +368 -0
  5. data/Rakefile +16 -0
  6. data/bin/deployinator-tailer.rb +78 -0
  7. data/deployinator.gemspec +31 -0
  8. data/lib/deployinator.rb +114 -0
  9. data/lib/deployinator/app.rb +204 -0
  10. data/lib/deployinator/base.rb +58 -0
  11. data/lib/deployinator/config.rb +7 -0
  12. data/lib/deployinator/controller.rb +147 -0
  13. data/lib/deployinator/helpers.rb +610 -0
  14. data/lib/deployinator/helpers/deploy.rb +68 -0
  15. data/lib/deployinator/helpers/dsh.rb +42 -0
  16. data/lib/deployinator/helpers/git.rb +348 -0
  17. data/lib/deployinator/helpers/plugin.rb +50 -0
  18. data/lib/deployinator/helpers/stack-tail.rb +32 -0
  19. data/lib/deployinator/helpers/version.rb +62 -0
  20. data/lib/deployinator/helpers/view.rb +67 -0
  21. data/lib/deployinator/logging.rb +16 -0
  22. data/lib/deployinator/plugin.rb +7 -0
  23. data/lib/deployinator/stack-tail.rb +34 -0
  24. data/lib/deployinator/static/css/diff_style.css +283 -0
  25. data/lib/deployinator/static/css/highlight.css +235 -0
  26. data/lib/deployinator/static/css/style.css +1223 -0
  27. data/lib/deployinator/static/js/flot/jquery.flot.min.js +1 -0
  28. data/lib/deployinator/static/js/flot/jquery.flot.selection.js +299 -0
  29. data/lib/deployinator/static/js/jquery-1.8.3.min.js +2 -0
  30. data/lib/deployinator/static/js/jquery-ui-1.8.24.min.js +5 -0
  31. data/lib/deployinator/static/js/jquery.timed_bar.js +36 -0
  32. data/lib/deployinator/tasks/initialize.rake +84 -0
  33. data/lib/deployinator/tasks/tests.rake +22 -0
  34. data/lib/deployinator/templates/deploys_status.mustache +42 -0
  35. data/lib/deployinator/templates/generic_single_push.mustache +64 -0
  36. data/lib/deployinator/templates/index.mustache +12 -0
  37. data/lib/deployinator/templates/layout.mustache +604 -0
  38. data/lib/deployinator/templates/log.mustache +24 -0
  39. data/lib/deployinator/templates/log_table.mustache +90 -0
  40. data/lib/deployinator/templates/messageboxes.mustache +29 -0
  41. data/lib/deployinator/templates/run_logs.mustache +15 -0
  42. data/lib/deployinator/templates/scroll_control.mustache +8 -0
  43. data/lib/deployinator/templates/stream.mustache +13 -0
  44. data/lib/deployinator/version.rb +3 -0
  45. data/lib/deployinator/views/deploys_status.rb +22 -0
  46. data/lib/deployinator/views/index.rb +14 -0
  47. data/lib/deployinator/views/layout.rb +48 -0
  48. data/lib/deployinator/views/log.rb +12 -0
  49. data/lib/deployinator/views/log_table.rb +35 -0
  50. data/lib/deployinator/views/run_logs.rb +32 -0
  51. data/templates/app.rb.erb +7 -0
  52. data/templates/config.ru.erb +10 -0
  53. data/templates/helper.rb.erb +15 -0
  54. data/templates/stack.rb.erb +17 -0
  55. data/templates/template.mustache +1 -0
  56. data/templates/view.rb.erb +7 -0
  57. data/test/unit/helpers_dsh_test.rb +40 -0
  58. data/test/unit/helpers_test.rb +77 -0
  59. data/test/unit/version_test.rb +104 -0
  60. metadata +245 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: cd2670b5a14fac9a1b92c16876c4d361f5d6f49a
4
+ data.tar.gz: a23addf61909a7feb8b9f56bdfbaff2faf30fcef
5
+ SHA512:
6
+ metadata.gz: be256bfe8ee18d7c03d4f92424f37ed98eb6ff2e92d776feafafd351f382bfdb981de5c5766a4a25d8819b4e035b447ba25b19a5c91bf6e17c6e4961eb604b29
7
+ data.tar.gz: 6f638ef633763f9ac4a4f4a2b097cb508031eae0fb2482e14d654991c42375c2942ef50d85b56f6ca9a01d6c6057042e39b1b26f6c4c9f622807343fead188b2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in deployinator.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Etsy
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,368 @@
1
+ <pre>
2
+ _________ ______ _____ _____
3
+ ______ /_____ ________ ___ /______ _____ _____(_)_______ ______ ___ /_______ ________
4
+ _ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __ \__ ___/
5
+ / /_/ / / __/__ /_/ /_ / / /_/ /_ /_/ / _ / _ / / // /_/ / / /_ / /_/ /_ /
6
+ \__,_/ \___/ _ .___/ /_/ \____/ _\__, / /_/ /_/ /_/ \__,_/ \__/ \____/ /_/
7
+ /_/ /____/ Deploy with style!
8
+ </pre>
9
+
10
+ Deployinator - Deploy code like Etsy
11
+ ====================================
12
+
13
+ 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.
14
+
15
+ **Table of Contents**
16
+
17
+ - [Stacks](#stacks)
18
+ - [Installation](#installation)
19
+ - [Usage](#usage)
20
+ - [Example Stack](#example-stack)
21
+ - [Customizing your stack](#customizing-your-stack)
22
+ - [Useful helper methods](#useful-helper-methods)
23
+ - [Plugins](#plugins)
24
+ - [Template Hooks](#template-hooks)
25
+ - [Hacking on the gem](#hacking-on-the-gem)
26
+ - [Contributing](#contributing)
27
+
28
+ ## Stacks
29
+ Deployments are grouped by "stacks". You might have a "web" and "search" stack.
30
+
31
+ Each of those stacks might have different deployment environments, such as "staging" or "production".
32
+
33
+ You can map a button to each of these environments, to create multi-stage pushes within each stack.
34
+
35
+ ## Installation
36
+
37
+ This demo assumes you are using bundler to install deployinator. If you aren't
38
+ you can skip the bundler steps.
39
+
40
+ - Create a directory for your project. `mkdir test_stacks`
41
+
42
+ - Create a Gemfile for bundler:
43
+
44
+ ```ruby
45
+ source 'https://rubygems.org'
46
+ gem 'etsy-deployinator', :git => 'https://github.com/etsy/deployinator.git', :branch => 'master', :require => 'deployinator'
47
+ ```
48
+
49
+ - Install all required gems with bundler:
50
+
51
+ ```sh
52
+ $ bundle install --path vendor/bundle
53
+ ```
54
+
55
+ - Run the following command to make deployinator gem's rake tasks available to you:
56
+
57
+ ```sh
58
+ $ echo "require 'deployinator'\nload 'deployinator/tasks/initialize.rake' " > Rakefile
59
+ ```
60
+
61
+ - Create a binstub for the deploy log tailing backend:
62
+
63
+ ```sh
64
+ bundle install --binstubs deployinator
65
+ ```
66
+
67
+ - Initialize the project directory by running the following command replacing ___Company___ with the name of your company/organization. This must start with a capital letter.
68
+
69
+ ```sh
70
+ $ bundle exec rake 'deployinator:init[Company]'
71
+ ```
72
+
73
+ - If you are using bundler add the following to the top of the config.ru that
74
+ shipped with deployinator
75
+ ```ruby
76
+ require 'rubygems'
77
+ require 'bundler'
78
+ Bundler.require
79
+ ```
80
+
81
+ - Run the tailer as a background service (using whatever init flavor you like)
82
+
83
+ ```sh
84
+ ./bin/deployinator-tailer.rb &
85
+ ```
86
+
87
+
88
+ ## Usage
89
+
90
+
91
+ ### Example Stack
92
+ - Use the deployinator rake task to create the stub for your stack. Replace
93
+ ___test_stack___ with the name of your stack. This should be all lowercase with
94
+ underscores but if you forget the rake task will convert from camelcase for you. The commands run by the rake tasks are logged to stderr.
95
+
96
+ ```sh
97
+ $ bundle exec rake 'deployinator:new_stack[test_stack]'
98
+ ```
99
+
100
+ - We need a server to run our Sinatra application. For the purpose of this demo, we will use [shotgun](https://github.com/rtomayko/shotgun). Let's install shotgun into our bundle. Add the following to your Gemfile:
101
+
102
+ ```ruby
103
+ gem 'shotgun'
104
+ ```
105
+
106
+ - Now update your bundler:
107
+
108
+ ```sh
109
+ bundle install --path vendor/bundle --no-deployment && bundle install --path vendor/bundle --deployment
110
+ ```
111
+
112
+ - Start the server by running:
113
+ - The host could be localhost or the dns name (or ip address of the server you are using). You can set any port you want that's not in use using the `-p` flag.
114
+
115
+ ```sh
116
+ $ bundle exec shotgun --host localhost -p 7777 config.ru
117
+ ```
118
+
119
+ - You will probably want a robust server like apache to handle production traffic.
120
+
121
+ - The `config/base.rb` file is the base config for the application. Replace all
122
+ occurences of ___test_stack___ with the name you chose above. Also the example
123
+ below uses a git repository of http://github.com/etsy/deployinator, feel free to
124
+ replace this with your specific repository
125
+
126
+ ```ruby
127
+ Deployinator.app_context['test_stack_config'] = {
128
+ :prod_host => "localhost",
129
+ :checkout_path => "/tmp/deployinator_dev/"
130
+ }
131
+ Deployinator.git_info_for_stack = {
132
+ :test_stack => {
133
+ :user => "etsy",
134
+ :repository => "deployinator"
135
+ }
136
+ }
137
+ ```
138
+
139
+ - Edit the stacks/test_stack.rb file to look like this (adding git version bumping and checkout)
140
+
141
+ ```ruby
142
+ require 'helpers/test_stack'
143
+ module Deployinator
144
+ module Stacks
145
+ class TestStackDeploy < Deployinator::Deploy
146
+ include Deployinator::Helpers::TestStackHelpers,
147
+ Deployinator::Helpers::GitHelpers
148
+
149
+ def test_stack_production(options={})
150
+ # save old version for announcement
151
+ old_version = test_stack_production_version
152
+
153
+ # Clone and update copy of git repository
154
+ git_freshen_or_clone(stack, "ssh #{Deployinator.app_context['test_stack_info'][:prod_host]}", stack_config[:checkout_path], "master")
155
+
156
+ # bump version
157
+ version = git_bump_version(stack, checkout_path, "ssh #{Deployinator.app_context['test_stack_info'][:prod_host]}", checkout_path)
158
+
159
+ # Write the sha1s of the different versions out to the logs for safe keeping.
160
+ log_and_stream "Updating application to #{version} from #{old_version}"
161
+
162
+ # log the deploy
163
+ log_and_shout :old_build => get_build(old_version), :build => get_build(version)
164
+ end
165
+ end
166
+ end
167
+ end
168
+ ```
169
+
170
+ - Next, edit the helpers/test_stack.rb file. You can delete the test_stack_head_build function since you are using the GitHelpers and that is automatically taken care of for you. Here is the final version:
171
+
172
+ ```ruby
173
+ module Deployinator
174
+ module Helpers
175
+ module TestStackHelpers
176
+ def test_stack_production_version
177
+ %x{ssh #{Deployinator.app_context["test_stack_config"][:prod_host]} cat #{Deployinator.app_context["test_stack_config"][:checkout_path]}/#{stack}/version.txt}
178
+ end
179
+ end
180
+ end
181
+ end
182
+ ```
183
+
184
+ - Create the directory that will contain the checkout if it doesn't exist already
185
+ (defined in `config/base.rb`)
186
+
187
+ - Load up deployinator and deploy your stack!
188
+
189
+ ### Customizing your stack
190
+
191
+ 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.
192
+
193
+ 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:
194
+
195
+ def web_environments
196
+ [
197
+ {
198
+ :name => "qa",
199
+ :method => "qa_rsync",
200
+ :current_version => proc{send(:qa_version)},
201
+ :current_build => proc{send(:current_qa_build)},
202
+ :next_build => proc{send(:next_qa_build)}
203
+ },
204
+ {
205
+ :name => "production",
206
+ :method => "prod_rsync",
207
+ :current_version => proc{send(:prod_version)},
208
+ :current_build => proc{send(:current_prod_build)},
209
+ :next_build => proc{send(:next_prod_build)}
210
+ }
211
+ ]
212
+ end
213
+
214
+ The keys of each hash describe what you will be pushing for that environment:
215
+
216
+ * __:name__ - name of the environment
217
+ * __:method__ - method name (string) that gets invoked when you press the button (this is defined in the stack)
218
+ * __:current_version__ - method that returns the version that is currently deployed in this environment (defined in the helper)
219
+ * __:current_build__ - method that returns the build that is currently deployed (usually inferred from the version, also defined in the helper)
220
+ * __:next_build__ - method that returns the next build that is about to be deployed (defined in the helper)
221
+
222
+ ### Useful helper methods
223
+ There are a few helpers built in that you can use after creating a new stack to assist you
224
+
225
+ #### run_cmd
226
+
227
+ Shell out to run a command line program.
228
+ Includes timing information streams and logs the output of the command.
229
+
230
+ For example you could wrap your capistrano deploy:
231
+
232
+ run_cmd %Q{cap deploy}
233
+
234
+
235
+ #### log_and_stream
236
+
237
+ Output information to the log file, and the streaming output handler.
238
+ The real time output console renders HTML so you should use markup here.
239
+
240
+ log_and_stream "starting deploy<br>"
241
+
242
+ #### log_and_shout
243
+
244
+ Output an announcement message with build related information.
245
+ Also includes hooks for Email.
246
+
247
+ log_and_shout({
248
+ :old_build => old_build,
249
+ :build => build,
250
+ :send_email => true
251
+ });
252
+
253
+ The supported keys for log_and_shout are:
254
+
255
+ * __:env__ - the environment that is being pushed
256
+ * __:user__ - the user that pushed
257
+ * __:start__ - the start time of the push (if provided the command will log timing output)
258
+ * __:end__ - the end time of the push (defaults to "now")
259
+ * __:old_build__ - the existing version to be replaced
260
+ * __:build__ - the new version to be pushed
261
+ * __:send_email__ - true if you want it to email the announcement (make sure to define settings in config)
262
+
263
+ ### Plugins
264
+ Deployinator provides various entry points to execute plugins without having to
265
+ modify the gem code. Here is a list of current pluggable events:
266
+
267
+ - __:logout_url__ for defining your own authentications logout url
268
+ - __:deploy_start__ for any actions to be performed at the start of a deploy
269
+ - __:deploy_end__ for any actions to be performed at the end of a deploy
270
+ - __:deploy_error__ for any actions to be performed when an error occurs during a
271
+ deploy
272
+ - __:run_command_start__ for any actions to be performed at the start of a run_cmd
273
+ call
274
+ - __:run_command_end__ for any actions to be performed at the end of a run_cmd call
275
+ - __:run_command_error__ for any actions to be performed when an error occurs during a
276
+ run_cmd call
277
+ - __:timeout__ for any actions to be performed when a with_timeout calls times out
278
+ - __:announce__ for any actions to be performed when announcing a deploy (IRC
279
+ integration)
280
+ - __:diff__ for generating diff links
281
+ - __:timing_log__ for sending timing log information to anywhere besides the log
282
+ file (Graphite for example)
283
+ - __:auth__ for handling auth however you please
284
+
285
+ To create a plugin simply create a new class (example from our code) that
286
+ defined a run method taking event and state. __event__ is a symbol from the
287
+ table above and __state__ is a hash of state data which varies from event to
288
+ event
289
+
290
+ ```ruby
291
+ require 'deployinator/plugin'
292
+ require 'helpers/etsy'
293
+ require 'deployinator/helpers'
294
+
295
+ module Deployinator
296
+ class GraphitePlugin < Plugin
297
+ include Deployinator::Helpers::EtsyHelpers,
298
+ Deployinator::Helpers
299
+
300
+ def run(event, state)
301
+ case event
302
+ when :run_command_end
303
+ unless state[:timing_metric].nil?
304
+ graphite_timing "deploylong.#{state[:stack]}.#{state[:timing_metric]}", "#{state[:time]}", state[:start_time].to_i
305
+ end
306
+ when :timing_log
307
+ graphite_timing("deploylong.#{state[:stack]}.#{state[:type]}", "#{state[:duration]}", state[:timestamp])
308
+ end
309
+ return nil
310
+ end
311
+ end
312
+ end
313
+ ```
314
+
315
+ Then simply require your plugin in `lib/app.rb` and add it to your
316
+ `config/base.rb` like this:
317
+
318
+ ```ruby
319
+ Deployinator.global_plugins = []
320
+ Deployinator.global_plugins << "GraphitePlugin"
321
+ ```
322
+
323
+ You can also configure plugins to only apply to a single stack like this:
324
+
325
+ ```ruby
326
+ Deployinator.stack_plugins = {}
327
+ Deployinator.stack_plugins["test_stack"] << "TestStackPlugin"
328
+ ```
329
+
330
+ ### Template Hooks
331
+ Since the main layout page is contained within the gem, there are tags provided
332
+ to allow you to add things to it in the header and body. List of points:
333
+
334
+ - __tailer_loading_message__ To customize the default deploy tailer loading
335
+ message
336
+ - __additional_header_html__ Additional html to add to the header
337
+ - __additional_body_top_html__ Additional html to add to the top of the body
338
+ - __additional_body_bottom_html__ Additional html to add to the bottom of the body
339
+
340
+ To set these simple override the methods in your view class. For example:
341
+
342
+ ```ruby
343
+ def additional_bottom_body_html
344
+ '<script src="/js/check_push_status.js"></script>'
345
+ end
346
+ ```
347
+
348
+ This can be done on a global layout that extends the gem's default layout or on
349
+ a stack by stack basis in their own view.
350
+
351
+ ## Hacking on the gem
352
+ 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.
353
+ First tell bundler to use your local copy instead by running:
354
+ ```sh
355
+ $ bundle config local.deployinator /path/to/DeployinatorGem
356
+ ```
357
+ 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)
358
+ ```sh
359
+ $ bundle install --no-deployment && bundle install --deployment
360
+ ```
361
+
362
+ ### Contributing
363
+
364
+ 1. Fork it
365
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
366
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
367
+ 4. Push to the branch (`git push origin my-new-feature`)
368
+ 5. Create new Pull Request
@@ -0,0 +1,16 @@
1
+
2
+ require 'rake/testtask'
3
+ require 'rdoc/task'
4
+ require 'bundler/gem_tasks'
5
+
6
+ #
7
+ # Helpers
8
+ #
9
+
10
+ def command?(command)
11
+ system("type #{command} &> /dev/null")
12
+ end
13
+
14
+ task :default => 'deployinator:test:unit'
15
+
16
+ load 'deployinator/tasks/tests.rake'
@@ -0,0 +1,78 @@
1
+ #!/bin/env ruby
2
+
3
+ require "deployinator"
4
+ require "deployinator/base"
5
+ require "deployinator/helpers"
6
+ require 'deployinator/stack-tail'
7
+ require 'deployinator/helpers/stack-tail'
8
+
9
+ # Note: If you change the protocol of how this communicates with the front end,
10
+ # please also update the version located in helpers/stack-tail.rb
11
+ EM.run do
12
+ @@tailers = Hash.new
13
+ @@channels = Hash.new
14
+ @@channels_count = Hash.new
15
+
16
+ # reload the list of stacks from Deployinator without affecting
17
+ # existing channels. does not remove stacks that no longer exist`
18
+ def refresh_stacks
19
+ Deployinator.get_stacks.each do |stack|
20
+ unless @@channels.key?(stack)
21
+ @@channels[stack] = EM::Channel.new
22
+ @@channels_count[stack] = 0
23
+ end
24
+ end
25
+ end
26
+
27
+ # refresh the available stacks when we receive a HUP signal
28
+ Signal.trap('HUP') { refresh_stacks }
29
+ refresh_stacks
30
+
31
+ EM::WebSocket.start(:host => "0.0.0.0", :port => Deployinator.stack_tailer_port) do |ws|
32
+
33
+ # This attempts to attach a tailer to the stack symlink. If it doesn't
34
+ # exist yet it will attempt next tick of the EventMachine
35
+ def attach_tailer(stack)
36
+ if @@channels.key?(stack)
37
+ tailer = Tailer.stack_tail(stack, @@channels[stack], @@channels_count[stack])
38
+ if tailer
39
+ @@tailers[stack] = tailer
40
+ else
41
+ EM.next_tick do
42
+ attach_tailer(stack)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ ws.onopen do |handshake|
49
+ session_id = false
50
+ stack = false
51
+ ws.send(Deployinator::Helpers::StackTailHelpers::get_stack_tail_version())
52
+
53
+ ws.onclose do
54
+ if stack && @@channels.key?(stack)
55
+ @@channels[stack].unsubscribe(session_id)
56
+ @@channels_count[stack] -= 1
57
+ if @@channels_count[stack] == 0
58
+ @@tailers[stack].close
59
+ @@tailers.delete(stack)
60
+ end
61
+ end
62
+ end
63
+
64
+ ws.onmessage do |msg|
65
+ stack = msg
66
+ if @@channels.key?(stack)
67
+ unless @@tailers.key?(stack)
68
+ attach_tailer(stack)
69
+ end
70
+ @@channels_count[stack] += 1
71
+ session_id = @@channels[stack].subscribe do |data|
72
+ ws.send(data)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end