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,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