capistrano_multiconfig_parallel 2.0.0.beta5 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +3 -3
- data/lib/capistrano_multiconfig_parallel/all.rb +0 -1
- data/lib/capistrano_multiconfig_parallel/base.rb +1 -1
- data/lib/capistrano_multiconfig_parallel/celluloid/bundler_worker.rb +34 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_manager.rb +5 -3
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_worker.rb +37 -17
- data/lib/capistrano_multiconfig_parallel/celluloid/process_runner.rb +150 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/state_machine.rb +1 -0
- data/lib/capistrano_multiconfig_parallel/classes/child_process_status.rb +72 -0
- data/lib/capistrano_multiconfig_parallel/classes/job.rb +1 -1
- data/lib/capistrano_multiconfig_parallel/classes/job_command.rb +25 -14
- data/lib/capistrano_multiconfig_parallel/classes/runner_status.rb +123 -0
- data/lib/capistrano_multiconfig_parallel/helpers/base_actor_helper.rb +5 -9
- data/lib/capistrano_multiconfig_parallel/version.rb +1 -1
- metadata +6 -3
- data/lib/capistrano_multiconfig_parallel/celluloid/child_process.rb +0 -132
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f293dd460a9b6468a63c41cabe277f8a948b419
|
4
|
+
data.tar.gz: 07307d95d8574a28775a03549afa110d233d730e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6209f4db889a5a4c29cf83476509405e56f796180f2024f5945057ce633eedbfde0dbcc4048684947aef4c981071cb1fa99caab3bd142672e7a8dc0c0c98f507
|
7
|
+
data.tar.gz: a5231899ad5ded76359d19f625e921114aa4f3dd4aa1dc8046ec1179f398f2eaf94b4cddbcac7ca65bd66c33fc7aa5d86b03a762aa0f629811f6b3ae7f56023c
|
data/README.md
CHANGED
@@ -17,8 +17,8 @@ IMPORTANT! The whole reason for this gem was for using [Caphub](https://github.c
|
|
17
17
|
|
18
18
|
CAUTION!! PLEASE READ CAREFULLY!! Capistrano is not thread-safe. However in order to work around this problem, each of the task is executing inside a thread that spawns a new process in order to run capistrano tasks The thread monitors the process. This works well, however if the tasks you are executing is working with files, you might get into deadlocks because multiple proceses try to access same resource. Instead of using files , please consider using StringIO instead.
|
19
19
|
|
20
|
-
NEW Improvements started in version 2.0.0.alpha ( currently gem is in
|
21
|
-
|
20
|
+
NEW Improvements started in version 2.0.0.alpha ( currently gem is in a release candidate version)
|
21
|
+
--------------------------------------------------------------------------------------------------
|
22
22
|
|
23
23
|
- Code for handling websocket events when a task is invoked was moved to a new gem [capistrano_sentinel](https://github.com/bogdanRada/capistrano_sentinel)
|
24
24
|
- You can now deploy applications from anywhere on your computer without having to add this gem to the Gemfile, however you need to add the [capistrano_sentinel](https://github.com/bogdanRada/capistrano_sentinel) gem to your Gemfile, if you want to use this .
|
@@ -76,7 +76,7 @@ Add the following to your Capfile after requiring **capistrano** and **capistran
|
|
76
76
|
Install locally on your system the capistrano_multiconfig_parallel gem using this command :
|
77
77
|
|
78
78
|
```ruby
|
79
|
-
gem install capistrano_multiconfig_parallel -v 2.0.0.
|
79
|
+
gem install capistrano_multiconfig_parallel -v 2.0.0.rc1
|
80
80
|
```
|
81
81
|
|
82
82
|
Please read [Release Details](https://github.com/bogdanRada/capistrano_multiconfig_parallel/releases) if you are upgrading. We break backward compatibility between large ticks but you can expect it to be specified at release notes.
|
@@ -51,7 +51,7 @@ module CapistranoMulticonfigParallel
|
|
51
51
|
Celluloid.logger = logger
|
52
52
|
Celluloid.task_class = defined?(Celluloid::TaskThread) ? Celluloid::TaskThread : Celluloid::Task::Threaded
|
53
53
|
Celluloid.exception_handler do |ex|
|
54
|
-
unless ex.is_a?(Interrupt)
|
54
|
+
unless ex.is_a?(Interrupt) || ex.is_a?(SystemExit) || ex.is_a?(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed)
|
55
55
|
rescue_error(ex, 'stderr')
|
56
56
|
end
|
57
57
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative './celluloid_worker'
|
2
|
+
require_relative './process_runner'
|
3
|
+
require_relative '../classes/runner_status'
|
4
|
+
module CapistranoMulticonfigParallel
|
5
|
+
class BundlerWorker
|
6
|
+
include CapistranoMulticonfigParallel::BaseActorHelper
|
7
|
+
|
8
|
+
def work(job, options = {}, &callback)
|
9
|
+
@job = job
|
10
|
+
@options = options.symbolize_keys
|
11
|
+
@job_id = job.id
|
12
|
+
@runner_status = nil
|
13
|
+
check_missing_deps
|
14
|
+
end
|
15
|
+
|
16
|
+
def actor_id
|
17
|
+
"bundler_worker_#{@job_id}".to_sym
|
18
|
+
end
|
19
|
+
|
20
|
+
def check_missing_deps
|
21
|
+
command = @job.fetch_bundler_worker_command
|
22
|
+
log_to_file("bundler worker #{@job_id} executes: #{command}")
|
23
|
+
do_bundle_sync_command(command)
|
24
|
+
end
|
25
|
+
|
26
|
+
def do_bundle_sync_command(command)
|
27
|
+
CapistranoMulticonfigParallel::ProcessRunner.supervise as: actor_id
|
28
|
+
result = Celluloid::Actor[actor_id].work(@job, command,sync: :sync, :callback => lambda { |runner_status| @runner_status = runner_status } )
|
29
|
+
sleep(0.1) until @runner_status.present?
|
30
|
+
@runner_status.output_text.include?("The Gemfile's dependencies are satisfied")
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
@@ -21,8 +21,8 @@ module CapistranoMulticonfigParallel
|
|
21
21
|
# Get a handle on the SupervisionGroup::Member
|
22
22
|
@mutex = Mutex.new
|
23
23
|
# http://rubydoc.info/gems/celluloid/Celluloid/SupervisionGroup/Member
|
24
|
-
setup_pool_of_actor(@worker_supervisor, actor_name: :workers, type: CapistranoMulticonfigParallel::CelluloidWorker, size: 10)
|
25
|
-
|
24
|
+
@workers = setup_pool_of_actor(@worker_supervisor, actor_name: :workers, type: CapistranoMulticonfigParallel::CelluloidWorker, size: 10)
|
25
|
+
#@workers = Celluloid::Actor[:workers].pool
|
26
26
|
Actor.current.link @workers
|
27
27
|
setup_actor_supervision(@worker_supervisor, actor_name: :terminal_server, type: CapistranoMulticonfigParallel::TerminalTable, args: [Actor.current, @job_manager, configuration.fetch(:terminal, {})])
|
28
28
|
setup_actor_supervision(@worker_supervisor, actor_name: :web_server, type: CapistranoMulticonfigParallel::WebServer, args: websocket_config)
|
@@ -200,6 +200,7 @@ module CapistranoMulticonfigParallel
|
|
200
200
|
env_opts = options['skip_env_options'].present? ? {} : @job_manager.get_app_additional_env_options(job.app, job.stage)
|
201
201
|
new_job_options = job.options.except!('id', 'status', 'exit_status').merge('env_options' => job.env_options.merge(env_opts))
|
202
202
|
new_job = CapistranoMulticonfigParallel::Job.new(@job_manager, new_job_options.merge(options))
|
203
|
+
log_to_file("Trying to DiSPATCH new JOB #{new_job.inspect}")
|
203
204
|
async.delegate_job(new_job) unless job.rolling_back?
|
204
205
|
end
|
205
206
|
|
@@ -220,9 +221,10 @@ module CapistranoMulticonfigParallel
|
|
220
221
|
|
221
222
|
def worker_died(worker, reason)
|
222
223
|
job = @worker_to_job[worker.mailbox.address]
|
224
|
+
mailbox = worker.mailbox
|
225
|
+
log_to_file("worker_died: worker job #{job.inspect} with mailbox #{mailbox.inspect} and #{mailbox.address.inspect} died for reason: #{reason}")
|
223
226
|
return true if job.blank? || job.rolling_back? || job.action != 'deploy'
|
224
227
|
job.rollback_changes_to_application
|
225
|
-
mailbox = worker.mailbox
|
226
228
|
@worker_to_job.delete(mailbox.address)
|
227
229
|
log_to_file("RESTARTING: worker job #{job.inspect} with mailbox #{mailbox.inspect} and #{mailbox.address.inspect} died for reason: #{reason}")
|
228
230
|
dispatch_new_job(job, skip_env_options: true, action: 'deploy:rollback')
|
@@ -1,6 +1,7 @@
|
|
1
|
-
require_relative './child_process'
|
2
|
-
require_relative './state_machine'
|
3
1
|
require_relative '../helpers/base_actor_helper'
|
2
|
+
require_relative '../classes/child_process_status'
|
3
|
+
require_relative './state_machine'
|
4
|
+
require_relative './process_runner'
|
4
5
|
module CapistranoMulticonfigParallel
|
5
6
|
# worker that will spawn a child process in order to execute a capistrano job and monitor that process
|
6
7
|
#
|
@@ -20,10 +21,16 @@ module CapistranoMulticonfigParallel
|
|
20
21
|
include CapistranoMulticonfigParallel::BaseActorHelper
|
21
22
|
class TaskFailed < StandardError; end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
|
25
|
+
ATTRIBUTE_LIST = [
|
26
|
+
:job, :manager, :job_id, :app_name, :env_name, :action_name, :env_options, :machine, :socket_connection, :task_argv,
|
27
|
+
:rake_tasks, :current_task_number, # tracking tasks
|
28
|
+
:successfull_subscription, :subscription_channel, :publisher_channel, # for subscriptions and publishing events
|
29
|
+
:job_termination_condition, :invocation_chain, :filename, :worker_log, :exit_status
|
30
|
+
]
|
31
|
+
|
32
|
+
attr_reader *CapistranoMulticonfigParallel::CelluloidWorker::ATTRIBUTE_LIST
|
33
|
+
attr_accessor *CapistranoMulticonfigParallel::CelluloidWorker::ATTRIBUTE_LIST
|
27
34
|
|
28
35
|
def initialize(*args)
|
29
36
|
end
|
@@ -43,7 +50,7 @@ module CapistranoMulticonfigParallel
|
|
43
50
|
|
44
51
|
|
45
52
|
def worker_state
|
46
|
-
if Actor.current.alive?
|
53
|
+
if job.status.to_s.downcase != 'dead' && Actor.current.alive?
|
47
54
|
@machine.state.to_s.green
|
48
55
|
else
|
49
56
|
job.status = 'dead'
|
@@ -89,14 +96,14 @@ module CapistranoMulticonfigParallel
|
|
89
96
|
check_child_proces
|
90
97
|
command = job.fetch_deploy_command
|
91
98
|
log_to_file("worker #{@job_id} executes: #{command}")
|
92
|
-
@child_process.async.work(@job, command, actor: Actor.current, silent: true)
|
99
|
+
@child_process.async.work(@job, command, actor: Actor.current, silent: true,sync: :async, runner_status_klass: CapistranoMulticonfigParallel::ChildProcessStatus)
|
93
100
|
end
|
94
101
|
|
95
102
|
def check_child_proces
|
96
|
-
@child_process = CapistranoMulticonfigParallel::
|
103
|
+
@child_process = CapistranoMulticonfigParallel::ProcessRunner.new
|
97
104
|
Actor.current.link @child_process
|
98
105
|
@child_process
|
99
|
-
|
106
|
+
end
|
100
107
|
|
101
108
|
def on_close(code, reason)
|
102
109
|
log_to_file("worker #{@job_id} websocket connection closed: #{code.inspect}, #{reason.inspect}")
|
@@ -117,13 +124,13 @@ module CapistranoMulticonfigParallel
|
|
117
124
|
elsif message_is_for_stdout?(message)
|
118
125
|
result = Celluloid::Actor[:terminal_server].show_confirmation(message['question'], message['default'])
|
119
126
|
publish_rake_event(message.merge('action' => 'stdin', 'result' => result, 'client_action' => 'stdin'))
|
120
|
-
|
127
|
+
elsif message_from_bundler?(message)
|
121
128
|
|
122
129
|
#gem_messsage = job.gem_specs.find{|spec| message['task'].include?(spec.name) }
|
123
130
|
# if gem_messsage.present?
|
124
131
|
# async.update_machine_state("insta")
|
125
132
|
# else
|
126
|
-
|
133
|
+
async.update_machine_state(message['task'])
|
127
134
|
#end
|
128
135
|
else
|
129
136
|
log_to_file(message, job_id: @job_id)
|
@@ -158,6 +165,7 @@ module CapistranoMulticonfigParallel
|
|
158
165
|
log_to_file("worker #{@job_id} triest to transition from #{@machine.state} to #{name}") unless options[:bundler]
|
159
166
|
@machine.go_to_transition(name.to_s, options)
|
160
167
|
error_message = "worker #{@job_id} task #{name} failed "
|
168
|
+
# @manager.worker_died(Actor.current, error_message) if job.failed?
|
161
169
|
raise(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed.new(error_message), error_message) if job.failed? # force worker to rollback
|
162
170
|
end
|
163
171
|
|
@@ -170,15 +178,27 @@ module CapistranoMulticonfigParallel
|
|
170
178
|
def finish_worker(exit_status)
|
171
179
|
log_to_file("worker #{job_id} tries to terminate with exit_status #{exit_status}")
|
172
180
|
@manager.mark_completed_remaining_tasks(@job) if Actor.current.alive?
|
173
|
-
update_machine_state('FINISHED')
|
181
|
+
exit_status == 0 ? update_machine_state('FINISHED') : update_machine_state('DEAD')
|
182
|
+
# @manager.worker_died(Actor.current, exit_status) if exit_status != 0
|
174
183
|
@manager.workers_terminated.signal('completed') if @manager.present? && @manager.alive? && @manager.all_workers_finished?
|
175
184
|
end
|
176
185
|
|
177
186
|
def notify_finished(exit_status)
|
178
187
|
finish_worker(exit_status)
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
188
|
+
return if exit_status == 0
|
189
|
+
error_message = "worker #{@job_id} task failed with exit status #{exit_status.inspect} "
|
190
|
+
raise(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed.new(error_message), error_message)
|
191
|
+
end
|
192
|
+
|
193
|
+
# def inspect
|
194
|
+
# to_s
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# def to_s
|
198
|
+
# "#<#{self.class}(#{Actor.current.mailbox.address.inspect}) alive>"
|
199
|
+
# rescue
|
200
|
+
# "#<#{self.class}(#{Actor.current.mailbox.address.inspect}) dead>"
|
201
|
+
# end
|
202
|
+
|
183
203
|
end
|
184
204
|
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require_relative '../helpers/base_actor_helper'
|
2
|
+
module CapistranoMulticonfigParallel
|
3
|
+
class ProcessRunner
|
4
|
+
include CapistranoMulticonfigParallel::BaseActorHelper
|
5
|
+
|
6
|
+
@attrs = [
|
7
|
+
:options,
|
8
|
+
:job,
|
9
|
+
:cmd,
|
10
|
+
:runner_status_klass,
|
11
|
+
:runner_status,
|
12
|
+
:actor,
|
13
|
+
:job_id,
|
14
|
+
:timer,
|
15
|
+
:synchronicity
|
16
|
+
]
|
17
|
+
|
18
|
+
attr_reader *@attrs
|
19
|
+
attr_accessor *@attrs
|
20
|
+
|
21
|
+
finalizer :process_finalizer
|
22
|
+
|
23
|
+
def work(job, cmd, options = {})
|
24
|
+
@options = options.is_a?(Hash) ? options.symbolize_keys : {}
|
25
|
+
@job = job
|
26
|
+
@cmd = cmd
|
27
|
+
|
28
|
+
@runner_status_klass = @options[:runner_status_klass].present? ? @options[:runner_status_klass] : RunnerStatus
|
29
|
+
@runner_status = @runner_status_klass.new(Actor.current, job, cmd, @options)
|
30
|
+
@synchronicity = @options[:sync]
|
31
|
+
start_running
|
32
|
+
end
|
33
|
+
|
34
|
+
def start_running
|
35
|
+
setup_attributes
|
36
|
+
run_right_popen3
|
37
|
+
setup_em_error_handler if @synchronicity == :async
|
38
|
+
end
|
39
|
+
|
40
|
+
def setup_attributes
|
41
|
+
@actor = @options.fetch(:actor, nil)
|
42
|
+
@job_id = @job.id
|
43
|
+
end
|
44
|
+
|
45
|
+
def setup_em_error_handler
|
46
|
+
EM.error_handler do|exception|
|
47
|
+
log_error(exception, job_id: @job_id, output: 'stderr')
|
48
|
+
EM.stop
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def setup_periodic_timer
|
53
|
+
@timer = EM::PeriodicTimer.new(0.1) do
|
54
|
+
check_exit_status
|
55
|
+
@timer.cancel if @runner_status.exit_status.present?
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def check_exit_status
|
60
|
+
return if @runner_status.exit_status.blank?
|
61
|
+
@timer.cancel
|
62
|
+
@job.exit_status = @runner_status.exit_status
|
63
|
+
log_to_file("worker #{@job_id} startsnotify finished with exit status #{@job.exit_status.inspect}")
|
64
|
+
if @actor.present? && @actor.respond_to?(:notify_finished)
|
65
|
+
if @actor.respond_to?(:async) && @synchronicity == :async
|
66
|
+
@actor.async.notify_finished(@job.exit_status)
|
67
|
+
elsif @synchronicity == :sync
|
68
|
+
@actor.notify_finished(@job.exit_status)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def process_finalizer
|
74
|
+
EM.stop if EM.reactor_running?
|
75
|
+
terminate
|
76
|
+
end
|
77
|
+
|
78
|
+
def run_right_popen3
|
79
|
+
popen3_options = {
|
80
|
+
# :timeout_seconds => @options.has_key?(:timeout) ? @options[:timeout] : 2,
|
81
|
+
:size_limit_bytes => @options[:size_limit_bytes],
|
82
|
+
:watch_directory => @options[:watch_directory],
|
83
|
+
:user => @options[:user],
|
84
|
+
:group => @options[:group],
|
85
|
+
}
|
86
|
+
command = @runner_status.command
|
87
|
+
case @synchronicity
|
88
|
+
when :sync
|
89
|
+
run_right_popen3_sync(command, popen3_options)
|
90
|
+
when :async
|
91
|
+
run_right_popen3_async(command, popen3_options)
|
92
|
+
else
|
93
|
+
raise "unknown synchronicity = #{synchronicity.inspect}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def run_right_popen3_sync(command, popen3_options)
|
98
|
+
do_right_popen3_sync(command, popen3_options)
|
99
|
+
sleep(0.1) until @runner_status.exit_status.present?
|
100
|
+
end
|
101
|
+
|
102
|
+
def run_right_popen3_async(command, popen3_options)
|
103
|
+
EM.run do
|
104
|
+
EM.defer do
|
105
|
+
begin
|
106
|
+
do_right_popen3_async(command, popen3_options)
|
107
|
+
rescue Exception => e
|
108
|
+
log_error(exception, job_id: @job_id, output: 'stderr')
|
109
|
+
EM.stop
|
110
|
+
end
|
111
|
+
end
|
112
|
+
setup_periodic_timer
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def do_right_popen3(synchronicity, command, popen3_options)
|
117
|
+
popen3_options = {
|
118
|
+
:target => @runner_status,
|
119
|
+
:environment => @options.fetch(:environment, nil),
|
120
|
+
:input => :on_input_stdin,
|
121
|
+
:stdout_handler => :on_read_stdout,
|
122
|
+
:stderr_handler => :on_read_stderr,
|
123
|
+
:watch_handler => :watch_handler,
|
124
|
+
:pid_handler => :on_pid,
|
125
|
+
:timeout_handler => :on_timeout,
|
126
|
+
:size_limit_handler => :on_size_limit,
|
127
|
+
:exit_handler => :on_exit,
|
128
|
+
:async_exception_handler => :async_exception_handler
|
129
|
+
}.merge(popen3_options)
|
130
|
+
case synchronicity
|
131
|
+
when :sync
|
132
|
+
result = ::RightScale::RightPopen.popen3_sync(command, popen3_options)
|
133
|
+
when :async
|
134
|
+
result = ::RightScale::RightPopen.popen3_async(command, popen3_options)
|
135
|
+
else
|
136
|
+
raise "Uknown synchronicity = #{synchronicity.inspect}"
|
137
|
+
end
|
138
|
+
result == true
|
139
|
+
end
|
140
|
+
|
141
|
+
def do_right_popen3_sync(command, popen3_options)
|
142
|
+
do_right_popen3(:sync, command, popen3_options)
|
143
|
+
end
|
144
|
+
|
145
|
+
def do_right_popen3_async( command, popen3_options)
|
146
|
+
do_right_popen3(:async, command, popen3_options)
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require_relative './runner_status'
|
2
|
+
module CapistranoMulticonfigParallel
|
3
|
+
# class that is used to execute the capistrano tasks and it is invoked by the celluloid worker
|
4
|
+
class ChildProcessStatus < CapistranoMulticonfigParallel::RunnerStatus
|
5
|
+
|
6
|
+
attr_accessor :show_bundler
|
7
|
+
|
8
|
+
def initialize(process_runner, job, command, options={})
|
9
|
+
super(process_runner, job, command, options)
|
10
|
+
@show_bundler = true
|
11
|
+
end
|
12
|
+
|
13
|
+
def print_error_if_exist
|
14
|
+
return unless development_debug?
|
15
|
+
[@job.stderr_buffer].each do |buffer|
|
16
|
+
buffer.rewind
|
17
|
+
data = buffer.read
|
18
|
+
log_output_error(nil, 'stderr', "Child process for worker #{@job_id} died for reason: #{data}") if data.present?
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def on_input_stdin(data)
|
23
|
+
io_callback('stdin', data)
|
24
|
+
end
|
25
|
+
|
26
|
+
def on_read_stdout(data)
|
27
|
+
@show_bundler = false if data.to_s.include?("The Gemfile's dependencies are satisfied") || data.to_s.include?("Bundle complete")
|
28
|
+
@actor.async.update_machine_state(truncate(data, 40), :bundler => true) if @show_bundler == true && data.strip.present? && data.strip != '.'
|
29
|
+
io_callback('stdout', data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def on_read_stderr(data)
|
33
|
+
@job.save_stderr_error(data) if development_debug?
|
34
|
+
io_callback('stderr', data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def on_timeout
|
38
|
+
log_to_file "Child process for worker #{@job_id} on_timeout disconnected"
|
39
|
+
@did_timeout = true
|
40
|
+
@callback.call(self) if @expect_timeout
|
41
|
+
end
|
42
|
+
|
43
|
+
def on_size_limit
|
44
|
+
log_to_file "Child process for worker #{@job_id} on_size_limit disconnected"
|
45
|
+
@did_size_limit = true
|
46
|
+
@callback.call(self) if @expect_size_limit
|
47
|
+
end
|
48
|
+
|
49
|
+
def on_exit(status)
|
50
|
+
log_to_file "Child process for worker #{@job_id} on_exit disconnected due to error #{exit_status.inspect}"
|
51
|
+
print_error_if_exist
|
52
|
+
@exit_status = status.exitstatus
|
53
|
+
process_runner.check_exit_status
|
54
|
+
end
|
55
|
+
|
56
|
+
def async_exception_handler(*data)
|
57
|
+
log_to_file "Child process for worker #{@job_id} async_exception_handler disconnected due to error #{data.inspect}"
|
58
|
+
io_callback('stderr', data)
|
59
|
+
@exit_status = 1
|
60
|
+
process_runner.check_exit_status
|
61
|
+
end
|
62
|
+
|
63
|
+
def watch_handler(process)
|
64
|
+
@process ||= process
|
65
|
+
process_runner.check_exit_status
|
66
|
+
end
|
67
|
+
|
68
|
+
def io_callback(io, data)
|
69
|
+
log_to_file("#{io.upcase} ---- #{data}", job_id: @job_id)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -23,6 +23,15 @@ module CapistranoMulticonfigParallel
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
+
def fetch_bundler_check_command(gemfile = job_gemfile)
|
27
|
+
"#{check_rvm_loaded} && if [ `which bundler |wc -l` = 0 ]; then gem install bundler;fi && (#{bundle_gemfile_env(gemfile)} bundle check || #{bundle_gemfile_env(gemfile)} bundle install )"
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_bundler_worker_command
|
31
|
+
get_command_script(fetch_bundler_check_command)
|
32
|
+
end
|
33
|
+
|
34
|
+
|
26
35
|
def find_capfile(custom_path = job_path)
|
27
36
|
@capfile_path ||= Pathname.new(custom_path).children.find { |file| check_file(file, 'capfile') }
|
28
37
|
end
|
@@ -158,7 +167,7 @@ module CapistranoMulticonfigParallel
|
|
158
167
|
end
|
159
168
|
|
160
169
|
def create_job_tempfile_command(output)
|
161
|
-
@tempfile
|
170
|
+
@tempfile = Tempfile.new(["multi_cap_#{job.id}_command_", ".rb"], encoding: 'utf-8')
|
162
171
|
@tempfile.write(output)
|
163
172
|
ObjectSpace.undefine_finalizer(@tempfile) # force garbage collector not to remove automatically the file
|
164
173
|
@tempfile.close
|
@@ -189,21 +198,13 @@ module CapistranoMulticonfigParallel
|
|
189
198
|
rvm_enabled_for_job? ? "bash --login -c '#{command}'" : command
|
190
199
|
end
|
191
200
|
|
192
|
-
def
|
193
|
-
prepare_application_for_deployment
|
194
|
-
# config_flags = CapistranoMulticonfigParallel.configuration_flags.merge("capistrano_version": job_capistrano_version)
|
195
|
-
environment_options = setup_command_line.join(' ')
|
196
|
-
command = "#{check_rvm_loaded} && if [ `which bundler |wc -l` = 0 ]; then gem install bundler;fi && (#{bundle_gemfile_env(@job_final_gemfile)} bundle check || #{bundle_gemfile_env(@job_final_gemfile)} bundle install ) && WEBSOCKET_LOGGING=#{debug_websocket?} LOG_FILE=#{websocket_config.fetch('log_file_path', nil)} #{bundle_gemfile_env(@job_final_gemfile)} bundle exec cap #{job_stage} #{capistrano_action} #{environment_options}"
|
197
|
-
|
201
|
+
def get_command_script(command)
|
198
202
|
command = rvm_bash_prefix(command)
|
199
203
|
command = command.inspect
|
200
|
-
|
201
204
|
command_text =<<-CMD
|
202
205
|
require 'rubygems'
|
203
206
|
require 'bundler'
|
204
207
|
Bundler.with_clean_env {
|
205
|
-
ENV['BUNDLE_GEMFILE'] = '#{job_gemfile_multi}'
|
206
|
-
ENV['#{CapistranoSentinel::RequestHooks::ENV_KEY_JOB_ID}']='#{job.id}'
|
207
208
|
Kernel.exec(#{command})
|
208
209
|
}
|
209
210
|
CMD
|
@@ -219,6 +220,16 @@ module CapistranoMulticonfigParallel
|
|
219
220
|
end
|
220
221
|
|
221
222
|
|
223
|
+
def fetch_deploy_command
|
224
|
+
prepare_application_for_deployment
|
225
|
+
# config_flags = CapistranoMulticonfigParallel.configuration_flags.merge("capistrano_version": job_capistrano_version)
|
226
|
+
environment_options = setup_command_line.join(' ')
|
227
|
+
command = "#{fetch_bundler_check_command(@job_final_gemfile)} && WEBSOCKET_LOGGING=#{debug_websocket?} LOG_FILE=#{websocket_config.fetch('log_file_path', nil)} #{bundle_gemfile_env(@job_final_gemfile)} bundle exec cap #{job_stage} #{capistrano_action} #{environment_options}"
|
228
|
+
|
229
|
+
get_command_script(command)
|
230
|
+
end
|
231
|
+
|
232
|
+
|
222
233
|
def job_capfile
|
223
234
|
File.join(job_path, capfile_name.to_s)
|
224
235
|
end
|
@@ -228,6 +239,10 @@ module CapistranoMulticonfigParallel
|
|
228
239
|
end
|
229
240
|
|
230
241
|
def prepare_application_for_deployment
|
242
|
+
if ENV['MULTI_CAP_WEB_APP']
|
243
|
+
bundler_worker = CapistranoMulticonfigParallel::BundlerWorker.new
|
244
|
+
result = bundler_worker.work(job)
|
245
|
+
end
|
231
246
|
check_capistrano_sentinel_availability
|
232
247
|
prepare_capfile
|
233
248
|
end
|
@@ -290,10 +305,6 @@ module CapistranoMulticonfigParallel
|
|
290
305
|
|
291
306
|
private
|
292
307
|
|
293
|
-
def get_bash_command(command)
|
294
|
-
Shellwords.escape(command)
|
295
|
-
end
|
296
|
-
|
297
308
|
def run_shell_command(command)
|
298
309
|
sh("#{command}")
|
299
310
|
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require_relative '../helpers/application_helper'
|
2
|
+
module CapistranoMulticonfigParallel
|
3
|
+
class RunnerStatus
|
4
|
+
include CapistranoMulticonfigParallel::ApplicationHelper
|
5
|
+
|
6
|
+
ATTRIBUTE_LIST = [
|
7
|
+
:job,
|
8
|
+
:process_runner,
|
9
|
+
:command,
|
10
|
+
:options,
|
11
|
+
:actor,
|
12
|
+
:job_id,
|
13
|
+
:output_text,
|
14
|
+
:error_text,
|
15
|
+
:exit_status,
|
16
|
+
:did_timeout,
|
17
|
+
:callback,
|
18
|
+
:pid,
|
19
|
+
:force_yield,
|
20
|
+
:expect_timeout,
|
21
|
+
:expect_size_limit,
|
22
|
+
:async_exception,
|
23
|
+
:process
|
24
|
+
]
|
25
|
+
|
26
|
+
attr_reader *CapistranoMulticonfigParallel::RunnerStatus::ATTRIBUTE_LIST
|
27
|
+
attr_accessor *CapistranoMulticonfigParallel::RunnerStatus::ATTRIBUTE_LIST
|
28
|
+
|
29
|
+
def initialize(process_runner, job, command, options={})
|
30
|
+
options = options.is_a?(Hash) ? options : {}
|
31
|
+
@job = job
|
32
|
+
@process_runner = process_runner
|
33
|
+
@command = command
|
34
|
+
@options = {:repeats=>1, :force_yield=>nil, :timeout=>nil, :expect_timeout=>false}.merge(options)
|
35
|
+
@options = @options.symbolize_keys
|
36
|
+
|
37
|
+
@actor = @options.fetch(:actor, nil)
|
38
|
+
@job_id = @job.id
|
39
|
+
@process_runner = process_runner
|
40
|
+
|
41
|
+
|
42
|
+
@output_text = ""
|
43
|
+
@error_text = ""
|
44
|
+
@exit_status = nil
|
45
|
+
@did_timeout = false
|
46
|
+
@callback = @options[:callback].present? ? @options[:callback] : nil
|
47
|
+
@pid = nil
|
48
|
+
@force_yield = @options[:force_yield]
|
49
|
+
|
50
|
+
@expect_timeout = @options[:expect_timeout] || false
|
51
|
+
@expect_size_limit = @options[:expect_size_limit] || false
|
52
|
+
@async_exception = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def on_pid(pid)
|
56
|
+
log_to_file "Child process for worker #{@job_id} on_pid #{pid.inspect}"
|
57
|
+
@pid ||= pid
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
def on_input_stdin(data)
|
62
|
+
log_to_file "Child process for worker #{@job_id} on_input_stdin #{data.inspect}"
|
63
|
+
@output_text << data
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_read_stdout(data)
|
67
|
+
log_to_file "Child process for worker #{@job_id} on_read_stdout #{data.inspect}"
|
68
|
+
@output_text << data
|
69
|
+
end
|
70
|
+
|
71
|
+
def on_read_stderr(data)
|
72
|
+
log_to_file "Child process for worker #{@job_id} on_read_stderr #{data.inspect}"
|
73
|
+
@error_text << data
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def on_timeout
|
78
|
+
log_to_file "Child process for worker #{@job_id} on_timeout disconnected"
|
79
|
+
@did_timeout = true
|
80
|
+
@callback.call(self) if @callback && @expect_timeout
|
81
|
+
end
|
82
|
+
|
83
|
+
def on_size_limit
|
84
|
+
log_to_file "Child process for worker #{@job_id} on_size_limit disconnected"
|
85
|
+
@did_size_limit = true
|
86
|
+
@callback.call(self) if @callback && @expect_size_limit
|
87
|
+
end
|
88
|
+
|
89
|
+
def on_exit(status)
|
90
|
+
log_to_file "Child process for worker #{@job_id} on_exit disconnected due to error #{status.inspect}"
|
91
|
+
@exit_status = status.exitstatus
|
92
|
+
@callback.call(self) if @callback
|
93
|
+
end
|
94
|
+
|
95
|
+
def async_exception_handler(async_exception)
|
96
|
+
@async_exception = async_exception
|
97
|
+
log_to_file "Child process for worker #{@job_id} async_exception_handler disconnected due to error #{data.inspect}"
|
98
|
+
@exit_status = 1
|
99
|
+
end
|
100
|
+
|
101
|
+
def watch_handler(process)
|
102
|
+
@process ||= process
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def inspect
|
107
|
+
to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
JSON.generate(to_json)
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_json
|
115
|
+
hash = {}
|
116
|
+
CapistranoMulticonfigParallel::RunnerStatus::ATTRIBUTE_LIST.delete_if{|a| [:process_runner].include?(a) }.each do |key|
|
117
|
+
hash[key] = send(key).inspect
|
118
|
+
end
|
119
|
+
hash
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -53,9 +53,7 @@ module CapistranoMulticonfigParallel
|
|
53
53
|
if version_less_than_seventeen?
|
54
54
|
class_name.supervise_as(*setup_actor_supervision_details(class_name, options))
|
55
55
|
else
|
56
|
-
|
57
|
-
supervisor.supervise setup_actor_supervision_details(class_name, options)
|
58
|
-
end
|
56
|
+
class_name.supervise setup_actor_supervision_details(class_name, options)
|
59
57
|
end
|
60
58
|
end
|
61
59
|
|
@@ -63,9 +61,7 @@ module CapistranoMulticonfigParallel
|
|
63
61
|
if version_less_than_seventeen?
|
64
62
|
Celluloid::SupervisionGroup.run!
|
65
63
|
else
|
66
|
-
|
67
|
-
yield(self) if block_given?
|
68
|
-
end.run!
|
64
|
+
Celluloid::Supervision::Container.run!
|
69
65
|
end
|
70
66
|
end
|
71
67
|
|
@@ -73,10 +69,10 @@ module CapistranoMulticonfigParallel
|
|
73
69
|
if version_less_than_seventeen?
|
74
70
|
class_name.pool(options[:type], as: options[:actor_name], size: options.fetch(:size, 10))
|
75
71
|
else
|
72
|
+
# config = Celluloid::Supervision::Configuration.new
|
73
|
+
# config.define setup_actor_supervision_details(class_name, options)
|
76
74
|
options = setup_actor_supervision_details(class_name, options)
|
77
|
-
|
78
|
-
supervisor.pool *[options[:type], options.except(:type)]
|
79
|
-
end
|
75
|
+
class_name.pool *[options[:type], options.except(:type)]
|
80
76
|
end
|
81
77
|
end
|
82
78
|
end
|
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: 2.0.0.
|
4
|
+
version: 2.0.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- bogdanRada
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-07-
|
11
|
+
date: 2016-07-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: celluloid
|
@@ -484,17 +484,20 @@ files:
|
|
484
484
|
- lib/capistrano_multiconfig_parallel/all.rb
|
485
485
|
- lib/capistrano_multiconfig_parallel/application.rb
|
486
486
|
- lib/capistrano_multiconfig_parallel/base.rb
|
487
|
+
- lib/capistrano_multiconfig_parallel/celluloid/bundler_worker.rb
|
487
488
|
- lib/capistrano_multiconfig_parallel/celluloid/celluloid_manager.rb
|
488
489
|
- lib/capistrano_multiconfig_parallel/celluloid/celluloid_worker.rb
|
489
|
-
- lib/capistrano_multiconfig_parallel/celluloid/
|
490
|
+
- lib/capistrano_multiconfig_parallel/celluloid/process_runner.rb
|
490
491
|
- lib/capistrano_multiconfig_parallel/celluloid/state_machine.rb
|
491
492
|
- lib/capistrano_multiconfig_parallel/celluloid/terminal_table.rb
|
492
493
|
- lib/capistrano_multiconfig_parallel/celluloid/web_server.rb
|
494
|
+
- lib/capistrano_multiconfig_parallel/classes/child_process_status.rb
|
493
495
|
- lib/capistrano_multiconfig_parallel/classes/cursor.rb
|
494
496
|
- lib/capistrano_multiconfig_parallel/classes/dependency_tracker.rb
|
495
497
|
- lib/capistrano_multiconfig_parallel/classes/interactive_menu.rb
|
496
498
|
- lib/capistrano_multiconfig_parallel/classes/job.rb
|
497
499
|
- lib/capistrano_multiconfig_parallel/classes/job_command.rb
|
500
|
+
- lib/capistrano_multiconfig_parallel/classes/runner_status.rb
|
498
501
|
- lib/capistrano_multiconfig_parallel/cli.rb
|
499
502
|
- lib/capistrano_multiconfig_parallel/configuration/default.yml
|
500
503
|
- lib/capistrano_multiconfig_parallel/helpers/application_helper.rb
|
@@ -1,132 +0,0 @@
|
|
1
|
-
require_relative '../helpers/base_actor_helper'
|
2
|
-
module CapistranoMulticonfigParallel
|
3
|
-
# class that is used to execute the capistrano tasks and it is invoked by the celluloid worker
|
4
|
-
class ChildProcess
|
5
|
-
include CapistranoMulticonfigParallel::BaseActorHelper
|
6
|
-
|
7
|
-
attr_accessor :options, :job, :actor, :job_id, :exit_status, :pid, :process
|
8
|
-
|
9
|
-
finalizer :process_finalizer
|
10
|
-
|
11
|
-
def work(job, cmd, options = {})
|
12
|
-
@options = options
|
13
|
-
@job = job
|
14
|
-
@cmd = cmd
|
15
|
-
start_running
|
16
|
-
end
|
17
|
-
|
18
|
-
def start_running
|
19
|
-
setup_attributes
|
20
|
-
run_event_machine
|
21
|
-
setup_em_error_handler
|
22
|
-
end
|
23
|
-
|
24
|
-
def setup_attributes
|
25
|
-
@actor = @options.fetch(:actor, nil)
|
26
|
-
@job_id = @job.id
|
27
|
-
@exit_status = nil
|
28
|
-
@show_bundler = true
|
29
|
-
end
|
30
|
-
|
31
|
-
def setup_em_error_handler
|
32
|
-
EM.error_handler do|exception|
|
33
|
-
log_error(exception, job_id: @job_id, output: 'stderr')
|
34
|
-
EM.stop
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
def run_event_machine
|
39
|
-
EM.run do
|
40
|
-
EM.next_tick do
|
41
|
-
start_async_deploy
|
42
|
-
end
|
43
|
-
setup_periodic_timer
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def setup_periodic_timer
|
48
|
-
@timer = EM::PeriodicTimer.new(0.1) do
|
49
|
-
check_exit_status
|
50
|
-
@timer.cancel if @exit_status.present?
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
def process_finalizer
|
55
|
-
EM.stop if EM.reactor_running?
|
56
|
-
terminate
|
57
|
-
end
|
58
|
-
|
59
|
-
def check_exit_status
|
60
|
-
return if @exit_status.blank?
|
61
|
-
@timer.cancel
|
62
|
-
@job.exit_status = @exit_status
|
63
|
-
print_error_if_exist
|
64
|
-
log_to_file("worker #{@job_id} startsnotify finished with exit status #{@exit_status.inspect}")
|
65
|
-
@actor.async.notify_finished(@exit_status)
|
66
|
-
end
|
67
|
-
|
68
|
-
def print_error_if_exist
|
69
|
-
return unless development_debug?
|
70
|
-
[@job.stderr_buffer].each do |buffer|
|
71
|
-
buffer.rewind
|
72
|
-
data = buffer.read
|
73
|
-
log_output_error(nil, 'stderr', "Child process for worker #{@job_id} died for reason: #{data}") if data.present?
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def start_async_deploy
|
78
|
-
RightScale::RightPopen.popen3_async(
|
79
|
-
@cmd,
|
80
|
-
target: self,
|
81
|
-
environment: @options.fetch(:environment, nil),
|
82
|
-
pid_handler: :on_pid,
|
83
|
-
input: :on_input_stdin,
|
84
|
-
stdout_handler: :on_read_stdout,
|
85
|
-
stderr_handler: :on_read_stderr,
|
86
|
-
watch_handler: :watch_handler,
|
87
|
-
async_exception_handler: :async_exception_handler,
|
88
|
-
exit_handler: :on_exit)
|
89
|
-
end
|
90
|
-
|
91
|
-
def on_pid(pid)
|
92
|
-
@pid ||= pid
|
93
|
-
end
|
94
|
-
|
95
|
-
def on_input_stdin(data)
|
96
|
-
io_callback('stdin', data)
|
97
|
-
end
|
98
|
-
|
99
|
-
def on_read_stdout(data)
|
100
|
-
@show_bundler = false if data.to_s.include?("The Gemfile's dependencies are satisfied") || data.to_s.include?("Bundle complete")
|
101
|
-
@actor.async.update_machine_state(truncate(data, 40), :bundler => true) if @show_bundler == true && data.strip.present? && data.strip != '.'
|
102
|
-
io_callback('stdout', data)
|
103
|
-
end
|
104
|
-
|
105
|
-
def on_read_stderr(data)
|
106
|
-
@job.save_stderr_error(data) if development_debug?
|
107
|
-
io_callback('stderr', data)
|
108
|
-
end
|
109
|
-
|
110
|
-
def on_exit(status)
|
111
|
-
log_to_file "Child process for worker #{@job_id} on_exit disconnected due to error #{status.inspect}"
|
112
|
-
@exit_status = status.exitstatus
|
113
|
-
check_exit_status
|
114
|
-
end
|
115
|
-
|
116
|
-
def async_exception_handler(*data)
|
117
|
-
log_to_file "Child process for worker #{@job_id} async_exception_handler disconnected due to error #{data.inspect}"
|
118
|
-
io_callback('stderr', data)
|
119
|
-
@exit_status = 1
|
120
|
-
check_exit_status
|
121
|
-
end
|
122
|
-
|
123
|
-
def watch_handler(process)
|
124
|
-
@process ||= process
|
125
|
-
check_exit_status
|
126
|
-
end
|
127
|
-
|
128
|
-
def io_callback(io, data)
|
129
|
-
log_to_file("#{io.upcase} ---- #{data}", job_id: @job_id)
|
130
|
-
end
|
131
|
-
end
|
132
|
-
end
|