mamiya 0.0.1.alpha24 → 0.0.1.beta1
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.
- checksums.yaml +4 -4
- data/README.md +24 -18
- data/example/config.agent.rb +3 -0
- data/example/config.rb +27 -0
- data/example/deploy.rb +19 -11
- data/lib/mamiya/agent.rb +38 -2
- data/lib/mamiya/agent/actions.rb +4 -0
- data/lib/mamiya/agent/tasks/clean.rb +35 -0
- data/lib/mamiya/agent/tasks/notifyable.rb +4 -1
- data/lib/mamiya/agent/tasks/prepare.rb +2 -0
- data/lib/mamiya/agent/tasks/switch.rb +123 -0
- data/lib/mamiya/cli.rb +1 -20
- data/lib/mamiya/cli/client.rb +225 -3
- data/lib/mamiya/configuration.rb +18 -0
- data/lib/mamiya/master/agent_monitor.rb +19 -4
- data/lib/mamiya/master/agent_monitor_handlers.rb +10 -0
- data/lib/mamiya/master/application_status.rb +72 -0
- data/lib/mamiya/master/package_status.rb +178 -0
- data/lib/mamiya/master/web.rb +48 -44
- data/lib/mamiya/steps/build.rb +1 -1
- data/lib/mamiya/steps/prepare.rb +1 -1
- data/lib/mamiya/steps/switch.rb +2 -1
- data/lib/mamiya/storages/filesystem.rb +1 -1
- data/lib/mamiya/storages/mock.rb +1 -1
- data/lib/mamiya/storages/s3.rb +1 -1
- data/lib/mamiya/storages/s3_proxy.rb +1 -1
- data/lib/mamiya/version.rb +1 -1
- data/spec/agent/actions_spec.rb +26 -1
- data/spec/agent/tasks/clean_spec.rb +88 -2
- data/spec/agent/tasks/notifyable_spec.rb +3 -3
- data/spec/agent/tasks/switch_spec.rb +176 -0
- data/spec/agent_spec.rb +142 -1
- data/spec/configuration_spec.rb +23 -0
- data/spec/master/agent_monitor_spec.rb +128 -38
- data/spec/master/application_status_spec.rb +171 -0
- data/spec/master/package_status_spec.rb +560 -0
- data/spec/master/web_spec.rb +116 -1
- data/spec/steps/build_spec.rb +1 -1
- data/spec/steps/prepare_spec.rb +6 -1
- data/spec/steps/switch_spec.rb +6 -2
- data/spec/storages/filesystem_spec.rb +2 -21
- data/spec/storages/s3_proxy_spec.rb +2 -22
- data/spec/storages/s3_spec.rb +2 -20
- metadata +11 -2
data/lib/mamiya/cli.rb
CHANGED
@@ -47,11 +47,6 @@ module Mamiya
|
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
50
|
-
desc "status", "Show status of servers"
|
51
|
-
def status
|
52
|
-
# TODO:
|
53
|
-
end
|
54
|
-
|
55
50
|
desc "list-packages", "List packages in storage"
|
56
51
|
method_option :name_only, aliases: '-n'
|
57
52
|
def list_packages
|
@@ -88,14 +83,6 @@ module Mamiya
|
|
88
83
|
|
89
84
|
# ---
|
90
85
|
|
91
|
-
desc "deploy PACKAGE", "Run build->push->distribute->prepare->finalize"
|
92
|
-
def deploy
|
93
|
-
end
|
94
|
-
|
95
|
-
desc "rollback", "Switch back to previous release then finalize"
|
96
|
-
def rollback
|
97
|
-
end
|
98
|
-
|
99
86
|
desc "build", "Build package."
|
100
87
|
method_option :build_from, aliases: %w(--source -f), type: :string
|
101
88
|
method_option :build_to, aliases: %w(--destination -t), type: :string
|
@@ -216,12 +203,6 @@ module Mamiya
|
|
216
203
|
@agent.run!
|
217
204
|
end
|
218
205
|
|
219
|
-
# def worker
|
220
|
-
# end
|
221
|
-
|
222
|
-
# def event_handler
|
223
|
-
# end
|
224
|
-
|
225
206
|
private
|
226
207
|
|
227
208
|
def prepare_agent_behavior!
|
@@ -247,7 +228,7 @@ module Mamiya
|
|
247
228
|
|
248
229
|
def config(dont_raise_error = false)
|
249
230
|
return @config if @config
|
250
|
-
path = [options[:config], './mamiya.conf.rb', './config.rb'].compact.find { |_| File.exists?(_) }
|
231
|
+
path = [options[:config], './mamiya.conf.rb', './config.rb', '/etc/mamiya/config.rb'].compact.find { |_| File.exists?(_) }
|
251
232
|
|
252
233
|
if path
|
253
234
|
logger.debug "Using configuration: #{path}"
|
data/lib/mamiya/cli/client.rb
CHANGED
@@ -28,7 +28,7 @@ module Mamiya
|
|
28
28
|
desc "show-package", "show package meta data"
|
29
29
|
method_option :format, aliases: %w(-f), type: :string, default: 'pp'
|
30
30
|
def show_package(package)
|
31
|
-
meta = master_get("/packages/#{application}/#{package}")
|
31
|
+
meta = @meta = master_get("/packages/#{application}/#{package}")
|
32
32
|
|
33
33
|
case options[:format]
|
34
34
|
when 'pp'
|
@@ -77,6 +77,7 @@ module Mamiya
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
+
# TODO: Deprecated. Remove this
|
80
81
|
desc "show-distribution package", "Show package distribution status"
|
81
82
|
method_option :format, aliases: %w(-f), type: :string, default: 'text'
|
82
83
|
method_option :verbose, aliases: %w(-v), type: :boolean
|
@@ -127,6 +128,19 @@ not distributed: #{dist['not_distributed_count']} agents
|
|
127
128
|
end
|
128
129
|
end
|
129
130
|
|
131
|
+
desc "status [PACKAGE]", "Show application or package status"
|
132
|
+
method_option :format, aliases: %w(-f), type: :string, default: 'text'
|
133
|
+
method_option :labels, type: :string
|
134
|
+
method_option :show_done, type: :boolean, default: false
|
135
|
+
def status(package=nil)
|
136
|
+
if package
|
137
|
+
pkg_status package
|
138
|
+
else
|
139
|
+
app_status
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
|
130
144
|
desc "distribute package", "order distributing package to agents"
|
131
145
|
method_option :labels, type: :string
|
132
146
|
def distribute(package)
|
@@ -145,17 +159,136 @@ not distributed: #{dist['not_distributed_count']} agents
|
|
145
159
|
p master_post("/packages/#{application}/#{package}/prepare", params.merge(type: :json))
|
146
160
|
end
|
147
161
|
|
162
|
+
desc "switch PACKAGE", "order switching package to agents"
|
163
|
+
method_option :labels, type: :string
|
164
|
+
method_option :no_release, type: :boolean, default: false
|
165
|
+
def switch(package)
|
166
|
+
params = {no_release: options[:no_release]}
|
167
|
+
if options[:labels]
|
168
|
+
params[:labels] = Mamiya::Util::LabelMatcher.parse_string_expr(options[:labels])
|
169
|
+
end
|
170
|
+
|
171
|
+
p master_post("/packages/#{application}/#{package}/switch", params.merge(type: :json))
|
172
|
+
end
|
173
|
+
|
148
174
|
desc "refresh", "order refreshing agent status"
|
149
175
|
def refresh
|
150
176
|
p master_post('/agents/refresh')
|
151
177
|
end
|
152
178
|
|
153
|
-
desc "deploy PACKAGE", "
|
154
|
-
|
179
|
+
desc "deploy PACKAGE", "Prepare, then switch"
|
180
|
+
method_option :labels, type: :string
|
181
|
+
method_option :no_release, type: :boolean, default: false
|
182
|
+
method_option :config, aliases: '-C', type: :string
|
183
|
+
method_option :no_switch, type: :boolean, default: false
|
184
|
+
def deploy(package)
|
185
|
+
@deploy_exception = nil
|
186
|
+
# TODO: move this run on master node side
|
187
|
+
puts "=> Deploying #{application}/#{package}"
|
188
|
+
puts " * with labels: #{options[:labels].inspect}" if options[:labels] && !options[:labels].empty?
|
189
|
+
|
190
|
+
show_package(package)
|
191
|
+
|
192
|
+
if config
|
193
|
+
config.set :application, application
|
194
|
+
config.set :package_name, package
|
195
|
+
config.set :package, @meta
|
196
|
+
|
197
|
+
config.before_deploy_or_rollback[]
|
198
|
+
config.before_deploy[]
|
199
|
+
end
|
200
|
+
|
201
|
+
do_prep = -> do
|
202
|
+
puts "=> Preparing..."
|
203
|
+
prepare(package)
|
204
|
+
end
|
205
|
+
|
206
|
+
do_prep[]
|
207
|
+
|
208
|
+
puts " * Wait until prepared"
|
209
|
+
puts ""
|
210
|
+
|
211
|
+
i = 0
|
212
|
+
loop do
|
213
|
+
i += 1
|
214
|
+
do_prep[] if i % 25 == 0
|
215
|
+
|
216
|
+
s = pkg_status(package, :short)
|
217
|
+
puts ""
|
218
|
+
break if 0 < s['participants_count'] && s['participants_count'] == s['prepare']['done'].size
|
219
|
+
sleep 2
|
220
|
+
end
|
221
|
+
|
222
|
+
###
|
223
|
+
|
224
|
+
unless options[:no_switch]
|
225
|
+
puts "=> Switching..."
|
226
|
+
switch(package)
|
227
|
+
|
228
|
+
puts " * Wait until switch"
|
229
|
+
puts ""
|
230
|
+
loop do
|
231
|
+
s = pkg_status(package, :short)
|
232
|
+
puts ""
|
233
|
+
break if s['participants_count'] == s['switch']['done'].size
|
234
|
+
sleep 2
|
235
|
+
end
|
236
|
+
end
|
237
|
+
rescue Exception => e
|
238
|
+
@deploy_exception = e
|
239
|
+
$stderr.puts "ERROR: #{e.inspect}"
|
240
|
+
$stderr.puts "\t#{e.backtrace("\n\t")}"
|
241
|
+
ensure
|
242
|
+
config.after_deploy[@deploy_exception] if config
|
243
|
+
config.after_deploy_or_rollback[@deploy_exception] if config
|
244
|
+
puts "=> Done."
|
155
245
|
end
|
156
246
|
|
157
247
|
desc "rollback", "Switch back to previous release then finalize"
|
248
|
+
method_option :labels, type: :string
|
249
|
+
method_option :no_release, type: :boolean, default: false
|
250
|
+
method_option :config, aliases: '-C', type: :string
|
158
251
|
def rollback
|
252
|
+
@deploy_exception = nil
|
253
|
+
# TODO: move this run on master node side
|
254
|
+
appstatus = master_get("/applications/#{application}/status", options[:labels] ? {labels: options[:labels]} : {})
|
255
|
+
package = appstatus['common_previous_release']
|
256
|
+
|
257
|
+
unless package
|
258
|
+
raise 'there is no common_previous_release for specified application'
|
259
|
+
end
|
260
|
+
|
261
|
+
puts "=> Rolling back #{application} to #{package}"
|
262
|
+
puts " * with labels: #{options[:labels].inspect}" if options[:labels] && !options[:labels].empty?
|
263
|
+
|
264
|
+
show_package(package)
|
265
|
+
|
266
|
+
if config
|
267
|
+
config.set :application, application
|
268
|
+
config.set :package_name, package
|
269
|
+
config.set :package, @meta
|
270
|
+
|
271
|
+
config.before_deploy_or_rollback[]
|
272
|
+
config.before_rollback[]
|
273
|
+
end
|
274
|
+
|
275
|
+
switch(package)
|
276
|
+
|
277
|
+
puts " * Wait until switch"
|
278
|
+
puts ""
|
279
|
+
loop do
|
280
|
+
s = pkg_status(package, :short)
|
281
|
+
puts ""
|
282
|
+
break if 0 < s['participants_count'] && s['participants_count'] == s['switch']['done'].size
|
283
|
+
sleep 2
|
284
|
+
end
|
285
|
+
rescue Exception => e
|
286
|
+
@deploy_exception = e
|
287
|
+
raise e
|
288
|
+
ensure
|
289
|
+
config.after_rollback[@deploy_exception] if config
|
290
|
+
config.after_deploy_or_rollback[@deploy_exception] if config
|
291
|
+
puts "=> Done."
|
159
292
|
end
|
160
293
|
|
161
294
|
desc "join HOST", "let serf to join to HOST"
|
@@ -174,6 +307,17 @@ not distributed: #{dist['not_distributed_count']} agents
|
|
174
307
|
options[:application] or fatal!('specify application')
|
175
308
|
end
|
176
309
|
|
310
|
+
def config
|
311
|
+
return @config if @config
|
312
|
+
path = [options[:config], './mamiya.conf.rb', './config.rb', '/etc/mamiya/config.rb'].compact.find { |_| File.exists?(_) }
|
313
|
+
|
314
|
+
if path
|
315
|
+
@config = Mamiya::Configuration.new.load!(File.expand_path(path))
|
316
|
+
end
|
317
|
+
|
318
|
+
@config
|
319
|
+
end
|
320
|
+
|
177
321
|
def master_get(path, params={})
|
178
322
|
path += "?#{Rack::Utils.build_query(params)}" unless params.empty?
|
179
323
|
master_http.start do |http|
|
@@ -217,6 +361,84 @@ not distributed: #{dist['not_distributed_count']} agents
|
|
217
361
|
fatal! 'specify master URL via --master(-u) option or $MAMIYA_MASTER_URL' unless url
|
218
362
|
URI.parse(url)
|
219
363
|
end
|
364
|
+
|
365
|
+
def app_status
|
366
|
+
params = options[:labels] ? {labels: options[:labels]} : {}
|
367
|
+
status = master_get("/applications/#{application}/status", params)
|
368
|
+
|
369
|
+
case options[:format]
|
370
|
+
when 'json'
|
371
|
+
require 'json'
|
372
|
+
puts status.to_json
|
373
|
+
return
|
374
|
+
|
375
|
+
when 'yaml'
|
376
|
+
require 'yaml'
|
377
|
+
puts status.to_yaml
|
378
|
+
return
|
379
|
+
|
380
|
+
end
|
381
|
+
|
382
|
+
puts <<-EOF
|
383
|
+
at: #{Time.now.inspect}
|
384
|
+
application: #{application}
|
385
|
+
participants: #{status['participants_count']} agents
|
386
|
+
|
387
|
+
major_current: #{status['major_current']}
|
388
|
+
currents:
|
389
|
+
#{status['currents'].sort_by { |pkg, as| -(as.size) }.flat_map { |pkg, as|
|
390
|
+
[" - #{pkg} (#{as.size} agents)"] + (pkg == status['major_current'] ? [" + (omitted)"] : as.map{ |_| " * #{_['name']}" })
|
391
|
+
}.join("\n")}
|
392
|
+
|
393
|
+
common_previous_release: #{status['common_previous_release']}
|
394
|
+
common_releases:
|
395
|
+
#{status['common_releases'].map { |_| _.prepend(' - ') }.join("\n")}
|
396
|
+
EOF
|
397
|
+
end
|
398
|
+
|
399
|
+
def pkg_status(package, short=false)
|
400
|
+
params = options[:labels] ? {labels: options[:labels]} : {}
|
401
|
+
status = master_get("/packages/#{application}/#{package}/status", params)
|
402
|
+
|
403
|
+
case options[:format]
|
404
|
+
when 'json'
|
405
|
+
require 'json'
|
406
|
+
puts status.to_json
|
407
|
+
return
|
408
|
+
|
409
|
+
when 'yaml'
|
410
|
+
require 'yaml'
|
411
|
+
puts status.to_yaml
|
412
|
+
return
|
413
|
+
|
414
|
+
end
|
415
|
+
|
416
|
+
total = status['participants_count']
|
417
|
+
|
418
|
+
if short
|
419
|
+
puts "#{Time.now.strftime("%H:%M:%S")} app:#{application} pkg:#{package} agents:#{total}"
|
420
|
+
else
|
421
|
+
puts <<-EOF
|
422
|
+
at: #{Time.now.inspect}
|
423
|
+
package: #{application}/#{package}
|
424
|
+
status: #{status['status'].join(',')}
|
425
|
+
|
426
|
+
participants: #{total} agents
|
427
|
+
|
428
|
+
EOF
|
429
|
+
end
|
430
|
+
|
431
|
+
%w(fetch prepare switch).each do |key|
|
432
|
+
status[key].tap do |st|
|
433
|
+
puts "#{key}: queued=#{st['queued'].size}, working=#{st['working'].size}, done=#{st['done'].size}"
|
434
|
+
puts " * queued: #{st['queued'].join(', ')}" if !st['queued'].empty? && st['queued'].size != total
|
435
|
+
puts " * working: #{st['working'].join(', ')}" if !st['working'].empty? && st['working'].size != total
|
436
|
+
puts " * done: #{st['done'].join(', ')}" if !st['done'].empty? && options[:show_done] && st['done'].size != total
|
437
|
+
end
|
438
|
+
end
|
439
|
+
|
440
|
+
status
|
441
|
+
end
|
220
442
|
end
|
221
443
|
|
222
444
|
desc "client", "client for master"
|
data/lib/mamiya/configuration.rb
CHANGED
@@ -20,6 +20,8 @@ module Mamiya
|
|
20
20
|
set_default :fetch_sleep, 12
|
21
21
|
set_default :keep_packages, 3
|
22
22
|
set_default :keep_prereleases, 3
|
23
|
+
set_default :keep_releases, 3
|
24
|
+
set_default :applications, {}
|
23
25
|
|
24
26
|
# master
|
25
27
|
set_default :master, {monitor: {refresh_interval: nil}} # TODO: don't nest
|
@@ -27,6 +29,15 @@ module Mamiya
|
|
27
29
|
|
28
30
|
add_hook :labels, chain: true
|
29
31
|
|
32
|
+
add_hook :before_deploy_or_rollback
|
33
|
+
add_hook :after_deploy_or_rollback
|
34
|
+
|
35
|
+
add_hook :before_deploy
|
36
|
+
add_hook :after_deploy
|
37
|
+
|
38
|
+
add_hook :before_rollback
|
39
|
+
add_hook :after_rollback
|
40
|
+
|
30
41
|
def storage_class
|
31
42
|
Storages.find(self[:storage][:type])
|
32
43
|
end
|
@@ -38,6 +49,13 @@ module Mamiya
|
|
38
49
|
def prereleases_dir
|
39
50
|
self[:prereleases_dir] && Pathname.new(self[:prereleases_dir])
|
40
51
|
end
|
52
|
+
|
53
|
+
# XXX: `config.app(app_name).deploy_to` form is better?
|
54
|
+
def deploy_to_for(app)
|
55
|
+
# XXX: to_sym
|
56
|
+
application = applications[app.to_sym] || applications[app.to_s]
|
57
|
+
application && application[:deploy_to] && Pathname.new(application[:deploy_to])
|
58
|
+
end
|
41
59
|
end
|
42
60
|
end
|
43
61
|
|
@@ -4,6 +4,8 @@ require 'thread'
|
|
4
4
|
|
5
5
|
require 'mamiya/master'
|
6
6
|
require 'mamiya/master/agent_monitor_handlers'
|
7
|
+
require 'mamiya/master/package_status'
|
8
|
+
require 'mamiya/master/application_status'
|
7
9
|
|
8
10
|
module Mamiya
|
9
11
|
class Master
|
@@ -17,6 +19,8 @@ module Mamiya
|
|
17
19
|
PACKAGES_QUERY = 'mamiya:packages'.freeze
|
18
20
|
DEFAULT_INTERVAL = 60
|
19
21
|
|
22
|
+
PACKAGE_STATUS_KEYS = %w(packages prereleases releases currents).map(&:freeze).freeze
|
23
|
+
|
20
24
|
def initialize(master, raise_exception: false)
|
21
25
|
@master = master
|
22
26
|
@interval = (master.config[:master] &&
|
@@ -47,6 +51,14 @@ module Mamiya
|
|
47
51
|
end
|
48
52
|
end
|
49
53
|
|
54
|
+
def package_status(app, pkg, labels: nil)
|
55
|
+
PackageStatus.new(self, app, pkg, labels: labels)
|
56
|
+
end
|
57
|
+
|
58
|
+
def application_status(app, labels: nil)
|
59
|
+
ApplicationStatus.new(self, app, labels: labels)
|
60
|
+
end
|
61
|
+
|
50
62
|
def start!
|
51
63
|
@thread ||= Thread.new do
|
52
64
|
loop do
|
@@ -140,8 +152,9 @@ module Mamiya
|
|
140
152
|
begin
|
141
153
|
resp = JSON.parse(json)
|
142
154
|
|
143
|
-
|
144
|
-
|
155
|
+
PACKAGE_STATUS_KEYS.each do |k|
|
156
|
+
new_statuses[name][k] = resp[k]
|
157
|
+
end
|
145
158
|
rescue JSON::ParserError => e
|
146
159
|
logger.warn "Failed to parse packages from #{name}: #{e.message}"
|
147
160
|
next
|
@@ -149,8 +162,10 @@ module Mamiya
|
|
149
162
|
end
|
150
163
|
|
151
164
|
(new_statuses.keys - packages_response["Responses"].keys).each do |name|
|
152
|
-
|
153
|
-
|
165
|
+
PACKAGE_STATUS_KEYS.each do |k|
|
166
|
+
if @statuses[name] && @statuses[name][k]
|
167
|
+
new_statuses[name][k] = @statuses[name][k]
|
168
|
+
end
|
154
169
|
end
|
155
170
|
end
|
156
171
|
|
@@ -72,6 +72,10 @@ module Mamiya
|
|
72
72
|
end
|
73
73
|
end
|
74
74
|
|
75
|
+
def task___switch__finish(status, task)
|
76
|
+
status['currents'] ||= {}
|
77
|
+
status['currents'][task['app']] = task['pkg']
|
78
|
+
end
|
75
79
|
|
76
80
|
|
77
81
|
def pkg__remove(status, payload, event)
|
@@ -85,6 +89,12 @@ module Mamiya
|
|
85
89
|
prereleases = status['prereleases'][payload['app']]
|
86
90
|
prereleases.delete(payload['pkg']) if prereleases
|
87
91
|
end
|
92
|
+
|
93
|
+
def release__remove(status, payload, event)
|
94
|
+
status['releases'] ||= {}
|
95
|
+
releases = status['releases'][payload['app']]
|
96
|
+
releases.delete(payload['pkg']) if releases
|
97
|
+
end
|
88
98
|
end
|
89
99
|
end
|
90
100
|
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'mamiya/master'
|
2
|
+
|
3
|
+
module Mamiya
|
4
|
+
class Master
|
5
|
+
##
|
6
|
+
# This class determines application cluster's status (what's majority package active, etc).
|
7
|
+
class ApplicationStatus
|
8
|
+
def initialize(agent_monitor, application, labels: nil)
|
9
|
+
@application = application
|
10
|
+
@agents = agent_monitor.statuses(labels: @labels).reject { |_, s| s['master'] }
|
11
|
+
@labels = labels
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :labels, :application, :agents
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
{
|
18
|
+
application: application,
|
19
|
+
labels: labels,
|
20
|
+
participants_count: participants.size,
|
21
|
+
|
22
|
+
major_current: major_current,
|
23
|
+
currents: currents,
|
24
|
+
|
25
|
+
common_releases: common_releases,
|
26
|
+
common_previous_release: common_previous_release,
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def participants
|
31
|
+
@participants ||= Hash[agents.select do |name, status|
|
32
|
+
(status['currents'] && status['currents'][application]) || \
|
33
|
+
(status['releases'] && status['releases'][application]) || \
|
34
|
+
(status['prereleases'] && status['prereleases'][application]) || \
|
35
|
+
(status['packages'] && status['packages'][application]) || \
|
36
|
+
|
37
|
+
status['queues'].any? { |_, q|
|
38
|
+
(q['working'] && q['working']['app'] == application) || \
|
39
|
+
(q['queue'] && q['queue'].any? { |t| t['app'] == application })
|
40
|
+
}
|
41
|
+
end]
|
42
|
+
end
|
43
|
+
|
44
|
+
def currents
|
45
|
+
@currents ||= Hash[participants.group_by do |name, status|
|
46
|
+
status['currents'] && status['currents'][application]
|
47
|
+
end.map { |package, as| [package, as.map(&:first).sort] }]
|
48
|
+
end
|
49
|
+
|
50
|
+
def major_current
|
51
|
+
@major_current ||= currents.max_by { |package, as| as.size }[0]
|
52
|
+
end
|
53
|
+
|
54
|
+
def common_releases
|
55
|
+
#@common_releases ||= participants.map { |_| _[].select { |package, as| as.size > 2 }.map(&:first).compact.sort
|
56
|
+
@common_releases ||= participants.
|
57
|
+
map { |name, agent| agent['releases'] && agent['releases'][application] }.
|
58
|
+
compact.
|
59
|
+
inject(:&).
|
60
|
+
sort
|
61
|
+
end
|
62
|
+
|
63
|
+
def common_previous_release
|
64
|
+
idx = common_releases.index(major_current)
|
65
|
+
return if !idx || idx < 1
|
66
|
+
|
67
|
+
common_releases[idx-1]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|