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,68 @@
1
+ module Deployinator
2
+ module Helpers
3
+ # Public: helper methods to interact with deploy processes
4
+ module DeployHelpers
5
+
6
+ # Public: get a list of all currently running deploys
7
+ #
8
+ # Returns an array of hashes of the form {:stack => stackname, :stage
9
+ # => stagename}
10
+ def get_list_of_deploys
11
+ ret = []
12
+ raw = `pgrep -d, -l -f Deployinator`.strip.split(",")
13
+ raw.each do |deploy|
14
+ deploy = deploy.strip
15
+ if deploy =~ /Deployinator - deploy (\S+?):(\S+?)$/
16
+ ret << {:stack => $1, :stage => $2}
17
+ end
18
+ end
19
+ ret
20
+ end
21
+
22
+
23
+ # Public: stop a running deploy indentified by stack and stage
24
+ #
25
+ # Parameters:
26
+ # stack - name of the stack
27
+ # stage - name of the stage
28
+ #
29
+ # Returns true if the deploy was stopped and false on error
30
+ def stop_deploy(stack, stage)
31
+ if deployname = get_deploy_process_title(stack,stage)
32
+ return system("pkill -f '#{deployname}'")
33
+ end
34
+ false
35
+ end
36
+
37
+ # Public: get the activity status of the deploy for a certain stack
38
+ # and stage
39
+ #
40
+ # Parameters:
41
+ # stack - name of the stack
42
+ # stage - name of the stage
43
+ #
44
+ # Returns true for a running deploy or false for a deploy that
45
+ # is not running
46
+ def is_deploy_active?(stack, stage)
47
+ if deployname = get_deploy_process_title(stack,stage)
48
+ return system("pgrep -f '#{deployname}'")
49
+ end
50
+ false
51
+ end
52
+
53
+ # Public: get the process title for the deployment process of a
54
+ # specific stage in a stack
55
+ #
56
+ # Parameters:
57
+ # stack - name of the stack
58
+ # stage - name of the stage
59
+ #
60
+ # Returns the title as a string or nil on error
61
+ def get_deploy_process_title(stack=nil, stage=nil)
62
+ return nil if (stack.nil? or stage.nil?)
63
+ "Deployinator - deploy #{stack}:#{stage}"
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,42 @@
1
+ module Deployinator
2
+ module Helpers
3
+ module DshHelpers
4
+ def dsh_fanout
5
+ @dsh_fanout || 30
6
+ end
7
+
8
+ def run_dsh(groups, cmd, &block)
9
+ groups = [groups] unless groups.is_a?(Array)
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]
12
+ end
13
+
14
+ # run dsh against a given host or array of hosts
15
+ def run_dsh_hosts(hosts, cmd, extra_opts='', &block)
16
+ hosts = [hosts] unless hosts.is_a?(Array)
17
+ if extra_opts.length > 0
18
+ run_cmd %Q{ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} 'dsh -m #{hosts.join(',')} -r ssh -F #{dsh_fanout} #{extra_opts} -- "#{cmd}"'}, &block
19
+ else
20
+ run_cmd %Q{ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} 'dsh -m #{hosts.join(',')} -r ssh -F #{dsh_fanout} -- "#{cmd}"'}, &block
21
+ end
22
+ end
23
+
24
+ def run_dsh_extra(dsh_group, cmd, extra_opts, &block)
25
+ # 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]
27
+ end
28
+
29
+ def hosts_for(group)
30
+ @hosts_for ||= {}
31
+ @hosts_for[group] ||= begin
32
+ dsh_file = "/home/#{Deployinator.default_user}/.dsh/group/#{group}"
33
+ hosts = `ssh #{Deployinator.default_user}@#{Deployinator.deploy_host} cat #{dsh_file}`.chomp
34
+ if $?.nil? || $?.exitstatus != 0
35
+ raise "DSH hosts file at #{Deployinator.deploy_host}:#{dsh_file} is likely missing!"
36
+ end
37
+ hosts.split("\n").delete_if { |x| x.lstrip[0..0] == "#" }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,348 @@
1
+ require 'enumerator'
2
+ require 'date'
3
+
4
+ module Deployinator
5
+ module Helpers
6
+ # Public: module containing helper methods for interacting with git
7
+ # repositories and extracting information from them.
8
+ module GitHelpers
9
+
10
+ # Where we cache the current head rev. stack name wil be appended
11
+ @@rev_head_cache = "/tmp/rev_head_cache"
12
+
13
+ # How many seconds the head rev cache is good for
14
+ @@rev_head_cache_ttl = 15
15
+
16
+ def build_git_cmd(cmd, extra_cmd)
17
+ unless extra_cmd.nil? or extra_cmd.empty?
18
+ "#{extra_cmd} '#{cmd}'"
19
+ else
20
+ cmd
21
+ end
22
+ end
23
+
24
+ # Public: method to get the short rev of a git commit and create a
25
+ # version tag from it. The tag is then dumped into a version text file.
26
+ #
27
+ # stack - String representing the stack, which determines where the
28
+ # version file should be located
29
+ # version_dir - String (or Array of Strings) representing the
30
+ # directories to contain the version file
31
+ # extra_cmd - String representing an additional command to prepend to
32
+ # the version echo command (default: "")
33
+ # path - String containing the base path where the version file is
34
+ # located (default: nil)
35
+ # rev - String containing the rev to parse for the short SHA id
36
+ # (default: "HEAD")
37
+ #
38
+ #
39
+ # Returns STDOUT of the echo command
40
+ def git_bump_version(stack, version_dir, extra_cmd="", path=nil, rev="HEAD", tee_cmd="tee")
41
+ unless version_dir.kind_of?(Array)
42
+ version_dir = [version_dir]
43
+ end
44
+
45
+ ts = Time.now.strftime("%Y%m%d-%H%M%S-%Z")
46
+
47
+ path ||= git_checkout_path(checkout_root, stack)
48
+
49
+ cmd = "cd #{path} && git rev-parse --short=#{Deployinator.git_sha_length} #{rev}"
50
+ cmd = build_git_cmd(cmd, extra_cmd)
51
+ sha1 = run_cmd(cmd)[:stdout]
52
+
53
+ version = "#{sha1.chomp}-#{ts}"
54
+
55
+ fullpaths = ""
56
+ version_dir.each do |dir|
57
+ fullpath = File.join(dir, "version.txt")
58
+ fullpaths << fullpath + " "
59
+ end
60
+
61
+ log_and_stream "Setting #{fullpaths} to #{version}"
62
+
63
+ cmd = "cd #{path} && echo #{version} | #{tee_cmd} #{fullpaths}"
64
+ cmd = build_git_cmd(cmd, extra_cmd)
65
+ run_cmd cmd
66
+
67
+ return version
68
+ end
69
+
70
+ # Public: git helper method to get the path to checkout the git repo to
71
+ #
72
+ # checkout_root - the directory base for the repo location
73
+ # stack - String representing the stack, which determines where the
74
+ # version file should be located
75
+ #
76
+ # Returns path to checkout git repo to
77
+ def git_checkout_path(checkout_root, stack)
78
+ if (git_info_for_stack[stack.intern].has_key?(:checkout_dir))
79
+ dir = git_info_for_stack[stack.intern][:checkout_dir].to_s
80
+ else
81
+ dir = stack.to_s
82
+ end
83
+ File.join(checkout_root, dir)
84
+ end
85
+
86
+ # Public: git helper method to bring a local repo up to date with the
87
+ # remote
88
+ #
89
+ # stack - the stack we want to update the repo for
90
+ # extra_cmd - a command that can be prepended before the actual command
91
+ # path - the path to the repo. This is also passed to the block as
92
+ # an argument if one is provided
93
+ # branch - the branch to checkout after the fetch
94
+ #
95
+ # Returns nothing
96
+ def git_freshen_clone(stack, extra_cmd="", path=nil, branch="master")
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)
100
+ run_cmd cmd
101
+ yield "#{path}" if block_given?
102
+ end
103
+
104
+ # Public: wrapper function which can be used to clone a non-existing repo
105
+ # or freshen an existing one. It tests if the path exists and fetches
106
+ # remotes if that's the case. Otherwise the repo is checked out at the
107
+ # given path.
108
+ #
109
+ # Examples:
110
+ # git_freshen_or_clone("web", etsy_qa, "/var/etsy/", "master")
111
+ #
112
+ # stack - the stack to refresh or clone the repo for
113
+ # extra_cmd - cmd to prepend before the actual command
114
+ # checkout_root - the directory base for the repo location
115
+ # branch - the branch the repo should be on (currently only used
116
+ # when the repo is refreshed). (default: master)
117
+ # read_write - boolean; True means clone the repo read/write
118
+ #
119
+ # Returns stdout of the respective git command.
120
+ def git_freshen_or_clone(stack, extra_cmd, checkout_root, branch="master", read_write=false)
121
+ path = git_checkout_path(checkout_root, stack)
122
+ is_git = is_git_repo(path, extra_cmd)
123
+ if is_git == :true
124
+ log_and_stream "</br>Refreshing repo #{stack} at #{path}</br>"
125
+ git_freshen_clone(stack, extra_cmd, path, branch)
126
+ elsif is_git == :missing
127
+ 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)
129
+ else
130
+ log_and_stream "</br><span class=\"stderr\">The path for #{stack} at #{path} exists but is not a git repo.</span></br>"
131
+ end
132
+ end
133
+
134
+ # Public: Filters a range of commits and either returns all commit shas that
135
+ # only include the filter_file, or the inverse list of shas.
136
+ #
137
+ # stack - the stack to refresh or clone the repo for
138
+ # extra_cmd - cmd to prepend before the actual command
139
+ # old_rev - the git sha representing an older rev.
140
+ # new_rev - the git sha representing a newer rev.
141
+ # path - optional parameter to overide the path to the git repo.
142
+ # filter_files - filepaths relative to the root of the git repo to use as the filter
143
+ # including - Should the returned list of filters include the filter_file commits, or all other commits
144
+ #
145
+ # Returns an array of shas
146
+ def git_filter_shas(stack, extra_cmd, old_rev, new_rev, path=nil, filter_files=[], including=false)
147
+ path ||= git_checkout_path(checkout_root, stack)
148
+ including_shas = []
149
+ excluding_shas = []
150
+ cmd = "cd #{path} && git log --no-merges --name-only --pretty=format:%H #{old_rev}..#{new_rev}"
151
+ cmd = build_git_cmd(cmd, extra_cmd)
152
+ committers = run_cmd(cmd)[:stdout]
153
+ committers.split(/\n\n/).each { |commit|
154
+ lines = commit.split(/\n/)
155
+ commit_sha = lines.shift
156
+ has_filter_file = false
157
+ filter_files.each { |filter_file|
158
+ if !has_filter_file && lines.include?(filter_file)
159
+ has_filter_file = true
160
+ end
161
+ }
162
+ if has_filter_file
163
+ including_shas.push(commit_sha)
164
+ #add to the exclude shas list as well if there are more than one file
165
+ excluding_shas.push(commit_sha) unless lines.length == 1
166
+ else
167
+ excluding_shas.push(commit_sha)
168
+ end
169
+ }
170
+ if including
171
+ return including_shas
172
+ else
173
+ return excluding_shas
174
+ end
175
+ end
176
+
177
+ # Public: Handles get and store of the head rev from a local
178
+ # file cache. Call this instead of get_git_head_rev.
179
+ #
180
+ # Paramaters:
181
+ # stack - name of the stack for the repo
182
+ # branch - name of the branch to test
183
+ #
184
+ # Returns the rev as a string
185
+ def git_head_rev(stack, branch="master")
186
+ filename = "#{@@rev_head_cache}_#{stack}"
187
+ head_rev = get_from_cache(filename, @@rev_head_cache_ttl)
188
+
189
+ unless head_rev
190
+ head_rev = get_git_head_rev(stack, branch)
191
+ File.open(filename, 'w') {|f| f.write(head_rev) }
192
+ end
193
+
194
+ return head_rev
195
+ end
196
+
197
+ # Public: get the short sha of the remote HEAD rev of a stack repo.
198
+ # Beware that a true git short rev can also be longer than git_sha_length chars and
199
+ # this way of retrieving it is no guarantee to get a unique short rev.
200
+ # But the alternative is cloning the repo and do a git rev-parse --short
201
+ #
202
+ # Parameters:
203
+ # stack - name of the stack for the repo
204
+ # branch - name of the branch to test (default: master)
205
+ #
206
+ # Returns the rev as a string
207
+ def get_git_head_rev(stack, branch='master', protocol='git')
208
+ cmd = %x{git ls-remote -h #{git_url(stack, protocol)} #{branch} | cut -c1-#{Deployinator.git_sha_length}}.chomp
209
+ end
210
+
211
+ # Public: helper method which wraps git clone
212
+ #
213
+ # Examples:
214
+ # git_clone('web', 'git@github.com:web.git)
215
+ #
216
+ # stack - the stack to clone
217
+ # repo_url - the remote url of the repo to clone
218
+ # extra_cmd - command to prepend before the actual command
219
+ # checkout_root - base directory to clone into
220
+ # branch - Git branch to checkout. Defaults to 'master'.
221
+ #
222
+ # Returns nothing
223
+ def git_clone(stack, repo_url, extra_cmd="", checkout_root=checkout_root, branch='master')
224
+ path = git_checkout_path(checkout_root, stack)
225
+ cmd = "git clone #{repo_url} -b #{branch} #{path}"
226
+ cmd = build_git_cmd(cmd, extra_cmd)
227
+ run_cmd cmd
228
+ end
229
+
230
+ # Public: helper method to build github urls
231
+ #
232
+ # Example:
233
+ # git_url('web', 'git', false)
234
+ #
235
+ # stack - the stack whose github url we want
236
+ # protocol - 'https', 'http' or 'git'
237
+ # read_write - if true then we give back a git url we can push to
238
+ #
239
+ # Returns string
240
+ def git_url(stack, protocol="git", read_write=false)
241
+ stack = stack.intern
242
+ repo = git_info_for_stack[stack][:repository]
243
+ github_host = which_github_host(stack)
244
+ repo += ".git" if protocol != "git"
245
+ if (read_write)
246
+ return "git@#{github_host}:#{git_info_for_stack[stack][:user]}/#{repo}"
247
+ else
248
+ return "#{protocol}://#{github_host}/#{git_info_for_stack[stack][:user]}/#{repo}"
249
+ end
250
+ end
251
+
252
+ # Public: helper method to determine which github hostname to use
253
+ #
254
+ # Example:
255
+ # which_github_host('statsd')
256
+ #
257
+ # stack - the stack for the github host we are looking for
258
+ #
259
+ # Returns string
260
+ def which_github_host(stack)
261
+ github_host = git_info_for_stack[stack][:host]
262
+ github_host || Deployinator.github_host
263
+ end
264
+
265
+ def git_info_for_stack
266
+ if Deployinator.git_info_for_stack
267
+ Deployinator.git_info_for_stack
268
+ else
269
+ {}
270
+ end
271
+ end
272
+
273
+ # Public: determines whether a given filesystem path is a git repo
274
+ #
275
+ # path - the path to the directory to test
276
+ # extra_cmd - optional extra_cmd to prepend to the testing command
277
+ # (default: "")
278
+ #
279
+ # Examples
280
+ # is_git_repo('/home/dev/repo')
281
+ # is_git_repo('/home/dev/repo', 'ssh dev@deployhost')
282
+ #
283
+ # Returns :true if it is a git repo,
284
+ # :false if the path exists but is not a git repo,
285
+ # :missing if the path doesn't exist
286
+ def is_git_repo(path, extra_cmd="")
287
+ cmd = "#{extra_cmd} test"
288
+ is_dir = system("#{cmd} -d #{path}")
289
+ is_file = system("#{cmd} -f #{path}")
290
+ is_git = system("#{cmd} -d #{path}/.git")
291
+
292
+ # check possibilities
293
+ if is_dir
294
+ return :true if is_git
295
+ return :false
296
+ elsif is_file
297
+ return :false
298
+ else
299
+ return :missing
300
+ end
301
+ end
302
+
303
+ # Public: determine whether we want to use the github diff based on the
304
+ # existence of the stack key in the github info dict
305
+ #
306
+ # Returns true for existing keys and false otherwise
307
+ def use_github_diff
308
+ git_info_for_stack.has_key? @stack.to_sym
309
+ end
310
+
311
+ def github_list_committers(github_commits_param)
312
+ commits = {}
313
+ unless github_commits_param.nil?
314
+ github_commits_param.each do |c|
315
+ name = c["commit"]["committer"]["name"]
316
+ sha = c["sha"]
317
+ message = c["commit"]["message"]
318
+ commits[sha] = { :name => name, :message => message}
319
+ end
320
+ end
321
+ return commits
322
+ end
323
+
324
+ # Public: list the files that changes between 2 revs
325
+ #
326
+ # Parameters:
327
+ # rev1: string of the older rev
328
+ # rev2: string of the newer rev
329
+ # ssh_cmd: string ssh cmd to get to a host where you've got this repo checked out
330
+ # extra: string any extra cmds like cd that you need to do on the remote host to get to your checkout
331
+ # quiet: boolean - if true we make no additional output and just return the files
332
+ #
333
+ # Returns:
334
+ # 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}
337
+ extra = "#{extra} &&" unless extra.nil?
338
+ if quiet
339
+ list_of_touched_files = %x{#{ssh_cmd} "#{extra} #{cmd}"}
340
+ else
341
+ list_of_touched_files = run_cmd(%Q{#{ssh_cmd} "#{extra} #{cmd}"})[:stdout]
342
+ end
343
+ return list_of_touched_files.split("\n")
344
+ end
345
+
346
+ end
347
+ end
348
+ end