etsy-deployinator 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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