capistrano_multiconfig_parallel 0.23.4 → 0.24.0

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: 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