etsy-deployinator 1.0.2 → 1.1.0

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.
@@ -0,0 +1,70 @@
1
+ require 'celluloid'
2
+ require 'celluloid/autostart'
3
+
4
+ module Deployinator
5
+ module Helpers
6
+ module ConcurrencyHelpers
7
+ extend Celluloid
8
+ # Hash of future objects that have been instantiated so far
9
+ @@futures = {}
10
+ # Public: run block of code in parallel
11
+ #
12
+ # Returns Handle to future object created
13
+ def run_parallel(name, &block)
14
+ name = name.to_sym
15
+ if reference_taken? name
16
+ raise DuplicateReferenceError, "Name #{name} already taken for future."
17
+ end
18
+ log_and_stream '</br>Queueing execution of future: ' + name.to_s + '</br>'
19
+ @@futures[name] = Celluloid::Future.new do
20
+ # Set filename for thread
21
+ runlog_filename(name)
22
+ # setting up separate logger
23
+ log_and_stream '</br>Starting execution of future: ' + name.to_s + '</br>'
24
+ block.call
25
+ end
26
+ @@futures[name]
27
+ end
28
+
29
+
30
+
31
+
32
+ # Public: check if the reference name for future is taken
33
+ #
34
+ # Returns boolean
35
+ def reference_taken?(name)
36
+ return @@futures.has_key?(name)
37
+ end
38
+
39
+ # Public: returns the value of the code block execution
40
+ # This also sends all the logged data in stream back to main
41
+ # log file and removes the temporary log file created for the thread
42
+ # Returns the return value of the last line executed in block
43
+ def get_value(future, timeout=nil)
44
+ if @filename
45
+ file_path = "#{RUN_LOG_PATH}" + runlog_thread_filename(future)
46
+ return_value = @@futures[future.to_sym].value(timeout)
47
+ log_and_stream File.read(file_path)
48
+ File.delete(file_path) if File.exists?(file_path)
49
+ return_value
50
+ else
51
+ @@futures[future.to_sym].value
52
+ end
53
+ end
54
+
55
+ # Public: returns the values of the specified futures
56
+ #
57
+ # Returns hash of values
58
+ def get_values(*futures)
59
+ value_hash = {}
60
+ futures.each do |future|
61
+ value_hash[future] = get_value(future)
62
+ end
63
+ value_hash
64
+ end
65
+
66
+ class DuplicateReferenceError < StandardError
67
+ end
68
+ end
69
+ end
70
+ end
@@ -5,14 +5,19 @@ module Deployinator
5
5
  @dsh_fanout || 30
6
6
  end
7
7
 
8
- def run_dsh(groups, cmd, &block)
8
+ def run_dsh(groups, cmd, only_stdout=true, &block)
9
9
  groups = [groups] unless groups.is_a?(Array)
10
10
  dsh_groups = groups.map {|group| "-g #{group} "}.join("")
11
- run_cmd(%Q{ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} dsh #{dsh_groups} -r ssh -F #{dsh_fanout} "#{cmd}"}, &block)[:stdout]
11
+ cmd_return = run_cmd(%Q{ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} dsh #{dsh_groups} -r ssh -F #{dsh_fanout} "#{cmd}"}, &block)
12
+ if only_stdout
13
+ cmd_return[:stdout]
14
+ else
15
+ cmd_return
16
+ end
12
17
  end
13
18
 
14
19
  # run dsh against a given host or array of hosts
15
- def run_dsh_hosts(hosts, cmd, extra_opts='', &block)
20
+ def run_dsh_hosts(hosts, cmd, extra_opts='', only_stdout=true, &block)
16
21
  hosts = [hosts] unless hosts.is_a?(Array)
17
22
  if extra_opts.length > 0
18
23
  run_cmd %Q{ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} 'dsh -m #{hosts.join(',')} -r ssh -F #{dsh_fanout} #{extra_opts} -- "#{cmd}"'}, &block
@@ -21,9 +26,14 @@ module Deployinator
21
26
  end
22
27
  end
23
28
 
24
- def run_dsh_extra(dsh_group, cmd, extra_opts, &block)
29
+ def run_dsh_extra(dsh_group, cmd, extra_opts, only_stdout=true, &block)
25
30
  # runs dsh to a single group with extra args to dsh
26
- run_cmd(%Q{ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} dsh -g #{dsh_group} -r ssh #{extra_opts} -F #{dsh_fanout} "#{cmd}"}, &block)[:stdout]
31
+ cmd_return = run_cmd(%Q{ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} dsh -g #{dsh_group} -r ssh #{extra_opts} -F #{dsh_fanout} "#{cmd}"}, &block)
32
+ if only_stdout
33
+ cmd_return[:stdout]
34
+ else
35
+ cmd_return
36
+ end
27
37
  end
28
38
 
29
39
  def hosts_for(group)
@@ -93,10 +93,16 @@ module Deployinator
93
93
  # branch - the branch to checkout after the fetch
94
94
  #
95
95
  # Returns nothing
96
- def git_freshen_clone(stack, extra_cmd="", path=nil, branch="master")
96
+ def git_freshen_clone(stack, extra_cmd="", path=nil, branch="master", force_checkout=false)
97
97
  path ||= git_checkout_path(checkout_root, stack)
98
- cmd = "cd #{path} && git fetch --quiet origin +refs/heads/#{branch}:refs/remotes/origin/#{branch} && git reset --hard origin/#{branch} 2>&1"
99
- cmd = build_git_cmd(cmd, extra_cmd)
98
+ cmd = [
99
+ "cd #{path}",
100
+ "git fetch --quiet origin +refs/heads/#{branch}:refs/remotes/origin/#{branch}",
101
+ "git reset --hard origin/#{branch} 2>&1",
102
+ "git checkout #{'--force' if force_checkout} #{branch} 2>&1",
103
+ ]
104
+ cmd << "git reset --hard origin/#{branch} 2>&1"
105
+ cmd = build_git_cmd(cmd.join(" && "), extra_cmd)
100
106
  run_cmd cmd
101
107
  yield "#{path}" if block_given?
102
108
  end
@@ -117,15 +123,15 @@ module Deployinator
117
123
  # read_write - boolean; True means clone the repo read/write
118
124
  #
119
125
  # Returns stdout of the respective git command.
120
- def git_freshen_or_clone(stack, extra_cmd, checkout_root, branch="master", read_write=false)
126
+ def git_freshen_or_clone(stack, extra_cmd, checkout_root, branch="master", read_write=false, protocol="git", force_checkout=false)
121
127
  path = git_checkout_path(checkout_root, stack)
122
128
  is_git = is_git_repo(path, extra_cmd)
123
129
  if is_git == :true
124
130
  log_and_stream "</br>Refreshing repo #{stack} at #{path}</br>"
125
- git_freshen_clone(stack, extra_cmd, path, branch)
131
+ git_freshen_clone(stack, extra_cmd, path, branch, force_checkout)
126
132
  elsif is_git == :missing
127
133
  log_and_stream "</br>Cloning branch #{branch} of #{stack} repo into #{path}</br>"
128
- git_clone(stack, git_url(stack, "git", read_write), extra_cmd, checkout_root, branch)
134
+ git_clone(stack, git_url(stack, protocol, read_write), extra_cmd, checkout_root, branch)
129
135
  else
130
136
  log_and_stream "</br><span class=\"stderr\">The path for #{stack} at #{path} exists but is not a git repo.</span></br>"
131
137
  end
@@ -188,7 +194,7 @@ module Deployinator
188
194
 
189
195
  unless head_rev
190
196
  head_rev = get_git_head_rev(stack, branch)
191
- File.open(filename, 'w') {|f| f.write(head_rev) }
197
+ write_to_cache(filename, head_rev)
192
198
  end
193
199
 
194
200
  return head_rev
@@ -329,11 +335,12 @@ module Deployinator
329
335
  # ssh_cmd: string ssh cmd to get to a host where you've got this repo checked out
330
336
  # extra: string any extra cmds like cd that you need to do on the remote host to get to your checkout
331
337
  # quiet: boolean - if true we make no additional output and just return the files
338
+ # diff_filter: string to pass to git to make it only show certain types of changes (added/removed)
332
339
  #
333
340
  # Returns:
334
341
  # Array of files names changed between these revs
335
- def git_show_changed_files(rev1, rev2, ssh_cmd, extra=nil, quiet=false)
336
- cmd = %Q{git log --name-only --pretty=oneline --full-index #{rev1}..#{rev2} | grep -vE '^[0-9a-f]{40} ' | sort | uniq}
342
+ def git_show_changed_files(rev1, rev2, ssh_cmd, extra=nil, quiet=false, diff_filter="")
343
+ cmd = %Q{git log --name-only --pretty=oneline --full-index #{rev1}..#{rev2} --diff-filter=#{diff_filter} | grep -vE '^[0-9a-f]{40} ' | sort | uniq}
337
344
  extra = "#{extra} &&" unless extra.nil?
338
345
  if quiet
339
346
  list_of_touched_files = %x{#{ssh_cmd} "#{extra} #{cmd}"}
@@ -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
@@ -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