capistrano_multiconfig_parallel 0.19.2 → 0.20.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/capistrano_multiconfig_parallel/application.rb +0 -10
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_manager.rb +8 -17
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_worker.rb +11 -2
- data/lib/capistrano_multiconfig_parallel/celluloid/state_machine.rb +5 -3
- data/lib/capistrano_multiconfig_parallel/celluloid/terminal_table.rb +43 -67
- data/lib/capistrano_multiconfig_parallel/classes/dependency_tracker.rb +19 -24
- data/lib/capistrano_multiconfig_parallel/classes/interactive_menu.rb +74 -31
- data/lib/capistrano_multiconfig_parallel/classes/job.rb +38 -47
- data/lib/capistrano_multiconfig_parallel/classes/job_command.rb +69 -0
- data/lib/capistrano_multiconfig_parallel/cli.rb +7 -7
- data/lib/capistrano_multiconfig_parallel/helpers/application_helper.rb +60 -8
- data/lib/capistrano_multiconfig_parallel/helpers/configuration.rb +39 -28
- data/lib/capistrano_multiconfig_parallel/helpers/core_helper.rb +18 -9
- data/lib/capistrano_multiconfig_parallel/helpers/internal_helper.rb +65 -5
- data/lib/capistrano_multiconfig_parallel/version.rb +2 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 641de7a89fa577cee18d2a6aa6b2419aea55d861
|
4
|
+
data.tar.gz: e240a8295e20d5c93e989021ef9368f8ec3ef6b2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc6090766c60e6a44d4a59e83e71c84714d308a1e210d460cd24472e27a1e470ba605357e3da095e14ce332cf3d7ea95754cf5ff720aae121e0bae31ab27f01d
|
7
|
+
data.tar.gz: b3357c295e0b6c2b04408678c8f04098589d7628ef66622f42f633da086375a85db23ebc9feec00d6ff9013d8f25b5258d63beeb6ff1c36c4d644d5400e128ec
|
@@ -246,16 +246,6 @@ module CapistranoMulticonfigParallel
|
|
246
246
|
options
|
247
247
|
end
|
248
248
|
|
249
|
-
def multi_fetch_argv(args)
|
250
|
-
options = {}
|
251
|
-
args.each do |arg|
|
252
|
-
if arg =~ /^(\w+)=(.*)$/m
|
253
|
-
options[Regexp.last_match(1)] = Regexp.last_match(2)
|
254
|
-
end
|
255
|
-
end
|
256
|
-
options
|
257
|
-
end
|
258
|
-
|
259
249
|
def execute_on_multiple_boxes(main_box_name, options)
|
260
250
|
boxes = strip_characters_from_string(main_box_name).split(',').compact
|
261
251
|
boxes.each do |box_name|
|
@@ -156,7 +156,7 @@ module CapistranoMulticonfigParallel
|
|
156
156
|
def confirm_task_approval(result, task, processed_job = nil)
|
157
157
|
return unless result.present?
|
158
158
|
result = print_confirm_task_approvall(result, task, processed_job)
|
159
|
-
return
|
159
|
+
return unless action_confirmed?(result)
|
160
160
|
@jobs.pmap do |job_id, job|
|
161
161
|
worker = get_worker_for_job(job_id)
|
162
162
|
worker.publish_rake_event('approved' => 'yes',
|
@@ -169,8 +169,7 @@ module CapistranoMulticonfigParallel
|
|
169
169
|
|
170
170
|
def get_worker_for_job(job)
|
171
171
|
if job.present?
|
172
|
-
if job.is_a?(
|
173
|
-
job = job.stringify_keys
|
172
|
+
if job.is_a?(CapistranoMulticonfigParallel::Job)
|
174
173
|
@job_to_worker[job.id]
|
175
174
|
else
|
176
175
|
@job_to_worker[job]
|
@@ -196,35 +195,27 @@ module CapistranoMulticonfigParallel
|
|
196
195
|
def get_job_status(job)
|
197
196
|
status = nil
|
198
197
|
if job.present?
|
199
|
-
if job.is_a?(
|
200
|
-
job = job.stringify_keys
|
198
|
+
if job.is_a?(CapistranoMulticonfigParallel::Job)
|
201
199
|
actor = @job_to_worker[job.id]
|
202
|
-
status = actor.
|
200
|
+
status = actor.job_status
|
203
201
|
else
|
204
202
|
actor = @job_to_worker[job]
|
205
|
-
status = actor.
|
203
|
+
status = actor.job_status
|
206
204
|
end
|
207
205
|
end
|
208
206
|
status
|
209
207
|
end
|
210
208
|
|
211
|
-
def job_crashed?(job)
|
212
|
-
job.action == 'deploy:rollback' || job.action == 'deploy:failed' || job_failed?(job)
|
213
|
-
end
|
214
|
-
|
215
|
-
def job_failed?(job)
|
216
|
-
job.status.present? && job.status == 'worker_died'
|
217
|
-
end
|
218
|
-
|
219
209
|
def worker_died(worker, reason)
|
220
210
|
job = @worker_to_job[worker.mailbox.address]
|
221
211
|
log_to_file("worker job #{job} with mailbox #{worker.mailbox.inspect} died for reason: #{reason}")
|
222
212
|
@worker_to_job.delete(worker.mailbox.address)
|
223
|
-
return if job.blank? ||
|
213
|
+
return if job.blank? || job.crashed?
|
224
214
|
return unless job.action == 'deploy'
|
225
215
|
log_to_file "restarting #{job} on new worker"
|
226
216
|
job.status = 'worker_died'
|
227
|
-
|
217
|
+
job.action = 'deploy:rollback'
|
218
|
+
dispatch_new_job(job)
|
228
219
|
end
|
229
220
|
end
|
230
221
|
end
|
@@ -41,6 +41,15 @@ module CapistranoMulticonfigParallel
|
|
41
41
|
manager.register_worker_for_job(job, Actor.current)
|
42
42
|
end
|
43
43
|
|
44
|
+
def worker_state
|
45
|
+
if Actor.current.alive?
|
46
|
+
status = @machine.state.to_s
|
47
|
+
job.crashed? ? status.red : status.green
|
48
|
+
else
|
49
|
+
'dead'.upcase.red
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
44
53
|
def start_task
|
45
54
|
@manager.setup_worker_conditions(@job)
|
46
55
|
log_to_file("exec worker #{@job_id} starts task with #{@job.inspect}")
|
@@ -101,7 +110,8 @@ module CapistranoMulticonfigParallel
|
|
101
110
|
|
102
111
|
def check_gitflow
|
103
112
|
return if @job.stage != 'staging' || !@manager.can_tag_staging? || !executed_task?(CapistranoMulticonfigParallel::GITFLOW_TAG_STAGING_TASK)
|
104
|
-
@
|
113
|
+
@job.stage = 'production'
|
114
|
+
@manager.dispatch_new_job(@job)
|
105
115
|
end
|
106
116
|
|
107
117
|
def handle_subscription(message)
|
@@ -150,7 +160,6 @@ module CapistranoMulticonfigParallel
|
|
150
160
|
|
151
161
|
def update_machine_state(name)
|
152
162
|
log_to_file("worker #{@job_id} triest to transition from #{@machine.state} to #{name}")
|
153
|
-
@machine.transitions.on(name.to_s, @machine.state => name.to_s)
|
154
163
|
@machine.go_to_transition(name.to_s)
|
155
164
|
abort(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed.new("task #{@action} failed ")) if name == 'deploy:failed' # force worker to rollback
|
156
165
|
end
|
@@ -2,17 +2,19 @@ module CapistranoMulticonfigParallel
|
|
2
2
|
# class that handles the states of the celluloid worker executing the child process in a fork process
|
3
3
|
class StateMachine
|
4
4
|
include ComposableStateMachine::CallbackRunner
|
5
|
-
attr_accessor :job, :actor, :initial_state, :state
|
5
|
+
attr_accessor :job, :actor, :initial_state, :state
|
6
6
|
|
7
7
|
def initialize(job, actor)
|
8
8
|
@job = job
|
9
9
|
@actor = actor
|
10
|
-
@initial_state =
|
10
|
+
@initial_state = @job.status
|
11
11
|
machine
|
12
12
|
end
|
13
13
|
|
14
14
|
def go_to_transition(action)
|
15
|
-
|
15
|
+
transitions.on(action, state.to_s => action)
|
16
|
+
@job.status = action
|
17
|
+
machine.trigger(action)
|
16
18
|
end
|
17
19
|
|
18
20
|
def machine
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require_relative '../helpers/application_helper'
|
2
2
|
module CapistranoMulticonfigParallel
|
3
3
|
# class used to display the progress of each worker on terminal screen using a table
|
4
|
-
# rubocop:disable ClassLength
|
5
4
|
class TerminalTable
|
6
5
|
include Celluloid
|
7
6
|
include Celluloid::Notifications
|
@@ -21,19 +20,18 @@ module CapistranoMulticonfigParallel
|
|
21
20
|
subscribe(CapistranoMulticonfigParallel::TerminalTable.topic, :notify_time_change)
|
22
21
|
end
|
23
22
|
|
24
|
-
def notify_time_change(
|
25
|
-
return unless topic == CapistranoMulticonfigParallel::TerminalTable.topic
|
23
|
+
def notify_time_change(_topic, _message)
|
26
24
|
default_headings = ['Job ID', 'Job UUID', 'App/Stage', 'Action', 'ENV Variables', 'Current Task']
|
27
25
|
# default_headings << 'Total'
|
28
26
|
# default_headings << 'Progress'
|
29
27
|
table = Terminal::Table.new(title: 'Deployment Status Table', headings: default_headings)
|
30
|
-
|
31
|
-
if
|
28
|
+
jobs = @manager.alive? ? @manager.jobs : []
|
29
|
+
if jobs.present?
|
32
30
|
count = 0
|
33
|
-
last_job_id =
|
34
|
-
|
31
|
+
last_job_id = jobs.keys.last.to_i
|
32
|
+
jobs.each do |_job_id, job|
|
35
33
|
count += 1
|
36
|
-
add_job_to_table(table,
|
34
|
+
add_job_to_table(table, job, count, last_job_id)
|
37
35
|
end
|
38
36
|
end
|
39
37
|
show_terminal_screen(table)
|
@@ -49,56 +47,33 @@ module CapistranoMulticonfigParallel
|
|
49
47
|
end
|
50
48
|
end
|
51
49
|
|
52
|
-
def message_valid?(message)
|
53
|
-
message[:type].present? && message[:type] == 'output' || message[:type] == 'event'
|
54
|
-
end
|
55
|
-
|
56
50
|
def show_terminal_screen(table)
|
57
51
|
return unless table.rows.present?
|
58
52
|
terminal_clear
|
59
|
-
puts "\n"
|
60
53
|
# table.style = { width: 20 }
|
61
|
-
puts table
|
62
|
-
|
63
|
-
sleep(1)
|
54
|
+
puts "\n#{table}\n"
|
55
|
+
sleep(0.1)
|
64
56
|
@job_manager.condition.signal('completed') if @manager.all_workers_finished?
|
65
57
|
end
|
66
58
|
|
67
|
-
def worker_state(worker
|
68
|
-
|
69
|
-
state = worker.machine.state.to_s
|
70
|
-
@manager.job_crashed?(job) ? state.red : state.green
|
71
|
-
else
|
72
|
-
'dead'.upcase.red
|
73
|
-
end
|
59
|
+
def worker_state(worker)
|
60
|
+
worker.alive? ? worker.worker_state : 'dead'.upcase.red
|
74
61
|
end
|
75
62
|
|
76
|
-
def
|
77
|
-
|
78
|
-
'job_id' => job_id,
|
79
|
-
'app_name' => job.app,
|
80
|
-
'env_name' => job.stage,
|
81
|
-
'full_stage' => job.job_stage,
|
82
|
-
'action_name' => job.capistrano_action,
|
83
|
-
'env_options' => job.setup_command_line_standard.join("\n"),
|
84
|
-
'task_arguments' => job.task_arguments,
|
85
|
-
'state' => worker_state(worker, job),
|
86
|
-
'processed_job' => job
|
87
|
-
}
|
63
|
+
def filtered_env_keys
|
64
|
+
%w(STAGES ACTION)
|
88
65
|
end
|
89
66
|
|
90
|
-
def add_job_to_table(table,
|
67
|
+
def add_job_to_table(table, job, count, last_job_id)
|
91
68
|
return unless @manager.alive?
|
92
|
-
worker = @manager.get_worker_for_job(
|
93
|
-
|
94
|
-
details = get_worker_details(job_id, job, worker)
|
69
|
+
worker = @manager.get_worker_for_job(job.id)
|
95
70
|
|
96
71
|
row = [{ value: count.to_s },
|
97
|
-
{ value:
|
98
|
-
{ value:
|
99
|
-
{ value:
|
100
|
-
{ value:
|
101
|
-
{ value:
|
72
|
+
{ value: job.id.to_s },
|
73
|
+
{ value: job.job_stage },
|
74
|
+
{ value: job.capistrano_action },
|
75
|
+
{ value: job.setup_command_line_standard(filtered_keys: [CapistranoMulticonfigParallel::ENV_KEY_JOB_ID]).join("\n") },
|
76
|
+
{ value: worker_state(worker) }
|
102
77
|
]
|
103
78
|
|
104
79
|
# if worker.alive?
|
@@ -109,34 +84,35 @@ module CapistranoMulticonfigParallel
|
|
109
84
|
# row << { value: worker_state(worker) }
|
110
85
|
# end
|
111
86
|
table.add_row(row)
|
112
|
-
table.add_separator if last_job_id !=
|
87
|
+
table.add_separator if last_job_id != job.id.to_i
|
88
|
+
table
|
113
89
|
end
|
114
90
|
|
115
91
|
def terminal_clear
|
116
92
|
system('cls') || system('clear') || puts("\e[H\e[2J")
|
117
93
|
end
|
118
94
|
|
119
|
-
def worker_progress(processed_job, worker)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
end
|
125
|
-
|
126
|
-
def show_worker_percent(worker, tasks, current_task, processed_job)
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
end
|
137
|
-
|
138
|
-
def percent_of(index, total)
|
139
|
-
|
140
|
-
end
|
95
|
+
# def worker_progress(processed_job, worker)
|
96
|
+
# return worker_state(worker) unless worker.alive?
|
97
|
+
# tasks = worker.alive? ? worker.invocation_chain : []
|
98
|
+
# current_task = worker.alive? ? worker.machine.state.to_s : ''
|
99
|
+
# show_worker_percent(worker, tasks, current_task, processed_job)
|
100
|
+
# end
|
101
|
+
#
|
102
|
+
# def show_worker_percent(worker, tasks, current_task, processed_job)
|
103
|
+
# total_tasks = worker.alive? ? tasks.size : nil
|
104
|
+
# task_index = worker.alive? ? tasks.index(current_task.to_s).to_i + 1 : 0
|
105
|
+
# percent = percent_of(task_index, total_tasks)
|
106
|
+
# result = "Progress [#{format('%.2f', percent)}%] (executed #{task_index} of #{total_tasks})"
|
107
|
+
# if worker.alive?
|
108
|
+
# processed_job.crashed? ? result.red : result.green
|
109
|
+
# else
|
110
|
+
# worker_state(worker)
|
111
|
+
# end
|
112
|
+
# end
|
113
|
+
#
|
114
|
+
# def percent_of(index, total)
|
115
|
+
# index.to_f / total.to_f * 100.0
|
116
|
+
# end
|
141
117
|
end
|
142
118
|
end
|
@@ -11,29 +11,30 @@ module CapistranoMulticonfigParallel
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def fetch_apps_needed_for_deployment(application, action)
|
14
|
-
|
15
|
-
return applications unless @job_manager.multi_apps?
|
14
|
+
return [] unless @job_manager.multi_apps?
|
16
15
|
if @job_manager.custom_command?
|
17
|
-
|
18
|
-
applications = get_applications_to_deploy(action, apps_selected)
|
19
|
-
elsif app_configuration.application_dependencies.present?
|
20
|
-
if application.present?
|
21
|
-
applications = get_applications_to_deploy(action, [application.camelcase])
|
22
|
-
applications = applications.delete_if { |hash| hash['app'] == application }
|
23
|
-
else
|
24
|
-
applications = []
|
25
|
-
end
|
16
|
+
show_interactive_menu(action)
|
26
17
|
else
|
27
|
-
|
18
|
+
fetch_application_dependencies(application, action)
|
28
19
|
end
|
29
|
-
applications
|
30
20
|
end
|
31
21
|
|
32
22
|
private
|
33
23
|
|
24
|
+
def fetch_application_dependencies(application, action)
|
25
|
+
return [] if app_configuration.application_dependencies.blank? || application.blank?
|
26
|
+
applications = get_applications_to_deploy(action, [application.camelcase])
|
27
|
+
applications.delete_if { |hash| hash['app'] == application }
|
28
|
+
end
|
29
|
+
|
30
|
+
def show_interactive_menu(action)
|
31
|
+
apps_selected = CapistranoMulticonfigParallel::InteractiveMenu.new(available_apps).fetch_menu
|
32
|
+
get_applications_to_deploy(action, apps_selected)
|
33
|
+
end
|
34
|
+
|
34
35
|
def application_dependencies
|
35
36
|
deps = app_configuration.application_dependencies
|
36
|
-
|
37
|
+
value_is_array?(deps) ? deps.map(&:stringify_keys) : []
|
37
38
|
end
|
38
39
|
|
39
40
|
def available_apps
|
@@ -42,12 +43,6 @@ module CapistranoMulticonfigParallel
|
|
42
43
|
applications
|
43
44
|
end
|
44
45
|
|
45
|
-
def all_websites_return_applications_selected
|
46
|
-
interactive_menu = CapistranoMulticonfigParallel::InteractiveMenu.new
|
47
|
-
applications_selected = interactive_menu.show_all_websites_interactive_menu(available_apps)
|
48
|
-
applications_selected.present? ? applications_selected.split(',') : []
|
49
|
-
end
|
50
|
-
|
51
46
|
def add_dependency_app(app_to_deploy, apps_dependencies, applications_to_deploy)
|
52
47
|
return unless app_to_deploy.present?
|
53
48
|
applications_to_deploy << app_to_deploy
|
@@ -71,7 +66,7 @@ module CapistranoMulticonfigParallel
|
|
71
66
|
def check_app_dependency_unique(applications_selected, apps_dependencies, applications_to_deploy, action)
|
72
67
|
return applications_to_deploy if applications_selected.blank? || apps_dependencies.blank? || (apps_dependencies.map { |app| app['app'] } - applications_to_deploy.map { |app| app['app'] }).blank?
|
73
68
|
apps_dependency_confirmation = ask_confirm("Do you want to #{action} all dependencies also ?", 'Y/N')
|
74
|
-
applications_to_deploy = applications_to_deploy.concat(apps_dependencies) if
|
69
|
+
applications_to_deploy = applications_to_deploy.concat(apps_dependencies) if action_confirmed?(apps_dependency_confirmation)
|
75
70
|
applications_to_deploy
|
76
71
|
end
|
77
72
|
|
@@ -105,10 +100,10 @@ module CapistranoMulticonfigParallel
|
|
105
100
|
def print_frameworks_used(app_names, applications_to_deploy, action)
|
106
101
|
app_names.each { |app| puts "#{app}" }
|
107
102
|
apps_deploy_confirmation = ask_confirm("Are you sure you want to #{action} these apps?", 'Y/N')
|
108
|
-
if
|
109
|
-
return []
|
110
|
-
elsif apps_deploy_confirmation.present? && apps_deploy_confirmation.downcase == 'y'
|
103
|
+
if action_confirmed?(apps_deploy_confirmation)
|
111
104
|
return applications_to_deploy
|
105
|
+
else
|
106
|
+
return []
|
112
107
|
end
|
113
108
|
end
|
114
109
|
end
|
@@ -4,21 +4,42 @@ module CapistranoMulticonfigParallel
|
|
4
4
|
class InteractiveMenu
|
5
5
|
include CapistranoMulticonfigParallel::ApplicationHelper
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
attr_accessor :msg, :choices, :applications
|
8
|
+
|
9
|
+
def initialize(applications)
|
10
|
+
@applications = applications
|
11
|
+
@msg = ' '
|
12
|
+
@choices = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def fetch_menu
|
16
|
+
print_menu_choices
|
17
|
+
default_printing
|
18
|
+
result = show_all_websites_interactive_menu
|
19
|
+
print "#{@msg}\n"
|
20
|
+
strip_characters_from_string(result).split(',')
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def default_printing
|
11
26
|
print "\nYou selected"
|
12
|
-
msg = ' nothing'
|
27
|
+
@msg = ' nothing'
|
28
|
+
end
|
29
|
+
|
30
|
+
def show_all_websites_interactive_menu
|
13
31
|
result = ''
|
14
|
-
applications.each_with_index do |option_name, index|
|
15
|
-
|
16
|
-
|
17
|
-
msg = ''
|
18
|
-
result += "#{option_name},"
|
32
|
+
@applications.each_with_index do |option_name, index|
|
33
|
+
result += "#{option_name}," if choices[index].present?
|
34
|
+
print_option_name(option_name, index)
|
19
35
|
end
|
20
|
-
|
21
|
-
|
36
|
+
result
|
37
|
+
end
|
38
|
+
|
39
|
+
def print_option_name(option_name, index)
|
40
|
+
return unless @choices[index].present?
|
41
|
+
print(" #{option_name}")
|
42
|
+
@msg = ''
|
22
43
|
end
|
23
44
|
|
24
45
|
def confirm_option_selected
|
@@ -26,39 +47,61 @@ module CapistranoMulticonfigParallel
|
|
26
47
|
$stdin.gets.squeeze(' ').strip
|
27
48
|
end
|
28
49
|
|
29
|
-
def print_menu_choices
|
30
|
-
while print_all_websites_available_options
|
50
|
+
def print_menu_choices
|
51
|
+
while print_all_websites_available_options && (option = confirm_option_selected).present?
|
31
52
|
if /^[0-9,]+/.match(option)
|
32
|
-
handle_menu_option(
|
53
|
+
handle_menu_option(option)
|
33
54
|
else
|
34
|
-
msg = "Invalid option: #{option}\n "
|
55
|
+
@msg = "Invalid option: #{option}\n "
|
35
56
|
next
|
36
57
|
end
|
37
58
|
end
|
38
59
|
end
|
39
60
|
|
40
|
-
def print_all_websites_available_options
|
61
|
+
def print_all_websites_available_options
|
41
62
|
puts 'Available options:'
|
42
|
-
applications.each_with_index do |option, index|
|
43
|
-
|
63
|
+
@applications.each_with_index do |option, index|
|
64
|
+
print_selected_index_option(index, option)
|
44
65
|
end
|
45
|
-
puts "\n#{msg}" if msg.present?
|
66
|
+
puts "\n#{@msg}" if @msg.present?
|
46
67
|
true
|
47
68
|
end
|
48
69
|
|
49
|
-
def handle_menu_option(
|
50
|
-
|
51
|
-
arr_in.each_with_index do |number_option, _index|
|
70
|
+
def handle_menu_option(option)
|
71
|
+
option.split(',').each_with_index do |number_option, _index|
|
52
72
|
num = number_option.to_i
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
choices[num] = choices[num].blank? ? '+' : ' '
|
57
|
-
else
|
58
|
-
msg = "Invalid option: #{num}\n"
|
59
|
-
next
|
60
|
-
end
|
73
|
+
show_option_selected(num)
|
74
|
+
setup_message_invalid(num)
|
75
|
+
next
|
61
76
|
end
|
62
77
|
end
|
78
|
+
|
79
|
+
def check_number_selected(num)
|
80
|
+
check_numeric(num) && (num > 0 && num <= @applications.size)
|
81
|
+
end
|
82
|
+
|
83
|
+
def show_option_selected(num)
|
84
|
+
return unless check_number_selected(num)
|
85
|
+
num -= 1
|
86
|
+
@msg += "#{@applications[num]} was #{@choices[num].present? ? 'un' : ''}checked\n"
|
87
|
+
setup_choices_number(num)
|
88
|
+
end
|
89
|
+
|
90
|
+
def setup_message_invalid(num)
|
91
|
+
return if check_number_selected(num)
|
92
|
+
@msg = "Invalid option: #{num}\n"
|
93
|
+
end
|
94
|
+
|
95
|
+
def print_selected_index_option(index, option)
|
96
|
+
puts "#{(index + 1)} #{fetch_choice(index)}) #{option} "
|
97
|
+
end
|
98
|
+
|
99
|
+
def setup_choices_number(num)
|
100
|
+
@choices[num] = @choices[num].blank? ? '+' : ' '
|
101
|
+
end
|
102
|
+
|
103
|
+
def fetch_choice(num)
|
104
|
+
@choices.fetch(num, '')
|
105
|
+
end
|
63
106
|
end
|
64
107
|
end
|
@@ -1,71 +1,62 @@
|
|
1
|
-
require 'fileutils'
|
2
1
|
require_relative '../helpers/application_helper'
|
2
|
+
require_relative './job_command'
|
3
3
|
module CapistranoMulticonfigParallel
|
4
|
-
# class used
|
4
|
+
# class used for defining the job class
|
5
5
|
class Job
|
6
|
-
include FileUtils
|
7
6
|
include CapistranoMulticonfigParallel::ApplicationHelper
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@env_options[key] = value if value.present? && !filtered_env_keys.include?(key)
|
19
|
-
end
|
20
|
-
@env_options["#{CapistranoMulticonfigParallel::ENV_KEY_JOB_ID}"] = @id
|
21
|
-
end
|
8
|
+
attr_reader :options, :command
|
9
|
+
attr_writer :status, :exit_status
|
10
|
+
|
11
|
+
delegate :job_stage,
|
12
|
+
:capistrano_action,
|
13
|
+
:build_capistrano_task,
|
14
|
+
:execute_standard_deploy,
|
15
|
+
:setup_command_line_standard,
|
16
|
+
to: :command
|
22
17
|
|
23
|
-
def
|
24
|
-
|
18
|
+
def initialize(options)
|
19
|
+
@options = options
|
20
|
+
@command = CapistranoMulticonfigParallel::JobCommand.new(self)
|
25
21
|
end
|
26
22
|
|
27
|
-
def
|
28
|
-
|
23
|
+
def id
|
24
|
+
@id ||= SecureRandom.uuid
|
29
25
|
end
|
30
26
|
|
31
|
-
def
|
32
|
-
@
|
27
|
+
def status
|
28
|
+
@status ||= :unstarted
|
33
29
|
end
|
34
30
|
|
35
|
-
def
|
36
|
-
|
37
|
-
"#{action}#{argv}"
|
31
|
+
def exit_status
|
32
|
+
@exit_status ||= nil
|
38
33
|
end
|
39
34
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
35
|
+
[
|
36
|
+
{ name: 'app', default: '' },
|
37
|
+
{ name: 'stage', default: '' },
|
38
|
+
{ name: 'action', default: '' },
|
39
|
+
{ name: 'task_arguments', default: [] },
|
40
|
+
{ name: 'env_options', default: {} }
|
41
|
+
].each do |hash|
|
42
|
+
define_method hash[:name] do
|
43
|
+
value = @options.fetch(hash[:name], hash[:default])
|
44
|
+
value["#{CapistranoMulticonfigParallel::ENV_KEY_JOB_ID}"] = id if hash[:name] == 'env_options'
|
45
|
+
verify_empty_options(value)
|
44
46
|
end
|
45
|
-
array_options << '--trace' if app_debug_enabled?
|
46
|
-
args.each do |arg|
|
47
|
-
array_options << arg if arg.present?
|
48
|
-
end
|
49
|
-
array_options
|
50
47
|
end
|
51
48
|
|
52
|
-
def
|
53
|
-
|
54
|
-
environment_options = setup_command_line_standard(env).join(' ')
|
55
|
-
"cd #{detect_root} && RAILS_ENV=#{@stage} bundle exec multi_cap #{job_stage} #{capistrano_action(action)} #{environment_options}"
|
49
|
+
def finished?
|
50
|
+
@status == 'finished'
|
56
51
|
end
|
57
52
|
|
58
|
-
def
|
59
|
-
|
60
|
-
|
61
|
-
sh("#{command}")
|
62
|
-
rescue => ex
|
63
|
-
log_error(ex)
|
64
|
-
execute_standard_deploy('deploy:rollback') if action.blank? && @name == 'deploy'
|
53
|
+
def crashed?
|
54
|
+
crashing_actions = ['deploy:rollback', 'deploy:failed']
|
55
|
+
crashing_actions.include?(action) || crashing_actions.include?(status) || failed?
|
65
56
|
end
|
66
57
|
|
67
|
-
def
|
68
|
-
|
58
|
+
def failed?
|
59
|
+
status.present? && status == 'worker_died'
|
69
60
|
end
|
70
61
|
end
|
71
62
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require_relative '../helpers/application_helper'
|
3
|
+
module CapistranoMulticonfigParallel
|
4
|
+
# class used to find application dependencies
|
5
|
+
class JobCommand
|
6
|
+
include FileUtils
|
7
|
+
include CapistranoMulticonfigParallel::ApplicationHelper
|
8
|
+
|
9
|
+
attr_reader :job
|
10
|
+
delegate :app, :stage, :action, :task_arguments, :env_options, to: :job
|
11
|
+
|
12
|
+
def initialize(job)
|
13
|
+
@job = job
|
14
|
+
end
|
15
|
+
|
16
|
+
def filtered_env_keys
|
17
|
+
%w(STAGES ACTION)
|
18
|
+
end
|
19
|
+
|
20
|
+
def job_stage
|
21
|
+
app.present? ? "#{app}:#{stage}" : "#{stage}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def capistrano_action(rake_action = action)
|
25
|
+
argv = task_arguments.present? ? "[#{task_arguments}]" : ''
|
26
|
+
"#{rake_action}#{argv}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def setup_env_options(options = {})
|
30
|
+
options.stringify_keys!
|
31
|
+
array_options = []
|
32
|
+
env_options.each do |key, value|
|
33
|
+
array_options << "#{key}=#{value}" if value.present? && (!filtered_env_keys.include?(key) && !options.fetch('filtered_keys', []).include?(key.to_s))
|
34
|
+
end
|
35
|
+
array_options << '--trace' if app_debug_enabled?
|
36
|
+
array_options
|
37
|
+
end
|
38
|
+
|
39
|
+
def setup_command_line_standard(*args)
|
40
|
+
options = args.extract_options!
|
41
|
+
array_options = setup_env_options(options)
|
42
|
+
args.each do |arg|
|
43
|
+
array_options << arg if arg.present?
|
44
|
+
end
|
45
|
+
array_options
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_capistrano_task(rake_action = nil, env = [])
|
49
|
+
rake_action = rake_action.present? ? rake_action : action
|
50
|
+
environment_options = setup_command_line_standard(env).join(' ')
|
51
|
+
"cd #{detect_root} && RAILS_ENV=#{@stage} bundle exec multi_cap #{job_stage} #{capistrano_action(rake_action)} #{environment_options}"
|
52
|
+
end
|
53
|
+
|
54
|
+
def execute_standard_deploy(action = nil)
|
55
|
+
command = build_capistrano_task(action)
|
56
|
+
run_shell_command(command)
|
57
|
+
rescue => ex
|
58
|
+
log_error(ex)
|
59
|
+
execute_standard_deploy('deploy:rollback') if action.blank? && @name == 'deploy'
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def run_shell_command(command)
|
65
|
+
puts("\n\n\n Executing '#{command}' \n\n\n .")
|
66
|
+
sh("#{command}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -8,14 +8,14 @@ module CapistranoMulticonfigParallel
|
|
8
8
|
|
9
9
|
# method used to start
|
10
10
|
def start
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
else
|
17
|
-
Capistrano::Application.new.run
|
11
|
+
verify_validation
|
12
|
+
arguments = multi_fetch_argv(ARGV.dup)
|
13
|
+
if arguments[CapistranoMulticonfigParallel::ENV_KEY_JOB_ID].blank?
|
14
|
+
execute_with_rescue('stderr') do
|
15
|
+
CapistranoMulticonfigParallel::Application.new.start
|
18
16
|
end
|
17
|
+
else
|
18
|
+
Capistrano::Application.new.run
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -10,33 +10,85 @@ module CapistranoMulticonfigParallel
|
|
10
10
|
|
11
11
|
module_function
|
12
12
|
|
13
|
+
def multi_fetch_argv(args)
|
14
|
+
options = {}
|
15
|
+
args.each do |arg|
|
16
|
+
if arg =~ /^(\w+)=(.*)$/m
|
17
|
+
options[Regexp.last_match(1)] = Regexp.last_match(2)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
options
|
21
|
+
end
|
22
|
+
|
23
|
+
def action_confirmed?(result)
|
24
|
+
result.present? && result.downcase == 'y'
|
25
|
+
end
|
26
|
+
|
27
|
+
def check_numeric(num)
|
28
|
+
/^[0-9]+/.match(num.to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def verify_empty_options(options)
|
32
|
+
if options.is_a?(Hash)
|
33
|
+
options.reject { |_key, value| value.blank? }
|
34
|
+
elsif options.is_a?(Array)
|
35
|
+
options.reject(&:blank?)
|
36
|
+
else
|
37
|
+
options
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def verify_array_of_strings(value)
|
42
|
+
value.reject(&:blank?)
|
43
|
+
warn_array_without_strings(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def warn_array_without_strings(value)
|
47
|
+
raise ArgumentError, 'the array must contain only task names' if value.find { |row| !row.is_a?(String) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def check_hash_set(hash, props)
|
51
|
+
!Set.new(props).subset?(hash.keys.to_set) || hash.values.find(&:blank?).present?
|
52
|
+
end
|
53
|
+
|
54
|
+
def value_is_array?(value)
|
55
|
+
value.present? && value.is_a?(Array)
|
56
|
+
end
|
57
|
+
|
13
58
|
def strip_characters_from_string(value)
|
14
59
|
return unless value.present?
|
15
60
|
value = value.delete("\r\n").delete("\n")
|
16
|
-
value = value.gsub(/\s+/, ' ').strip
|
61
|
+
value = value.gsub(/\s+/, ' ').strip
|
17
62
|
value
|
18
63
|
end
|
19
64
|
|
65
|
+
def regex_last_match(number)
|
66
|
+
Regexp.last_match(number)
|
67
|
+
end
|
68
|
+
|
20
69
|
def parse_task_string(string) # :nodoc:
|
21
70
|
/^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s
|
22
71
|
|
23
|
-
name =
|
24
|
-
remaining_args =
|
72
|
+
name = regex_last_match(1)
|
73
|
+
remaining_args = regex_last_match(2)
|
25
74
|
|
26
75
|
return string, [] unless name
|
27
76
|
return name, [] if remaining_args.empty?
|
28
77
|
|
29
|
-
args =
|
78
|
+
args = find_remaaining_args(remaining_args)
|
79
|
+
[name, args]
|
80
|
+
end
|
30
81
|
|
82
|
+
def find_remaining_args(remaining_args)
|
83
|
+
args = []
|
31
84
|
loop do
|
32
85
|
/((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args
|
33
86
|
|
34
|
-
remaining_args =
|
35
|
-
args <<
|
87
|
+
remaining_args = regex_last_match(2)
|
88
|
+
args << regex_last_match(1).gsub(/\\(.)/, '\1')
|
36
89
|
break if remaining_args.blank?
|
37
90
|
end
|
38
|
-
|
39
|
-
[name, args]
|
91
|
+
args
|
40
92
|
end
|
41
93
|
end
|
42
94
|
end
|
@@ -15,33 +15,34 @@ module CapistranoMulticonfigParallel
|
|
15
15
|
|
16
16
|
def fetch_configuration
|
17
17
|
@fetched_config = Configliere::Param.new
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
end
|
18
|
+
setup_default_config
|
19
|
+
setup_configuration
|
20
|
+
end
|
22
21
|
|
23
|
-
|
22
|
+
def setup_default_config
|
23
|
+
default_internal_config.each do |array_param|
|
24
|
+
@fetched_config.define array_param[0], array_param[1].symbolize_keys
|
25
|
+
end
|
26
|
+
end
|
24
27
|
|
25
|
-
|
28
|
+
def setup_configuration
|
26
29
|
@fetched_config.read config_file if File.file?(config_file)
|
27
30
|
@fetched_config.use :commandline
|
28
31
|
|
29
32
|
@fetched_config.use :config_block
|
30
|
-
|
31
|
-
|
33
|
+
validate_configuration
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate_configuration
|
37
|
+
@fetched_config.finally do |config|
|
38
|
+
@check_config = config.stringify_keys
|
39
|
+
check_configuration
|
32
40
|
end
|
33
41
|
@fetched_config.process_argv!
|
34
42
|
@fetched_config.resolve!
|
35
43
|
end
|
36
44
|
|
37
|
-
def
|
38
|
-
return true if value.blank?
|
39
|
-
value.reject(&:blank?)
|
40
|
-
raise ArgumentError, 'the array must contain only task names' if value.find { |row| !row.is_a?(String) }
|
41
|
-
end
|
42
|
-
|
43
|
-
def verify_application_dependencies(c, prop, props)
|
44
|
-
value = c[prop.to_sym]
|
45
|
+
def verify_application_dependencies(value, props)
|
45
46
|
return unless value.is_a?(Array)
|
46
47
|
value.reject { |val| val.blank? || !val.is_a?(Hash) }
|
47
48
|
wrong = check_array_of_hash(value, props.map(&:to_sym))
|
@@ -50,35 +51,45 @@ module CapistranoMulticonfigParallel
|
|
50
51
|
|
51
52
|
def check_array_of_hash(value, props)
|
52
53
|
value.find do|hash|
|
53
|
-
|
54
|
-
hash.values.find(&:blank?).present?
|
54
|
+
check_hash_set(hash, props)
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
def check_boolean(
|
59
|
-
|
58
|
+
def check_boolean(prop)
|
59
|
+
value = get_prop_config(prop)
|
60
|
+
raise ArgumentError, "the property `#{prop}` must be boolean" unless %w(true false).include?(value.to_s.downcase)
|
60
61
|
end
|
61
62
|
|
62
63
|
def configuration_valid?
|
63
64
|
configuration
|
64
65
|
end
|
65
66
|
|
66
|
-
def check_boolean_props(
|
67
|
+
def check_boolean_props(props)
|
67
68
|
props.each do |prop|
|
68
|
-
|
69
|
+
@check_config.send("#{prop}=", get_prop_config(prop)) if check_boolean(prop)
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
72
|
-
def check_array_props(
|
73
|
+
def check_array_props(props)
|
73
74
|
props.each do |prop|
|
74
|
-
|
75
|
+
value = get_prop_config(prop)
|
76
|
+
@check_config.send("#{prop}=", value) if value_is_array?(value) && verify_array_of_strings(value)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_prop_config(prop)
|
81
|
+
config = @check_config
|
82
|
+
if prop.include?('.')
|
83
|
+
multi_level_prop(config, prop)
|
84
|
+
else
|
85
|
+
config[prop]
|
75
86
|
end
|
76
87
|
end
|
77
88
|
|
78
|
-
def check_configuration
|
79
|
-
check_boolean_props(
|
80
|
-
check_array_props(
|
81
|
-
verify_application_dependencies(
|
89
|
+
def check_configuration
|
90
|
+
check_boolean_props(%w(multi_debug multi_secvential websocket_server.enable_debug))
|
91
|
+
check_array_props(%w(task_confirmations development_stages apply_stage_confirmation))
|
92
|
+
verify_application_dependencies(@check_config['application_dependencies'], %w(app priority dependencies))
|
82
93
|
end
|
83
94
|
end
|
84
95
|
end
|
@@ -3,10 +3,6 @@ module CapistranoMulticonfigParallel
|
|
3
3
|
module CoreHelper
|
4
4
|
module_function
|
5
5
|
|
6
|
-
def find_config_type(type)
|
7
|
-
['boolean'].include?(type.to_s) ? type.to_s.delete(':').to_sym : type.to_s.constantize
|
8
|
-
end
|
9
|
-
|
10
6
|
def app_debug_enabled?
|
11
7
|
app_configuration.multi_debug.to_s.downcase == 'true'
|
12
8
|
end
|
@@ -32,9 +28,15 @@ module CapistranoMulticonfigParallel
|
|
32
28
|
Gem.loaded_specs.values.find { |repo| repo.name == name }
|
33
29
|
end
|
34
30
|
|
31
|
+
def ask_stdout_confirmation(message, default)
|
32
|
+
result = Ask.input message, default: default
|
33
|
+
$stdout.flush
|
34
|
+
result
|
35
|
+
end
|
36
|
+
|
35
37
|
def ask_confirm(message, default)
|
36
38
|
force_confirmation do
|
37
|
-
|
39
|
+
ask_stdout_confirmation(message, default)
|
38
40
|
end
|
39
41
|
rescue
|
40
42
|
return nil
|
@@ -44,7 +46,6 @@ module CapistranoMulticonfigParallel
|
|
44
46
|
`stty -raw echo`
|
45
47
|
check_terminal_tty
|
46
48
|
result = block.call
|
47
|
-
$stdout.flush
|
48
49
|
`stty -raw echo`
|
49
50
|
result
|
50
51
|
end
|
@@ -55,7 +56,7 @@ module CapistranoMulticonfigParallel
|
|
55
56
|
|
56
57
|
def format_error(error)
|
57
58
|
JSON.pretty_generate(class_name: error.class,
|
58
|
-
message: error.respond_to?(:message) ?
|
59
|
+
message: error.respond_to?(:message) ? error.message : error.inspect,
|
59
60
|
backtrace: error.respond_to?(:backtrace) ? error.backtrace.join("\n\n") : '')
|
60
61
|
end
|
61
62
|
|
@@ -68,13 +69,21 @@ module CapistranoMulticonfigParallel
|
|
68
69
|
return if job_id.blank?
|
69
70
|
FileUtils.mkdir_p(log_directory) unless File.directory?(log_directory)
|
70
71
|
filename = File.join(log_directory, "worker_#{job_id}.log")
|
72
|
+
setup_filename_logger(filename)
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup_filename_logger(filename)
|
71
76
|
worker_log = ::Logger.new(filename)
|
72
77
|
worker_log.level = ::Logger::Severity::DEBUG
|
73
|
-
worker_log
|
78
|
+
setup_logger_formatter(worker_log)
|
79
|
+
worker_log
|
80
|
+
end
|
81
|
+
|
82
|
+
def setup_logger_formatter(logger)
|
83
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
74
84
|
date_format = datetime.strftime('%Y-%m-%d %H:%M:%S')
|
75
85
|
"[#{date_format}] #{severity} (#{progname}): #{msg}\n"
|
76
86
|
end
|
77
|
-
worker_log
|
78
87
|
end
|
79
88
|
|
80
89
|
def debug_websocket?
|
@@ -3,6 +3,11 @@ module CapistranoMulticonfigParallel
|
|
3
3
|
module InternalHelper
|
4
4
|
module_function
|
5
5
|
|
6
|
+
def multi_level_prop(config, prop)
|
7
|
+
prop.split('.').each { |new_prop| config = config[new_prop] }
|
8
|
+
config
|
9
|
+
end
|
10
|
+
|
6
11
|
def internal_config_directory
|
7
12
|
File.join(root.to_s, 'capistrano_multiconfig_parallel', 'configuration')
|
8
13
|
end
|
@@ -12,10 +17,42 @@ module CapistranoMulticonfigParallel
|
|
12
17
|
end
|
13
18
|
|
14
19
|
def default_internal_config
|
15
|
-
@default_config ||=
|
20
|
+
@default_config ||= fetch_default_internal_config
|
16
21
|
@default_config
|
17
22
|
end
|
18
23
|
|
24
|
+
def fetch_default_internal_config
|
25
|
+
config = YAML.load_file(internal_config_file)['default_config']
|
26
|
+
new_config = config.map do |hash|
|
27
|
+
setup_default_configuration_types(hash)
|
28
|
+
end
|
29
|
+
default_internal_configuration_params(new_config)
|
30
|
+
end
|
31
|
+
|
32
|
+
def default_internal_configuration_params(new_config)
|
33
|
+
array = []
|
34
|
+
new_config.each do |hash|
|
35
|
+
array << [hash['name'], sliced_default_config(hash)]
|
36
|
+
end
|
37
|
+
array
|
38
|
+
end
|
39
|
+
|
40
|
+
def sliced_default_config(hash)
|
41
|
+
hash.slice('type', 'description', 'default')
|
42
|
+
end
|
43
|
+
|
44
|
+
def setup_default_configuration_types(hash)
|
45
|
+
hash.each_with_object({}) do |(key, value), memo|
|
46
|
+
memo[key] = (key == 'type') ? find_config_type(value) : value
|
47
|
+
memo
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def find_config_type(type)
|
52
|
+
type = type.to_s
|
53
|
+
['boolean'].include?(type) ? type.delete(':').to_sym : type.constantize
|
54
|
+
end
|
55
|
+
|
19
56
|
def find_env_multi_cap_root
|
20
57
|
ENV['MULTI_CAP_ROOT']
|
21
58
|
end
|
@@ -24,11 +61,34 @@ module CapistranoMulticonfigParallel
|
|
24
61
|
File.expand_path(File.dirname(File.dirname(__dir__)))
|
25
62
|
end
|
26
63
|
|
64
|
+
def pathname_is_root?(root)
|
65
|
+
root.root?
|
66
|
+
end
|
67
|
+
|
68
|
+
def fail_capfile_not_found(root)
|
69
|
+
fail "Can't detect Capfile in the application root".red if pathname_is_root?(root)
|
70
|
+
end
|
71
|
+
|
72
|
+
def pwd_parent_dir
|
73
|
+
pwd_directory.directory? ? pwd_directory : pwd_directory.parent
|
74
|
+
end
|
75
|
+
|
76
|
+
def pwd_directory
|
77
|
+
Pathname.new(FileUtils.pwd)
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_file(file, filename)
|
81
|
+
file.file? && file.basename.to_s.downcase == filename.to_s.downcase
|
82
|
+
end
|
83
|
+
|
84
|
+
def find_file_in_directory(root, filename)
|
85
|
+
root.children.find { |file| check_file(file, filename) }.present? || pathname_is_root?(root)
|
86
|
+
end
|
87
|
+
|
27
88
|
def try_detect_capfile
|
28
|
-
root =
|
29
|
-
root = root.parent
|
30
|
-
root
|
31
|
-
fail "Can't detect Capfile in the application root".red if root.root?
|
89
|
+
root = pwd_parent_dir
|
90
|
+
root = root.parent until find_file_in_directory(root, 'capfile')
|
91
|
+
fail_capfile_not_found(root)
|
32
92
|
root
|
33
93
|
end
|
34
94
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: capistrano_multiconfig_parallel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.20.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- bogdanRada
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-12-
|
11
|
+
date: 2015-12-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: celluloid-pmap
|
@@ -633,6 +633,7 @@ files:
|
|
633
633
|
- lib/capistrano_multiconfig_parallel/classes/input_stream.rb
|
634
634
|
- lib/capistrano_multiconfig_parallel/classes/interactive_menu.rb
|
635
635
|
- lib/capistrano_multiconfig_parallel/classes/job.rb
|
636
|
+
- lib/capistrano_multiconfig_parallel/classes/job_command.rb
|
636
637
|
- lib/capistrano_multiconfig_parallel/classes/output_stream.rb
|
637
638
|
- lib/capistrano_multiconfig_parallel/classes/rake_hook_actor.rb
|
638
639
|
- lib/capistrano_multiconfig_parallel/cli.rb
|