etsy-deployinator 1.0.1 → 1.1.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.
@@ -1,62 +1,62 @@
1
1
  module Deployinator
2
2
  module Helpers
3
- module VersionHelpers
4
- # Public: wrapper function to get the short SHA of a revision. The function
5
- # checks retrieves the part of the string before the first dash. If the part
6
- # is a valid default git short rev, i.e. alphanumeric and length 7 it is
7
- # returned. For an invalid rev, nil is returned.
8
- #
9
- # ver - String representing the revision
10
- #
11
- # Returns the short SHA consisting of the alphanumerics until the first dash
12
- # or nil for an invalid version string
13
- def get_build(ver)
14
- # return the short sha of the rev
15
- the_sha = (ver || "")[/^([^-]+)/]
16
- # check that we have a default git SHA
17
- val = /^[a-zA-Z0-9]{7,}$/.match the_sha
18
- val.nil? ? nil : the_sha
19
- end
3
+ module VersionHelpers
4
+ # Public: wrapper function to get the short SHA of a revision. The function
5
+ # checks retrieves the part of the string before the first dash. If the part
6
+ # is a valid default git short rev, i.e. alphanumeric and length 7 it is
7
+ # returned. For an invalid rev, nil is returned.
8
+ #
9
+ # ver - String representing the revision
10
+ #
11
+ # Returns the short SHA consisting of the alphanumerics until the first dash
12
+ # or nil for an invalid version string
13
+ def get_build(ver)
14
+ # return the short sha of the rev
15
+ the_sha = (ver || "")[/^([^-]+)/]
16
+ # check that we have a default git SHA
17
+ val = /^[a-zA-Z0-9]{7,}$/.match the_sha
18
+ val.nil? ? nil : the_sha
19
+ end
20
20
  module_function :get_build
21
21
 
22
- # Public: function to get the current software version running on a host
23
- #
24
- # host - String of the hostname to check
25
- #
26
- # Returns the full version of the current software running on the host
27
- def get_version(host)
28
- host_url = "http://#{host}/"
29
- get_version_by_url("#{host_url}version.txt")
30
- end
22
+ # Public: function to get the current software version running on a host
23
+ #
24
+ # host - String of the hostname to check
25
+ #
26
+ # Returns the full version of the current software running on the host
27
+ def get_version(host)
28
+ host_url = "https://#{host}/"
29
+ get_version_by_url("#{host_url}version.txt")
30
+ end
31
31
  module_function :get_version
32
32
 
33
- # Public: function to fetch a version string from a URL. The version string
34
- # is validated to have a valid format. The function calls a lower level
35
- # implementation method for actually getting the version.
36
- #
37
- # url - String representing where to get the version from
38
- #
39
- # Returns the version string or nil if the format is invalid
40
- def get_version_by_url(url)
41
- version = curl_get_url(url)
42
- val = /^[a-zA-Z0-9]{7,}-[0-9]{8}-[0-9]{6}-UTC$/.match version
43
- val.nil? ? nil : version.chomp
44
- end
33
+ # Public: function to fetch a version string from a URL. The version string
34
+ # is validated to have a valid format. The function calls a lower level
35
+ # implementation method for actually getting the version.
36
+ #
37
+ # url - String representing where to get the version from
38
+ #
39
+ # Returns the version string or nil if the format is invalid
40
+ def get_version_by_url(url)
41
+ version = curl_get_url(url)
42
+ val = /^[a-zA-Z0-9]{7,}-[0-9]{8}-[0-9]{6}-UTC$/.match version
43
+ val.nil? ? nil : version.chomp
44
+ end
45
45
  module_function :get_version_by_url
46
46
 
47
- # Public: this helper function wraps the actual call to get the contents of a
48
- # version file. This helps with reducing code duplication and also stubbing
49
- # out the actual call for unit testing.
50
- #
51
- # url - String representing the complete URL to query
52
- #
53
- # Returns the contents of the URL resource
54
- def curl_get_url(url)
55
- with_timeout 2, "getting version via curl from #{url}" do
56
- `curl -s #{url}`
57
- end
58
- end
47
+ # Public: this helper function wraps the actual call to get the contents of a
48
+ # version file. This helps with reducing code duplication and also stubbing
49
+ # out the actual call for unit testing.
50
+ #
51
+ # url - String representing the complete URL to query
52
+ #
53
+ # Returns the contents of the URL resource
54
+ def curl_get_url(url)
55
+ with_timeout 2, "getting version via curl from #{url}" do
56
+ `curl -s #{url}`
57
+ end
58
+ end
59
59
  module_function :curl_get_url
60
- end
61
- end
60
+ end
61
+ end
62
62
  end
@@ -75,11 +75,37 @@ module Deployinator
75
75
  #
76
76
  # Returns nothing
77
77
  def log_and_stream(output)
78
- write_file output, @filename if @filename
78
+ write_file output, runlog_filename if runlog_filename
79
79
  return @block.call(output) unless @block.nil?
80
80
  ""
81
81
  end
82
82
 
83
+ # gives the filename to send runlog to based on whether we are in the main thread or not
84
+ # We do this because we want to be able to use log_and_stream seamlessly in a
85
+ # parallel thread. So, all log_and_stream calls in all but the main thread will
86
+ # log to a seaparate file
87
+ # output - String filename to log to
88
+ def runlog_filename(name=nil)
89
+ if @filename
90
+ if Thread.main == Thread.current
91
+ @filename
92
+ elsif Thread.current[:logfile_name]
93
+ Thread.current[:logfile_name]
94
+ elsif name
95
+ Thread.current[:logfile_name] = runlog_thread_filename(name)
96
+ Thread.current[:logfile_name]
97
+ else
98
+ raise 'Logfile name not defined in thread. Expecting name parameter to be passed in.'
99
+ end
100
+ end
101
+ end
102
+
103
+ # gives us the filename to log to in thread
104
+ # output - String filename for thread
105
+ def runlog_thread_filename(name)
106
+ @filename + '-' + name.to_s
107
+ end
108
+
83
109
  # Run external command with timing information
84
110
  # streams and logs the output of the command as well
85
111
  # If the command fails, it is retried some number of times
@@ -343,7 +369,7 @@ module Deployinator
343
369
  end
344
370
  end
345
371
 
346
- # Public: gets the contes from a cache file if it hasn't expired
372
+ # Public: gets the contents from a cache file if it hasn't expired
347
373
  #
348
374
  # Paramaters:
349
375
  # cache_file: path to a cache file
@@ -370,14 +396,30 @@ module Deployinator
370
396
  end
371
397
  end
372
398
 
399
+ # Public: writes the supplied contents to the cache file, ensuring that
400
+ # encoding is correct
401
+ #
402
+ # Parameters:
403
+ # cache_file: path to the cache file
404
+ # content: the data to write to the file
405
+ #
406
+ # Returns nothing
407
+ def write_to_cache(cache_file, contents)
408
+ File.open(cache_file, 'w:UTF-8') do |f|
409
+ f.write(contents.force_encoding('UTF-8'))
410
+ end
411
+ end
412
+
373
413
  def lock_pushes(stack, who, method)
374
- log_and_stream("LOCKING #{stack}")
414
+ log_and_stream("LOCKING #{stack}<br>")
375
415
  if lock_info = push_lock_info(stack)
376
- return log_and_stream("Pushes locked by #{lock_info[:who]} - #{lock_info[:method]}")
416
+ log_and_stream("Pushes locked by #{lock_info[:who]} - #{lock_info[:method]}<br>")
417
+ return false
377
418
  end
378
419
 
379
420
  dt = Time.now.strftime("%m/%d/%Y %H:%M")
380
421
  log_string_to_file("#{who}|#{method}|#{dt}", push_lock_path(stack))
422
+ return true
381
423
  end
382
424
 
383
425
  def unlock_pushes(stack)
@@ -424,16 +466,19 @@ module Deployinator
424
466
 
425
467
  # Public: wrap a block into a timeout
426
468
  #
427
- # seconds - timeout in seconds
428
- # description - optional description for logging (default:"")
429
- # &block - block to call
469
+ # seconds - timeout in seconds
470
+ # description - optional description for logging (default:"")
471
+ # throw_exception - options param to throw exception back up stack
472
+ # quiet - optional boolean for logging as a big red warning using the stderr div class
473
+ # extra_opts - optional hash to pass along to plugins
474
+ # &block - block to call
430
475
  #
431
476
  # Example
432
477
  # with_timeout(20){system("curl -s http://google.com")}
433
478
  # with_timeout 30 do; system("curl -s http://google.com"); end
434
479
  #
435
480
  # Returns nothing
436
- def with_timeout(seconds, description=nil, &block)
481
+ def with_timeout(seconds, description=nil, throw_exception=false, quiet=false, extra_opts={}, &block)
437
482
  begin
438
483
  Timeout.timeout(seconds) do
439
484
  yield
@@ -443,13 +488,22 @@ module Deployinator
443
488
  info += " for #{description}" unless description.nil?
444
489
  # log and stream if log filename is not undefined
445
490
  if (/undefined/ =~ @filename).nil?
446
- log_and_stream "<div class=\"stderr\">#{info}</div>"
491
+ if quiet
492
+ log_and_stream "#{info}<br>"
493
+ else
494
+ log_and_stream "<div class=\"stderr\">#{info}</div>"
495
+ end
447
496
  end
448
497
  state = {
449
498
  :seconds => seconds,
450
- :info => info
499
+ :info => info,
500
+ :stack => stack,
501
+ :extra_opts => extra_opts
451
502
  }
452
503
  raise_event(:timeout, state)
504
+ if throw_exception
505
+ raise e
506
+ end
453
507
  ""
454
508
  end
455
509
  end
@@ -492,7 +546,7 @@ module Deployinator
492
546
  raise "Must have stack" unless options[:stack]
493
547
 
494
548
  options[:env] ||= "PROD"
495
- options[:nice_env] = nicify_env(options[:env])
549
+ options[:nice_env] ||= nicify_env(options[:env])
496
550
  options[:user] ||= @username
497
551
  options[:start] = @start_time unless options[:start] || ! @start_time
498
552
 
@@ -548,7 +602,7 @@ module Deployinator
548
602
  # stack should be the last part of the log line from the last pipe to the end
549
603
  # modified this to take into account the run_log entry at the end
550
604
  unless stacks.empty? && env.empty?
551
- grep = "| egrep '#{env}.*\\|\(#{stacks.join("|")}\)(|(\\||$))?'"
605
+ grep = "| egrep '#{env}.*\\|\(#{stacks.join("|")}\)\\|'"
552
606
  end
553
607
 
554
608
  # extra grep does another filter to the line, needed to get CONFIG PRODUCTION
@@ -600,11 +654,53 @@ module Deployinator
600
654
  log_msg = e.nil? ? msg : "#{msg} (#{e.message})"
601
655
  log_and_stream "<div class=\"stderr\">#{log_msg}</div>"
602
656
  if !e.nil?
603
- log_and_stream e.backtrace.inspect
657
+ begin
658
+ template = open("#{File.dirname(__FILE__)}/templates/exception.mustache").read
659
+
660
+ regex = /(?<file>.*?):(?<line>\d+):.*?`(?<method>.*)'/
661
+ context = e.backtrace.map do |line|
662
+ match = regex.match(line)
663
+ {
664
+ :file => match['file'],
665
+ :line => match['line'],
666
+ :method => match['method']
667
+ }
668
+ end
669
+
670
+ output = Mustache.render(template, {:exceptions => context})
671
+ log_and_stream output
672
+ rescue
673
+ log_and_stream e.backtrace.inspect
674
+ end
604
675
  end
605
676
  # This is so we have something in the log if/when this fails
606
677
  puts log_msg
607
678
  end
608
679
 
680
+ # Public: helper method to check if the current user is part of
681
+ # an group. This method let's you pass in groups for explicitness and
682
+ # testing but will fall back on @group and Deployinator.admin_groups.
683
+ #
684
+ # Param:
685
+ # user_groups - groups a user is part of
686
+ # admin_groups - admin groups
687
+ #
688
+ # Returns true if the current user is part of and admin group and false
689
+ # otherwise
690
+ def is_admin?(user_groups=nil, admin_groups=nil)
691
+ user_groups ||= @groups
692
+ admin_groups ||= Deployinator.admin_groups
693
+
694
+ return false if user_groups.nil?
695
+ return false if admin_groups.nil?
696
+
697
+ intersected_groups = user_groups & admin_groups
698
+
699
+ # if we found an intersection between admin and user groups, the current
700
+ # user is an admin
701
+ return (intersected_groups.length > 0)
702
+
703
+ end
704
+
609
705
  end
610
706
  end
@@ -0,0 +1,9 @@
1
+ #maintenance {
2
+ position: relative;
3
+ font-size: 18pt;
4
+ text-align: center;
5
+ }
6
+
7
+ #maintenance h1 {
8
+ margin: 35px;
9
+ }
@@ -941,19 +941,29 @@ section#main.config{
941
941
  /*consol*/
942
942
  .code {
943
943
  background-color: #222;
944
- color: #ccc;
945
- font-family: monospace;
944
+ color: #ddd;
945
+ font-size: 16px;
946
946
  padding: 8px;
947
+
947
948
  }
948
949
 
949
950
  .code .command {
951
+ font-family: monospace;
952
+ font-size:14px;
953
+ margin-top:2px;
950
954
  border-top: 1px solid #555;
951
- padding: 20px 0;
955
+ padding: 10px 20px 5px 10px;
952
956
  }
953
957
  .code .command h4 {
954
- color: #fff;
958
+ color: #ccc;
955
959
  font-weight: normal;
956
960
  }
961
+
962
+ .code .command h5 {
963
+ color: #abc;
964
+ font-size: 12px;
965
+ margin-bottom:10px;
966
+ }
957
967
  .code .command p {
958
968
  margin: 10px;
959
969
  }
@@ -967,6 +977,17 @@ section#main.config{
967
977
  font-size: 12px;
968
978
  white-space: pre;
969
979
  }
980
+ .code .success_msg {
981
+ color: #7fbf4d;
982
+ font-size: 12px;
983
+ white-space: pre;
984
+ }
985
+ .code .warning_msg {
986
+ color: #f0ad4e;
987
+ font-size: 12px;
988
+ white-space: pre;
989
+ }
990
+
970
991
  .hidden {
971
992
  display:none;
972
993
  }
@@ -1135,7 +1156,6 @@ header.web_config {
1135
1156
  margin-top: 20px;
1136
1157
  -webkit-column-width: 220px;
1137
1158
  -moz-column-width: 220px;
1138
- height: 420px;
1139
1159
  }
1140
1160
 
1141
1161
  .pinned-stack-list {
@@ -0,0 +1,10 @@
1
+ $(function() {
2
+ $.each(data, function(key, val) {
3
+ $("#choices").append('<h3><input type="checkbox" name="' + key +
4
+ '" checked="checked" id="id' + key + '">' +
5
+ '<label for="id' + key + '">'+ val.label + '</label></h3>');
6
+ });
7
+
8
+ $("#choices").find("input").click(drawGraphs);
9
+ drawGraphs();
10
+ })
@@ -0,0 +1,11 @@
1
+ <div>
2
+ <code>
3
+ {{#exceptions}}
4
+ <div style='white-space: nowrap;'>
5
+ <span>{{file}}</span>
6
+ <span>+{{line}}</span>
7
+ <span>({{method}})</span>
8
+ </div>
9
+ {{/exceptions}}
10
+ </code>
11
+ </div>
@@ -50,8 +50,9 @@
50
50
  <div class="push-topic">
51
51
  <div class="title"></div>
52
52
  <span class="log-run">
53
- <form action="/run_logs">
53
+ <form action="/log">
54
54
  <button class="button small" type="submit">See run logs</button>
55
+ <input type="hidden" name="stack" value="{{stack}}" />
55
56
  </form>
56
57
  </span>
57
58
  </div>
@@ -2,10 +2,10 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
5
- <title>Deployinator</title>
5
+ <title>Deployinator{{#stack}} - {{stack}}{{/stack}}</title>
6
6
  <link rel="shortcut icon" href="/favicon.ico">
7
7
  <link rel="stylesheet" href="/static/css/highlight.css" type="text/css" media="screen">
8
- <link rel="stylesheet" href="/static/css/style.css?v=10" type="text/css" media="screen">
8
+ <link rel="stylesheet" href="/static/css/style.css?v=11" type="text/css" media="screen">
9
9
  <link rel="stylesheet" href="/static/css/diff_style.css" type="text/css" media="screen">
10
10
  <script src="/js/jquery-1.8.3.min.js"></script>
11
11
  <script src="/js/jquery-ui-1.8.24.min.js"></script>
@@ -53,6 +53,7 @@ _ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __
53
53
  <span class="label">Stack: </span>
54
54
  <span>
55
55
  <select name='stacks' id='stacks'>
56
+ <option>Choose...</option>
56
57
  {{#get_stack_select}}
57
58
  <option value="{{stack}}"{{#current}} selected{{/current}}>{{stack}}</option>
58
59
  {{/get_stack_select}}
@@ -99,7 +100,7 @@ _ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __
99
100
  window.location = '/' + stack;
100
101
  });
101
102
 
102
- // listen for changes on the watch button
103
+ // listen for changes on the watch button
103
104
  $('.watch').click(function () {
104
105
  $.ajax({
105
106
  url : '/{{stack}}/can-deploy',
@@ -110,7 +111,7 @@ _ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __
110
111
  console.log(form);
111
112
  if (form.length == 1) {
112
113
  stream_log_websocket(form[0]);
113
- }
114
+ }
114
115
  }
115
116
  });
116
117
  });
@@ -3,7 +3,12 @@
3
3
  <tr>
4
4
  <th>
5
5
  {{# prev_page }}
6
- <a href="?page={{prev_page}}">prev</a>
6
+ <form action="/log">
7
+ {{#prev_page_params}}
8
+ <input type="hidden" name="{{name}}" value="{{value}}" />
9
+ {{/prev_page_params}}
10
+ <button class="button small">Prev</button>
11
+ </form>
7
12
  {{/ prev_page }}
8
13
  </th>
9
14
  <th></th>
@@ -11,9 +16,13 @@
11
16
  <th></th>
12
17
  <th></th>
13
18
  <th></th>
14
- <th></th>
15
19
  <th>
16
- <a href="?page={{next_page}}">next</a>
20
+ <form action="/log">
21
+ {{#next_page_params}}
22
+ <input type="hidden" name="{{name}}" value="{{value}}" />
23
+ {{/next_page_params}}
24
+ <button class="button small">Next</button>
25
+ </form>
17
26
  </th>
18
27
  </tr>
19
28
  <tr>
@@ -67,7 +76,9 @@
67
76
  <th>
68
77
  {{# prev_page }}
69
78
  <form action="/log">
70
- <input type="hidden" name="page" value="{{prev_page}}" />
79
+ {{#prev_page_params}}
80
+ <input type="hidden" name="{{name}}" value="{{value}}" />
81
+ {{/prev_page_params}}
71
82
  <button class="button small">Prev</button>
72
83
  </form>
73
84
  {{/ prev_page }}
@@ -79,7 +90,9 @@
79
90
  <th></th>
80
91
  <th>
81
92
  <form action="/log">
82
- <input type="hidden" name="page" value="{{next_page}}" />
93
+ {{#next_page_params}}
94
+ <input type="hidden" name="{{name}}" value="{{value}}" />
95
+ {{/next_page_params}}
83
96
  <button class="button small">Next</button>
84
97
  </form>
85
98
 
@@ -0,0 +1,5 @@
1
+ <div id="maintenance">
2
+ <h1> Deployinator is in maintenance mode.</h1>
3
+ <img src="/images/maintenance.gif"></img>
4
+ <h1> We'll be back shortly. But please see {{ maintenance_contact }} for more details. </h1>
5
+ </div>