capistrano_multiconfig_parallel 0.19.2 → 0.20.0
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/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
|