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,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('"', "&quot;").gsub("'", "&apos;")
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