etsy-deployinator 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'deployinator/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "etsy-deployinator"
8
+ gem.version = Deployinator::VERSION
9
+ gem.authors = ["JPaul"]
10
+ gem.email = ["jpaul@etsy.com"]
11
+ gem.description = %q{Deployinator as a Gem}
12
+ gem.summary = %q{Rewrite of deployinator to be a gem}
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "mocha"
21
+
22
+ gem.add_runtime_dependency "rake"
23
+ gem.add_runtime_dependency "json"
24
+ gem.add_runtime_dependency "mustache", "~> 0.99"
25
+ gem.add_runtime_dependency "pony"
26
+ gem.add_runtime_dependency "tlsmail"
27
+ gem.add_runtime_dependency "eventmachine"
28
+ gem.add_runtime_dependency "eventmachine-tail"
29
+ gem.add_runtime_dependency "em-websocket"
30
+ gem.add_runtime_dependency "sinatra", ">=1.4.3"
31
+ end
@@ -0,0 +1,114 @@
1
+ require 'deployinator/version'
2
+ # = Deployinator
3
+ #
4
+ # This is the main entry point for all things in the Deployination.
5
+ module Deployinator
6
+
7
+ # = Config settings
8
+ class << self
9
+ # File to log to
10
+ attr_accessor :log_file
11
+
12
+ # Your company domain name
13
+ attr_accessor :domain
14
+
15
+ # Hostname where deployinator runs
16
+ attr_accessor :hostname
17
+
18
+ # Default username for passwordless ssh
19
+ attr_accessor :default_user
20
+
21
+ # Default github_host
22
+ attr_accessor :github_host
23
+
24
+ # Bug or issue tracker - proc that takes the issue id as an argument
25
+ # ex: Deployinator.issue_tracker = proc {|issue| "http://foo/browse/#{issue}"}
26
+ attr_accessor :issue_tracker
27
+
28
+ # a hash for context specifics settings (test,dev,production) of deployinator itself
29
+ attr_accessor :app_context
30
+
31
+ # Your install root
32
+ attr_accessor :root_dir
33
+
34
+ # Git info per stack
35
+ attr_accessor :git_info_for_stack
36
+
37
+ # Deploy Host
38
+ attr_accessor :deploy_host
39
+
40
+ # Timing Log path
41
+ attr_accessor :timing_log_path
42
+
43
+ # Log path
44
+ attr_accessor :log_path
45
+
46
+ attr_accessor :git_sha_length
47
+
48
+ attr_accessor :global_plugins
49
+
50
+ attr_accessor :stack_plugins
51
+
52
+ attr_accessor :stack_tailer_port
53
+
54
+ attr_accessor :admin_groups
55
+ @admin_groups = []
56
+
57
+ # the controller class. defaults to Deployinator::Controller
58
+ # if you override this it should be a subclass of Deployinator::Controller
59
+ attr_accessor :deploy_controller
60
+
61
+ def initialize
62
+ @stack_plugins = {}
63
+ @global_plugins = []
64
+ @admin_groups = []
65
+ end
66
+
67
+ # Base root path
68
+ # Takes an optional argument of a string or array and returns the path(s)
69
+ # From the root of deployinator
70
+ def root(path = nil)
71
+ base = Deployinator.root_dir
72
+ path ? File.join(base, path) : base
73
+ end
74
+
75
+ # is a log file defined?
76
+ def log_file?
77
+ log_file
78
+ end
79
+
80
+ # Running environment for deployinator
81
+ # This is taken from RACK_ENV or RAILS_ENV
82
+ # *note* this is different from deployinator's concept of stacks/environments
83
+ def env
84
+ ENV["RACK_ENV"] || ENV["RAILS_ENV"] || "development"
85
+ end
86
+
87
+ # Gets all the stack files in the stack directory
88
+ def get_stack_files
89
+ Dir[Deployinator.root(["stacks", "*.rb"])]
90
+ end
91
+
92
+ # Gets all the stack names without the .rb extension
93
+ def get_stacks
94
+ self.get_stack_files.sort.map do |file|
95
+ File.basename(file, ".rb")
96
+ end
97
+ end
98
+ end
99
+ end
100
+
101
+ Deployinator.root_dir = Dir.pwd
102
+ Deployinator.app_context = {}
103
+ Deployinator.admin_groups = []
104
+ Deployinator.global_plugins = []
105
+ Deployinator.log_file = Deployinator.root(["log", "development.log"])
106
+ Deployinator.log_path = Deployinator.root(["log", "deployinator.log"])
107
+ Deployinator.timing_log_path = Deployinator.root(["log", "deployinator-timing.log"])
108
+ Deployinator.git_sha_length = "10"
109
+ Deployinator.default_user = `whoami`
110
+ Deployinator.stack_tailer_port = 7778
111
+ Deployinator.github_host = 'github.com'
112
+ Deployinator.app_context['context'] = 'dev'
113
+
114
+
@@ -0,0 +1,204 @@
1
+ require 'sinatra/base'
2
+ require 'mustache/sinatra'
3
+ require 'deployinator'
4
+ require 'deployinator/controller'
5
+ require 'deployinator/helpers'
6
+ require 'deployinator/helpers/deploy'
7
+ require 'deployinator/helpers/version'
8
+ require 'deployinator/helpers/git'
9
+ require 'deployinator/helpers/plugin'
10
+ require 'deployinator/views/index'
11
+ require 'deployinator/views/log'
12
+ require 'deployinator/views/run_logs'
13
+ require 'deployinator/views/log_table'
14
+ require 'deployinator/views/deploys_status'
15
+
16
+ module Deployinator
17
+ class DeployinatorApp < Sinatra::Base
18
+ register Mustache::Sinatra
19
+ helpers Deployinator::Helpers,
20
+ Deployinator::Helpers::DeployHelpers,
21
+ Deployinator::Helpers::GitHelpers,
22
+ Deployinator::Helpers::VersionHelpers,
23
+ Deployinator::Helpers::PluginHelpers
24
+
25
+ def github_diff_url(params)
26
+ stack = params[:stack].intern
27
+ gh_info = git_info_for_stack[stack]
28
+ "https://#{which_github_host(stack)}/#{gh_info[:user]}/#{gh_info[:repository]}/compare/#{params[:r1]}...#{params[:r2]}"
29
+ end
30
+
31
+ set :mustache, {
32
+ :views => Deployinator.root('views'),
33
+ :templates => Deployinator.root('templates'),
34
+ :namespace => Deployinator::Views
35
+ }
36
+
37
+ set :public_folder, Deployinator.root('public')
38
+ set :static, true
39
+
40
+ before do
41
+ register_plugins(nil)
42
+ init(env)
43
+ @disabled_override = params[:override].nil? ? false : true
44
+ end
45
+
46
+ get '/' do
47
+ mustache Deployinator::Views::Index
48
+ end
49
+
50
+ get '/run_logs/view/:log' do
51
+ template = open("#{File.dirname(__FILE__)}/templates/stream.mustache").read
52
+ template.gsub!("{{ yield }}", "{{{yield}}}")
53
+ Mustache.render(template, :yield => open("run_logs/" + params[:log]).read)
54
+ end
55
+
56
+ get '/run_logs/?' do
57
+ @stack = "log"
58
+ @params = params
59
+ mustache Deployinator::Views::RunLogs
60
+ end
61
+
62
+ get '/run_logs/latest/:log_type' do
63
+ run_logs = get_run_logs(:limit => 1, :type => params[:log_type])
64
+ if run_logs.count == 1
65
+ run_log = run_logs.first[:name]
66
+ redirect "/run_logs/view/#{run_log}"
67
+ else
68
+ redirect "/"
69
+ end
70
+ end
71
+
72
+ get '/log/?' do
73
+ @stack = "log"
74
+ @params = params
75
+ mustache Deployinator::Views::LogTable
76
+ end
77
+
78
+ get '/:stack/can-deploy' do
79
+ lock_info = push_lock_info(params["stack"]) || {}
80
+ lock_info[:can_deploy] = lock_info.empty?
81
+ content_type "application/json"
82
+ lock_info.to_json
83
+ end
84
+
85
+ get '/diff/:stack/:r1/:r2/?' do
86
+ @stack = params[:stack]
87
+ diff(params["r1"], params["r2"], params["stack"], params[:time])
88
+ end
89
+
90
+ get '/diff/:stack/:r1/:r2/github/?' do
91
+ redirect github_diff_url(params)
92
+ end
93
+
94
+ get '/head_rev/:stack' do
95
+ git_head_rev(params[:stack]).chomp
96
+ end
97
+
98
+ get '/:thing' do
99
+ @stack = params[:thing]
100
+ @params = params
101
+ register_plugins(@stack)
102
+ begin
103
+ mustache @stack
104
+ rescue Errno::ENOENT
105
+ pass
106
+ end
107
+ end
108
+
109
+ get '/:stack/remove-lock' do
110
+ stack = params[:stack]
111
+ unlock_pushes(stack) if can_remove_stack_lock?
112
+ redirect "/#{stack}"
113
+ end
114
+
115
+ # return a list of all deploys as JSON
116
+ get '/deploys/?' do
117
+ get_list_of_deploys.to_json
118
+ end
119
+
120
+ get '/static/css/style.css?:version' do
121
+ send_file "#{File.dirname(__FILE__)}/static/css/style.css"
122
+ end
123
+
124
+ get '/static/css/diff_style.css' do
125
+ send_file "#{File.dirname(__FILE__)}/static/css/diff_style.css"
126
+ end
127
+
128
+ get '/static/css/highlight.css' do
129
+ send_file "#{File.dirname(__FILE__)}/static/css/highlight.css"
130
+ end
131
+
132
+ get '/js/flot/jquery.flot.min.js' do
133
+ send_file "#{File.dirname(__FILE__)}/static/js/flot/jquery.flot.min.js"
134
+ end
135
+
136
+ get '/js/flot/jquery.flot.selection.js' do
137
+ send_file "#{File.dirname(__FILE__)}/static/js/flot/jquery.flot.selection.js"
138
+ end
139
+
140
+ get '/js/jquery-1.8.3.min.js' do
141
+ send_file "#{File.dirname(__FILE__)}/static/js/jquery-1.8.3.min.js"
142
+ end
143
+
144
+ get '/js/jquery-ui-1.8.24.min.js' do
145
+ send_file "#{File.dirname(__FILE__)}/static/js/jquery-ui-1.8.24.min.js"
146
+ end
147
+
148
+ get '/js/jquery.timed_bar.js' do
149
+ send_file "#{File.dirname(__FILE__)}/static/js/jquery.timed_bar.js"
150
+ end
151
+
152
+ get '/deploys_status' do
153
+ mustache Deployinator::Views::DeploysStatus
154
+ end
155
+
156
+ get '/log.txt' do
157
+ content_type :text
158
+ `tac #{Deployinator.log_path}#{ " | head -n #{params[:limit]}" if params[:limit]}`
159
+ end
160
+
161
+ get '/timing_log.txt' do
162
+ content_type :text
163
+ `tac #{Deployinator.timing_log_path}`
164
+ end
165
+
166
+ get '/ti/:stack/:env' do
167
+ @stack = params[:stack]
168
+ average_duration(params["env"], params["stack"]).to_s
169
+ end
170
+
171
+ # this is the API endpoint to asynchronously start a deploy that runs in
172
+ # the background.
173
+ post '/deploys/?' do
174
+ params[:username] = @username
175
+ params[:block] = Proc.new { |line| foo = line }
176
+ deploy_running = is_deploy_active?(params[:stack], params[:stage])
177
+
178
+ # if this deploy is already running, return 403
179
+ if deploy_running
180
+ return 403
181
+ end
182
+
183
+ fork {
184
+ Signal.trap("HUP") { exit }
185
+ Deployinator.setup_logging
186
+ $0 = get_deploy_process_title(params[:stack], params[:stage])
187
+ controller = Deployinator.deploy_controller || Deployinator::Controller
188
+ d = controller.new
189
+ d.run(params)
190
+ }
191
+ 200
192
+ end
193
+
194
+ delete '/deploys/?' do
195
+ return 400 if (params[:stack].nil? || params[:stage].nil?)
196
+ res = stop_deploy(params[:stack], params[:stage])
197
+ unless res
198
+ return 404
199
+ end
200
+
201
+ 200
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,58 @@
1
+ require "rubygems"
2
+ require "bundler"
3
+ Bundler.setup
4
+
5
+ require 'deployinator'
6
+
7
+ $LOAD_PATH.unshift Deployinator.root unless $LOAD_PATH.include? Deployinator.root
8
+ $LOAD_PATH.unshift Deployinator.root("lib") unless $LOAD_PATH.include? Deployinator.root("lib")
9
+
10
+ require 'deployinator/config'
11
+
12
+ require "socket"
13
+ require 'pony'
14
+
15
+ require 'sinatra/base'
16
+ require "mustache/sinatra"
17
+
18
+ # Silence mustache warnings
19
+ module Mustache::Sinatra::Helpers
20
+ def warn(msg); nil; end
21
+ end
22
+
23
+ # ruby Std lib
24
+ require 'open3'
25
+ require 'benchmark'
26
+ require 'net/http'
27
+ require 'open-uri'
28
+ require 'uri'
29
+ require 'time'
30
+ require 'json'
31
+ require 'resolv'
32
+
33
+ require "deployinator/helpers"
34
+ require "deployinator/views/layout"
35
+ require "deployinator/helpers/view"
36
+ require "deployinator/app"
37
+
38
+ class Mustache
39
+ include Deployinator::Helpers,
40
+ Deployinator::Helpers::ViewHelpers
41
+ end
42
+
43
+ # Ruby 1.8.6 is teh LAMEZ0Rz
44
+ unless Symbol.respond_to?(:to_proc)
45
+ class Symbol
46
+ def to_proc
47
+ Proc.new { |obj, *args| obj.send(self, *args) }
48
+ end
49
+ end
50
+ end
51
+
52
+ unless String.respond_to?(:start_with?)
53
+ class String
54
+ def start_with?(str)
55
+ self.index(str) == 0
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,7 @@
1
+ require 'deployinator'
2
+ # Load in the current environment and override settings
3
+ begin
4
+ require Deployinator.root(["config", "base"])
5
+ require Deployinator.root(["config", Deployinator.env])
6
+ rescue LoadError
7
+ end
@@ -0,0 +1,147 @@
1
+ require 'deployinator'
2
+ require 'deployinator/helpers'
3
+ require 'deployinator/helpers/version'
4
+ require "deployinator/helpers/plugin"
5
+
6
+ module Deployinator
7
+
8
+ # Public: this class represents the Deploy object with all the properties
9
+ # the different helper and stack methods need to do the deploy. It is
10
+ # basically an almost empty class and gets all of its functionality by the
11
+ # modules it includes/extends based on the stack we are deploying.
12
+ class Deploy
13
+ include Deployinator::Helpers,
14
+ Deployinator::Helpers::VersionHelpers,
15
+ Deployinator::Helpers::PluginHelpers
16
+
17
+ # Public: initialize the deploy class with instance variables that are
18
+ # needed by the deploy methods, runlog helpers and all that
19
+ #
20
+ # Params:
21
+ # args - hash which at least has the following fields:
22
+ # { :username => "name of the person that is deploying",
23
+ # :stack => "the name of the stack to deploy",
24
+ # :stage => "stage of the stack to deploy"
25
+ # }
26
+ #
27
+ # Returns the runlog filename
28
+ def initialize(args)
29
+ @deploy_start_time = Time.now.to_i
30
+ @start_time = Time.now.to_i
31
+ @username = args[:username]
32
+ @host = `hostname -s`
33
+ @stack = args[:stack]
34
+ @method = args[:method]
35
+ @filename = "#{@deploy_start_time}-#{@username}-#{args[:method]}.html"
36
+ @deploy_time = Time.now.to_i
37
+
38
+ # This gets the runlog output on the console; is used by log_and_stream
39
+ @block = args[:block] || Proc.new do |output|
40
+ $stdout.write output.gsub!(/(<[^>]*>)|\n|\t/s) {" "}
41
+ $stdout.write "\n"
42
+ end
43
+ end
44
+
45
+ def get_filename
46
+ @filename
47
+ end
48
+
49
+ def get_deploy_time
50
+ @deploy_time
51
+ end
52
+ end
53
+
54
+ # Public: the main Controller class knows how to extract options from the
55
+ # options argument it gets passed from its run method and then prepares the
56
+ # Deploy class to be ready to deploy.
57
+ class Controller
58
+
59
+ # Public: get the correct method name for the stage to deploy. This allows us to
60
+ # call things princess and prod even though the methods in the stacks have crazy
61
+ # names.
62
+ #
63
+ # Params:
64
+ # stack - the name of the stack to deploy
65
+ # stage - the stage of the stack to deploy
66
+ #
67
+ # Returns the name of the deploy method as a String which then can be sent
68
+ # to the Deploy class
69
+ def stage_to_method(stack, stage)
70
+ "#{stack}_#{stage}"
71
+ end
72
+
73
+ # Public: run the actual deploy from the given parameters
74
+ #
75
+ # Params:
76
+ # options - hash that includes at least the following fields:
77
+ # { :username => "name of the user that is deploying",
78
+ # :stack => "name of the stack to deploy",
79
+ # :stage => "name of the stage of the stack to deploy"
80
+ # }
81
+ #
82
+ # Returns nothing
83
+ def run(options)
84
+ options[:method] = stage_to_method(options[:stack], options[:stage])
85
+ if options[:method].nil?
86
+ raise "No method defined for me to call: #{options[:stack]}, #{options[:stage]}"
87
+ end
88
+
89
+ # config pus needs :env populated here
90
+ options[:env] = {
91
+ :username => options[:username]
92
+ }
93
+
94
+ if Deployinator.get_stacks.include?(options[:stack])
95
+ require "stacks/#{options[:stack]}"
96
+ klass = "#{Mustache.classify(options[:stack])}Deploy"
97
+ deploy_class = Deployinator::Stacks.const_get("#{klass}")
98
+ else
99
+ raise "No such stack #{options[:stack]}"
100
+ end
101
+
102
+ deploy_instance = deploy_class.new(options)
103
+ deploy_instance.register_plugins(options[:stack])
104
+
105
+ deploy_instance.lock_pushes(options[:stack], options[:username], options[:method])
106
+
107
+ @start_time = Time.now
108
+ deploy_instance.log_and_stream "Push started at #{@start_time.to_i}\n"
109
+ deploy_instance.log_and_stream "Calling #{options[:method]}\n";
110
+ deploy_instance.link_stack_logfile(deploy_instance.get_filename, options[:stack])
111
+
112
+ deploy_instance.raise_event(:deploy_start)
113
+
114
+ begin
115
+ state = deploy_instance.send(options[:method], options)
116
+ rescue Exception => e
117
+ deploy_instance.log_error("There was an exception during this deploy. Aborted!", e)
118
+ deploy_instance.raise_event(:deploy_error)
119
+ end
120
+
121
+ if state.nil? || !state.is_a?(Hash)
122
+ state = {}
123
+ end
124
+ deploy_instance.raise_event(:deploy_end, state)
125
+
126
+ if options[:method].match(/config_push/)
127
+ env = options[:method].match(/prod/) ? "production" : "princess"
128
+ elsif options[:method].match(/force_builda/)
129
+ env = "force asset rebuild"
130
+ else
131
+ env = options[:method][/(dev|qa|production|princess|prod|webs|stage|config)/i, 1] || "other"
132
+ env = "production" if env.match(/prod|webs/)
133
+ end
134
+
135
+ # display a message that the deploy is done and call the JavaScript
136
+ # deploy done function
137
+ msg = "<h4>#{env.to_s.upcase} deploy in #{options[:stack]} stack complete</h4>"
138
+ deploy_instance.log_and_stream(msg+"<p class='output'>")
139
+ deploy_instance.log_and_stream("<script id='deploy-done'>window.deploy_done('#{msg}', '#{options[:stage]}');</script>")
140
+
141
+ deploy_instance.unlock_pushes(options[:stack])
142
+ deploy_instance.move_stack_logfile(options[:stack])
143
+
144
+ return deploy_instance
145
+ end
146
+ end
147
+ end