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.
- checksums.yaml +5 -13
- data/.gitignore +3 -0
- data/.travis.yml +8 -0
- data/Gemfile +0 -1
- data/README.md +47 -10
- data/Rakefile +0 -1
- data/bin/deployinator-tailer.rb +2 -2
- data/deployinator.gemspec +5 -1
- data/lib/deployinator.rb +20 -1
- data/lib/deployinator/app.rb +53 -12
- data/lib/deployinator/controller.rb +8 -3
- data/lib/deployinator/helpers.rb +109 -13
- data/lib/deployinator/helpers/concurrency.rb +70 -0
- data/lib/deployinator/helpers/dsh.rb +15 -5
- data/lib/deployinator/helpers/git.rb +16 -9
- data/lib/deployinator/helpers/version.rb +52 -52
- data/lib/deployinator/static/css/maintenance.css +9 -0
- data/lib/deployinator/static/css/style.css +25 -5
- data/lib/deployinator/static/images/maintenance.gif +0 -0
- data/lib/deployinator/static/js/stats_load.js +10 -0
- data/lib/deployinator/templates/exception.mustache +11 -0
- data/lib/deployinator/templates/generic_single_push.mustache +2 -1
- data/lib/deployinator/templates/layout.mustache +5 -4
- data/lib/deployinator/templates/log_table.mustache +18 -5
- data/lib/deployinator/templates/maintenance.mustache +5 -0
- data/lib/deployinator/templates/stats.mustache +180 -0
- data/lib/deployinator/version.rb +1 -1
- data/lib/deployinator/views/log_table.rb +36 -0
- data/lib/deployinator/views/maintenance.rb +15 -0
- data/lib/deployinator/views/stats.rb +96 -0
- data/test/unit/concurrency_test.rb +72 -0
- data/test/unit/git_test.rb +70 -0
- data/test/unit/helpers_test.rb +61 -2
- data/test/unit/version_test.rb +12 -12
- metadata +74 -17
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
YjE2MjYwOGUyOTRmYzI1MmI0Y2RkMjQ3ZGQzOTYyZjZkYzYyOGY3Yg==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f825a038dbf37c774aada076bb6d5c16185ed23e
|
4
|
+
data.tar.gz: df3bcfdc241bb68177693fa2a3603510844dd989
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
NDBiYWM5OTkwYjYyZGQ4MjhiYTliOWJmNDU2YTEzNjFmMmRlOWRiYzMyNzdj
|
11
|
-
Y2E5MGU5Zjc2M2ExMmU2NjU0ZjRmZDVhNDZjZDM0NWY2ZGZmM2I=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZTQ5OWJiNzNkZmQzNzJmOTI1ZTkzMjFmNzNjMzBkZDIxNjhmN2Y0YWVmYjkw
|
14
|
-
NjlhODNjZjc4YzQ4MDgxOWQzYmY1NjdmNzA5NGU2NDEwNzg5NTY5MTFmNzYy
|
15
|
-
YjZmODRmMTE5ZTYwMjA4ZmY4MjY2OTI1NzQ3ZDk0YzczZmVhYWY=
|
6
|
+
metadata.gz: 8a63d5192abcbc7d262f61ef706713988e74c1080111329c0c6fe28781214ec82b441e7e06f6a7fc79e31b371384597f263a3a814f40296c3c846e600b28b2d9
|
7
|
+
data.tar.gz: 21122e91e6b8a87f49c69bb567c4c8a710359f467d98498e65de600e43937c3905cef52c5e13cbd388a2c5514f0d4c06e58cae9bc688492bb0cbce121ab2682b
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
_________ ______ _____ _____
|
3
3
|
______ /_____ ________ ___ /______ _____ _____(_)_______ ______ ___ /_______ ________
|
4
4
|
_ __ / _ _ \___ __ \__ / _ __ \__ / / /__ / __ __ \_ __ `/_ __/_ __ \__ ___/
|
5
|
-
/ /_/ / / __/__ /_/ /_ / / /_/ /_ /_/ / _ / _ / / // /_/ / / /_ / /_/ /_ /
|
6
|
-
\__,_/ \___/ _ .___/ /_/ \____/ _\__, / /_/ /_/ /_/ \__,_/ \__/ \____/ /_/
|
5
|
+
/ /_/ / / __/__ /_/ /_ / / /_/ /_ /_/ / _ / _ / / // /_/ / / /_ / /_/ /_ /
|
6
|
+
\__,_/ \___/ _ .___/ /_/ \____/ _\__, / /_/ /_/ /_/ \__,_/ \__/ \____/ /_/
|
7
7
|
/_/ /____/ Deploy with style!
|
8
8
|
</pre>
|
9
9
|
|
@@ -28,9 +28,9 @@ Deployinator is a deployment framework extracted from Etsy. We've been using it
|
|
28
28
|
## Stacks
|
29
29
|
Deployments are grouped by "stacks". You might have a "web" and "search" stack.
|
30
30
|
|
31
|
-
Each of those stacks might have different deployment environments, such as "staging" or "production".
|
31
|
+
Each of those stacks might have different deployment environments, such as "staging" or "production".
|
32
32
|
|
33
|
-
You can map a button to each of these environments, to create multi-stage pushes within each stack.
|
33
|
+
You can map a button to each of these environments, to create multi-stage pushes within each stack.
|
34
34
|
|
35
35
|
## Installation
|
36
36
|
|
@@ -55,13 +55,14 @@ you can skip the bundler steps.
|
|
55
55
|
- Run the following command to make deployinator gem's rake tasks available to you:
|
56
56
|
|
57
57
|
```sh
|
58
|
+
$ shopt -s xpg_echo
|
58
59
|
$ echo "require 'deployinator'\nload 'deployinator/tasks/initialize.rake' " > Rakefile
|
59
60
|
```
|
60
61
|
|
61
62
|
- Create a binstub for the deploy log tailing backend:
|
62
63
|
|
63
64
|
```sh
|
64
|
-
bundle
|
65
|
+
bundle binstub etsy-deployinator
|
65
66
|
```
|
66
67
|
|
67
68
|
- Initialize the project directory by running the following command replacing ___Company___ with the name of your company/organization. This must start with a capital letter.
|
@@ -116,14 +117,14 @@ underscores but if you forget the rake task will convert from camelcase for you.
|
|
116
117
|
$ bundle exec shotgun --host localhost -p 7777 config.ru
|
117
118
|
```
|
118
119
|
|
119
|
-
- You will probably want a robust server like apache to handle production traffic.
|
120
|
+
- You will probably want a robust server like apache to handle production traffic.
|
120
121
|
|
121
122
|
- The `config/base.rb` file is the base config for the application. Replace all
|
122
123
|
occurences of ___test_stack___ with the name you chose above. Also the example
|
123
124
|
below uses a git repository of http://github.com/etsy/deployinator, feel free to
|
124
125
|
replace this with your specific repository
|
125
126
|
|
126
|
-
```ruby
|
127
|
+
```ruby
|
127
128
|
Deployinator.app_context['test_stack_config'] = {
|
128
129
|
:prod_host => "localhost",
|
129
130
|
:checkout_path => "/tmp/deployinator_dev/"
|
@@ -232,7 +233,7 @@ For example you could wrap your capistrano deploy:
|
|
232
233
|
run_cmd %Q{cap deploy}
|
233
234
|
|
234
235
|
|
235
|
-
#### log_and_stream
|
236
|
+
#### log_and_stream
|
236
237
|
|
237
238
|
Output information to the log file, and the streaming output handler.
|
238
239
|
The real time output console renders HTML so you should use markup here.
|
@@ -241,7 +242,7 @@ The real time output console renders HTML so you should use markup here.
|
|
241
242
|
|
242
243
|
#### log_and_shout
|
243
244
|
|
244
|
-
Output an announcement message with build related information.
|
245
|
+
Output an announcement message with build related information.
|
245
246
|
Also includes hooks for Email.
|
246
247
|
|
247
248
|
log_and_shout({
|
@@ -348,8 +349,20 @@ To set these simple override the methods in your view class. For example:
|
|
348
349
|
This can be done on a global layout that extends the gem's default layout or on
|
349
350
|
a stack by stack basis in their own view.
|
350
351
|
|
352
|
+
### Maintenance mode
|
353
|
+
Deployinator has a setting for maintenance mode, which is mostly useful if you
|
354
|
+
have major changes that affect all stacks and you want to make sure no deploys
|
355
|
+
are going on while you make the change. In order to enable it, you have to set
|
356
|
+
`Deployinator.maintenance_mode = true`. This will make all pages for anyone
|
357
|
+
not in `Deployinator.admin_groups` go to `/maintenance`. As a Deployinator
|
358
|
+
admin you can then still deploy stacks and use the app.
|
359
|
+
|
360
|
+
On the maintenance page Deployinator will show the value of
|
361
|
+
`Deployinator.maintenance_contact` as the place to get help in case you need
|
362
|
+
any or are confused about maintenance mode.
|
363
|
+
|
351
364
|
## Hacking on the gem
|
352
|
-
If you find issues with the gem, or would like to play around with it, you can check it out from git and start hacking on it.
|
365
|
+
If you find issues with the gem, or would like to play around with it, you can check it out from git and start hacking on it.
|
353
366
|
First tell bundler to use your local copy instead by running:
|
354
367
|
```sh
|
355
368
|
$ bundle config local.deployinator /path/to/DeployinatorGem
|
@@ -359,6 +372,30 @@ Next, on every code change, you can install from the checked out gem by running
|
|
359
372
|
$ bundle install --no-deployment && bundle install --deployment
|
360
373
|
```
|
361
374
|
|
375
|
+
### Stats dashboard
|
376
|
+
The `/stats` page pulls from `log/deployinator.log` to show a graph of deployments per day for each stack over time. By default, it shows all stacks. To blacklist or whitelist certain stacks, update `config/base.rb` with:
|
377
|
+
```rb
|
378
|
+
Deployinator.stats_included_stacks = ['my_whitelisted_stack', 'another_whitelisted_stack']
|
379
|
+
Deployinator.stats_ignored_stacks = ['my_stack_to_ignore', 'another_stack_to_ignore']
|
380
|
+
Deployinator.stats_extra_grep = 'Production deploy' # filter out log entries matching this string
|
381
|
+
```
|
382
|
+
|
383
|
+
Whitelisting stacks or applying a custom extra grep can help speed up graph rendering when you have a large log file.
|
384
|
+
|
385
|
+
If at some point you change the name of a stack, you can group the old log entries with the new by adding the following to `config/base.rb`:
|
386
|
+
|
387
|
+
```rb
|
388
|
+
Deployinator.stats_renamed_stacks = [
|
389
|
+
{
|
390
|
+
:previous_stack => {
|
391
|
+
:stack => [ "old_stack_name" ]
|
392
|
+
},
|
393
|
+
:new_name => "new_stack_name"
|
394
|
+
}
|
395
|
+
]
|
396
|
+
```
|
397
|
+
|
398
|
+
|
362
399
|
### Contributing
|
363
400
|
|
364
401
|
1. Fork it
|
data/Rakefile
CHANGED
data/bin/deployinator-tailer.rb
CHANGED
@@ -54,8 +54,8 @@ EM.run do
|
|
54
54
|
if stack && @@channels.key?(stack)
|
55
55
|
@@channels[stack].unsubscribe(session_id)
|
56
56
|
@@channels_count[stack] -= 1
|
57
|
-
if @@channels_count[stack] == 0
|
58
|
-
@@tailers[stack].close
|
57
|
+
if @@channels_count[stack] == 0 && @@tailers.has_key?(stack)
|
58
|
+
@@tailers[stack].close
|
59
59
|
@@tailers.delete(stack)
|
60
60
|
end
|
61
61
|
end
|
data/deployinator.gemspec
CHANGED
@@ -20,14 +20,18 @@ Gem::Specification.new do |gem|
|
|
20
20
|
gem.required_ruby_version = '>= 1.9.3'
|
21
21
|
|
22
22
|
gem.add_development_dependency "mocha", "~> 0.14"
|
23
|
+
gem.add_development_dependency "test-unit", ">= 3"
|
23
24
|
|
24
25
|
gem.add_runtime_dependency "rake", "~> 10", ">= 10.3.2"
|
25
26
|
gem.add_runtime_dependency "json", "~> 1.8"
|
26
27
|
gem.add_runtime_dependency "mustache", "~> 0.99"
|
28
|
+
# pony pulls in mail >2.0. mail 3.0 needs ruby 2 or greater. to maintain ruby 1.9 compat we're pinning mail here
|
29
|
+
gem.add_runtime_dependency "mail", "2.6.3"
|
27
30
|
gem.add_runtime_dependency "pony", "~> 1.5"
|
28
31
|
gem.add_runtime_dependency "tlsmail", "~> 0.0"
|
29
32
|
gem.add_runtime_dependency "eventmachine", "~> 1.0", ">= 1.0.4"
|
30
|
-
gem.add_runtime_dependency "eventmachine-tail", "~> 0.6", ">= 0.6.
|
33
|
+
gem.add_runtime_dependency "eventmachine-tail", "~> 0.6", ">= 0.6.5"
|
31
34
|
gem.add_runtime_dependency "em-websocket", "~> 0.5", ">= 0.5.1"
|
32
35
|
gem.add_runtime_dependency "sinatra", "~> 1.4", ">=1.4.3"
|
36
|
+
gem.add_runtime_dependency "celluloid", "~> 0.16"
|
33
37
|
end
|
data/lib/deployinator.rb
CHANGED
@@ -54,10 +54,27 @@ module Deployinator
|
|
54
54
|
attr_accessor :admin_groups
|
55
55
|
@admin_groups = []
|
56
56
|
|
57
|
+
attr_accessor :maintenance_mode
|
58
|
+
|
59
|
+
attr_accessor :maintenance_contact
|
60
|
+
|
57
61
|
# the controller class. defaults to Deployinator::Controller
|
58
62
|
# if you override this it should be a subclass of Deployinator::Controller
|
59
63
|
attr_accessor :deploy_controller
|
60
64
|
|
65
|
+
# include stacks in /stats
|
66
|
+
attr_accessor :stats_included_stacks
|
67
|
+
|
68
|
+
# exclude stacks in /stats, even if present in stats_included_stacks
|
69
|
+
attr_accessor :stats_ignored_stacks
|
70
|
+
|
71
|
+
# filter log entries in /stats
|
72
|
+
attr_accessor :stats_extra_grep
|
73
|
+
|
74
|
+
# list of configurations for grouping historical / renamed stacks in /stats
|
75
|
+
attr_accessor :stats_renamed_stacks
|
76
|
+
@@stats_renamed_stacks = []
|
77
|
+
|
61
78
|
def initialize
|
62
79
|
@stack_plugins = {}
|
63
80
|
@global_plugins = []
|
@@ -101,12 +118,14 @@ end
|
|
101
118
|
Deployinator.root_dir = Dir.pwd
|
102
119
|
Deployinator.app_context = {}
|
103
120
|
Deployinator.admin_groups = []
|
121
|
+
Deployinator.maintenance_mode = false
|
122
|
+
Deployinator.maintenance_contact = "admin@deployinator.example.com"
|
104
123
|
Deployinator.global_plugins = []
|
105
124
|
Deployinator.log_file = Deployinator.root(["log", "development.log"])
|
106
125
|
Deployinator.log_path = Deployinator.root(["log", "deployinator.log"])
|
107
126
|
Deployinator.timing_log_path = Deployinator.root(["log", "deployinator-timing.log"])
|
108
127
|
Deployinator.git_sha_length = "10"
|
109
|
-
Deployinator.default_user =
|
128
|
+
Deployinator.default_user = `/usr/bin/whoami`
|
110
129
|
Deployinator.stack_tailer_port = 7778
|
111
130
|
Deployinator.github_host = 'github.com'
|
112
131
|
Deployinator.app_context['context'] = 'dev'
|
data/lib/deployinator/app.rb
CHANGED
@@ -12,6 +12,8 @@ require 'deployinator/views/log'
|
|
12
12
|
require 'deployinator/views/run_logs'
|
13
13
|
require 'deployinator/views/log_table'
|
14
14
|
require 'deployinator/views/deploys_status'
|
15
|
+
require 'deployinator/views/stats'
|
16
|
+
require 'deployinator/views/maintenance'
|
15
17
|
|
16
18
|
module Deployinator
|
17
19
|
class DeployinatorApp < Sinatra::Base
|
@@ -41,12 +43,32 @@ module Deployinator
|
|
41
43
|
register_plugins(nil)
|
42
44
|
init(env)
|
43
45
|
@disabled_override = params[:override].nil? ? false : true
|
46
|
+
pass if [
|
47
|
+
"/maintenance",
|
48
|
+
"/css/maintenance.css",
|
49
|
+
"/images/maintenance.gif",
|
50
|
+
"/static/css/style.css"
|
51
|
+
].include? request.path_info
|
52
|
+
if Deployinator.maintenance_mode == true && !is_admin?
|
53
|
+
redirect "/maintenance"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
before do
|
44
58
|
end
|
45
59
|
|
46
60
|
get '/' do
|
47
61
|
mustache Deployinator::Views::Index
|
48
62
|
end
|
49
63
|
|
64
|
+
get '/stats' do
|
65
|
+
mustache Deployinator::Views::Stats
|
66
|
+
end
|
67
|
+
|
68
|
+
get '/maintenance' do
|
69
|
+
mustache Deployinator::Views::Maintenance
|
70
|
+
end
|
71
|
+
|
50
72
|
get '/run_logs/view/:log' do
|
51
73
|
template = open("#{File.dirname(__FILE__)}/templates/stream.mustache").read
|
52
74
|
template.gsub!("{{ yield }}", "{{{yield}}}")
|
@@ -95,17 +117,6 @@ module Deployinator
|
|
95
117
|
git_head_rev(params[:stack]).chomp
|
96
118
|
end
|
97
119
|
|
98
|
-
get '/:thing' do
|
99
|
-
@stack = params[:thing]
|
100
|
-
@params = params
|
101
|
-
register_plugins(@stack)
|
102
|
-
begin
|
103
|
-
mustache @stack
|
104
|
-
rescue Errno::ENOENT
|
105
|
-
pass
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
120
|
get '/:stack/remove-lock' do
|
110
121
|
stack = params[:stack]
|
111
122
|
unlock_pushes(stack) if can_remove_stack_lock?
|
@@ -129,6 +140,10 @@ module Deployinator
|
|
129
140
|
send_file "#{File.dirname(__FILE__)}/static/css/highlight.css"
|
130
141
|
end
|
131
142
|
|
143
|
+
get '/css/maintenance.css' do
|
144
|
+
send_file "#{File.dirname(__FILE__)}/static/css/maintenance.css"
|
145
|
+
end
|
146
|
+
|
132
147
|
get '/js/flot/jquery.flot.min.js' do
|
133
148
|
send_file "#{File.dirname(__FILE__)}/static/js/flot/jquery.flot.min.js"
|
134
149
|
end
|
@@ -149,6 +164,14 @@ module Deployinator
|
|
149
164
|
send_file "#{File.dirname(__FILE__)}/static/js/jquery.timed_bar.js"
|
150
165
|
end
|
151
166
|
|
167
|
+
get '/js/stats_load.js' do
|
168
|
+
send_file "#{File.dirname(__FILE__)}/static/js/stats_load.js"
|
169
|
+
end
|
170
|
+
|
171
|
+
get '/images/maintenance.gif' do
|
172
|
+
send_file "#{File.dirname(__FILE__)}/static/images/maintenance.gif"
|
173
|
+
end
|
174
|
+
|
152
175
|
get '/deploys_status' do
|
153
176
|
mustache Deployinator::Views::DeploysStatus
|
154
177
|
end
|
@@ -172,6 +195,7 @@ module Deployinator
|
|
172
195
|
# the background.
|
173
196
|
post '/deploys/?' do
|
174
197
|
params[:username] = @username
|
198
|
+
params[:groups] = @groups
|
175
199
|
params[:block] = Proc.new { |line| foo = line }
|
176
200
|
deploy_running = is_deploy_active?(params[:stack], params[:stage])
|
177
201
|
|
@@ -180,7 +204,7 @@ module Deployinator
|
|
180
204
|
return 403
|
181
205
|
end
|
182
206
|
|
183
|
-
fork {
|
207
|
+
pid = fork {
|
184
208
|
Signal.trap("HUP") { exit }
|
185
209
|
Deployinator.setup_logging
|
186
210
|
$0 = get_deploy_process_title(params[:stack], params[:stage])
|
@@ -188,6 +212,7 @@ module Deployinator
|
|
188
212
|
d = controller.new
|
189
213
|
d.run(params)
|
190
214
|
}
|
215
|
+
Process.detach(pid)
|
191
216
|
200
|
192
217
|
end
|
193
218
|
|
@@ -200,5 +225,21 @@ module Deployinator
|
|
200
225
|
|
201
226
|
200
|
202
227
|
end
|
228
|
+
|
229
|
+
get '/:thing' do
|
230
|
+
@stack = params[:thing]
|
231
|
+
|
232
|
+
unless Deployinator.get_stacks.include?(@stack)
|
233
|
+
raise "No such stack #{@stack}"
|
234
|
+
end
|
235
|
+
|
236
|
+
@params = params
|
237
|
+
register_plugins(@stack)
|
238
|
+
begin
|
239
|
+
mustache @stack
|
240
|
+
rescue Errno::ENOENT
|
241
|
+
pass
|
242
|
+
end
|
243
|
+
end
|
203
244
|
end
|
204
245
|
end
|
@@ -29,6 +29,7 @@ module Deployinator
|
|
29
29
|
@deploy_start_time = Time.now.to_i
|
30
30
|
@start_time = Time.now.to_i
|
31
31
|
@username = args[:username]
|
32
|
+
@groups = args[:groups]
|
32
33
|
@host = `hostname -s`
|
33
34
|
@stack = args[:stack]
|
34
35
|
@method = args[:method]
|
@@ -37,7 +38,7 @@ module Deployinator
|
|
37
38
|
|
38
39
|
# This gets the runlog output on the console; is used by log_and_stream
|
39
40
|
@block = args[:block] || Proc.new do |output|
|
40
|
-
$stdout.write output.gsub!(/(<[^>]*>)|\n|\t/
|
41
|
+
$stdout.write output.gsub!(/(<[^>]*>)|\n|\t/m) {" "}
|
41
42
|
$stdout.write "\n"
|
42
43
|
end
|
43
44
|
end
|
@@ -102,7 +103,11 @@ module Deployinator
|
|
102
103
|
deploy_instance = deploy_class.new(options)
|
103
104
|
deploy_instance.register_plugins(options[:stack])
|
104
105
|
|
105
|
-
deploy_instance.lock_pushes(options[:stack], options[:username], options[:method])
|
106
|
+
locked = deploy_instance.lock_pushes(options[:stack], options[:username], options[:method])
|
107
|
+
|
108
|
+
unless locked
|
109
|
+
return deploy_instance
|
110
|
+
end
|
106
111
|
|
107
112
|
@start_time = Time.now
|
108
113
|
deploy_instance.log_and_stream "Push started at #{@start_time.to_i}\n"
|
@@ -115,7 +120,7 @@ module Deployinator
|
|
115
120
|
state = deploy_instance.send(options[:method], options)
|
116
121
|
rescue Exception => e
|
117
122
|
deploy_instance.log_error("There was an exception during this deploy. Aborted!", e)
|
118
|
-
deploy_instance.raise_event(:deploy_error)
|
123
|
+
deploy_instance.raise_event(:deploy_error, {:exception => e})
|
119
124
|
end
|
120
125
|
|
121
126
|
if state.nil? || !state.is_a?(Hash)
|
data/lib/deployinator/helpers.rb
CHANGED
@@ -75,11 +75,37 @@ module Deployinator
|
|
75
75
|
#
|
76
76
|
# Returns nothing
|
77
77
|
def log_and_stream(output)
|
78
|
-
write_file output,
|
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
|
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
|
-
|
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
|
428
|
-
# description
|
429
|
-
#
|
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,
|
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
|
-
|
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]
|
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
|
-
|
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
|