capistrano_multiconfig_parallel 0.18.2 → 0.19.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/capistrano_multiconfig_parallel.gemspec +1 -1
- data/lib/capistrano_multiconfig_parallel/application.rb +7 -13
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_manager.rb +48 -85
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_worker.rb +18 -81
- data/lib/capistrano_multiconfig_parallel/celluloid/child_process.rb +16 -10
- data/lib/capistrano_multiconfig_parallel/celluloid/rake_worker.rb +1 -1
- data/lib/capistrano_multiconfig_parallel/celluloid/terminal_table.rb +28 -41
- data/lib/capistrano_multiconfig_parallel/classes/dependency_tracker.rb +8 -6
- data/lib/capistrano_multiconfig_parallel/classes/interactive_menu.rb +4 -1
- data/lib/capistrano_multiconfig_parallel/classes/job.rb +73 -0
- data/lib/capistrano_multiconfig_parallel/classes/rake_hook_actor.rb +1 -1
- data/lib/capistrano_multiconfig_parallel/helpers/core_helper.rb +1 -1
- data/lib/capistrano_multiconfig_parallel/version.rb +2 -2
- metadata +5 -5
- data/lib/capistrano_multiconfig_parallel/classes/standard_deploy.rb +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c16a337397b541e4bed172a5abda0ad5b625d81b
|
4
|
+
data.tar.gz: 2d452cdd7abcb3b9f7cbad1e134b6a6199cab0e9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f6537946ef0fa1a9a70ad8aea9a255a5db0b85c4ed019f565f443b4079067d1650d353f53b13b62f5c157e7c2299462a5d2a9083f0c615f9ca4105826623300
|
7
|
+
data.tar.gz: 2c59e8ba2d189d3c757de7c4567629f16dba74933c0ddd1be57f3607f000420805e354cacaf7299926b6a2f89d785fd143abe30dc8b7b52ea65620cca1503bad
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = s.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
19
|
|
20
20
|
s.add_runtime_dependency 'celluloid-pmap', '~> 0.2', '>= 0.2.2'
|
21
|
-
s.add_runtime_dependency 'celluloid_pubsub', '~> 0.0', '>= 0.0.
|
21
|
+
s.add_runtime_dependency 'celluloid_pubsub', '~> 0.0', '>= 0.0.22'
|
22
22
|
s.add_runtime_dependency 'composable_state_machine', '~> 1.0', '>= 1.0.2'
|
23
23
|
s.add_runtime_dependency 'terminal-table', '~> 1.5', '>= 1.5.2'
|
24
24
|
s.add_runtime_dependency 'colorize', '~> 0.7', '>= 0.7'
|
@@ -43,7 +43,7 @@ module CapistranoMulticonfigParallel
|
|
43
43
|
options = options.stringify_keys
|
44
44
|
return unless applications.present?
|
45
45
|
applications.each do |app|
|
46
|
-
deploy_app(options.merge('app' => app))
|
46
|
+
deploy_app(options.merge('app' => app['app']))
|
47
47
|
end
|
48
48
|
end
|
49
49
|
|
@@ -111,7 +111,7 @@ module CapistranoMulticonfigParallel
|
|
111
111
|
return unless @jobs.present?
|
112
112
|
FileUtils.rm Dir["#{log_directory}/worker_*.log"]
|
113
113
|
if app_configuration.multi_secvential.to_s.downcase == 'true'
|
114
|
-
@jobs.each { |job|
|
114
|
+
@jobs.each { |job| job.execute_standard_deploy }
|
115
115
|
else
|
116
116
|
run_async_jobs
|
117
117
|
end
|
@@ -142,11 +142,10 @@ module CapistranoMulticonfigParallel
|
|
142
142
|
|
143
143
|
def deploy_app(options = {})
|
144
144
|
options = options.stringify_keys
|
145
|
-
app = options['app'].is_a?(Hash) ? options['app'] : { 'app' => options['app'] }
|
146
145
|
branch = @branch_backup.present? ? @branch_backup : @argv['BRANCH'].to_s
|
147
146
|
call_task_deploy_app({
|
148
147
|
branch: branch,
|
149
|
-
app: app,
|
148
|
+
app: options['app'],
|
150
149
|
action: options['action']
|
151
150
|
}.reverse_merge(options))
|
152
151
|
end
|
@@ -210,8 +209,7 @@ module CapistranoMulticonfigParallel
|
|
210
209
|
def prepare_job(options)
|
211
210
|
options = options.stringify_keys
|
212
211
|
branch_name = options.fetch('branch', {})
|
213
|
-
app = options.fetch('app',
|
214
|
-
app = app.fetch('app', '')
|
212
|
+
app = options.fetch('app', '')
|
215
213
|
box = options['env_options']['BOX']
|
216
214
|
message = box.present? ? "BOX #{box}:" : "stage #{options['stage']}:"
|
217
215
|
env_opts = get_app_additional_env_options(app, message)
|
@@ -221,15 +219,11 @@ module CapistranoMulticonfigParallel
|
|
221
219
|
env_options = branch_name.present? ? { 'BRANCH' => branch_name }.merge(options['env_options']) : options['env_options']
|
222
220
|
job_env_options = custom_command? && env_options['ACTION'].present? ? env_options.except('ACTION') : env_options
|
223
221
|
|
224
|
-
job =
|
225
|
-
id: SecureRandom.random_number(500),
|
226
|
-
app: app,
|
227
|
-
env: options['stage'],
|
222
|
+
job = CapistranoMulticonfigParallel::Job.new(options.merge(
|
228
223
|
action: custom_command? && env_options['ACTION'].present? ? env_options['ACTION'] : options['action'],
|
229
|
-
task_arguments: options['task_arguments'],
|
230
224
|
env_options: job_env_options
|
231
|
-
|
232
|
-
@jobs << job
|
225
|
+
))
|
226
|
+
@jobs << job
|
233
227
|
end
|
234
228
|
|
235
229
|
def prepare_options(options)
|
@@ -25,6 +25,9 @@ module CapistranoMulticonfigParallel
|
|
25
25
|
# http://rubydoc.info/gems/celluloid/Celluloid/SupervisionGroup/Member
|
26
26
|
@workers = @worker_supervisor.pool(CapistranoMulticonfigParallel::CelluloidWorker, as: :workers, size: 10)
|
27
27
|
Actor.current.link @workers
|
28
|
+
@worker_supervisor.supervise_as(:terminal_server, CapistranoMulticonfigParallel::TerminalTable, Actor.current, @job_manager)
|
29
|
+
@worker_supervisor.supervise_as(:web_server, CapistranoMulticonfigParallel::WebServer, websocket_config)
|
30
|
+
|
28
31
|
# Get a handle on the PoolManager
|
29
32
|
# http://rubydoc.info/gems/celluloid/Celluloid/PoolManager
|
30
33
|
# @workers = workers_pool.actor
|
@@ -33,22 +36,12 @@ module CapistranoMulticonfigParallel
|
|
33
36
|
@job_to_worker = {}
|
34
37
|
@worker_to_job = {}
|
35
38
|
@job_to_condition = {}
|
36
|
-
@worker_supervisor.supervise_as(:terminal_server, CapistranoMulticonfigParallel::TerminalTable, Actor.current, @job_manager)
|
37
|
-
@worker_supervisor.supervise_as(:web_server, CapistranoMulticonfigParallel::WebServer, websocket_config)
|
38
|
-
end
|
39
|
-
|
40
|
-
def generate_job_id(job)
|
41
|
-
@jobs[job['id']] = job
|
42
|
-
job['id']
|
43
39
|
end
|
44
40
|
|
45
41
|
# call to send an actor
|
46
42
|
# a job
|
47
43
|
def delegate(job)
|
48
|
-
job = job
|
49
|
-
job['id'] = generate_job_id(job) unless job_failed?(job)
|
50
|
-
@jobs[job['id']] = job
|
51
|
-
job['env_options'][CapistranoMulticonfigParallel::ENV_KEY_JOB_ID] = job['id']
|
44
|
+
@jobs[job.id] = job
|
52
45
|
# debug(@jobs)
|
53
46
|
# start work and send it to the background
|
54
47
|
@workers.async.work(job, Actor.current)
|
@@ -57,18 +50,8 @@ module CapistranoMulticonfigParallel
|
|
57
50
|
# call back from actor once it has received it's job
|
58
51
|
# actor should do this asap
|
59
52
|
def register_worker_for_job(job, worker)
|
60
|
-
|
61
|
-
|
62
|
-
log_to_file("job id not found. delegating again the job #{job.inspect}")
|
63
|
-
delegate(job)
|
64
|
-
else
|
65
|
-
start_worker(job, worker)
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def start_worker(job, worker)
|
70
|
-
worker.job_id = job['id'] if worker.job_id.blank?
|
71
|
-
@job_to_worker[job['id']] = worker
|
53
|
+
worker.job_id = job.id if worker.job_id.blank?
|
54
|
+
@job_to_worker[job.id] = worker
|
72
55
|
@worker_to_job[worker.mailbox.address] = job
|
73
56
|
log_to_file("worker #{worker.job_id} registed into manager")
|
74
57
|
Actor.current.link worker
|
@@ -77,7 +60,7 @@ module CapistranoMulticonfigParallel
|
|
77
60
|
end
|
78
61
|
|
79
62
|
def all_workers_finished?
|
80
|
-
@job_to_worker.all? { |job_id, _worker| @jobs[job_id]
|
63
|
+
@job_to_worker.all? { |job_id, _worker| @jobs[job_id].finished? }
|
81
64
|
end
|
82
65
|
|
83
66
|
def process_jobs
|
@@ -105,24 +88,24 @@ module CapistranoMulticonfigParallel
|
|
105
88
|
!@job_manager.can_tag_staging?
|
106
89
|
end
|
107
90
|
|
108
|
-
def
|
109
|
-
|
91
|
+
def apply_confirmation_for_job(job)
|
92
|
+
app_configuration.apply_stage_confirmation.include?(job.stage) && apply_confirmations?
|
110
93
|
end
|
111
94
|
|
112
|
-
def setup_worker_conditions(
|
113
|
-
return unless
|
95
|
+
def setup_worker_conditions(job)
|
96
|
+
return unless apply_confirmation_for_job(job)
|
114
97
|
hash_conditions = {}
|
115
98
|
app_configuration.task_confirmations.each do |task|
|
116
99
|
hash_conditions[task] = { condition: Celluloid::Condition.new, status: 'unconfirmed' }
|
117
100
|
end
|
118
|
-
@job_to_condition[
|
101
|
+
@job_to_condition[job.id] = hash_conditions
|
119
102
|
end
|
120
103
|
|
121
|
-
def mark_completed_remaining_tasks(
|
122
|
-
return unless
|
104
|
+
def mark_completed_remaining_tasks(job)
|
105
|
+
return unless apply_confirmation_for_job(job)
|
123
106
|
app_configuration.task_confirmations.each_with_index do |task, _index|
|
124
107
|
fake_result = proc { |sum| sum }
|
125
|
-
task_confirmation = @job_to_condition[
|
108
|
+
task_confirmation = @job_to_condition[job.id][task]
|
126
109
|
if task_confirmation[:status] != 'confirmed'
|
127
110
|
task_confirmation[:status] = 'confirmed'
|
128
111
|
task_confirmation[:condition].signal(fake_result)
|
@@ -130,11 +113,12 @@ module CapistranoMulticonfigParallel
|
|
130
113
|
end
|
131
114
|
end
|
132
115
|
|
133
|
-
def wait_task_confirmations_worker(
|
134
|
-
return
|
116
|
+
def wait_task_confirmations_worker(job)
|
117
|
+
return unless job.finished? || job.exit_status.present?
|
118
|
+
return if !apply_confirmation_for_job(job) || !syncronized_confirmation?
|
135
119
|
app_configuration.task_confirmations.each_with_index do |task, _index|
|
136
|
-
result = wait_condition_for_task(
|
137
|
-
confirm_task_approval(result, task,
|
120
|
+
result = wait_condition_for_task(job.id, task)
|
121
|
+
confirm_task_approval(result, task, job) if result.present?
|
138
122
|
end
|
139
123
|
end
|
140
124
|
|
@@ -157,10 +141,10 @@ module CapistranoMulticonfigParallel
|
|
157
141
|
end
|
158
142
|
end
|
159
143
|
|
160
|
-
def print_confirm_task_approvall(result, task,
|
144
|
+
def print_confirm_task_approvall(result, task, job)
|
161
145
|
return if result.is_a?(Proc)
|
162
146
|
message = "Do you want to continue the deployment and execute #{task.upcase}"
|
163
|
-
message += " for JOB #{
|
147
|
+
message += " for JOB #{job.id}" if job.present?
|
164
148
|
message += '?'
|
165
149
|
apps_symlink_confirmation = Celluloid::Actor[:terminal_server].show_confirmation(message, 'Y/N')
|
166
150
|
until apps_symlink_confirmation.present?
|
@@ -169,17 +153,17 @@ module CapistranoMulticonfigParallel
|
|
169
153
|
apps_symlink_confirmation
|
170
154
|
end
|
171
155
|
|
172
|
-
def confirm_task_approval(result, task,
|
156
|
+
def confirm_task_approval(result, task, job = nil)
|
173
157
|
return unless result.present?
|
174
|
-
result = print_confirm_task_approvall(result, task,
|
158
|
+
result = print_confirm_task_approvall(result, task, job)
|
175
159
|
return if result.blank? || result.downcase != 'y'
|
176
160
|
@jobs.pmap do |job_id, job|
|
177
161
|
worker = get_worker_for_job(job_id)
|
178
162
|
worker.publish_rake_event('approved' => 'yes',
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
163
|
+
'action' => 'invoke',
|
164
|
+
'job_id' => job.id,
|
165
|
+
'task' => task
|
166
|
+
)
|
183
167
|
end
|
184
168
|
end
|
185
169
|
|
@@ -187,50 +171,25 @@ module CapistranoMulticonfigParallel
|
|
187
171
|
if job.present?
|
188
172
|
if job.is_a?(Hash)
|
189
173
|
job = job.stringify_keys
|
190
|
-
@job_to_worker[job
|
174
|
+
@job_to_worker[job.id]
|
191
175
|
else
|
192
|
-
@job_to_worker[job
|
176
|
+
@job_to_worker[job]
|
193
177
|
end
|
194
178
|
else
|
195
179
|
return nil
|
196
|
-
end
|
197
180
|
end
|
181
|
+
end
|
198
182
|
|
199
183
|
def can_tag_staging?
|
200
184
|
@job_manager.can_tag_staging? &&
|
201
|
-
|
202
|
-
end
|
203
|
-
|
204
|
-
def dispatch_new_job(job)
|
205
|
-
original_env = job['env_options']
|
206
|
-
env_opts = @job_manager.get_app_additional_env_options(job['app_name'], job['env'])
|
207
|
-
job['env_options'] = original_env.merge(env_opts)
|
208
|
-
async.delegate(job)
|
185
|
+
@jobs.find { |_job_id, job| job['env'] == 'production' }.blank?
|
209
186
|
end
|
210
187
|
|
211
|
-
def
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
if job['processed']
|
217
|
-
@jobs[job['job_id']]
|
218
|
-
else
|
219
|
-
env_options = {}
|
220
|
-
job['env_options'].each do |key, value|
|
221
|
-
env_options[key] = value if value.present? && !filtered_env_keys.include?(key)
|
222
|
-
end
|
223
|
-
{
|
224
|
-
'job_id' => job['id'],
|
225
|
-
'app_name' => job['app'],
|
226
|
-
'env_name' => job['env'],
|
227
|
-
'action_name' => job['action'],
|
228
|
-
'env_options' => env_options,
|
229
|
-
'task_arguments' => job['task_arguments'],
|
230
|
-
'job_argv' => job.fetch('job_argv', []),
|
231
|
-
'processed' => true
|
232
|
-
}
|
233
|
-
end
|
188
|
+
def dispatch_new_job(job, options = {})
|
189
|
+
env_opts = @job_manager.get_app_additional_env_options(job.app, job.stage)
|
190
|
+
job.env_options = options.merge(env_opts)
|
191
|
+
new_job = CapistranoMulticonfigParallel::Job.new(job.to_s)
|
192
|
+
async.delegate(new_job)
|
234
193
|
end
|
235
194
|
|
236
195
|
# lookup status of job by asking actor running it
|
@@ -239,29 +198,33 @@ module CapistranoMulticonfigParallel
|
|
239
198
|
if job.present?
|
240
199
|
if job.is_a?(Hash)
|
241
200
|
job = job.stringify_keys
|
242
|
-
actor = @
|
201
|
+
actor = @job_to_worker[job.id]
|
243
202
|
status = actor.status
|
244
203
|
else
|
245
|
-
actor = @
|
204
|
+
actor = @job_to_worker[job]
|
246
205
|
status = actor.status
|
247
206
|
end
|
248
207
|
end
|
249
208
|
status
|
250
209
|
end
|
251
210
|
|
211
|
+
def job_crashed?(job)
|
212
|
+
job.action == 'deploy:rollback' || job.action == 'deploy:failed' || job_failed?(job)
|
213
|
+
end
|
214
|
+
|
252
215
|
def job_failed?(job)
|
253
|
-
job
|
216
|
+
job.status.present? && job.status == 'worker_died'
|
254
217
|
end
|
255
218
|
|
256
219
|
def worker_died(worker, reason)
|
257
220
|
job = @worker_to_job[worker.mailbox.address]
|
258
221
|
log_to_file("worker job #{job} with mailbox #{worker.mailbox.inspect} died for reason: #{reason}")
|
259
222
|
@worker_to_job.delete(worker.mailbox.address)
|
260
|
-
return if job.blank? ||
|
261
|
-
return unless job
|
223
|
+
return if job.blank? || job_crashed?(job)
|
224
|
+
return unless job.action == 'deploy'
|
262
225
|
log_to_file "restarting #{job} on new worker"
|
263
|
-
job =
|
264
|
-
|
226
|
+
job.status = 'worker_died'
|
227
|
+
dispatch_new_job(job, :action => 'deploy:rollback')
|
265
228
|
end
|
266
229
|
end
|
267
230
|
end
|
@@ -27,14 +27,14 @@ module CapistranoMulticonfigParallel
|
|
27
27
|
attr_accessor :job, :manager, :job_id, :app_name, :env_name, :action_name, :env_options, :machine, :client, :task_argv,
|
28
28
|
:rake_tasks, :current_task_number, # tracking tasks
|
29
29
|
:successfull_subscription, :subscription_channel, :publisher_channel, # for subscriptions and publishing events
|
30
|
-
:job_termination_condition, :worker_state, :invocation_chain, :filename, :worker_log
|
30
|
+
:job_termination_condition, :worker_state, :invocation_chain, :filename, :worker_log, :exit_status
|
31
31
|
|
32
32
|
def work(job, manager)
|
33
33
|
@job = job
|
34
|
+
@job_id = job.id
|
34
35
|
@worker_state = 'started'
|
35
36
|
@manager = manager
|
36
37
|
@job_confirmation_conditions = []
|
37
|
-
process_job(job) if job.present?
|
38
38
|
log_to_file("worker #{@job_id} received #{job.inspect}")
|
39
39
|
@subscription_channel = "worker_#{@job_id}"
|
40
40
|
@machine = CapistranoMulticonfigParallel::StateMachine.new(job, Actor.current)
|
@@ -42,7 +42,7 @@ module CapistranoMulticonfigParallel
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def start_task
|
45
|
-
@manager.setup_worker_conditions(
|
45
|
+
@manager.setup_worker_conditions(@job)
|
46
46
|
log_to_file("exec worker #{@job_id} starts task with #{@job.inspect}")
|
47
47
|
@client = CelluloidPubsub::Client.connect(actor: Actor.current, enable_debug: debug_websocket?, channel: subscription_channel)
|
48
48
|
end
|
@@ -52,7 +52,7 @@ module CapistranoMulticonfigParallel
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def rake_actor_id(data)
|
55
|
-
|
55
|
+
"rake_worker_#{@job_id}"
|
56
56
|
end
|
57
57
|
|
58
58
|
def on_message(message)
|
@@ -77,29 +77,14 @@ module CapistranoMulticonfigParallel
|
|
77
77
|
@invocation_chain ||= []
|
78
78
|
end
|
79
79
|
|
80
|
-
def cd_working_directory
|
81
|
-
"cd #{detect_root}"
|
82
|
-
end
|
83
|
-
|
84
|
-
# def generate_command_new
|
85
|
-
# <<-CMD
|
86
|
-
# bundle exec ruby -e "require 'bundler' ; Bundler.with_clean_env { %x[cd #{cd_working_directory} && bundle install && RAILS_ENV=#{@env_name} bundle exec cap #{@task_argv.join(' ')}] } "
|
87
|
-
# CMD
|
88
|
-
# end
|
89
80
|
|
90
|
-
def generate_command
|
91
|
-
<<-CMD
|
92
|
-
#{cd_working_directory} && RAILS_ENV=#{@env_name} bundle exec multi_cap #{@task_argv.join(' ')}
|
93
|
-
CMD
|
94
|
-
end
|
95
81
|
|
96
82
|
def execute_deploy
|
97
83
|
log_to_file("invocation chain #{@job_id} is : #{@rake_tasks.inspect}")
|
98
84
|
check_child_proces
|
99
|
-
|
100
|
-
|
101
|
-
@
|
102
|
-
@manager.wait_task_confirmations_worker(Actor.current)
|
85
|
+
log_to_file("worker #{@job_id} executes: #{@job.build_capistrano_task}")
|
86
|
+
@child_process.async.work(@job, @job.build_capistrano_task, actor: Actor.current, silent: true)
|
87
|
+
@manager.wait_task_confirmations_worker(@job)
|
103
88
|
end
|
104
89
|
|
105
90
|
def check_child_proces
|
@@ -117,15 +102,15 @@ module CapistranoMulticonfigParallel
|
|
117
102
|
end
|
118
103
|
|
119
104
|
def check_gitflow
|
120
|
-
return if @
|
121
|
-
@manager.dispatch_new_job(@job
|
105
|
+
return if @job.stage != 'staging' || !@manager.can_tag_staging? || !executed_task?(CapistranoMulticonfigParallel::GITFLOW_TAG_STAGING_TASK)
|
106
|
+
@manager.dispatch_new_job(@job,'env' => 'production')
|
122
107
|
end
|
123
108
|
|
124
109
|
def handle_subscription(message)
|
125
110
|
if message_is_about_a_task?(message)
|
126
111
|
check_gitflow
|
127
112
|
save_tasks_to_be_executed(message)
|
128
|
-
update_machine_state(message['task']) # if message['action'] == 'invoke'
|
113
|
+
async.update_machine_state(message['task']) # if message['action'] == 'invoke'
|
129
114
|
log_to_file("worker #{@job_id} state is #{@machine.state}")
|
130
115
|
task_approval(message)
|
131
116
|
elsif message_is_for_stdout?(message)
|
@@ -169,77 +154,29 @@ module CapistranoMulticonfigParallel
|
|
169
154
|
log_to_file("worker #{@job_id} triest to transition from #{@machine.state} to #{name}")
|
170
155
|
@machine.transitions.on(name.to_s, @machine.state => name.to_s)
|
171
156
|
@machine.go_to_transition(name.to_s)
|
172
|
-
abort(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed
|
173
|
-
end
|
174
|
-
|
175
|
-
def setup_command_line(*options)
|
176
|
-
@task_argv = []
|
177
|
-
options.each do |option|
|
178
|
-
@task_argv << option
|
179
|
-
end
|
180
|
-
@task_argv
|
181
|
-
end
|
182
|
-
|
183
|
-
def worker_stage
|
184
|
-
@app_name.present? ? "#{@app_name}:#{@env_name}" : "#{@env_name}"
|
185
|
-
end
|
186
|
-
|
187
|
-
def worker_action
|
188
|
-
"#{@action_name}[#{@task_arguments.join(',')}]"
|
157
|
+
abort(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed.new("task #{@action} failed ")) if name == 'deploy:failed' # force worker to rollback
|
189
158
|
end
|
190
159
|
|
191
|
-
def setup_task_arguments(*args)
|
192
|
-
# stage = "#{@app_name}:#{@env_name} #{@action_name}"
|
193
|
-
array_options = []
|
194
|
-
@env_options.each do |key, value|
|
195
|
-
array_options << "#{key}=#{value}" if value.present?
|
196
|
-
end
|
197
|
-
array_options << '--trace' if app_debug_enabled?
|
198
|
-
args.each do |arg|
|
199
|
-
array_options << arg
|
200
|
-
end
|
201
|
-
@manager.jobs[@job_id]['job_argv'] = array_options.clone
|
202
|
-
array_options.unshift("#{worker_action}")
|
203
|
-
array_options.unshift("#{worker_stage}")
|
204
|
-
setup_command_line(*array_options)
|
205
|
-
end
|
206
160
|
|
207
161
|
def send_msg(channel, message = nil)
|
208
162
|
publish channel, message.present? && message.is_a?(Hash) ? { job_id: @job_id }.merge(message) : { job_id: @job_id, time: Time.now }
|
209
163
|
end
|
210
164
|
|
211
|
-
def process_job(job)
|
212
|
-
processed_job = @manager.process_job(job)
|
213
|
-
@job_id = processed_job['job_id']
|
214
|
-
@app_name = processed_job['app_name']
|
215
|
-
@env_name = processed_job['env_name']
|
216
|
-
@action_name = processed_job['action_name']
|
217
|
-
@env_options = processed_job['env_options']
|
218
|
-
@task_arguments = processed_job['task_arguments']
|
219
|
-
end
|
220
|
-
|
221
|
-
def crashed?
|
222
|
-
@action_name == 'deploy:rollback' || @action_name == 'deploy:failed' || @manager.job_failed?(@job)
|
223
|
-
end
|
224
|
-
|
225
165
|
def finish_worker
|
226
|
-
@manager.mark_completed_remaining_tasks(
|
227
|
-
@
|
228
|
-
@manager.workers_terminated.signal('completed') if @manager.all_workers_finished?
|
166
|
+
@manager.mark_completed_remaining_tasks(@job)
|
167
|
+
@job.status = 'finished'
|
168
|
+
@manager.workers_terminated.signal('completed') if @manager.alive? && @manager.all_workers_finished?
|
229
169
|
end
|
230
170
|
|
231
|
-
def worker_finshed?
|
232
|
-
@manager.jobs[@job_id]['worker_action'] == 'finished'
|
233
|
-
end
|
234
171
|
|
235
172
|
def notify_finished(exit_status)
|
236
|
-
if exit_status
|
173
|
+
if exit_status != 0
|
237
174
|
log_to_file("worker #{job_id} tries to terminate")
|
238
|
-
abort(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed
|
175
|
+
abort(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed.new("task failed with exit status #{exit_status.inspect} ")) # force worker to rollback
|
239
176
|
else
|
240
|
-
update_machine_state('FINISHED')
|
177
|
+
async.update_machine_state('FINISHED')
|
241
178
|
log_to_file("worker #{job_id} notifies manager has finished")
|
242
|
-
finish_worker
|
179
|
+
async.finish_worker
|
243
180
|
end
|
244
181
|
end
|
245
182
|
end
|
@@ -7,14 +7,16 @@ module CapistranoMulticonfigParallel
|
|
7
7
|
include Celluloid::Logger
|
8
8
|
include CapistranoMulticonfigParallel::ApplicationHelper
|
9
9
|
|
10
|
-
attr_accessor :actor, :
|
10
|
+
attr_accessor :options, :actor, :job_id, :exit_status, :pid, :process, :job
|
11
11
|
|
12
|
-
|
12
|
+
finalizer :process_finalizer
|
13
13
|
|
14
|
-
def work(cmd, options = {})
|
14
|
+
def work(job, cmd, options = {})
|
15
15
|
@options = options
|
16
|
+
@job = job
|
16
17
|
@actor = @options.fetch(:actor, nil)
|
17
|
-
@job_id = @
|
18
|
+
@job_id = @job.id
|
19
|
+
@exit_status = nil
|
18
20
|
EM.run do
|
19
21
|
EM.next_tick do
|
20
22
|
start_async_deploy(cmd, options)
|
@@ -31,14 +33,16 @@ module CapistranoMulticonfigParallel
|
|
31
33
|
end
|
32
34
|
|
33
35
|
def process_finalizer
|
34
|
-
@timer.cancel
|
35
36
|
EM.stop if EM.reactor_running?
|
36
37
|
end
|
37
38
|
|
38
39
|
def check_exit_status
|
39
|
-
|
40
|
-
|
41
|
-
@
|
40
|
+
log_to_file("worker #{@job_id} checking exit status #{@exit_status.inspect}") if @exit_status.present?
|
41
|
+
return if @exit_status.blank?
|
42
|
+
@job.exit_status = @exit_status
|
43
|
+
log_to_file("worker #{@job_id} startsnotify finished")
|
44
|
+
@actor.async.notify_finished(@exit_status)
|
45
|
+
@timer.cancel
|
42
46
|
end
|
43
47
|
|
44
48
|
def start_async_deploy(cmd, options)
|
@@ -72,14 +76,16 @@ module CapistranoMulticonfigParallel
|
|
72
76
|
end
|
73
77
|
|
74
78
|
def on_exit(status)
|
75
|
-
|
76
|
-
@
|
79
|
+
@exit_status = status.exitstatus
|
80
|
+
log_to_file "Child process for worker #{@job_id} on_exit disconnected due to error #{status.inspect} and #{@exit_status.inspect}"
|
81
|
+
check_exit_status
|
77
82
|
end
|
78
83
|
|
79
84
|
def async_exception_handler(*data)
|
80
85
|
log_to_file "Child process for worker #{@job_id} async_exception_handler disconnected due to error #{data.inspect}"
|
81
86
|
io_callback('stderr', data)
|
82
87
|
@exit_status = 1
|
88
|
+
check_exit_status
|
83
89
|
end
|
84
90
|
|
85
91
|
def watch_handler(process)
|
@@ -27,17 +27,19 @@ module CapistranoMulticonfigParallel
|
|
27
27
|
# default_headings << 'Total'
|
28
28
|
# default_headings << 'Progress'
|
29
29
|
table = Terminal::Table.new(title: 'Deployment Status Table', headings: default_headings)
|
30
|
-
|
30
|
+
|
31
|
+
if @manager.alive? && @manager.jobs.present? && message_valid?(message)
|
31
32
|
count = 0
|
32
|
-
@manager.jobs.
|
33
|
+
last_job_id = @manager.jobs.keys.last.to_i
|
34
|
+
@manager.jobs.each do |job_id, job|
|
33
35
|
count += 1
|
34
|
-
add_job_to_table(table, job_id, count)
|
36
|
+
add_job_to_table(table, job_id, job, count, last_job_id)
|
35
37
|
end
|
36
38
|
end
|
37
39
|
show_terminal_screen(table)
|
38
40
|
rescue => ex
|
39
|
-
|
40
|
-
|
41
|
+
log_to_file("Terminal Table client disconnected due to error #{ex.inspect}")
|
42
|
+
log_to_file(ex.backtrace)
|
41
43
|
terminate
|
42
44
|
end
|
43
45
|
|
@@ -62,50 +64,35 @@ module CapistranoMulticonfigParallel
|
|
62
64
|
@job_manager.condition.signal('completed') if @manager.all_workers_finished?
|
63
65
|
end
|
64
66
|
|
65
|
-
def worker_state(worker)
|
67
|
+
def worker_state(worker, job)
|
66
68
|
if worker.alive?
|
67
69
|
state = worker.machine.state.to_s
|
68
|
-
|
70
|
+
@manager.job_crashed?(job) ? state.red : state.green
|
69
71
|
else
|
70
72
|
'dead'.upcase.red
|
71
73
|
end
|
72
74
|
end
|
73
75
|
|
74
|
-
def worker_env_options(processed_job)
|
75
|
-
worker_optons = ''
|
76
|
-
processed_job['job_argv'].each do |elem|
|
77
|
-
worker_optons << "#{elem}\n"
|
78
|
-
end
|
79
|
-
worker_optons
|
80
|
-
end
|
81
|
-
|
82
|
-
def worker_action(processed_job)
|
83
|
-
processed_job['task_arguments'].present? ? "#{processed_job['action_name']}[#{processed_job['task_arguments'].join(',')}]" : processed_job['action_name']
|
84
|
-
end
|
85
|
-
|
86
|
-
def worker_stage(processed_job)
|
87
|
-
processed_job['app_name'].present? ? "#{processed_job['app_name']}\n#{processed_job['env_name']}" : "#{processed_job['env_name']}"
|
88
|
-
end
|
89
|
-
|
90
|
-
def get_worker_details(job_id, worker)
|
91
|
-
job = @manager.jobs[job_id]
|
92
|
-
processed_job = @manager.process_job(job)
|
93
76
|
|
77
|
+
def get_worker_details(job_id, job, worker)
|
94
78
|
{
|
95
79
|
'job_id' => job_id,
|
96
|
-
'app_name' =>
|
97
|
-
'env_name' =>
|
98
|
-
'full_stage' =>
|
99
|
-
'action_name' =>
|
100
|
-
'env_options' =>
|
101
|
-
'task_arguments' => job
|
102
|
-
'state' => worker_state(worker)
|
80
|
+
'app_name' => job.app,
|
81
|
+
'env_name' => job.stage,
|
82
|
+
'full_stage' => job.job_stage,
|
83
|
+
'action_name' => job.capistrano_action,
|
84
|
+
'env_options' => job.setup_command_line_standard.join("\n"),
|
85
|
+
'task_arguments' => job.task_arguments,
|
86
|
+
'state' => worker_state(worker, job),
|
87
|
+
'processed_job' => job
|
103
88
|
}
|
104
89
|
end
|
105
90
|
|
106
|
-
def add_job_to_table(table, job_id, count)
|
91
|
+
def add_job_to_table(table, job_id, job, count, last_job_id)
|
92
|
+
return unless @manager.alive?
|
107
93
|
worker = @manager.get_worker_for_job(job_id)
|
108
|
-
|
94
|
+
|
95
|
+
details = get_worker_details(job_id, job, worker)
|
109
96
|
|
110
97
|
row = [{ value: count.to_s },
|
111
98
|
{ value: job_id.to_s },
|
@@ -117,33 +104,33 @@ module CapistranoMulticonfigParallel
|
|
117
104
|
|
118
105
|
# if worker.alive?
|
119
106
|
# row << { value: worker.rake_tasks.size }
|
120
|
-
# row << { value: worker_progress(details, worker) }
|
107
|
+
# row << { value: worker_progress(details['processed_job'], worker) }
|
121
108
|
# else
|
122
109
|
# row << { value: 0 }
|
123
110
|
# row << { value: worker_state(worker) }
|
124
111
|
# end
|
125
112
|
table.add_row(row)
|
126
|
-
table.add_separator if
|
113
|
+
table.add_separator if last_job_id != job_id.to_i
|
127
114
|
end
|
128
115
|
|
129
116
|
def terminal_clear
|
130
117
|
system('cls') || system('clear') || puts("\e[H\e[2J")
|
131
118
|
end
|
132
119
|
|
133
|
-
def worker_progress(
|
120
|
+
def worker_progress(processed_job, worker)
|
134
121
|
return worker_state(worker) unless worker.alive?
|
135
122
|
tasks = worker.alive? ? worker.invocation_chain : []
|
136
123
|
current_task = worker.alive? ? worker.machine.state.to_s : ''
|
137
|
-
show_worker_percent(worker, tasks, current_task)
|
124
|
+
show_worker_percent(worker, tasks, current_task, processed_job)
|
138
125
|
end
|
139
126
|
|
140
|
-
def show_worker_percent(worker, tasks, current_task)
|
127
|
+
def show_worker_percent(worker, tasks, current_task, processed_job)
|
141
128
|
total_tasks = worker.alive? ? tasks.size : nil
|
142
129
|
task_index = worker.alive? ? tasks.index(current_task.to_s).to_i + 1 : 0
|
143
130
|
percent = percent_of(task_index, total_tasks)
|
144
131
|
result = "Progress [#{format('%.2f', percent)}%] (executed #{task_index} of #{total_tasks})"
|
145
132
|
if worker.alive?
|
146
|
-
|
133
|
+
@manager.job_crashed?(processed_job) ? result.red : result.green
|
147
134
|
else
|
148
135
|
worker_state(worker)
|
149
136
|
end
|
@@ -27,6 +27,7 @@ module CapistranoMulticonfigParallel
|
|
27
27
|
applications = []
|
28
28
|
end
|
29
29
|
applications
|
30
|
+
|
30
31
|
end
|
31
32
|
|
32
33
|
private
|
@@ -36,15 +37,16 @@ module CapistranoMulticonfigParallel
|
|
36
37
|
deps.present? && deps.is_a?(Array) ? deps.map(&:stringify_keys) : []
|
37
38
|
end
|
38
39
|
|
39
|
-
def
|
40
|
+
def available_apps
|
40
41
|
applications = application_dependencies.map { |hash| hash['app'].camelcase }
|
41
42
|
applications << 'all_frameworks'
|
43
|
+
applications
|
44
|
+
end
|
45
|
+
|
46
|
+
def all_websites_return_applications_selected
|
42
47
|
interactive_menu = CapistranoMulticonfigParallel::InteractiveMenu.new
|
43
|
-
applications_selected = interactive_menu.show_all_websites_interactive_menu(
|
44
|
-
applications_selected
|
45
|
-
applications_selected = applications_selected.delete("\n") if applications_selected.present?
|
46
|
-
applications_selected = applications_selected.split(',') if applications_selected.present?
|
47
|
-
applications_selected.present? ? applications_selected : []
|
48
|
+
applications_selected = interactive_menu.show_all_websites_interactive_menu(available_apps)
|
49
|
+
applications_selected.present? ? applications_selected.split(',') : []
|
48
50
|
end
|
49
51
|
|
50
52
|
def add_dependency_app(app_to_deploy, apps_dependencies, applications_to_deploy)
|
@@ -1,6 +1,9 @@
|
|
1
|
+
require_relative '../helpers/application_helper'
|
1
2
|
module CapistranoMulticonfigParallel
|
2
3
|
# methods used for the interactive menu where are listed all aplications
|
3
4
|
class InteractiveMenu
|
5
|
+
include CapistranoMulticonfigParallel::ApplicationHelper
|
6
|
+
|
4
7
|
def show_all_websites_interactive_menu(applications)
|
5
8
|
msg = ''
|
6
9
|
choices = []
|
@@ -15,7 +18,7 @@ module CapistranoMulticonfigParallel
|
|
15
18
|
result += "#{option_name},"
|
16
19
|
end
|
17
20
|
print "#{msg}\n"
|
18
|
-
result
|
21
|
+
strip_characters_from_string(result)
|
19
22
|
end
|
20
23
|
|
21
24
|
def confirm_option_selected
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require_relative '../helpers/application_helper'
|
3
|
+
module CapistranoMulticonfigParallel
|
4
|
+
# class used to find application dependencies
|
5
|
+
class Job
|
6
|
+
include FileUtils
|
7
|
+
include CapistranoMulticonfigParallel::ApplicationHelper
|
8
|
+
|
9
|
+
attr_accessor :id, :app, :stage, :action, :task_arguments, :env_options, :status, :exit_status
|
10
|
+
def initialize(options)
|
11
|
+
@id = SecureRandom.uuid
|
12
|
+
@app = options.fetch('app', '')
|
13
|
+
@stage = options.fetch('stage', '')
|
14
|
+
@action = options.fetch('action', '')
|
15
|
+
@task_arguments = options.fetch('task_arguments', [])
|
16
|
+
@env_options = {}
|
17
|
+
@env_options = options.fetch('env_options', {}).each do |key, value|
|
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
|
22
|
+
|
23
|
+
def filtered_env_keys
|
24
|
+
%w(STAGES ACTION)
|
25
|
+
end
|
26
|
+
|
27
|
+
def finished?
|
28
|
+
status == 'finished'
|
29
|
+
end
|
30
|
+
|
31
|
+
def job_stage
|
32
|
+
@app.present? ? "#{@app}:#{@stage}" : "#{@stage}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def capistrano_action(action = @action)
|
36
|
+
argv = @task_arguments.present? ? "[#{@task_arguments}]" : ''
|
37
|
+
"#{action}#{argv}"
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_command_line_standard(*args)
|
41
|
+
array_options = []
|
42
|
+
@env_options.each do |key, value|
|
43
|
+
array_options << "#{key}=#{value}" if value.present?
|
44
|
+
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
|
+
end
|
51
|
+
|
52
|
+
def build_capistrano_task(action = nil, env = [])
|
53
|
+
action = action.present? ? action : @action
|
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}"
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def execute_standard_deploy(action = nil)
|
60
|
+
command = build_capistrano_task(action)
|
61
|
+
puts("\n\n\n Executing '#{command}' \n\n\n .")
|
62
|
+
sh("#{command}")
|
63
|
+
rescue => ex
|
64
|
+
log_error(ex)
|
65
|
+
execute_standard_deploy('deploy:rollback') if action.blank? && @name == 'deploy'
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_s
|
69
|
+
self.to_json
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
@@ -55,7 +55,7 @@ module CapistranoMulticonfigParallel
|
|
55
55
|
|
56
56
|
def format_error(error)
|
57
57
|
JSON.pretty_generate(class_name: error.class,
|
58
|
-
message: error.respond_to?(:message) ? error.message : error.inspect,
|
58
|
+
message: error.respond_to?(:message) ? "#{error.message}#{error.message.class}" : error.inspect,
|
59
59
|
backtrace: error.respond_to?(:backtrace) ? error.backtrace.join("\n\n") : '')
|
60
60
|
end
|
61
61
|
|
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.19.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-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: celluloid-pmap
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
version: '0.0'
|
40
40
|
- - ">="
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: 0.0.
|
42
|
+
version: 0.0.22
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,7 +49,7 @@ dependencies:
|
|
49
49
|
version: '0.0'
|
50
50
|
- - ">="
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: 0.0.
|
52
|
+
version: 0.0.22
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: composable_state_machine
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -632,9 +632,9 @@ files:
|
|
632
632
|
- lib/capistrano_multiconfig_parallel/classes/dependency_tracker.rb
|
633
633
|
- lib/capistrano_multiconfig_parallel/classes/input_stream.rb
|
634
634
|
- lib/capistrano_multiconfig_parallel/classes/interactive_menu.rb
|
635
|
+
- lib/capistrano_multiconfig_parallel/classes/job.rb
|
635
636
|
- lib/capistrano_multiconfig_parallel/classes/output_stream.rb
|
636
637
|
- lib/capistrano_multiconfig_parallel/classes/rake_hook_actor.rb
|
637
|
-
- lib/capistrano_multiconfig_parallel/classes/standard_deploy.rb
|
638
638
|
- lib/capistrano_multiconfig_parallel/cli.rb
|
639
639
|
- lib/capistrano_multiconfig_parallel/configuration/default.yml
|
640
640
|
- lib/capistrano_multiconfig_parallel/helpers/application_helper.rb
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require_relative '../helpers/application_helper'
|
3
|
-
module CapistranoMulticonfigParallel
|
4
|
-
# class used to find application dependencies
|
5
|
-
class StandardDeploy
|
6
|
-
include FileUtils
|
7
|
-
include CapistranoMulticonfigParallel::ApplicationHelper
|
8
|
-
|
9
|
-
attr_reader :app, :stage, :action, :task_arguments, :env_options
|
10
|
-
def initialize(options)
|
11
|
-
@app = options.fetch('app', '')
|
12
|
-
@stage = options.fetch('env', '')
|
13
|
-
@action = options.fetch('action', '')
|
14
|
-
@task_arguments = options.fetch('task_arguments:', [])
|
15
|
-
@env_options = options.fetch('env_options', {})
|
16
|
-
execute_standard_deploy
|
17
|
-
end
|
18
|
-
|
19
|
-
def job_stage
|
20
|
-
@app.present? ? "#{@app}:#{@stage}" : "#{@stage}"
|
21
|
-
end
|
22
|
-
|
23
|
-
def capistrano_action(action)
|
24
|
-
argv = task_arguments.present? ? "[#{@task_arguments}]" : ''
|
25
|
-
"#{action}#{argv}"
|
26
|
-
end
|
27
|
-
|
28
|
-
def setup_command_line_standard(options)
|
29
|
-
opts = ''
|
30
|
-
options.each do |key, value|
|
31
|
-
opts << "#{key}=#{value} " if value.present?
|
32
|
-
end
|
33
|
-
opts
|
34
|
-
end
|
35
|
-
|
36
|
-
def build_capistrano_task(action = @action, env = {})
|
37
|
-
environment_options = setup_command_line_standard(@env_options.merge(env))
|
38
|
-
"bundle exec cap #{job_stage} #{capistrano_action(action)} #{environment_options} --trace"
|
39
|
-
end
|
40
|
-
|
41
|
-
def execute_standard_deploy(action = @action)
|
42
|
-
command = build_capistrano_task(action)
|
43
|
-
puts("\n\n\n Executing '#{command}' \n\n\n .")
|
44
|
-
sh("#{command}")
|
45
|
-
rescue => ex
|
46
|
-
log_error(ex)
|
47
|
-
execute_standard_deploy('deploy:rollback') if action.blank? && @name == 'deploy'
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|