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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +368 -0
- data/Rakefile +16 -0
- data/bin/deployinator-tailer.rb +78 -0
- data/deployinator.gemspec +31 -0
- data/lib/deployinator.rb +114 -0
- data/lib/deployinator/app.rb +204 -0
- data/lib/deployinator/base.rb +58 -0
- data/lib/deployinator/config.rb +7 -0
- data/lib/deployinator/controller.rb +147 -0
- data/lib/deployinator/helpers.rb +610 -0
- data/lib/deployinator/helpers/deploy.rb +68 -0
- data/lib/deployinator/helpers/dsh.rb +42 -0
- data/lib/deployinator/helpers/git.rb +348 -0
- data/lib/deployinator/helpers/plugin.rb +50 -0
- data/lib/deployinator/helpers/stack-tail.rb +32 -0
- data/lib/deployinator/helpers/version.rb +62 -0
- data/lib/deployinator/helpers/view.rb +67 -0
- data/lib/deployinator/logging.rb +16 -0
- data/lib/deployinator/plugin.rb +7 -0
- data/lib/deployinator/stack-tail.rb +34 -0
- data/lib/deployinator/static/css/diff_style.css +283 -0
- data/lib/deployinator/static/css/highlight.css +235 -0
- data/lib/deployinator/static/css/style.css +1223 -0
- data/lib/deployinator/static/js/flot/jquery.flot.min.js +1 -0
- data/lib/deployinator/static/js/flot/jquery.flot.selection.js +299 -0
- data/lib/deployinator/static/js/jquery-1.8.3.min.js +2 -0
- data/lib/deployinator/static/js/jquery-ui-1.8.24.min.js +5 -0
- data/lib/deployinator/static/js/jquery.timed_bar.js +36 -0
- data/lib/deployinator/tasks/initialize.rake +84 -0
- data/lib/deployinator/tasks/tests.rake +22 -0
- data/lib/deployinator/templates/deploys_status.mustache +42 -0
- data/lib/deployinator/templates/generic_single_push.mustache +64 -0
- data/lib/deployinator/templates/index.mustache +12 -0
- data/lib/deployinator/templates/layout.mustache +604 -0
- data/lib/deployinator/templates/log.mustache +24 -0
- data/lib/deployinator/templates/log_table.mustache +90 -0
- data/lib/deployinator/templates/messageboxes.mustache +29 -0
- data/lib/deployinator/templates/run_logs.mustache +15 -0
- data/lib/deployinator/templates/scroll_control.mustache +8 -0
- data/lib/deployinator/templates/stream.mustache +13 -0
- data/lib/deployinator/version.rb +3 -0
- data/lib/deployinator/views/deploys_status.rb +22 -0
- data/lib/deployinator/views/index.rb +14 -0
- data/lib/deployinator/views/layout.rb +48 -0
- data/lib/deployinator/views/log.rb +12 -0
- data/lib/deployinator/views/log_table.rb +35 -0
- data/lib/deployinator/views/run_logs.rb +32 -0
- data/templates/app.rb.erb +7 -0
- data/templates/config.ru.erb +10 -0
- data/templates/helper.rb.erb +15 -0
- data/templates/stack.rb.erb +17 -0
- data/templates/template.mustache +1 -0
- data/templates/view.rb.erb +7 -0
- data/test/unit/helpers_dsh_test.rb +40 -0
- data/test/unit/helpers_test.rb +77 -0
- data/test/unit/version_test.rb +104 -0
- metadata +245 -0
@@ -0,0 +1,610 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'timeout'
|
3
|
+
require 'deployinator/helpers/version'
|
4
|
+
require 'deployinator/helpers/plugin'
|
5
|
+
|
6
|
+
module Deployinator
|
7
|
+
module Helpers
|
8
|
+
include Deployinator::Helpers::VersionHelpers,
|
9
|
+
Deployinator::Helpers::PluginHelpers
|
10
|
+
|
11
|
+
RUN_LOG_PATH = "run_logs/"
|
12
|
+
|
13
|
+
def dev_context?
|
14
|
+
Deployinator.app_context['context'] == 'dev'
|
15
|
+
end
|
16
|
+
|
17
|
+
def not_dev?
|
18
|
+
Deployinator.app_context['context'] != 'dev'
|
19
|
+
end
|
20
|
+
|
21
|
+
def run_log_path
|
22
|
+
RUN_LOG_PATH
|
23
|
+
end
|
24
|
+
|
25
|
+
def init(env)
|
26
|
+
@username = 'nobody'
|
27
|
+
@groups = ['nogroup']
|
28
|
+
@local = false
|
29
|
+
@host = env['HTTP_HOST']
|
30
|
+
auth_info = raise_event(:auth, {:env => env, :request => request})
|
31
|
+
if !auth_info.nil?
|
32
|
+
raise "You must login." unless auth_info[:authorized]
|
33
|
+
@username = auth_info[:username]
|
34
|
+
@groups = auth_info[:groups]
|
35
|
+
@host = auth_info[:host]
|
36
|
+
@local = auth_info[:local]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def write_file(str, file)
|
41
|
+
File.open("#{RUN_LOG_PATH}#{file}", "a:UTF-8") do |f|
|
42
|
+
f.print str.force_encoding("UTF-8")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Creates a current-stackname symlink for each deploy for easier tailing
|
47
|
+
#
|
48
|
+
# filename - String of the current run log filename
|
49
|
+
# stack - String containing the stack for this deploy
|
50
|
+
def link_stack_logfile(filename, stack)
|
51
|
+
run_cmd %Q{ln -nfs #{Deployinator.root_dir}/#{run_log_path}#{filename} #{Deployinator.root_dir}/#{run_log_path}current-#{stack}}
|
52
|
+
end
|
53
|
+
|
54
|
+
# Moves current-stackname symlink so tailer won't accidentally pick up on last push
|
55
|
+
# race condition
|
56
|
+
#
|
57
|
+
# stack - String containing the stack for this deploy
|
58
|
+
def move_stack_logfile(stack)
|
59
|
+
run_cmd %Q{mv #{Deployinator.root_dir}/#{run_log_path}current-#{stack} #{Deployinator.root_dir}/#{run_log_path}last-#{stack}}
|
60
|
+
end
|
61
|
+
|
62
|
+
# output to a file, and the streaming output handler
|
63
|
+
# Public: helper function to write a message to the logfile and have it
|
64
|
+
# streamed in the webfrontend also. The frontend is HTML markuped so you
|
65
|
+
# can use HTML in the log message and it will be rendered with the given
|
66
|
+
# CSS of the site. Some classes can be used per default in Deployinator to
|
67
|
+
# show the output also in an error or info notification box. These are then
|
68
|
+
# displayed in a box above the logging output.
|
69
|
+
#
|
70
|
+
# output - String to be logged and shown in the output
|
71
|
+
#
|
72
|
+
# Examples:
|
73
|
+
# log_and_stream(<div class="stderror"> ERROR! </div>)
|
74
|
+
# log_and_stream(<div class="info_msg"> INFO! </div>)
|
75
|
+
#
|
76
|
+
# Returns nothing
|
77
|
+
def log_and_stream(output)
|
78
|
+
write_file output, @filename if @filename
|
79
|
+
return @block.call(output) unless @block.nil?
|
80
|
+
""
|
81
|
+
end
|
82
|
+
|
83
|
+
# Run external command with timing information
|
84
|
+
# streams and logs the output of the command as well
|
85
|
+
# If the command fails, it is retried some number of times
|
86
|
+
# This defaults to 5, but can be specified with the num_retries parameter
|
87
|
+
# If all the retries fail, an exception is thrown
|
88
|
+
# Between retries it will sleep for a given period, defaulting to 2 seconds
|
89
|
+
def run_cmd_with_retries(cmd, num_retries=5, sleep_seconds=2, timing_metric=nil)
|
90
|
+
for i in 1..num_retries
|
91
|
+
if i == num_retries then
|
92
|
+
result = run_cmd(cmd, timing_metric)
|
93
|
+
else
|
94
|
+
result = run_cmd(cmd, timing_metric, false)
|
95
|
+
end
|
96
|
+
if result[:exit_code] == 0
|
97
|
+
return result
|
98
|
+
else
|
99
|
+
retries_remaining = num_retries - i
|
100
|
+
unless i == num_retries
|
101
|
+
log_and_stream("`#{cmd}` failed, will retry #{retries_remaining} more times<br>")
|
102
|
+
sleep sleep_seconds
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
raise "Unable to execute `#{cmd}` after retrying #{num_retries} times"
|
108
|
+
end
|
109
|
+
|
110
|
+
# Run external command with timing information
|
111
|
+
# streams and logs the output of the command as well
|
112
|
+
# does not (currently) check exit status codes
|
113
|
+
def run_cmd(cmd, timing_metric=nil, log_errors=true)
|
114
|
+
ret = ""
|
115
|
+
exit_code = 0
|
116
|
+
start = Time.now.to_i
|
117
|
+
timestamp = Time.now.to_s
|
118
|
+
plugin_state = {
|
119
|
+
:cmd => cmd,
|
120
|
+
:timing_metric => timing_metric,
|
121
|
+
:start_time => start,
|
122
|
+
:log_errors => log_errors
|
123
|
+
}
|
124
|
+
raise_event(:run_command_start, plugin_state)
|
125
|
+
log_and_stream "<div class='command'><h4>#{timestamp}: Running #{cmd}</h4>\n<p class='output'>"
|
126
|
+
time = Benchmark.measure do
|
127
|
+
Open3.popen3(cmd) do |inn, out, err, wait_thr|
|
128
|
+
output = ""
|
129
|
+
until out.eof?
|
130
|
+
# raise "Timeout" if output.empty? && Time.now.to_i - start > 300
|
131
|
+
chr = out.read(1)
|
132
|
+
output << chr
|
133
|
+
ret << chr
|
134
|
+
if chr == "\n" || chr == "\r"
|
135
|
+
log_and_stream output + "<br>"
|
136
|
+
output = ""
|
137
|
+
end
|
138
|
+
end
|
139
|
+
error_message = nil
|
140
|
+
log_and_stream(output) unless output.empty?
|
141
|
+
|
142
|
+
error_message = err.read unless err.eof?
|
143
|
+
if (log_errors) then
|
144
|
+
log_and_stream("<span class='stderr'>STDERR: #{error_message}</span><br>") unless error_message.nil?
|
145
|
+
else
|
146
|
+
log_and_stream("STDERR:" + error_message + "<br>") unless error_message.nil?
|
147
|
+
end
|
148
|
+
|
149
|
+
unless error_message.nil? then
|
150
|
+
plugin_state[:error_message] = error_message
|
151
|
+
raise_event(:run_command_error, plugin_state)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Log non-zero exits
|
155
|
+
if wait_thr.value.exitstatus != 0 then
|
156
|
+
log_and_stream("<span class='stderr'>DANGER! #{cmd} had an exit value of: #{wait_thr.value.exitstatus}</span><br>")
|
157
|
+
exit_code = wait_thr.value.exitstatus
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
log_and_stream "</p>"
|
162
|
+
log_and_stream "<h5>Time: #{time}</h5></div>"
|
163
|
+
plugin_state[:exit_code] = exit_code
|
164
|
+
plugin_state[:stdout] = ret
|
165
|
+
plugin_state[:time] = time.real
|
166
|
+
raise_event(:run_command_end, plugin_state)
|
167
|
+
return { :stdout => ret, :exit_code => exit_code }
|
168
|
+
end
|
169
|
+
|
170
|
+
def nicify_env(env)
|
171
|
+
env = "production" if env == "PROD"
|
172
|
+
env.downcase
|
173
|
+
end
|
174
|
+
|
175
|
+
def http_host
|
176
|
+
@host
|
177
|
+
end
|
178
|
+
|
179
|
+
def stack
|
180
|
+
@stack
|
181
|
+
end
|
182
|
+
|
183
|
+
%w[dev qa stage princess production].each do |env|
|
184
|
+
define_method "#{env}_version" do
|
185
|
+
version = self.send("#{stack}_#{env}_version")
|
186
|
+
if @disabled_override == true
|
187
|
+
@disabled = false
|
188
|
+
else
|
189
|
+
@disabled = !version || version.empty?
|
190
|
+
end
|
191
|
+
version
|
192
|
+
end
|
193
|
+
|
194
|
+
define_method "#{env}_build" do
|
195
|
+
get_build(self.send("#{env}_version"))
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def diff(r1, r2, stack="web", time=null)
|
200
|
+
if (!time)
|
201
|
+
time = Time.now.to_i
|
202
|
+
end
|
203
|
+
redirect "/diff/#{stack}/#{r1}/#{r2}/github?time=#{time}"
|
204
|
+
return
|
205
|
+
end
|
206
|
+
|
207
|
+
def send_email(options)
|
208
|
+
Pony.mail(options)
|
209
|
+
end
|
210
|
+
|
211
|
+
def get_log
|
212
|
+
log_entries.collect do |line|
|
213
|
+
"[" + line.split("|").join("] [") + "]"
|
214
|
+
end.join("<br>")
|
215
|
+
end
|
216
|
+
|
217
|
+
# Public: log a given message to the log_path file. The method calls
|
218
|
+
# log_string_to_file for lower level logging functionality.
|
219
|
+
#
|
220
|
+
# env - String which represents the environment the log was produced in
|
221
|
+
# who - String which represents the active user
|
222
|
+
# msg - String representing the actual log message
|
223
|
+
# stack - String representing the current deploy stack
|
224
|
+
#
|
225
|
+
# Returns the return code of log_string_to_file
|
226
|
+
def log(env, who, msg, stack)
|
227
|
+
s = stack
|
228
|
+
log_string_to_file("#{now}|#{env}|#{clean(who)}|#{clean(msg)}|#{s}|#{@filename}", Deployinator.log_path)
|
229
|
+
end
|
230
|
+
|
231
|
+
# Public: wrapper method around appending stdout to a logfile.
|
232
|
+
#
|
233
|
+
# string - String representing the log message
|
234
|
+
# path - String representing the path to the logfile
|
235
|
+
#
|
236
|
+
# Returns true if echo exited with 0, false for non-zero exit and nil if
|
237
|
+
# the call fails
|
238
|
+
def log_string_to_file(string, path)
|
239
|
+
cmd = %Q{echo "#{string}" >> #{path}}
|
240
|
+
system(cmd)
|
241
|
+
end
|
242
|
+
|
243
|
+
def clean(msg)
|
244
|
+
(msg || "").gsub("|", "/").gsub('"', """).gsub("'", "'")
|
245
|
+
end
|
246
|
+
|
247
|
+
def nice_time
|
248
|
+
"%Y-%m-%d %H:%M:%S"
|
249
|
+
end
|
250
|
+
|
251
|
+
def now
|
252
|
+
Time.now.gmtime.strftime(nice_time)
|
253
|
+
end
|
254
|
+
|
255
|
+
def hyperlink(msg)
|
256
|
+
(msg || "").gsub(/([A-Z]{2,10}-[0-9]{2,})/) do |issue|
|
257
|
+
issue_url = Deployinator.issue_tracker.call(issue)
|
258
|
+
"<a href='#{issue_url}' target='_blank'>#{issue}</a>"
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def environments
|
263
|
+
custom_env = "#{stack}_environments"
|
264
|
+
envs = send(custom_env) if respond_to?(custom_env.to_sym)
|
265
|
+
envs ||=
|
266
|
+
[{
|
267
|
+
:name => "production",
|
268
|
+
:title => "Deploy #{stack} production",
|
269
|
+
:method => "production",
|
270
|
+
:current_version => proc{send(:"#{stack}_production_version")},
|
271
|
+
:current_build => proc{get_build(send(:"#{stack}_production_version"))},
|
272
|
+
:next_build => proc{send(:head_build)}
|
273
|
+
}]
|
274
|
+
|
275
|
+
# Support simplified symbol for methods
|
276
|
+
envs.map! do |env|
|
277
|
+
new_env = env
|
278
|
+
new_env[:current_version] = proc{send(env[:current_version])} if env[:current_version].is_a? Symbol
|
279
|
+
new_env[:current_build] = proc{send(env[:current_build])} if env[:current_build].is_a? Symbol
|
280
|
+
new_env[:next_build] = proc{send(env[:next_build])} if env[:next_build].is_a? Symbol
|
281
|
+
new_env
|
282
|
+
end
|
283
|
+
|
284
|
+
envs.each_with_index { |env, i| env[:number] = "%02d." % (i + 1); env[:not_last] = (i < envs.size - 1) }
|
285
|
+
end
|
286
|
+
|
287
|
+
|
288
|
+
# Public: fetch the run_logs in 'run_logs/' based on sevaral parameters
|
289
|
+
#
|
290
|
+
# Parameters:
|
291
|
+
# opts
|
292
|
+
# :offset
|
293
|
+
# :limit
|
294
|
+
# :filename - <stack>-<method>
|
295
|
+
#
|
296
|
+
# Returns an array of hashes with name and time keys
|
297
|
+
def get_run_logs(opts={})
|
298
|
+
offset = opts[:offset] || 0
|
299
|
+
limit = opts[:limit] || -1
|
300
|
+
filename = opts[:filename] || ""
|
301
|
+
glob = Deployinator::Helpers::RUN_LOG_PATH + "*.html"
|
302
|
+
files = Dir.glob(glob)
|
303
|
+
|
304
|
+
# filter for config files
|
305
|
+
files.select! {|file| file.match(/^((?!web[-_]config_diff.html).)*$/) && file.match(/html/)}
|
306
|
+
|
307
|
+
# filter for princess or production run_logs
|
308
|
+
files.select! {|file| file.match(/#{filename}/)}
|
309
|
+
|
310
|
+
# map files to hash with name and time keys
|
311
|
+
files.map! do |file|
|
312
|
+
{ :name => File.basename(file), :time => Time.at(file[/(\d{8,})/].to_i) }
|
313
|
+
end
|
314
|
+
|
315
|
+
# sort files chronologically,
|
316
|
+
files.sort_by! {|file| file[:time]}.reverse!
|
317
|
+
|
318
|
+
# select files within an index range
|
319
|
+
files[offset...offset +limit]
|
320
|
+
|
321
|
+
end
|
322
|
+
|
323
|
+
# Public: strips all of the whitespace from a string. If the string only whitespace, return nil.
|
324
|
+
#
|
325
|
+
# s - the string to strip whitespace from
|
326
|
+
#
|
327
|
+
# Example
|
328
|
+
# if strip_ws_to_nil(hostname).nil?
|
329
|
+
# puts "blank hostname is not valid!"
|
330
|
+
# end
|
331
|
+
#
|
332
|
+
# Returns A whitespace-free string or nil.
|
333
|
+
def strip_ws_to_nil(s)
|
334
|
+
if s.nil?
|
335
|
+
nil
|
336
|
+
else
|
337
|
+
s = s.gsub(/\s+/, "")
|
338
|
+
if s == ''
|
339
|
+
nil
|
340
|
+
else
|
341
|
+
s
|
342
|
+
end
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
# Public: gets the contes from a cache file if it hasn't expired
|
347
|
+
#
|
348
|
+
# Paramaters:
|
349
|
+
# cache_file: path to a cache file
|
350
|
+
# cache_ttl : how long in seconds the cached content is good for
|
351
|
+
# A negative number will indicate you don't care how old the cache
|
352
|
+
# file is.
|
353
|
+
#
|
354
|
+
# Returns: cached content or false if expired or cache file doesn't exist.
|
355
|
+
def get_from_cache(cache_file, cache_ttl=5)
|
356
|
+
if File.exists?(cache_file)
|
357
|
+
now = Time.now
|
358
|
+
file_mtime = File.mtime(cache_file)
|
359
|
+
file_age = now - file_mtime
|
360
|
+
if ((cache_ttl < 0) || (file_age <= cache_ttl))
|
361
|
+
file = File.open(cache_file, "r:UTF-8")
|
362
|
+
return file.read
|
363
|
+
else
|
364
|
+
# Return False if the cache is old
|
365
|
+
return false
|
366
|
+
end
|
367
|
+
else
|
368
|
+
# Return False if the cache file doesn't exist
|
369
|
+
return false
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
def lock_pushes(stack, who, method)
|
374
|
+
log_and_stream("LOCKING #{stack}")
|
375
|
+
if lock_info = push_lock_info(stack)
|
376
|
+
return log_and_stream("Pushes locked by #{lock_info[:who]} - #{lock_info[:method]}")
|
377
|
+
end
|
378
|
+
|
379
|
+
dt = Time.now.strftime("%m/%d/%Y %H:%M")
|
380
|
+
log_string_to_file("#{who}|#{method}|#{dt}", push_lock_path(stack))
|
381
|
+
end
|
382
|
+
|
383
|
+
def unlock_pushes(stack)
|
384
|
+
system(%Q{rm #{push_lock_path(stack)}})
|
385
|
+
end
|
386
|
+
|
387
|
+
def push_lock_info(stack)
|
388
|
+
d = `test -f #{push_lock_path(stack)} && cat #{push_lock_path(stack)}`.chomp
|
389
|
+
d.empty? ? nil : Hash[*[:who, :method, :lock_time].zip(d.split("|")).flatten]
|
390
|
+
end
|
391
|
+
|
392
|
+
def pushes_locked?(stack)
|
393
|
+
push_lock_info(stack)
|
394
|
+
end
|
395
|
+
|
396
|
+
def push_lock_path(stack)
|
397
|
+
"#{Deployinator.root(["log"])}/#{stack}-push-lock"
|
398
|
+
end
|
399
|
+
|
400
|
+
# Public: Outputs stack data for use in templating
|
401
|
+
# the stack selection box in the header.
|
402
|
+
#
|
403
|
+
# Returns an array of hashes with the fields "stack" and "current"
|
404
|
+
# where "current" is true for the currently selected stack.
|
405
|
+
def get_stack_select
|
406
|
+
stacks = Deployinator.get_stacks
|
407
|
+
output = Array.new
|
408
|
+
stacks.each do |s|
|
409
|
+
current = stack == s
|
410
|
+
output << { "stack" => s, "current" => current }
|
411
|
+
end
|
412
|
+
output
|
413
|
+
end
|
414
|
+
|
415
|
+
# Public: given a run logs filename, return a full URL to the runlg
|
416
|
+
#
|
417
|
+
# Params:
|
418
|
+
# filename - string of the filename
|
419
|
+
#
|
420
|
+
# Returns a string URL where that runlog can be viewed
|
421
|
+
def run_log_url(filename)
|
422
|
+
"http://#{Deployinator.hostname}/run_logs/view/#{filename}"
|
423
|
+
end
|
424
|
+
|
425
|
+
# Public: wrap a block into a timeout
|
426
|
+
#
|
427
|
+
# seconds - timeout in seconds
|
428
|
+
# description - optional description for logging (default:"")
|
429
|
+
# &block - block to call
|
430
|
+
#
|
431
|
+
# Example
|
432
|
+
# with_timeout(20){system("curl -s http://google.com")}
|
433
|
+
# with_timeout 30 do; system("curl -s http://google.com"); end
|
434
|
+
#
|
435
|
+
# Returns nothing
|
436
|
+
def with_timeout(seconds, description=nil, &block)
|
437
|
+
begin
|
438
|
+
Timeout.timeout(seconds) do
|
439
|
+
yield
|
440
|
+
end
|
441
|
+
rescue Timeout::Error => e
|
442
|
+
info = "#{Time.now}: Timeout: #{e}"
|
443
|
+
info += " for #{description}" unless description.nil?
|
444
|
+
# log and stream if log filename is not undefined
|
445
|
+
if (/undefined/ =~ @filename).nil?
|
446
|
+
log_and_stream "<div class=\"stderr\">#{info}</div>"
|
447
|
+
end
|
448
|
+
state = {
|
449
|
+
:seconds => seconds,
|
450
|
+
:info => info
|
451
|
+
}
|
452
|
+
raise_event(:timeout, state)
|
453
|
+
""
|
454
|
+
end
|
455
|
+
end
|
456
|
+
|
457
|
+
def can_remove_stack_lock?
|
458
|
+
unless @groups.nil? then
|
459
|
+
Deployinator.admin_groups.each { |cg| return true if @groups.include?(cg) }
|
460
|
+
end
|
461
|
+
|
462
|
+
# get the lock info to see if the user is the locker
|
463
|
+
info = push_lock_info(@stack) || {}
|
464
|
+
return true if info.empty?
|
465
|
+
if info[:who] == @username
|
466
|
+
return true
|
467
|
+
end
|
468
|
+
|
469
|
+
return false
|
470
|
+
end
|
471
|
+
|
472
|
+
def announce(announcement, options = {})
|
473
|
+
raise_event(:announce, {:announcement => announcement, :options => options})
|
474
|
+
|
475
|
+
if options[:send_email] && options[:send_email] == true
|
476
|
+
stack = options[:stack]
|
477
|
+
|
478
|
+
send_email({
|
479
|
+
:subject => "#{stack} deployed #{options[:env]} by #{@username}",
|
480
|
+
:html_body => announcement
|
481
|
+
})
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
def diff_url(stack, old_build, new_build)
|
486
|
+
raise_event(:diff, {:stack => stack, :old_build => old_build, :new_build => new_build})
|
487
|
+
end
|
488
|
+
|
489
|
+
def log_and_shout(options={})
|
490
|
+
options[:stack] ||= @stack
|
491
|
+
|
492
|
+
raise "Must have stack" unless options[:stack]
|
493
|
+
|
494
|
+
options[:env] ||= "PROD"
|
495
|
+
options[:nice_env] = nicify_env(options[:env])
|
496
|
+
options[:user] ||= @username
|
497
|
+
options[:start] = @start_time unless options[:start] || ! @start_time
|
498
|
+
|
499
|
+
if (options[:start])
|
500
|
+
options[:end] = Time.now.to_i unless options.key?(:end)
|
501
|
+
options[:duration] = options[:end] - options[:start]
|
502
|
+
|
503
|
+
log_and_stream "Ended at #{options[:end]}<br>Took: #{options[:duration]} seconds<br>"
|
504
|
+
timing_log options[:duration], options[:nice_env], options[:stack]
|
505
|
+
end
|
506
|
+
|
507
|
+
if (options[:old_build] && options[:build])
|
508
|
+
log_str = "#{options[:stack]} #{options[:nice_env]} deploy: old #{options[:old_build]}, new: #{options[:build]}"
|
509
|
+
log options[:env], options[:user], log_str, options[:stack]
|
510
|
+
d_url = diff_url(options[:stack], options[:old_build], options[:build])
|
511
|
+
end
|
512
|
+
|
513
|
+
if (options[:old_build] && options[:build] && (options[:irc_channels] || options[:send_email]))
|
514
|
+
announcement = "#{options[:stack]} #{options[:env]} deployed by #{options[:user]}"
|
515
|
+
announcement << " build: #{options[:build]} took: #{options[:duration]} seconds "
|
516
|
+
announcement << "diff: #{d_url}" if d_url
|
517
|
+
announce announcement, options
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
def timing_log(duration, type, stack, timestamp=nil)
|
522
|
+
if (timestamp == nil) then
|
523
|
+
timestamp = Time.now.to_i
|
524
|
+
end
|
525
|
+
|
526
|
+
current = now()
|
527
|
+
log_string_to_file("#{current}|#{type}|#{stack}|#{duration}", Deployinator.timing_log_path)
|
528
|
+
raise_event(:timing_log, {:duration => duration, :current => current, :type => type, :timestamp => timestamp})
|
529
|
+
end
|
530
|
+
|
531
|
+
def average_duration(type, stack)
|
532
|
+
log = `grep "#{type}|#{stack}" #{Deployinator.timing_log_path} | tac | head -5`
|
533
|
+
timings = log.split("\n").collect { |line| line.split("|").last.to_f }
|
534
|
+
avg_time = (timings.empty?) ? 30 : timings.inject(0) {|a,v| a+v} / timings.size
|
535
|
+
puts "avg time for #{stack}/#{type}: #{avg_time}"
|
536
|
+
avg_time
|
537
|
+
end
|
538
|
+
|
539
|
+
def log_entries(options = {})
|
540
|
+
stacks = []
|
541
|
+
stacks << "LOG MESSAGE" unless (options[:no_limit] || options[:no_global])
|
542
|
+
|
543
|
+
stacks << options[:stack] if options[:stack]
|
544
|
+
|
545
|
+
env = options[:env] ? "\\|#{options[:env].upcase}\\|" : ""
|
546
|
+
limit = (options[:no_limit] && options[:no_limit] == true) ? nil : (options[:limit] || 40)
|
547
|
+
|
548
|
+
# stack should be the last part of the log line from the last pipe to the end
|
549
|
+
# modified this to take into account the run_log entry at the end
|
550
|
+
unless stacks.empty? && env.empty?
|
551
|
+
grep = "| egrep '#{env}.*\\|\(#{stacks.join("|")}\)(|(\\||$))?'"
|
552
|
+
end
|
553
|
+
|
554
|
+
# extra grep does another filter to the line, needed to get CONFIG PRODUCTION
|
555
|
+
if defined? options[:extragrep]
|
556
|
+
extragrep = "| egrep -i '#{options[:extragrep]}' "
|
557
|
+
end
|
558
|
+
|
559
|
+
if options[:page]
|
560
|
+
num_per_page = 40
|
561
|
+
limit = "| head -#{num_per_page * options[:page].to_i} | tail -#{num_per_page}"
|
562
|
+
else
|
563
|
+
limit = "| head -#{limit}" if limit
|
564
|
+
end
|
565
|
+
|
566
|
+
log = `tac #{Deployinator.log_path} #{grep} #{extragrep} #{limit}`
|
567
|
+
log.split("\n")
|
568
|
+
end
|
569
|
+
|
570
|
+
# Public: check if the deploy host is up or not
|
571
|
+
# show a little slug in the header with the deploy host name and status
|
572
|
+
def get_deploy_target_status
|
573
|
+
status = %x{ssh -o ConnectTimeout=5 #{Deployinator.deploy_host} uptime | awk '{print $2}' }.rstrip
|
574
|
+
if status != 'up'
|
575
|
+
status = "<b>DOWN!</b>"
|
576
|
+
end
|
577
|
+
"#{Deployinator.deploy_host} is #{status}"
|
578
|
+
end
|
579
|
+
|
580
|
+
def deploy_host?
|
581
|
+
! Deployinator.deploy_host.nil?
|
582
|
+
end
|
583
|
+
|
584
|
+
def head_build
|
585
|
+
meth = "#{stack}_head_build"
|
586
|
+
if self.respond_to?(meth)
|
587
|
+
self.send(meth)
|
588
|
+
else
|
589
|
+
if git_info_for_stack.key?(stack.to_sym)
|
590
|
+
rev = get_git_head_rev(stack)
|
591
|
+
puts rev
|
592
|
+
return rev
|
593
|
+
else
|
594
|
+
puts "ERROR: add your stack in git_info_for_stack"
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def log_error(msg, e = nil)
|
600
|
+
log_msg = e.nil? ? msg : "#{msg} (#{e.message})"
|
601
|
+
log_and_stream "<div class=\"stderr\">#{log_msg}</div>"
|
602
|
+
if !e.nil?
|
603
|
+
log_and_stream e.backtrace.inspect
|
604
|
+
end
|
605
|
+
# This is so we have something in the log if/when this fails
|
606
|
+
puts log_msg
|
607
|
+
end
|
608
|
+
|
609
|
+
end
|
610
|
+
end
|