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