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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8dcf6eedb9a056078cbf9751eec4891962435a25
4
- data.tar.gz: 23169131c3f9ad637f3fba23aa6cb0e0ff72999f
3
+ metadata.gz: 3f293dd460a9b6468a63c41cabe277f8a948b419
4
+ data.tar.gz: 07307d95d8574a28775a03549afa110d233d730e
5
5
  SHA512:
6
- metadata.gz: 0ba7953d5f457de62eb60117b04ab7857bab1a127cb0c17865d951343e8d4a3a937eecb25364c8b65e6f74d5b9337a117fa0957888bccb1bd845e4d73a53e31f
7
- data.tar.gz: f75e4abf68ef7323ac2c6669dac8e2ae5baf49f09b4126f1e2d481bfd634dfbd9169fcf00e853d55b79f6216efbb9680b1e17048286ecf89ea3f1aaf1b8f89c0
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 beta version)
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.beta4
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.
@@ -28,7 +28,6 @@ require 'pp'
28
28
  require 'yaml'
29
29
  require 'stringio'
30
30
  require 'io/console'
31
- require 'shellwords'
32
31
 
33
32
  # fix error with not files that can not be found
34
33
  Gem.find_files('composable_state_machine/**/*.rb').each { |path| require path }
@@ -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
- @workers = Celluloid::Actor[:workers]
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
- attr_accessor :job, :manager, :job_id, :app_name, :env_name, :action_name, :env_options, :machine, :socket_connection, :task_argv,
24
- :rake_tasks, :current_task_number, # tracking tasks
25
- :successfull_subscription, :subscription_channel, :publisher_channel, # for subscriptions and publishing events
26
- :job_termination_condition, :worker_state, :invocation_chain, :filename, :worker_log, :exit_status
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::ChildProcess.new
103
+ @child_process = CapistranoMulticonfigParallel::ProcessRunner.new
97
104
  Actor.current.link @child_process
98
105
  @child_process
99
- end
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
- elsif message_from_bundler?(message)
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
- async.update_machine_state(message['task'])
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') if exit_status == 0
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
- return if exit_status == 0
180
- error_message = "worker #{@job_id} task failed with exit status #{exit_status.inspect} "
181
- raise(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed.new(error_message), error_message)
182
- end
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
@@ -12,6 +12,7 @@ module CapistranoMulticonfigParallel
12
12
  end
13
13
 
14
14
  def go_to_transition(action, options = {})
15
+ return if @job.status.to_s.downcase == 'dead'
15
16
  transitions.on(action, state.to_s => action)
16
17
  @job.status = action
17
18
  if options[:bundler]
@@ -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
@@ -132,7 +132,7 @@ module CapistranoMulticonfigParallel
132
132
  end
133
133
 
134
134
  def worker_died?
135
- !worker.alive?
135
+ worker.blank? || !worker.alive?
136
136
  end
137
137
 
138
138
  def work_done?
@@ -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 ||= Tempfile.new(["multi_cap_#{job.id}_command_", ".rb"], encoding: 'utf-8')
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 fetch_deploy_command
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
- setup_supervision_group do |supervisor|
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
- Class.new(Celluloid::Supervision::Container) do
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
- setup_supervision_group do |supervisor|
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
@@ -10,7 +10,7 @@ module CapistranoMulticonfigParallel
10
10
  MAJOR = 2
11
11
  MINOR = 0
12
12
  TINY = 0
13
- PRE = 'beta5'
13
+ PRE = 'rc1'
14
14
 
15
15
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
16
16
  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.beta5
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-14 00:00:00.000000000 Z
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/child_process.rb
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