capistrano_multiconfig_parallel 0.23.4 → 0.24.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7c51a46e3d5c4f6634dc50f06d6ceafbc3efdc7e
4
- data.tar.gz: b2e3c1c0f6194e27d7df99932ec4c0429413487e
3
+ metadata.gz: 10e0f45b56213ac92e9ca67846cfd2de1c7df368
4
+ data.tar.gz: c90e83e1cef6d093af08cb6294c3f8f2066f90a7
5
5
  SHA512:
6
- metadata.gz: 4fe12cdd323d3531c5407bdb7c15ee7f22b17b0357f7e3afb2cc8b5f84327d3bcddf0d1b018b44c254f797e02dedc6aee2673e7edb0365751d7ba1c98a95f862
7
- data.tar.gz: cb96330f7889e54e94dd38820e5c82ba83ee8db2e80568d07b05ab5798e86c0c7a55e0dd0ce0b61ac4263e15558a565e32c8b9cd53eee8f0b6a2cc391aa6da7e
6
+ metadata.gz: 9591eeed7ec5e34a4e48d84220a2a8f9d385ef565436e3d95ac4d19708bca5682e728938df816f5d1d2ff0dc6f418a69adced59026acfaa63f1a95e6120b913d
7
+ data.tar.gz: 0650d360299158eafd25a6127c2c30c873c2c5502c35123fd538e7885e990c91d848797462d7cffe6cc8093cdcc0d3b59e80bf0c69cc40132c7e1c5a3e41cd0f
data/.rubocop.yml CHANGED
@@ -6,6 +6,9 @@ AllCops:
6
6
  - vendor/**/*
7
7
  - Rakefile
8
8
 
9
+ ClassLength:
10
+ Max: 500
11
+
9
12
  Documentation:
10
13
  Enabled: true
11
14
 
@@ -1,7 +1,6 @@
1
1
  require 'capistrano/multiconfig/dsl'
2
2
  module CapistranoMulticonfigParallel
3
3
  # finds app dependencies, shows menu and delegates jobs to celluloid manager
4
- # rubocop:disable ClassLength
5
4
  class Application
6
5
  include Celluloid
7
6
  include Celluloid::Logger
@@ -9,12 +8,14 @@ module CapistranoMulticonfigParallel
9
8
  include Capistrano::DSL
10
9
  include Capistrano::Multiconfig::DSL
11
10
 
12
- attr_reader :stage_apps, :top_level_tasks, :jobs, :branch_backup, :condition, :manager, :dependency_tracker, :application, :stage, :name, :args, :argv, :default_stage
11
+ attr_reader :stage_apps, :top_level_tasks, :jobs, :branch_backup, :condition, :manager, :dependency_tracker, :application, :stage, :name, :args, :argv, :default_stage, :job_count
12
+ attr_writer :job_count
13
13
 
14
14
  def initialize
15
15
  Celluloid.boot
16
16
  @stage_apps = multi_apps? ? stages.map { |stage| stage.split(':').reverse[1] }.uniq : []
17
17
  collect_command_line_tasks(CapistranoMulticonfigParallel.original_args)
18
+ @job_count = 0
18
19
  @jobs = []
19
20
  end
20
21
 
@@ -191,11 +192,13 @@ module CapistranoMulticonfigParallel
191
192
  @jobs.pmap do |job|
192
193
  @manager.async.delegate(job)
193
194
  end
194
- until @manager.registration_complete
195
- sleep(0.1) # keep current thread alive
195
+ unless can_tag_staging?
196
+ until @manager.registration_complete
197
+ sleep(0.1) # keep current thread alive
198
+ end
199
+ return unless @manager.registration_complete
200
+ @manager.async.process_jobs
196
201
  end
197
- return unless @manager.registration_complete
198
- @manager.async.process_jobs
199
202
  wait_jobs_termination
200
203
  end
201
204
 
@@ -220,10 +223,11 @@ module CapistranoMulticonfigParallel
220
223
 
221
224
  env_options = branch_name.present? ? { 'BRANCH' => branch_name }.merge(options['env_options']) : options['env_options']
222
225
  job_env_options = custom_command? && env_options['ACTION'].present? ? env_options.except('ACTION') : env_options
223
-
226
+ @job_count += 1
224
227
  job = CapistranoMulticonfigParallel::Job.new(Actor.current, options.merge(
225
228
  action: custom_command? && env_options['ACTION'].present? ? env_options['ACTION'] : options['action'],
226
- env_options: job_env_options
229
+ env_options: job_env_options,
230
+ count: @job_count
227
231
  ))
228
232
  @jobs << job
229
233
  end
@@ -3,7 +3,7 @@ require_relative './terminal_table'
3
3
  require_relative './web_server'
4
4
  require_relative '../helpers/application_helper'
5
5
  module CapistranoMulticonfigParallel
6
- # rubocop:disable ClassLength
6
+ # manager class that handles workers
7
7
  class CelluloidManager
8
8
  include Celluloid
9
9
  include Celluloid::Notifications
@@ -44,23 +44,23 @@ module CapistranoMulticonfigParallel
44
44
  @jobs[job.id] = job
45
45
  # debug(@jobs)
46
46
  # start work and send it to the background
47
- @workers.async.work(job, Actor.current)
47
+ @workers.work(job, Actor.current)
48
48
  end
49
49
 
50
50
  # call back from actor once it has received it's job
51
51
  # actor should do this asap
52
52
  def register_worker_for_job(job, worker)
53
- worker.job_id = job.id if worker.job_id.blank?
54
53
  @job_to_worker[job.id] = worker
55
54
  @worker_to_job[worker.mailbox.address] = job
56
55
  log_to_file("worker #{worker.job_id} registed into manager")
57
56
  Actor.current.link worker
58
57
  worker.async.start_task if !syncronized_confirmation? || job.failed? || job.rolling_back?
59
- @registration_complete = true if @job_manager.jobs.size == @job_to_worker.size
58
+ return unless syncronized_confirmation?
59
+ @registration_complete = true if @job_manager.jobs.size == @jobs.size
60
60
  end
61
61
 
62
62
  def all_workers_finished?
63
- @jobs.all? { |_job_id, job| job.finished? || job.crashed? || job.rolling_back? }
63
+ @jobs.all? { |_job_id, job| job.finished? || job.crashed? }
64
64
  end
65
65
 
66
66
  def process_jobs
@@ -193,9 +193,9 @@ module CapistranoMulticonfigParallel
193
193
  return unless job.is_a?(CapistranoMulticonfigParallel::Job)
194
194
  options.stringify_keys! if options.present?
195
195
  env_opts = options['skip_env_options'].present? ? {} : @job_manager.get_app_additional_env_options(job.app, job.stage)
196
- new_job_options = job.options.merge('env_options' => job.env_options.merge(env_opts))
197
- new_job = CapistranoMulticonfigParallel::Job.new(@job_manager, new_job_options.merge(options.except(job.job_writer_attributes)))
198
- new_job.setup_writer_attributes(options)
196
+ @job_manager.job_count += 1
197
+ new_job_options = job.options.merge('env_options' => job.env_options.merge(env_opts), 'count' => @job_manager.job_count)
198
+ new_job = CapistranoMulticonfigParallel::Job.new(@job_manager, new_job_options.merge(options))
199
199
  async.delegate(new_job) unless job.worker_died?
200
200
  end
201
201
 
@@ -2,7 +2,6 @@ require_relative './child_process'
2
2
  require_relative './state_machine'
3
3
  require_relative '../helpers/application_helper'
4
4
  module CapistranoMulticonfigParallel
5
- # rubocop:disable ClassLength
6
5
  # worker that will spawn a child process in order to execute a capistrano job and monitor that process
7
6
  #
8
7
  # @!attribute job
@@ -32,12 +31,13 @@ module CapistranoMulticonfigParallel
32
31
  def work(job, manager)
33
32
  @job = job
34
33
  @job_id = job.id
35
- @worker_state = 'started'
34
+ @worker_state = job.status
36
35
  @manager = manager
37
36
  @job_confirmation_conditions = []
38
37
  log_to_file("worker #{@job_id} received #{job.inspect}")
39
38
  @subscription_channel = "worker_#{@job_id}"
40
- @machine = CapistranoMulticonfigParallel::StateMachine.new(job, Actor.current)
39
+ @machine = CapistranoMulticonfigParallel::StateMachine.new(@job, Actor.current)
40
+ @manager.setup_worker_conditions(@job)
41
41
  manager.register_worker_for_job(job, Actor.current)
42
42
  end
43
43
 
@@ -51,8 +51,7 @@ module CapistranoMulticonfigParallel
51
51
  end
52
52
 
53
53
  def start_task
54
- @manager.setup_worker_conditions(@job)
55
- log_to_file("exec worker #{@job_id} starts task with #{@job.inspect}")
54
+ log_to_file("exec worker #{@job_id} starts task")
56
55
  @client = CelluloidPubsub::Client.connect(actor: Actor.current, enable_debug: debug_websocket?, channel: subscription_channel)
57
56
  end
58
57
 
@@ -14,27 +14,48 @@ module CapistranoMulticonfigParallel
14
14
  def work(job, cmd, options = {})
15
15
  @options = options
16
16
  @job = job
17
+ @cmd = cmd
18
+ start_running
19
+ end
20
+
21
+ def start_running
22
+ setup_attributes
23
+ run_event_machine
24
+ setup_em_error_handler
25
+ end
26
+
27
+ def setup_attributes
17
28
  @actor = @options.fetch(:actor, nil)
18
29
  @job_id = @job.id
19
30
  @exit_status = nil
31
+ end
32
+
33
+ def setup_em_error_handler
34
+ EM.error_handler do|exception|
35
+ log_to_file("Error during event loop for worker #{@job_id}: #{format_error(exception)}", job_id: @job_id)
36
+ EM.stop
37
+ end
38
+ end
39
+
40
+ def run_event_machine
20
41
  EM.run do
21
42
  EM.next_tick do
22
- start_async_deploy(cmd, options)
23
- end
24
- @timer = EM::PeriodicTimer.new(0.1) do
25
- check_exit_status
26
- @timer.cancel if @exit_status.present?
43
+ start_async_deploy
27
44
  end
45
+ setup_periodic_timer
28
46
  end
29
- EM.error_handler do|e|
30
- log_to_file("Error during event loop for worker #{@job_id}: #{e.inspect}", job_id: @job_id)
31
- log_to_file(e.backtrace, job_id: @job_id)
32
- EM.stop
47
+ end
48
+
49
+ def setup_periodic_timer
50
+ @timer = EM::PeriodicTimer.new(0.1) do
51
+ check_exit_status
52
+ @timer.cancel if @exit_status.present?
33
53
  end
34
54
  end
35
55
 
36
56
  def process_finalizer
37
57
  EM.stop if EM.reactor_running?
58
+ terminate
38
59
  end
39
60
 
40
61
  def check_exit_status
@@ -45,11 +66,11 @@ module CapistranoMulticonfigParallel
45
66
  @actor.async.notify_finished(@exit_status)
46
67
  end
47
68
 
48
- def start_async_deploy(cmd, options)
69
+ def start_async_deploy
49
70
  RightScale::RightPopen.popen3_async(
50
- cmd,
71
+ @cmd,
51
72
  target: self,
52
- environment: options[:environment].present? ? options[:environment] : nil,
73
+ environment: @options.fetch(:environment, nil),
53
74
  pid_handler: :on_pid,
54
75
  input: :on_input_stdin,
55
76
  stdout_handler: :on_read_stdout,
@@ -78,6 +99,7 @@ module CapistranoMulticonfigParallel
78
99
  def on_exit(status)
79
100
  log_to_file "Child process for worker #{@job_id} on_exit disconnected due to error #{status.inspect}"
80
101
  @exit_status = status.exitstatus
102
+ check_exit_status
81
103
  end
82
104
 
83
105
  def async_exception_handler(*data)
@@ -1,15 +1,14 @@
1
1
  require_relative '../helpers/application_helper'
2
2
  module CapistranoMulticonfigParallel
3
3
  # class that handles the rake task and waits for approval from the celluloid worker
4
- # rubocop:disable ClassLength
5
4
  class RakeWorker
6
5
  include Celluloid
7
6
  include Celluloid::Logger
8
7
  include CapistranoMulticonfigParallel::ApplicationHelper
9
8
 
10
- attr_accessor :env, :client, :job_id, :action, :task,
11
- :task_approved, :successfull_subscription,
12
- :subscription_channel, :publisher_channel, :stdin_result
9
+ attr_reader :env, :client, :job_id, :action, :task,
10
+ :task_approved, :successfull_subscription,
11
+ :subscription_channel, :publisher_channel, :stdin_result
13
12
 
14
13
  def work(env, options = {})
15
14
  @options = options.stringify_keys
@@ -73,32 +72,21 @@ module CapistranoMulticonfigParallel
73
72
 
74
73
  def on_message(message)
75
74
  return unless message.present?
76
- log_debug('on_message', message)
75
+ log_to_file("Rake worker #{@job_id} received after on message:", message)
77
76
  if @client.succesfull_subscription?(message)
78
77
  publish_subscription_successfull(message)
79
- elsif msg_for_task?(message) || msg_for_stdin?(message)
78
+ elsif msg_for_task?(message)
80
79
  task_approval(message)
80
+ elsif msg_for_stdin?(message)
81
81
  stdin_approval(message)
82
82
  else
83
83
  show_warning "unknown action: #{message.inspect}"
84
84
  end
85
85
  end
86
86
 
87
- def log_debug(action, message)
88
- log_to_file("Rake worker #{@job_id} received after #{action}: #{message}")
89
- end
90
-
91
- def msg_for_stdin?(message)
92
- message['action'] == 'stdin'
93
- end
94
-
95
- def msg_for_task?(message)
96
- message['task'].present?
97
- end
98
-
99
87
  def publish_subscription_successfull(message)
100
88
  return unless @client.succesfull_subscription?(message)
101
- log_debug('publish_subscription_successfull', message)
89
+ log_to_file("Rake worker #{@job_id} received after publish_subscription_successfull:", message)
102
90
  @successfull_subscription = true
103
91
  publish_to_worker(task_data)
104
92
  end
@@ -106,14 +94,14 @@ module CapistranoMulticonfigParallel
106
94
  def wait_for_stdin_input
107
95
  wait_execution until @stdin_result.present?
108
96
  output = @stdin_result.clone
109
- Actor.current.stdin_result = nil
97
+ @stdin_result = nil
110
98
  output
111
99
  end
112
100
 
113
101
  def stdin_approval(message)
114
102
  return unless msg_for_stdin?(message)
115
- if @job_id.to_i == message['job_id'].to_i && message['result'].present?
116
- @stdin_result = message['result']
103
+ if @job_id == message['job_id']
104
+ @stdin_result = message.fetch('result', '')
117
105
  else
118
106
  show_warning "unknown invocation #{message.inspect}"
119
107
  end
@@ -121,7 +109,7 @@ module CapistranoMulticonfigParallel
121
109
 
122
110
  def task_approval(message)
123
111
  return unless msg_for_task?(message)
124
- if @job_id.to_i == message['job_id'].to_i && message['task'] == task_name && message['approved'] == 'yes'
112
+ if @job_id == message['job_id'] && message['task'] == task_name && message['approved'] == 'yes'
125
113
  @task_approved = true
126
114
  else
127
115
  show_warning "unknown invocation #{message.inspect}"
@@ -133,27 +121,11 @@ module CapistranoMulticonfigParallel
133
121
  terminate
134
122
  end
135
123
 
136
- def get_question_details(data)
137
- question = ''
138
- default = nil
139
- if data =~ /(.*)\?*\s*\:*\s*(\([^)]*\))*/m
140
- question = Regexp.last_match(1)
141
- default = Regexp.last_match(2)
142
- end
143
- question.present? ? [question, default] : nil
144
- end
145
-
146
- def printing_question?(data)
147
- get_question_details(data).present?
148
- end
149
-
150
124
  def user_prompt_needed?(data)
151
- return if !printing_question?(data) || @action != 'invoke'
152
-
153
- details = get_question_details(data)
154
- default = details.second.present? ? details.second : nil
125
+ question, default = get_question_details(data)
126
+ return if question.blank? || @action != 'invoke'
155
127
  publish_to_worker(action: 'stdout',
156
- question: details.first,
128
+ question: question,
157
129
  default: default.delete('()'),
158
130
  job_id: @job_id)
159
131
  wait_for_stdin_input
@@ -28,10 +28,9 @@ module CapistranoMulticonfigParallel
28
28
  end
29
29
 
30
30
  def notify_time_change(_channel, _message)
31
- @table = Terminal::Table.new(title: 'Deployment Status Table', headings: default_heaadings)
32
- jobs = @manager.alive? ? @manager.jobs.dup : []
33
- setup_table_jobs(jobs)
34
- display_table_on_terminal
31
+ table = Terminal::Table.new(title: 'Deployment Status Table', headings: default_heaadings)
32
+ setup_table_jobs(table)
33
+ display_table_on_terminal(table)
35
34
  end
36
35
 
37
36
  def rescue_exception(ex)
@@ -40,16 +39,17 @@ module CapistranoMulticonfigParallel
40
39
  terminate
41
40
  end
42
41
 
43
- def display_table_on_terminal
42
+ def display_table_on_terminal(table)
44
43
  terminal_clear
45
- puts "\n#{@table}\n"
44
+ puts "\n#{table}\n"
46
45
  signal_complete
47
46
  end
48
47
 
49
- def setup_table_jobs(jobs)
50
- jobs.each_with_index do |(_job_id, job), count|
51
- @table.add_row(job.terminal_row(count))
52
- @table.add_separator
48
+ def setup_table_jobs(table)
49
+ jobs = @manager.alive? ? @manager.jobs.dup : []
50
+ jobs.each do |_job_id, job|
51
+ table.add_row(job.terminal_row)
52
+ table.add_separator
53
53
  end
54
54
  end
55
55
 
@@ -59,9 +59,16 @@ module CapistranoMulticonfigParallel
59
59
  end
60
60
  end
61
61
 
62
+ def managers_alive?
63
+ @job_manager.alive? && @manager.alive?
64
+ end
65
+
62
66
  def signal_complete
63
- return if !@job_manager.alive? || @manager.alive?
64
- @job_manager.condition.signal('completed') if @manager.all_workers_finished?
67
+ if managers_alive? && @manager.all_workers_finished?
68
+ @job_manager.condition.signal('completed')
69
+ elsif !managers_alive?
70
+ terminate
71
+ end
65
72
  end
66
73
 
67
74
  def terminal_clear
@@ -5,7 +5,7 @@ module CapistranoMulticonfigParallel
5
5
  class Job
6
6
  include CapistranoMulticonfigParallel::ApplicationHelper
7
7
 
8
- attr_reader :options, :command
8
+ attr_reader :options
9
9
  attr_writer :status, :exit_status
10
10
 
11
11
  delegate :job_stage,
@@ -16,7 +16,7 @@ module CapistranoMulticonfigParallel
16
16
  to: :command
17
17
 
18
18
  def initialize(application, options)
19
- @options = options
19
+ @options = options.stringify_keys
20
20
  @application = application
21
21
  @manager = @application.manager
22
22
  end
@@ -33,9 +33,9 @@ module CapistranoMulticonfigParallel
33
33
  setup_command_line(filtered_keys: [env_variable])
34
34
  end
35
35
 
36
- def terminal_row(index)
36
+ def terminal_row
37
37
  [
38
- { value: (index + 1).to_s },
38
+ { value: count.to_s },
39
39
  { value: id.to_s },
40
40
  { value: wrap_string(job_stage) },
41
41
  { value: wrap_string(capistrano_action) },
@@ -51,43 +51,26 @@ module CapistranoMulticonfigParallel
51
51
  worker.alive? ? worker.worker_state : default
52
52
  end
53
53
 
54
- def job_writer_attributes
55
- %w(status exit_status)
56
- end
57
-
58
- def setup_writer_attributes(options)
59
- job_writer_attributes.each do |attribute|
60
- send("#{attribute}=", options.fetch("#{attribute}", send(attribute)))
61
- end
62
- end
63
-
64
54
  def id
65
55
  @id ||= @options.fetch('id', SecureRandom.uuid)
66
56
  end
67
57
 
68
- def status
69
- @status ||= @options.fetch('status', :unstarted)
70
- end
71
-
72
- def exit_status
73
- @exit_status ||= @options.fetch('exit_status', nil)
74
- end
75
-
76
58
  [
77
59
  { name: 'app', default: '' },
78
60
  { name: 'stage', default: '' },
79
61
  { name: 'action', default: '' },
80
62
  { name: 'task_arguments', default: [] },
81
- { name: 'env_options', default: {} }
63
+ { name: 'env_options', default: {} },
64
+ { name: 'status', default: :unstarted },
65
+ { name: 'exit_status', default: nil },
66
+ { name: 'count', default: nil }
82
67
  ].each do |hash|
83
68
  define_method hash[:name] do
84
69
  value = @options.fetch(hash[:name], hash[:default])
85
70
  value["#{env_variable}"] = id if hash[:name] == 'env_options'
86
- verify_empty_options(value)
71
+ value = verify_empty_options(value)
72
+ instance_variable_set("@#{hash[:name]}", instance_variable_get("@#{hash[:name]}") || value)
87
73
  end
88
- # define_method "#{hash[:name]}=" do |value|
89
- # self.send("#{hash[:name]}=", value)
90
- # end
91
74
  end
92
75
 
93
76
  def finished?
@@ -14,6 +14,19 @@ module CapistranoMulticonfigParallel
14
14
 
15
15
  module_function
16
16
 
17
+ def msg_for_stdin?(message)
18
+ message['action'] == 'stdin'
19
+ end
20
+
21
+ def msg_for_task?(message)
22
+ message['task'].present?
23
+ end
24
+
25
+ def get_question_details(data)
26
+ /(.*)\?*\s*\:*\s*(\([^)]*\))*/m =~ data
27
+ [regex_last_match(1), regex_last_match(2)]
28
+ end
29
+
17
30
  def setup_command_line_standard(*args)
18
31
  options = args.extract_options!
19
32
  args.select(&:present?)
@@ -38,12 +38,8 @@ module CapistranoMulticonfigParallel
38
38
  result
39
39
  end
40
40
 
41
- def filtered_errors
42
- [CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed, Celluloid::DeadActorError, Celluloid::Task::TerminatedError]
43
- end
44
-
45
41
  def error_filtered?(error)
46
- filtered_errors.find { |class_name| error.is_a?(class_name) }.present?
42
+ [CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed].find { |class_name| error.is_a?(class_name) }.present?
47
43
  end
48
44
 
49
45
  def log_error(error, output = nil)
@@ -7,8 +7,8 @@ module CapistranoMulticonfigParallel
7
7
  # module used for generating the version
8
8
  module VERSION
9
9
  MAJOR = 0
10
- MINOR = 23
11
- TINY = 4
10
+ MINOR = 24
11
+ TINY = 0
12
12
  PRE = nil
13
13
 
14
14
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join('.')
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.23.4
4
+ version: 0.24.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-10 00:00:00.000000000 Z
11
+ date: 2015-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: celluloid-pmap