capistrano_multiconfig_parallel 0.19.2 → 0.20.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: 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