capistrano_multiconfig_parallel 0.19.2 → 0.20.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: 0f4b58bbdce3cdd3899eff03a73404925292f109
4
- data.tar.gz: 2a3f5e01b27dcb4f1b5d1fc067b9dc4404e7fd8c
3
+ metadata.gz: 641de7a89fa577cee18d2a6aa6b2419aea55d861
4
+ data.tar.gz: e240a8295e20d5c93e989021ef9368f8ec3ef6b2
5
5
  SHA512:
6
- metadata.gz: beeacc1ff046749c4c2803f23e2533787ed0e4298d10105967e853e83ddd9910afcd9bfa78b41f4185360f88c623c19a3e38bbe17d59ba6fd3db852ef3e33a30
7
- data.tar.gz: 9a582a0574b9a38154efa7f58377dc0886d5457b34ab9258aace05fa62a1c1d5d69feb70e9c099241a4c86b6960dd558dc9396061baeb99252c7d1ccc7a6429e
6
+ metadata.gz: fc6090766c60e6a44d4a59e83e71c84714d308a1e210d460cd24472e27a1e470ba605357e3da095e14ce332cf3d7ea95754cf5ff720aae121e0bae31ab27f01d
7
+ data.tar.gz: b3357c295e0b6c2b04408678c8f04098589d7628ef66622f42f633da086375a85db23ebc9feec00d6ff9013d8f25b5258d63beeb6ff1c36c4d644d5400e128ec
@@ -246,16 +246,6 @@ module CapistranoMulticonfigParallel
246
246
  options
247
247
  end
248
248
 
249
- def multi_fetch_argv(args)
250
- options = {}
251
- args.each do |arg|
252
- if arg =~ /^(\w+)=(.*)$/m
253
- options[Regexp.last_match(1)] = Regexp.last_match(2)
254
- end
255
- end
256
- options
257
- end
258
-
259
249
  def execute_on_multiple_boxes(main_box_name, options)
260
250
  boxes = strip_characters_from_string(main_box_name).split(',').compact
261
251
  boxes.each do |box_name|
@@ -156,7 +156,7 @@ module CapistranoMulticonfigParallel
156
156
  def confirm_task_approval(result, task, processed_job = nil)
157
157
  return unless result.present?
158
158
  result = print_confirm_task_approvall(result, task, processed_job)
159
- return if result.blank? || result.downcase != 'y'
159
+ return unless action_confirmed?(result)
160
160
  @jobs.pmap do |job_id, job|
161
161
  worker = get_worker_for_job(job_id)
162
162
  worker.publish_rake_event('approved' => 'yes',
@@ -169,8 +169,7 @@ module CapistranoMulticonfigParallel
169
169
 
170
170
  def get_worker_for_job(job)
171
171
  if job.present?
172
- if job.is_a?(Hash)
173
- job = job.stringify_keys
172
+ if job.is_a?(CapistranoMulticonfigParallel::Job)
174
173
  @job_to_worker[job.id]
175
174
  else
176
175
  @job_to_worker[job]
@@ -196,35 +195,27 @@ module CapistranoMulticonfigParallel
196
195
  def get_job_status(job)
197
196
  status = nil
198
197
  if job.present?
199
- if job.is_a?(Hash)
200
- job = job.stringify_keys
198
+ if job.is_a?(CapistranoMulticonfigParallel::Job)
201
199
  actor = @job_to_worker[job.id]
202
- status = actor.status
200
+ status = actor.job_status
203
201
  else
204
202
  actor = @job_to_worker[job]
205
- status = actor.status
203
+ status = actor.job_status
206
204
  end
207
205
  end
208
206
  status
209
207
  end
210
208
 
211
- def job_crashed?(job)
212
- job.action == 'deploy:rollback' || job.action == 'deploy:failed' || job_failed?(job)
213
- end
214
-
215
- def job_failed?(job)
216
- job.status.present? && job.status == 'worker_died'
217
- end
218
-
219
209
  def worker_died(worker, reason)
220
210
  job = @worker_to_job[worker.mailbox.address]
221
211
  log_to_file("worker job #{job} with mailbox #{worker.mailbox.inspect} died for reason: #{reason}")
222
212
  @worker_to_job.delete(worker.mailbox.address)
223
- return if job.blank? || job_crashed?(job)
213
+ return if job.blank? || job.crashed?
224
214
  return unless job.action == 'deploy'
225
215
  log_to_file "restarting #{job} on new worker"
226
216
  job.status = 'worker_died'
227
- dispatch_new_job(job, action: 'deploy:rollback')
217
+ job.action = 'deploy:rollback'
218
+ dispatch_new_job(job)
228
219
  end
229
220
  end
230
221
  end
@@ -41,6 +41,15 @@ module CapistranoMulticonfigParallel
41
41
  manager.register_worker_for_job(job, Actor.current)
42
42
  end
43
43
 
44
+ def worker_state
45
+ if Actor.current.alive?
46
+ status = @machine.state.to_s
47
+ job.crashed? ? status.red : status.green
48
+ else
49
+ 'dead'.upcase.red
50
+ end
51
+ end
52
+
44
53
  def start_task
45
54
  @manager.setup_worker_conditions(@job)
46
55
  log_to_file("exec worker #{@job_id} starts task with #{@job.inspect}")
@@ -101,7 +110,8 @@ module CapistranoMulticonfigParallel
101
110
 
102
111
  def check_gitflow
103
112
  return if @job.stage != 'staging' || !@manager.can_tag_staging? || !executed_task?(CapistranoMulticonfigParallel::GITFLOW_TAG_STAGING_TASK)
104
- @manager.dispatch_new_job(@job, 'env' => 'production')
113
+ @job.stage = 'production'
114
+ @manager.dispatch_new_job(@job)
105
115
  end
106
116
 
107
117
  def handle_subscription(message)
@@ -150,7 +160,6 @@ module CapistranoMulticonfigParallel
150
160
 
151
161
  def update_machine_state(name)
152
162
  log_to_file("worker #{@job_id} triest to transition from #{@machine.state} to #{name}")
153
- @machine.transitions.on(name.to_s, @machine.state => name.to_s)
154
163
  @machine.go_to_transition(name.to_s)
155
164
  abort(CapistranoMulticonfigParallel::CelluloidWorker::TaskFailed.new("task #{@action} failed ")) if name == 'deploy:failed' # force worker to rollback
156
165
  end
@@ -2,17 +2,19 @@ module CapistranoMulticonfigParallel
2
2
  # class that handles the states of the celluloid worker executing the child process in a fork process
3
3
  class StateMachine
4
4
  include ComposableStateMachine::CallbackRunner
5
- attr_accessor :job, :actor, :initial_state, :state, :output
5
+ attr_accessor :job, :actor, :initial_state, :state
6
6
 
7
7
  def initialize(job, actor)
8
8
  @job = job
9
9
  @actor = actor
10
- @initial_state = :unstarted
10
+ @initial_state = @job.status
11
11
  machine
12
12
  end
13
13
 
14
14
  def go_to_transition(action)
15
- machine.trigger(action.to_s)
15
+ transitions.on(action, state.to_s => action)
16
+ @job.status = action
17
+ machine.trigger(action)
16
18
  end
17
19
 
18
20
  def machine
@@ -1,7 +1,6 @@
1
1
  require_relative '../helpers/application_helper'
2
2
  module CapistranoMulticonfigParallel
3
3
  # class used to display the progress of each worker on terminal screen using a table
4
- # rubocop:disable ClassLength
5
4
  class TerminalTable
6
5
  include Celluloid
7
6
  include Celluloid::Notifications
@@ -21,19 +20,18 @@ module CapistranoMulticonfigParallel
21
20
  subscribe(CapistranoMulticonfigParallel::TerminalTable.topic, :notify_time_change)
22
21
  end
23
22
 
24
- def notify_time_change(topic, message)
25
- return unless topic == CapistranoMulticonfigParallel::TerminalTable.topic
23
+ def notify_time_change(_topic, _message)
26
24
  default_headings = ['Job ID', 'Job UUID', 'App/Stage', 'Action', 'ENV Variables', 'Current Task']
27
25
  # default_headings << 'Total'
28
26
  # default_headings << 'Progress'
29
27
  table = Terminal::Table.new(title: 'Deployment Status Table', headings: default_headings)
30
-
31
- if @manager.alive? && @manager.jobs.present? && message_valid?(message)
28
+ jobs = @manager.alive? ? @manager.jobs : []
29
+ if jobs.present?
32
30
  count = 0
33
- last_job_id = @manager.jobs.keys.last.to_i
34
- @manager.jobs.each do |job_id, job|
31
+ last_job_id = jobs.keys.last.to_i
32
+ jobs.each do |_job_id, job|
35
33
  count += 1
36
- add_job_to_table(table, job_id, job, count, last_job_id)
34
+ add_job_to_table(table, job, count, last_job_id)
37
35
  end
38
36
  end
39
37
  show_terminal_screen(table)
@@ -49,56 +47,33 @@ module CapistranoMulticonfigParallel
49
47
  end
50
48
  end
51
49
 
52
- def message_valid?(message)
53
- message[:type].present? && message[:type] == 'output' || message[:type] == 'event'
54
- end
55
-
56
50
  def show_terminal_screen(table)
57
51
  return unless table.rows.present?
58
52
  terminal_clear
59
- puts "\n"
60
53
  # table.style = { width: 20 }
61
- puts table
62
- puts "\n"
63
- sleep(1)
54
+ puts "\n#{table}\n"
55
+ sleep(0.1)
64
56
  @job_manager.condition.signal('completed') if @manager.all_workers_finished?
65
57
  end
66
58
 
67
- def worker_state(worker, job)
68
- if worker.alive?
69
- state = worker.machine.state.to_s
70
- @manager.job_crashed?(job) ? state.red : state.green
71
- else
72
- 'dead'.upcase.red
73
- end
59
+ def worker_state(worker)
60
+ worker.alive? ? worker.worker_state : 'dead'.upcase.red
74
61
  end
75
62
 
76
- def get_worker_details(job_id, job, worker)
77
- {
78
- 'job_id' => job_id,
79
- 'app_name' => job.app,
80
- 'env_name' => job.stage,
81
- 'full_stage' => job.job_stage,
82
- 'action_name' => job.capistrano_action,
83
- 'env_options' => job.setup_command_line_standard.join("\n"),
84
- 'task_arguments' => job.task_arguments,
85
- 'state' => worker_state(worker, job),
86
- 'processed_job' => job
87
- }
63
+ def filtered_env_keys
64
+ %w(STAGES ACTION)
88
65
  end
89
66
 
90
- def add_job_to_table(table, job_id, job, count, last_job_id)
67
+ def add_job_to_table(table, job, count, last_job_id)
91
68
  return unless @manager.alive?
92
- worker = @manager.get_worker_for_job(job_id)
93
-
94
- details = get_worker_details(job_id, job, worker)
69
+ worker = @manager.get_worker_for_job(job.id)
95
70
 
96
71
  row = [{ value: count.to_s },
97
- { value: job_id.to_s },
98
- { value: details['full_stage'] },
99
- { value: details['action_name'] },
100
- { value: details['env_options'] },
101
- { value: "#{details['state']}" }
72
+ { value: job.id.to_s },
73
+ { value: job.job_stage },
74
+ { value: job.capistrano_action },
75
+ { value: job.setup_command_line_standard(filtered_keys: [CapistranoMulticonfigParallel::ENV_KEY_JOB_ID]).join("\n") },
76
+ { value: worker_state(worker) }
102
77
  ]
103
78
 
104
79
  # if worker.alive?
@@ -109,34 +84,35 @@ module CapistranoMulticonfigParallel
109
84
  # row << { value: worker_state(worker) }
110
85
  # end
111
86
  table.add_row(row)
112
- table.add_separator if last_job_id != job_id.to_i
87
+ table.add_separator if last_job_id != job.id.to_i
88
+ table
113
89
  end
114
90
 
115
91
  def terminal_clear
116
92
  system('cls') || system('clear') || puts("\e[H\e[2J")
117
93
  end
118
94
 
119
- def worker_progress(processed_job, worker)
120
- return worker_state(worker) unless worker.alive?
121
- tasks = worker.alive? ? worker.invocation_chain : []
122
- current_task = worker.alive? ? worker.machine.state.to_s : ''
123
- show_worker_percent(worker, tasks, current_task, processed_job)
124
- end
125
-
126
- def show_worker_percent(worker, tasks, current_task, processed_job)
127
- total_tasks = worker.alive? ? tasks.size : nil
128
- task_index = worker.alive? ? tasks.index(current_task.to_s).to_i + 1 : 0
129
- percent = percent_of(task_index, total_tasks)
130
- result = "Progress [#{format('%.2f', percent)}%] (executed #{task_index} of #{total_tasks})"
131
- if worker.alive?
132
- @manager.job_crashed?(processed_job) ? result.red : result.green
133
- else
134
- worker_state(worker)
135
- end
136
- end
137
-
138
- def percent_of(index, total)
139
- index.to_f / total.to_f * 100.0
140
- end
95
+ # def worker_progress(processed_job, worker)
96
+ # return worker_state(worker) unless worker.alive?
97
+ # tasks = worker.alive? ? worker.invocation_chain : []
98
+ # current_task = worker.alive? ? worker.machine.state.to_s : ''
99
+ # show_worker_percent(worker, tasks, current_task, processed_job)
100
+ # end
101
+ #
102
+ # def show_worker_percent(worker, tasks, current_task, processed_job)
103
+ # total_tasks = worker.alive? ? tasks.size : nil
104
+ # task_index = worker.alive? ? tasks.index(current_task.to_s).to_i + 1 : 0
105
+ # percent = percent_of(task_index, total_tasks)
106
+ # result = "Progress [#{format('%.2f', percent)}%] (executed #{task_index} of #{total_tasks})"
107
+ # if worker.alive?
108
+ # processed_job.crashed? ? result.red : result.green
109
+ # else
110
+ # worker_state(worker)
111
+ # end
112
+ # end
113
+ #
114
+ # def percent_of(index, total)
115
+ # index.to_f / total.to_f * 100.0
116
+ # end
141
117
  end
142
118
  end
@@ -11,29 +11,30 @@ module CapistranoMulticonfigParallel
11
11
  end
12
12
 
13
13
  def fetch_apps_needed_for_deployment(application, action)
14
- applications = []
15
- return applications unless @job_manager.multi_apps?
14
+ return [] unless @job_manager.multi_apps?
16
15
  if @job_manager.custom_command?
17
- apps_selected = all_websites_return_applications_selected
18
- applications = get_applications_to_deploy(action, apps_selected)
19
- elsif app_configuration.application_dependencies.present?
20
- if application.present?
21
- applications = get_applications_to_deploy(action, [application.camelcase])
22
- applications = applications.delete_if { |hash| hash['app'] == application }
23
- else
24
- applications = []
25
- end
16
+ show_interactive_menu(action)
26
17
  else
27
- applications = []
18
+ fetch_application_dependencies(application, action)
28
19
  end
29
- applications
30
20
  end
31
21
 
32
22
  private
33
23
 
24
+ def fetch_application_dependencies(application, action)
25
+ return [] if app_configuration.application_dependencies.blank? || application.blank?
26
+ applications = get_applications_to_deploy(action, [application.camelcase])
27
+ applications.delete_if { |hash| hash['app'] == application }
28
+ end
29
+
30
+ def show_interactive_menu(action)
31
+ apps_selected = CapistranoMulticonfigParallel::InteractiveMenu.new(available_apps).fetch_menu
32
+ get_applications_to_deploy(action, apps_selected)
33
+ end
34
+
34
35
  def application_dependencies
35
36
  deps = app_configuration.application_dependencies
36
- deps.present? && deps.is_a?(Array) ? deps.map(&:stringify_keys) : []
37
+ value_is_array?(deps) ? deps.map(&:stringify_keys) : []
37
38
  end
38
39
 
39
40
  def available_apps
@@ -42,12 +43,6 @@ module CapistranoMulticonfigParallel
42
43
  applications
43
44
  end
44
45
 
45
- def all_websites_return_applications_selected
46
- interactive_menu = CapistranoMulticonfigParallel::InteractiveMenu.new
47
- applications_selected = interactive_menu.show_all_websites_interactive_menu(available_apps)
48
- applications_selected.present? ? applications_selected.split(',') : []
49
- end
50
-
51
46
  def add_dependency_app(app_to_deploy, apps_dependencies, applications_to_deploy)
52
47
  return unless app_to_deploy.present?
53
48
  applications_to_deploy << app_to_deploy
@@ -71,7 +66,7 @@ module CapistranoMulticonfigParallel
71
66
  def check_app_dependency_unique(applications_selected, apps_dependencies, applications_to_deploy, action)
72
67
  return applications_to_deploy if applications_selected.blank? || apps_dependencies.blank? || (apps_dependencies.map { |app| app['app'] } - applications_to_deploy.map { |app| app['app'] }).blank?
73
68
  apps_dependency_confirmation = ask_confirm("Do you want to #{action} all dependencies also ?", 'Y/N')
74
- applications_to_deploy = applications_to_deploy.concat(apps_dependencies) if apps_dependency_confirmation.present? && apps_dependency_confirmation.downcase == 'y'
69
+ applications_to_deploy = applications_to_deploy.concat(apps_dependencies) if action_confirmed?(apps_dependency_confirmation)
75
70
  applications_to_deploy
76
71
  end
77
72
 
@@ -105,10 +100,10 @@ module CapistranoMulticonfigParallel
105
100
  def print_frameworks_used(app_names, applications_to_deploy, action)
106
101
  app_names.each { |app| puts "#{app}" }
107
102
  apps_deploy_confirmation = ask_confirm("Are you sure you want to #{action} these apps?", 'Y/N')
108
- if apps_deploy_confirmation.blank? || (apps_deploy_confirmation.present? && apps_deploy_confirmation.downcase != 'y')
109
- return []
110
- elsif apps_deploy_confirmation.present? && apps_deploy_confirmation.downcase == 'y'
103
+ if action_confirmed?(apps_deploy_confirmation)
111
104
  return applications_to_deploy
105
+ else
106
+ return []
112
107
  end
113
108
  end
114
109
  end
@@ -4,21 +4,42 @@ module CapistranoMulticonfigParallel
4
4
  class InteractiveMenu
5
5
  include CapistranoMulticonfigParallel::ApplicationHelper
6
6
 
7
- def show_all_websites_interactive_menu(applications)
8
- msg = ''
9
- choices = []
10
- print_menu_choices(msg, choices, applications)
7
+ attr_accessor :msg, :choices, :applications
8
+
9
+ def initialize(applications)
10
+ @applications = applications
11
+ @msg = ' '
12
+ @choices = {}
13
+ end
14
+
15
+ def fetch_menu
16
+ print_menu_choices
17
+ default_printing
18
+ result = show_all_websites_interactive_menu
19
+ print "#{@msg}\n"
20
+ strip_characters_from_string(result).split(',')
21
+ end
22
+
23
+ private
24
+
25
+ def default_printing
11
26
  print "\nYou selected"
12
- msg = ' nothing'
27
+ @msg = ' nothing'
28
+ end
29
+
30
+ def show_all_websites_interactive_menu
13
31
  result = ''
14
- applications.each_with_index do |option_name, index|
15
- next unless choices[index].present?
16
- print(" #{option_name}")
17
- msg = ''
18
- result += "#{option_name},"
32
+ @applications.each_with_index do |option_name, index|
33
+ result += "#{option_name}," if choices[index].present?
34
+ print_option_name(option_name, index)
19
35
  end
20
- print "#{msg}\n"
21
- strip_characters_from_string(result)
36
+ result
37
+ end
38
+
39
+ def print_option_name(option_name, index)
40
+ return unless @choices[index].present?
41
+ print(" #{option_name}")
42
+ @msg = ''
22
43
  end
23
44
 
24
45
  def confirm_option_selected
@@ -26,39 +47,61 @@ module CapistranoMulticonfigParallel
26
47
  $stdin.gets.squeeze(' ').strip
27
48
  end
28
49
 
29
- def print_menu_choices(msg, choices, applications)
30
- while print_all_websites_available_options(applications, msg, choices) && (option = confirm_option_selected).present?
50
+ def print_menu_choices
51
+ while print_all_websites_available_options && (option = confirm_option_selected).present?
31
52
  if /^[0-9,]+/.match(option)
32
- handle_menu_option(msg, option, choices, applications)
53
+ handle_menu_option(option)
33
54
  else
34
- msg = "Invalid option: #{option}\n "
55
+ @msg = "Invalid option: #{option}\n "
35
56
  next
36
57
  end
37
58
  end
38
59
  end
39
60
 
40
- def print_all_websites_available_options(applications, msg, choices)
61
+ def print_all_websites_available_options
41
62
  puts 'Available options:'
42
- applications.each_with_index do |option, index|
43
- puts "#{(index + 1)} #{choices[index].present? ? "#{choices[index]}" : ''}) #{option} "
63
+ @applications.each_with_index do |option, index|
64
+ print_selected_index_option(index, option)
44
65
  end
45
- puts "\n#{msg}" if msg.present?
66
+ puts "\n#{@msg}" if @msg.present?
46
67
  true
47
68
  end
48
69
 
49
- def handle_menu_option(msg, option, choices, applications)
50
- arr_in = option.split(',')
51
- arr_in.each_with_index do |number_option, _index|
70
+ def handle_menu_option(option)
71
+ option.split(',').each_with_index do |number_option, _index|
52
72
  num = number_option.to_i
53
- if /^[0-9]+/.match(num.to_s) && ((num.to_i > 0 && num.to_i <= applications.size))
54
- num -= 1
55
- msg += "#{applications[num]} was #{choices[num].present? ? 'un' : ''}checked\n"
56
- choices[num] = choices[num].blank? ? '+' : ' '
57
- else
58
- msg = "Invalid option: #{num}\n"
59
- next
60
- end
73
+ show_option_selected(num)
74
+ setup_message_invalid(num)
75
+ next
61
76
  end
62
77
  end
78
+
79
+ def check_number_selected(num)
80
+ check_numeric(num) && (num > 0 && num <= @applications.size)
81
+ end
82
+
83
+ def show_option_selected(num)
84
+ return unless check_number_selected(num)
85
+ num -= 1
86
+ @msg += "#{@applications[num]} was #{@choices[num].present? ? 'un' : ''}checked\n"
87
+ setup_choices_number(num)
88
+ end
89
+
90
+ def setup_message_invalid(num)
91
+ return if check_number_selected(num)
92
+ @msg = "Invalid option: #{num}\n"
93
+ end
94
+
95
+ def print_selected_index_option(index, option)
96
+ puts "#{(index + 1)} #{fetch_choice(index)}) #{option} "
97
+ end
98
+
99
+ def setup_choices_number(num)
100
+ @choices[num] = @choices[num].blank? ? '+' : ' '
101
+ end
102
+
103
+ def fetch_choice(num)
104
+ @choices.fetch(num, '')
105
+ end
63
106
  end
64
107
  end
@@ -1,71 +1,62 @@
1
- require 'fileutils'
2
1
  require_relative '../helpers/application_helper'
2
+ require_relative './job_command'
3
3
  module CapistranoMulticonfigParallel
4
- # class used to find application dependencies
4
+ # class used for defining the job class
5
5
  class Job
6
- include FileUtils
7
6
  include CapistranoMulticonfigParallel::ApplicationHelper
8
7
 
9
- attr_accessor :id, :app, :stage, :action, :task_arguments, :env_options, :status, :exit_status
10
- def initialize(options)
11
- @id = SecureRandom.uuid
12
- @app = options.fetch('app', '')
13
- @stage = options.fetch('stage', '')
14
- @action = options.fetch('action', '')
15
- @task_arguments = options.fetch('task_arguments', [])
16
- @env_options = {}
17
- @env_options = options.fetch('env_options', {}).each do |key, value|
18
- @env_options[key] = value if value.present? && !filtered_env_keys.include?(key)
19
- end
20
- @env_options["#{CapistranoMulticonfigParallel::ENV_KEY_JOB_ID}"] = @id
21
- end
8
+ attr_reader :options, :command
9
+ attr_writer :status, :exit_status
10
+
11
+ delegate :job_stage,
12
+ :capistrano_action,
13
+ :build_capistrano_task,
14
+ :execute_standard_deploy,
15
+ :setup_command_line_standard,
16
+ to: :command
22
17
 
23
- def filtered_env_keys
24
- %w(STAGES ACTION)
18
+ def initialize(options)
19
+ @options = options
20
+ @command = CapistranoMulticonfigParallel::JobCommand.new(self)
25
21
  end
26
22
 
27
- def finished?
28
- status == 'finished'
23
+ def id
24
+ @id ||= SecureRandom.uuid
29
25
  end
30
26
 
31
- def job_stage
32
- @app.present? ? "#{@app}:#{@stage}" : "#{@stage}"
27
+ def status
28
+ @status ||= :unstarted
33
29
  end
34
30
 
35
- def capistrano_action(action = @action)
36
- argv = @task_arguments.present? ? "[#{@task_arguments}]" : ''
37
- "#{action}#{argv}"
31
+ def exit_status
32
+ @exit_status ||= nil
38
33
  end
39
34
 
40
- def setup_command_line_standard(*args)
41
- array_options = []
42
- @env_options.each do |key, value|
43
- array_options << "#{key}=#{value}" if value.present?
35
+ [
36
+ { name: 'app', default: '' },
37
+ { name: 'stage', default: '' },
38
+ { name: 'action', default: '' },
39
+ { name: 'task_arguments', default: [] },
40
+ { name: 'env_options', default: {} }
41
+ ].each do |hash|
42
+ define_method hash[:name] do
43
+ value = @options.fetch(hash[:name], hash[:default])
44
+ value["#{CapistranoMulticonfigParallel::ENV_KEY_JOB_ID}"] = id if hash[:name] == 'env_options'
45
+ verify_empty_options(value)
44
46
  end
45
- array_options << '--trace' if app_debug_enabled?
46
- args.each do |arg|
47
- array_options << arg if arg.present?
48
- end
49
- array_options
50
47
  end
51
48
 
52
- def build_capistrano_task(action = nil, env = [])
53
- action = action.present? ? action : @action
54
- environment_options = setup_command_line_standard(env).join(' ')
55
- "cd #{detect_root} && RAILS_ENV=#{@stage} bundle exec multi_cap #{job_stage} #{capistrano_action(action)} #{environment_options}"
49
+ def finished?
50
+ @status == 'finished'
56
51
  end
57
52
 
58
- def execute_standard_deploy(action = nil)
59
- command = build_capistrano_task(action)
60
- puts("\n\n\n Executing '#{command}' \n\n\n .")
61
- sh("#{command}")
62
- rescue => ex
63
- log_error(ex)
64
- execute_standard_deploy('deploy:rollback') if action.blank? && @name == 'deploy'
53
+ def crashed?
54
+ crashing_actions = ['deploy:rollback', 'deploy:failed']
55
+ crashing_actions.include?(action) || crashing_actions.include?(status) || failed?
65
56
  end
66
57
 
67
- def to_s
68
- to_json
58
+ def failed?
59
+ status.present? && status == 'worker_died'
69
60
  end
70
61
  end
71
62
  end
@@ -0,0 +1,69 @@
1
+ require 'fileutils'
2
+ require_relative '../helpers/application_helper'
3
+ module CapistranoMulticonfigParallel
4
+ # class used to find application dependencies
5
+ class JobCommand
6
+ include FileUtils
7
+ include CapistranoMulticonfigParallel::ApplicationHelper
8
+
9
+ attr_reader :job
10
+ delegate :app, :stage, :action, :task_arguments, :env_options, to: :job
11
+
12
+ def initialize(job)
13
+ @job = job
14
+ end
15
+
16
+ def filtered_env_keys
17
+ %w(STAGES ACTION)
18
+ end
19
+
20
+ def job_stage
21
+ app.present? ? "#{app}:#{stage}" : "#{stage}"
22
+ end
23
+
24
+ def capistrano_action(rake_action = action)
25
+ argv = task_arguments.present? ? "[#{task_arguments}]" : ''
26
+ "#{rake_action}#{argv}"
27
+ end
28
+
29
+ def setup_env_options(options = {})
30
+ options.stringify_keys!
31
+ array_options = []
32
+ env_options.each do |key, value|
33
+ array_options << "#{key}=#{value}" if value.present? && (!filtered_env_keys.include?(key) && !options.fetch('filtered_keys', []).include?(key.to_s))
34
+ end
35
+ array_options << '--trace' if app_debug_enabled?
36
+ array_options
37
+ end
38
+
39
+ def setup_command_line_standard(*args)
40
+ options = args.extract_options!
41
+ array_options = setup_env_options(options)
42
+ args.each do |arg|
43
+ array_options << arg if arg.present?
44
+ end
45
+ array_options
46
+ end
47
+
48
+ def build_capistrano_task(rake_action = nil, env = [])
49
+ rake_action = rake_action.present? ? rake_action : action
50
+ environment_options = setup_command_line_standard(env).join(' ')
51
+ "cd #{detect_root} && RAILS_ENV=#{@stage} bundle exec multi_cap #{job_stage} #{capistrano_action(rake_action)} #{environment_options}"
52
+ end
53
+
54
+ def execute_standard_deploy(action = nil)
55
+ command = build_capistrano_task(action)
56
+ run_shell_command(command)
57
+ rescue => ex
58
+ log_error(ex)
59
+ execute_standard_deploy('deploy:rollback') if action.blank? && @name == 'deploy'
60
+ end
61
+
62
+ private
63
+
64
+ def run_shell_command(command)
65
+ puts("\n\n\n Executing '#{command}' \n\n\n .")
66
+ sh("#{command}")
67
+ end
68
+ end
69
+ end
@@ -8,14 +8,14 @@ module CapistranoMulticonfigParallel
8
8
 
9
9
  # method used to start
10
10
  def start
11
- execute_with_rescue('stderr') do
12
- verify_validation
13
- job_manager = CapistranoMulticonfigParallel::Application.new
14
- if job_manager.argv[CapistranoMulticonfigParallel::ENV_KEY_JOB_ID].blank?
15
- job_manager.start
16
- else
17
- Capistrano::Application.new.run
11
+ verify_validation
12
+ arguments = multi_fetch_argv(ARGV.dup)
13
+ if arguments[CapistranoMulticonfigParallel::ENV_KEY_JOB_ID].blank?
14
+ execute_with_rescue('stderr') do
15
+ CapistranoMulticonfigParallel::Application.new.start
18
16
  end
17
+ else
18
+ Capistrano::Application.new.run
19
19
  end
20
20
  end
21
21
 
@@ -10,33 +10,85 @@ module CapistranoMulticonfigParallel
10
10
 
11
11
  module_function
12
12
 
13
+ def multi_fetch_argv(args)
14
+ options = {}
15
+ args.each do |arg|
16
+ if arg =~ /^(\w+)=(.*)$/m
17
+ options[Regexp.last_match(1)] = Regexp.last_match(2)
18
+ end
19
+ end
20
+ options
21
+ end
22
+
23
+ def action_confirmed?(result)
24
+ result.present? && result.downcase == 'y'
25
+ end
26
+
27
+ def check_numeric(num)
28
+ /^[0-9]+/.match(num.to_s)
29
+ end
30
+
31
+ def verify_empty_options(options)
32
+ if options.is_a?(Hash)
33
+ options.reject { |_key, value| value.blank? }
34
+ elsif options.is_a?(Array)
35
+ options.reject(&:blank?)
36
+ else
37
+ options
38
+ end
39
+ end
40
+
41
+ def verify_array_of_strings(value)
42
+ value.reject(&:blank?)
43
+ warn_array_without_strings(value)
44
+ end
45
+
46
+ def warn_array_without_strings(value)
47
+ raise ArgumentError, 'the array must contain only task names' if value.find { |row| !row.is_a?(String) }
48
+ end
49
+
50
+ def check_hash_set(hash, props)
51
+ !Set.new(props).subset?(hash.keys.to_set) || hash.values.find(&:blank?).present?
52
+ end
53
+
54
+ def value_is_array?(value)
55
+ value.present? && value.is_a?(Array)
56
+ end
57
+
13
58
  def strip_characters_from_string(value)
14
59
  return unless value.present?
15
60
  value = value.delete("\r\n").delete("\n")
16
- value = value.gsub(/\s+/, ' ').strip if value.present?
61
+ value = value.gsub(/\s+/, ' ').strip
17
62
  value
18
63
  end
19
64
 
65
+ def regex_last_match(number)
66
+ Regexp.last_match(number)
67
+ end
68
+
20
69
  def parse_task_string(string) # :nodoc:
21
70
  /^([^\[]+)(?:\[(.*)\])$/ =~ string.to_s
22
71
 
23
- name = Regexp.last_match(1)
24
- remaining_args = Regexp.last_match(2)
72
+ name = regex_last_match(1)
73
+ remaining_args = regex_last_match(2)
25
74
 
26
75
  return string, [] unless name
27
76
  return name, [] if remaining_args.empty?
28
77
 
29
- args = []
78
+ args = find_remaaining_args(remaining_args)
79
+ [name, args]
80
+ end
30
81
 
82
+ def find_remaining_args(remaining_args)
83
+ args = []
31
84
  loop do
32
85
  /((?:[^\\,]|\\.)*?)\s*(?:,\s*(.*))?$/ =~ remaining_args
33
86
 
34
- remaining_args = Regexp.last_match(2)
35
- args << Regexp.last_match(1).gsub(/\\(.)/, '\1')
87
+ remaining_args = regex_last_match(2)
88
+ args << regex_last_match(1).gsub(/\\(.)/, '\1')
36
89
  break if remaining_args.blank?
37
90
  end
38
-
39
- [name, args]
91
+ args
40
92
  end
41
93
  end
42
94
  end
@@ -15,33 +15,34 @@ module CapistranoMulticonfigParallel
15
15
 
16
16
  def fetch_configuration
17
17
  @fetched_config = Configliere::Param.new
18
- default_internal_config.each do |param|
19
- param_type = find_config_type(param['type'])
20
- @fetched_config.define param['name'], type: param_type, description: param['description'], default: param['default']
21
- end
18
+ setup_default_config
19
+ setup_configuration
20
+ end
22
21
 
23
- ARGV.clear
22
+ def setup_default_config
23
+ default_internal_config.each do |array_param|
24
+ @fetched_config.define array_param[0], array_param[1].symbolize_keys
25
+ end
26
+ end
24
27
 
25
- CapistranoMulticonfigParallel.original_args.each { |a| ARGV << a }
28
+ def setup_configuration
26
29
  @fetched_config.read config_file if File.file?(config_file)
27
30
  @fetched_config.use :commandline
28
31
 
29
32
  @fetched_config.use :config_block
30
- @fetched_config.finally do |c|
31
- check_configuration(c)
33
+ validate_configuration
34
+ end
35
+
36
+ def validate_configuration
37
+ @fetched_config.finally do |config|
38
+ @check_config = config.stringify_keys
39
+ check_configuration
32
40
  end
33
41
  @fetched_config.process_argv!
34
42
  @fetched_config.resolve!
35
43
  end
36
44
 
37
- def verify_array_of_strings(value)
38
- return true if value.blank?
39
- value.reject(&:blank?)
40
- raise ArgumentError, 'the array must contain only task names' if value.find { |row| !row.is_a?(String) }
41
- end
42
-
43
- def verify_application_dependencies(c, prop, props)
44
- value = c[prop.to_sym]
45
+ def verify_application_dependencies(value, props)
45
46
  return unless value.is_a?(Array)
46
47
  value.reject { |val| val.blank? || !val.is_a?(Hash) }
47
48
  wrong = check_array_of_hash(value, props.map(&:to_sym))
@@ -50,35 +51,45 @@ module CapistranoMulticonfigParallel
50
51
 
51
52
  def check_array_of_hash(value, props)
52
53
  value.find do|hash|
53
- !Set.new(props).subset?(hash.keys.to_set) ||
54
- hash.values.find(&:blank?).present?
54
+ check_hash_set(hash, props)
55
55
  end
56
56
  end
57
57
 
58
- def check_boolean(c, prop)
59
- raise ArgumentError, "the property `#{prop}` must be boolean" unless %w(true false).include?(c[prop].to_s.downcase)
58
+ def check_boolean(prop)
59
+ value = get_prop_config(prop)
60
+ raise ArgumentError, "the property `#{prop}` must be boolean" unless %w(true false).include?(value.to_s.downcase)
60
61
  end
61
62
 
62
63
  def configuration_valid?
63
64
  configuration
64
65
  end
65
66
 
66
- def check_boolean_props(c, props)
67
+ def check_boolean_props(props)
67
68
  props.each do |prop|
68
- c.send("#{prop}=", c[prop.to_sym]) if check_boolean(c, prop.to_sym)
69
+ @check_config.send("#{prop}=", get_prop_config(prop)) if check_boolean(prop)
69
70
  end
70
71
  end
71
72
 
72
- def check_array_props(c, props)
73
+ def check_array_props(props)
73
74
  props.each do |prop|
74
- c.send("#{prop}=", c[prop.to_sym]) if c[prop.to_sym].is_a?(Array) && verify_array_of_strings(c[prop.to_sym])
75
+ value = get_prop_config(prop)
76
+ @check_config.send("#{prop}=", value) if value_is_array?(value) && verify_array_of_strings(value)
77
+ end
78
+ end
79
+
80
+ def get_prop_config(prop)
81
+ config = @check_config
82
+ if prop.include?('.')
83
+ multi_level_prop(config, prop)
84
+ else
85
+ config[prop]
75
86
  end
76
87
  end
77
88
 
78
- def check_configuration(c)
79
- check_boolean_props(c, %w(multi_debug multi_secvential websocket_server.enable_debug))
80
- check_array_props(c, %w(task_confirmations development_stages apply_stage_confirmation))
81
- verify_application_dependencies(c, 'application_dependencies', %w(app priority dependencies))
89
+ def check_configuration
90
+ check_boolean_props(%w(multi_debug multi_secvential websocket_server.enable_debug))
91
+ check_array_props(%w(task_confirmations development_stages apply_stage_confirmation))
92
+ verify_application_dependencies(@check_config['application_dependencies'], %w(app priority dependencies))
82
93
  end
83
94
  end
84
95
  end
@@ -3,10 +3,6 @@ module CapistranoMulticonfigParallel
3
3
  module CoreHelper
4
4
  module_function
5
5
 
6
- def find_config_type(type)
7
- ['boolean'].include?(type.to_s) ? type.to_s.delete(':').to_sym : type.to_s.constantize
8
- end
9
-
10
6
  def app_debug_enabled?
11
7
  app_configuration.multi_debug.to_s.downcase == 'true'
12
8
  end
@@ -32,9 +28,15 @@ module CapistranoMulticonfigParallel
32
28
  Gem.loaded_specs.values.find { |repo| repo.name == name }
33
29
  end
34
30
 
31
+ def ask_stdout_confirmation(message, default)
32
+ result = Ask.input message, default: default
33
+ $stdout.flush
34
+ result
35
+ end
36
+
35
37
  def ask_confirm(message, default)
36
38
  force_confirmation do
37
- Ask.input message, default: default
39
+ ask_stdout_confirmation(message, default)
38
40
  end
39
41
  rescue
40
42
  return nil
@@ -44,7 +46,6 @@ module CapistranoMulticonfigParallel
44
46
  `stty -raw echo`
45
47
  check_terminal_tty
46
48
  result = block.call
47
- $stdout.flush
48
49
  `stty -raw echo`
49
50
  result
50
51
  end
@@ -55,7 +56,7 @@ module CapistranoMulticonfigParallel
55
56
 
56
57
  def format_error(error)
57
58
  JSON.pretty_generate(class_name: error.class,
58
- message: error.respond_to?(:message) ? "#{error.message}#{error.message.class}" : error.inspect,
59
+ message: error.respond_to?(:message) ? error.message : error.inspect,
59
60
  backtrace: error.respond_to?(:backtrace) ? error.backtrace.join("\n\n") : '')
60
61
  end
61
62
 
@@ -68,13 +69,21 @@ module CapistranoMulticonfigParallel
68
69
  return if job_id.blank?
69
70
  FileUtils.mkdir_p(log_directory) unless File.directory?(log_directory)
70
71
  filename = File.join(log_directory, "worker_#{job_id}.log")
72
+ setup_filename_logger(filename)
73
+ end
74
+
75
+ def setup_filename_logger(filename)
71
76
  worker_log = ::Logger.new(filename)
72
77
  worker_log.level = ::Logger::Severity::DEBUG
73
- worker_log.formatter = proc do |severity, datetime, progname, msg|
78
+ setup_logger_formatter(worker_log)
79
+ worker_log
80
+ end
81
+
82
+ def setup_logger_formatter(logger)
83
+ logger.formatter = proc do |severity, datetime, progname, msg|
74
84
  date_format = datetime.strftime('%Y-%m-%d %H:%M:%S')
75
85
  "[#{date_format}] #{severity} (#{progname}): #{msg}\n"
76
86
  end
77
- worker_log
78
87
  end
79
88
 
80
89
  def debug_websocket?
@@ -3,6 +3,11 @@ module CapistranoMulticonfigParallel
3
3
  module InternalHelper
4
4
  module_function
5
5
 
6
+ def multi_level_prop(config, prop)
7
+ prop.split('.').each { |new_prop| config = config[new_prop] }
8
+ config
9
+ end
10
+
6
11
  def internal_config_directory
7
12
  File.join(root.to_s, 'capistrano_multiconfig_parallel', 'configuration')
8
13
  end
@@ -12,10 +17,42 @@ module CapistranoMulticonfigParallel
12
17
  end
13
18
 
14
19
  def default_internal_config
15
- @default_config ||= YAML.load_file(internal_config_file)['default_config']
20
+ @default_config ||= fetch_default_internal_config
16
21
  @default_config
17
22
  end
18
23
 
24
+ def fetch_default_internal_config
25
+ config = YAML.load_file(internal_config_file)['default_config']
26
+ new_config = config.map do |hash|
27
+ setup_default_configuration_types(hash)
28
+ end
29
+ default_internal_configuration_params(new_config)
30
+ end
31
+
32
+ def default_internal_configuration_params(new_config)
33
+ array = []
34
+ new_config.each do |hash|
35
+ array << [hash['name'], sliced_default_config(hash)]
36
+ end
37
+ array
38
+ end
39
+
40
+ def sliced_default_config(hash)
41
+ hash.slice('type', 'description', 'default')
42
+ end
43
+
44
+ def setup_default_configuration_types(hash)
45
+ hash.each_with_object({}) do |(key, value), memo|
46
+ memo[key] = (key == 'type') ? find_config_type(value) : value
47
+ memo
48
+ end
49
+ end
50
+
51
+ def find_config_type(type)
52
+ type = type.to_s
53
+ ['boolean'].include?(type) ? type.delete(':').to_sym : type.constantize
54
+ end
55
+
19
56
  def find_env_multi_cap_root
20
57
  ENV['MULTI_CAP_ROOT']
21
58
  end
@@ -24,11 +61,34 @@ module CapistranoMulticonfigParallel
24
61
  File.expand_path(File.dirname(File.dirname(__dir__)))
25
62
  end
26
63
 
64
+ def pathname_is_root?(root)
65
+ root.root?
66
+ end
67
+
68
+ def fail_capfile_not_found(root)
69
+ fail "Can't detect Capfile in the application root".red if pathname_is_root?(root)
70
+ end
71
+
72
+ def pwd_parent_dir
73
+ pwd_directory.directory? ? pwd_directory : pwd_directory.parent
74
+ end
75
+
76
+ def pwd_directory
77
+ Pathname.new(FileUtils.pwd)
78
+ end
79
+
80
+ def check_file(file, filename)
81
+ file.file? && file.basename.to_s.downcase == filename.to_s.downcase
82
+ end
83
+
84
+ def find_file_in_directory(root, filename)
85
+ root.children.find { |file| check_file(file, filename) }.present? || pathname_is_root?(root)
86
+ end
87
+
27
88
  def try_detect_capfile
28
- root = Pathname.new(FileUtils.pwd)
29
- root = root.parent unless root.directory?
30
- root = root.parent until root.children.find { |f| f.file? && f.basename.to_s.downcase == 'capfile' }.present? || root.root?
31
- fail "Can't detect Capfile in the application root".red if root.root?
89
+ root = pwd_parent_dir
90
+ root = root.parent until find_file_in_directory(root, 'capfile')
91
+ fail_capfile_not_found(root)
32
92
  root
33
93
  end
34
94
 
@@ -7,8 +7,8 @@ module CapistranoMulticonfigParallel
7
7
  # module used for generating the version
8
8
  module VERSION
9
9
  MAJOR = 0
10
- MINOR = 19
11
- TINY = 2
10
+ MINOR = 20
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.19.2
4
+ version: 0.20.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-07 00:00:00.000000000 Z
11
+ date: 2015-12-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: celluloid-pmap
@@ -633,6 +633,7 @@ files:
633
633
  - lib/capistrano_multiconfig_parallel/classes/input_stream.rb
634
634
  - lib/capistrano_multiconfig_parallel/classes/interactive_menu.rb
635
635
  - lib/capistrano_multiconfig_parallel/classes/job.rb
636
+ - lib/capistrano_multiconfig_parallel/classes/job_command.rb
636
637
  - lib/capistrano_multiconfig_parallel/classes/output_stream.rb
637
638
  - lib/capistrano_multiconfig_parallel/classes/rake_hook_actor.rb
638
639
  - lib/capistrano_multiconfig_parallel/cli.rb