capistrano_multiconfig_parallel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/.coveralls.yml +2 -0
  3. data/.gitignore +21 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +68 -0
  6. data/.travis.yml +12 -0
  7. data/CONTRIBUTING.md +44 -0
  8. data/Gemfile +3 -0
  9. data/Guardfile +12 -0
  10. data/LICENSE +20 -0
  11. data/README.md +220 -0
  12. data/Rakefile +56 -0
  13. data/bin/multi_cap +7 -0
  14. data/capistrano_multiconfig_parallel.gemspec +51 -0
  15. data/img/parallel_demo.png +0 -0
  16. data/init.rb +1 -0
  17. data/lib/capistrano_multiconfig_parallel/application.rb +57 -0
  18. data/lib/capistrano_multiconfig_parallel/base.rb +92 -0
  19. data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_manager.rb +178 -0
  20. data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_worker.rb +238 -0
  21. data/lib/capistrano_multiconfig_parallel/celluloid/child_process.rb +104 -0
  22. data/lib/capistrano_multiconfig_parallel/celluloid/rake_worker.rb +83 -0
  23. data/lib/capistrano_multiconfig_parallel/celluloid/state_machine.rb +49 -0
  24. data/lib/capistrano_multiconfig_parallel/celluloid/terminal_table.rb +122 -0
  25. data/lib/capistrano_multiconfig_parallel/cli.rb +55 -0
  26. data/lib/capistrano_multiconfig_parallel/configuration.rb +70 -0
  27. data/lib/capistrano_multiconfig_parallel/helpers/base_manager.rb +217 -0
  28. data/lib/capistrano_multiconfig_parallel/helpers/multi_app_manager.rb +84 -0
  29. data/lib/capistrano_multiconfig_parallel/helpers/single_app_manager.rb +48 -0
  30. data/lib/capistrano_multiconfig_parallel/helpers/standard_deploy.rb +40 -0
  31. data/lib/capistrano_multiconfig_parallel/initializers/conf.rb +6 -0
  32. data/lib/capistrano_multiconfig_parallel/initializers/confirm_question.rb +25 -0
  33. data/lib/capistrano_multiconfig_parallel/initializers/i18n.rb +10 -0
  34. data/lib/capistrano_multiconfig_parallel/initializers/rake.rb +28 -0
  35. data/lib/capistrano_multiconfig_parallel/multi_app_helpers/dependency_tracker.rb +111 -0
  36. data/lib/capistrano_multiconfig_parallel/multi_app_helpers/interactive_menu.rb +61 -0
  37. data/lib/capistrano_multiconfig_parallel/version.rb +16 -0
  38. data/lib/capistrano_multiconfig_parallel.rb +2 -0
  39. data/spec/spec_helper.rb +48 -0
  40. metadata +648 -0
@@ -0,0 +1,49 @@
1
+ module CapistranoMulticonfigParallel
2
+ # class that handles the states of the celluloid worker executing the child process in a fork process
3
+ class StateMachine
4
+ include ComposableStateMachine::CallbackRunner
5
+ attr_accessor :state, :model, :machine, :job, :initial_state, :transitions, :output
6
+
7
+ def initialize(job, actor)
8
+ @job = job
9
+ @actor = actor
10
+ @initial_state = :unstarted
11
+ @model = generate_model
12
+ build_machine
13
+ end
14
+
15
+ def go_to_transition(action)
16
+ @machine.trigger(action.to_s)
17
+ end
18
+
19
+ private
20
+
21
+ def build_machine
22
+ @machine = ComposableStateMachine::MachineWithExternalState.new(
23
+ @model, method(:state), method(:state=), state: initial_state.to_s, callback_runner: self)
24
+ end
25
+
26
+ def generate_transitions
27
+ @transitions = ComposableStateMachine::Transitions.new
28
+ @transitions
29
+ end
30
+
31
+ def generate_model
32
+ ComposableStateMachine.model(
33
+ transitions: generate_transitions,
34
+ behaviors: {
35
+ enter: {
36
+ any: proc do |current_state, event, new_state|
37
+ actor_notify_state_change(current_state, event, new_state)
38
+ end
39
+ }
40
+ },
41
+ initial_state: @initial_state
42
+ )
43
+ end
44
+
45
+ def actor_notify_state_change(current_state, event, new_state)
46
+ @actor.send_msg(CapistranoMulticonfigParallel::TerminalTable::TOPIC, type: 'event', message: "Going from #{current_state} to #{new_state} due to a #{event} event")
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,122 @@
1
+
2
+ module CapistranoMulticonfigParallel
3
+ # class used to display the progress of each worker on terminal screen using a table
4
+ # rubocop:disable ClassLength
5
+ class TerminalTable
6
+ include Celluloid
7
+ include Celluloid::Notifications
8
+ include Celluloid::Logger
9
+ TOPIC = 'sshkit_terminal'
10
+
11
+ def initialize(manager)
12
+ @manager = manager
13
+ async.run
14
+ end
15
+
16
+ def run
17
+ subscribe(CapistranoMulticonfigParallel::TerminalTable::TOPIC, :notify_time_change)
18
+ end
19
+
20
+ def notify_time_change(topic, message)
21
+ return unless topic == CapistranoMulticonfigParallel::TerminalTable::TOPIC
22
+ default_headings = ['Job ID', 'App/Stage', 'Action', 'ENV Variables', 'Current Task']
23
+ if CapistranoMulticonfigParallel.show_task_progress
24
+ default_headings << 'Total'
25
+ default_headings << 'Progress'
26
+ end
27
+ table = Terminal::Table.new(title: 'Deployment Status Table', headings: default_headings)
28
+ if @manager.jobs.present? && message_valid?(message)
29
+ @manager.jobs.each do |job_id, _job|
30
+ add_job_to_table(table, job_id)
31
+ end
32
+ end
33
+ show_terminal_screen(table)
34
+ rescue => ex
35
+ info "Terminal Table client disconnected due to error #{ex.inspect}"
36
+ info ex.backtrace
37
+ terminate
38
+ end
39
+
40
+ def message_valid?(message)
41
+ message[:type].present? && message[:type] == 'output' || message[:type] == 'event'
42
+ end
43
+
44
+ def show_terminal_screen(table)
45
+ return unless table.rows.present?
46
+ terminal_clear
47
+ puts "\n"
48
+ # table.style = { width: 20 }
49
+ puts table
50
+ puts "\n"
51
+ sleep(1)
52
+ end
53
+
54
+ def add_job_to_table(table, job_id)
55
+ worker = @manager.get_worker_for_job(job_id)
56
+ return unless worker.present?
57
+ worker_optons = ''
58
+ worker.env_options.each do |key, value|
59
+ worker_optons << "#{key}=#{value}\n"
60
+ end
61
+ state = worker.machine.state.to_s
62
+ state = worker_crashed?(worker) ? state.red : state.green
63
+ row = [{ value: worker.job_id.to_s },
64
+ { value: "#{worker.app_name}\n#{worker.env_name}" },
65
+ { value: worker.action_name },
66
+ { value: worker_optons },
67
+ { value: state }
68
+ ]
69
+ if CapistranoMulticonfigParallel.show_task_progress
70
+ row << { value: worker.rake_tasks.size }
71
+ row << { value: worker_progress(worker) }
72
+ end
73
+ table.add_row(row)
74
+ table.add_separator if @manager.jobs.keys.last.to_i != job_id.to_i
75
+ end
76
+
77
+ def terminal_clear
78
+ system('cls') || system('clear') || puts("\e[H\e[2J")
79
+ end
80
+
81
+ def worker_crashed?(worker)
82
+ worker.crashed?
83
+ end
84
+ # rubocop:disable Lint/Eval
85
+ def capture(stream)
86
+ stream = stream.to_s
87
+ captured_stream = Tempfile.new(stream)
88
+ stream_io = eval("$#{stream}")
89
+ origin_stream = stream_io.dup
90
+ stream_io.reopen(captured_stream)
91
+
92
+ yield
93
+
94
+ stream_io.rewind
95
+ return captured_stream.read
96
+ ensure
97
+ captured_stream.close
98
+ captured_stream.unlink
99
+ stream_io.reopen(origin_stream)
100
+ end
101
+
102
+ def worker_progress(worker)
103
+ tasks = worker.rake_tasks
104
+ current_task = worker.machine.state
105
+ total_tasks = tasks.size
106
+ task_index = tasks.index(current_task)
107
+ progress = Formatador::ProgressBar.new(total_tasks, color: 'green', start: task_index.to_i)
108
+ result = capture(:stdout) do
109
+ progress.increment
110
+ end
111
+ result = result.gsub("\r\n", '')
112
+ result = result.gsub("\n", '')
113
+ result = result.gsub('|', '#')
114
+ result = result.gsub(/\s+/, ' ')
115
+ if worker_crashed?(worker)
116
+ return result.red
117
+ else
118
+ return result.green
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'bundler/setup'
4
+ require 'rake'
5
+ require 'active_support/core_ext/object/blank'
6
+ require 'active_support/core_ext/hash/keys'
7
+ require 'active_support/concern'
8
+ require 'celluloid/autostart'
9
+ require 'celluloid/pmap'
10
+ require 'composable_state_machine'
11
+ require 'formatador'
12
+ require 'eventmachine'
13
+ require 'right_popen'
14
+ require 'colorize'
15
+ require 'logger'
16
+ require 'terminal-table'
17
+ require 'colorize'
18
+ require 'celluloid_pubsub'
19
+ require 'capistrano/all'
20
+ require 'fileutils'
21
+ require 'logger'
22
+ require 'pp'
23
+ require 'configurations'
24
+ # fix error with not files that can not be found
25
+ Gem.find_files('composable_state_machine/**/*.rb').each { |path| require path }
26
+
27
+ Gem.find_files('capistrano_multiconfig_parallel/initializers/**/*.rb').each { |path| require path }
28
+ Gem.find_files('capistrano_multiconfig_parallel/helpers/**/*.rb').each { |path| require path }
29
+ Gem.find_files('capistrano_multiconfig_parallel/celluloid/**/*.rb').each { |path| require path }
30
+
31
+ require_relative './base'
32
+ require_relative 'application'
33
+
34
+ module CapistranoMulticonfigParallel
35
+ # this is the class that will be invoked from terminal , and willl use the invoke task as the primary function.
36
+ class CLI
37
+ def self.start
38
+ if $stdin.isatty
39
+ $stdin.sync = true
40
+ end
41
+ if $stdout.isatty
42
+ $stdout.sync = true
43
+ end
44
+ CapistranoMulticonfigParallel.configuration_valid?
45
+ CapistranoMulticonfigParallel.verify_app_dependencies(stages) if CapistranoMulticonfigParallel.configuration.track_dependencies
46
+ CapistranoMulticonfigParallel::Application.new.run
47
+ rescue Interrupt
48
+ `stty icanon echo`
49
+ $stderr.puts 'Command cancelled.'
50
+ rescue => error
51
+ $stderr.puts error
52
+ exit(1)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,70 @@
1
+ require_relative './initializers/conf'
2
+ module CapistranoMulticonfigParallel
3
+ # class that holds the options that are configurable for this gem
4
+ module Configuration
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ include Configurations
9
+
10
+ configurable Hash, :websocket_server
11
+ configurable Array, :development_stages
12
+
13
+ configurable :track_dependencies do |value|
14
+ check_boolean(:track_dependencies, value)
15
+ end
16
+
17
+ configurable Array, :application_dependencies do |value|
18
+ value.reject { |val| val.blank? || !val.is_a?(Hash) }
19
+ wrong = value.find do|hash|
20
+ !Set[:app, :priority, :dependencies].subset?(hash.keys.to_set) ||
21
+ hash[:app].blank? ||
22
+ hash[:priority].blank?
23
+ !hash[:priority].is_a?(Numeric) ||
24
+ !hash[:dependencies].is_a?(Array)
25
+ end
26
+ raise ArgumentError, "invalid configuration for #{wrong.inspect}" if wrong.present?
27
+ end
28
+
29
+ configurable :task_confirmation_active do |value|
30
+ check_boolean(:task_confirmation_active, value)
31
+ end
32
+
33
+ configurable Array, :task_confirmations do |value|
34
+ value.reject(&:blank?)
35
+ if value.find { |row| !row.is_a?(String) }
36
+ raise ArgumentError, 'the array must contain only task names'
37
+ end
38
+ end
39
+
40
+ configuration_defaults do |c|
41
+ c.task_confirmations = ['deploy:symlink:release']
42
+ c.task_confirmation_active = false
43
+ c.track_dependencies = false
44
+ c.websocket_server = { enable_debug: false }
45
+ c.development_stages = ['development', 'webdev']
46
+ end
47
+
48
+ not_configured do |prop| # omit the arguments to get a catch-all not_configured
49
+ raise NoMethodError, "Please configure the property `#{prop}` by assigning a value of type #{configuration.property_type(prop)}"
50
+ end
51
+
52
+ def self.value_is_boolean?(value)
53
+ [true, false, 'true', 'false'].include?(value)
54
+ end
55
+
56
+ def self.check_boolean(prop, value)
57
+ unless value_is_boolean?(value)
58
+ raise ArgumentError, "the property `#{prop}` must be boolean"
59
+ end
60
+ end
61
+
62
+ def self.configuration_valid?
63
+ configuration.nil? &&
64
+ configuration.task_confirmations &&
65
+ ((configuration.track_dependencies && configuration.application_dependencies) ||
66
+ configuration.track_dependencies == false)
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,217 @@
1
+ require_relative './standard_deploy'
2
+ module CapistranoMulticonfigParallel
3
+ # finds app dependencies, shows menu and delegates jobs to celluloid manager
4
+ # rubocop:disable ClassLength
5
+ class BaseManager
6
+ include Celluloid
7
+ include Celluloid::Logger
8
+
9
+ attr_accessor :condition, :manager, :deps, :application, :stage, :name, :args, :argv, :jobs, :job_registered_condition, :default_stage
10
+
11
+ def initialize(cap_app, top_level_tasks, stages)
12
+ @cap_app = cap_app
13
+ @top_level_tasks = top_level_tasks
14
+ @stages = stages
15
+ @jobs = []
16
+ end
17
+
18
+ def can_start?
19
+ @top_level_tasks.size > 1 && (stages.include?(@top_level_tasks.first) || custom_command?) && ENV[CapistranoMulticonfigParallel::ENV_KEY_JOB_ID].blank?
20
+ end
21
+
22
+ def custom_command?
23
+ @top_level_tasks.first == 'ensure_stage' && !stages.include?(@top_level_tasks.second) && !stages.include?(@top_level_tasks.first) && custom_commands.values.include?(@top_level_tasks.second)
24
+ end
25
+
26
+ def custom_commands
27
+ key = multi_apps? ? CapistranoMulticonfigParallel::MULTI_KEY : CapistranoMulticonfigParallel::SINGLE_KEY
28
+ CapistranoMulticonfigParallel::CUSTOM_COMMANDS[key]
29
+ end
30
+
31
+ def multi_apps?
32
+ @cap_app.multi_apps?
33
+ end
34
+
35
+ def start(&block)
36
+ @application = custom_command? ? nil : @top_level_tasks.first.split(':').reverse[1]
37
+ @stage = custom_command? ? nil : @top_level_tasks.first.split(':').reverse[0]
38
+ @name, @args = @cap_app.parse_task_string(@top_level_tasks.second)
39
+ @argv = @cap_app.handle_options.delete_if { |arg| arg == @stage || arg == @name || arg == @top_level_tasks.first }
40
+ @argv = multi_fetch_argv(@argv)
41
+ CapistranoMulticonfigParallel.enable_logging
42
+ block.call if block_given?
43
+ run
44
+ end
45
+
46
+ def collect_jobs(options = {}, &block)
47
+ options = prepare_options(options)
48
+ block.call(options) if block_given?
49
+ rescue => e
50
+ raise [e, e.backtrace].inspect
51
+ end
52
+
53
+ def process_jobs(&block)
54
+ return unless @jobs.present?
55
+ if CapistranoMulticonfigParallel.execute_in_sequence
56
+ @jobs.each { |job| CapistranoMulticonfigParallel::StandardDeploy.execute_standard_deploy(job) }
57
+ else
58
+ run_async_jobs(&block)
59
+ end
60
+ end
61
+
62
+ def fetch_multi_stages
63
+ stages = @argv['STAGES'].blank? ? '' : @argv['STAGES']
64
+ stages = parse_inputted_value(value: stages).split(',').compact if stages.present?
65
+ stages
66
+ end
67
+
68
+
69
+ def deploy_app(options = {})
70
+ options = options.stringify_keys
71
+ app = options['app'].is_a?(Hash) ? options['app'] : {'app' => options['app'] }
72
+ branch = @branch_backup.present? ? @branch_backup : @argv['BRANCH'].to_s
73
+ call_task_deploy_app({
74
+ branch: branch,
75
+ app: app ,
76
+ action: options['action']
77
+ }.reverse_merge(options))
78
+ end
79
+
80
+ private
81
+
82
+ def call_task_deploy_app(options = {})
83
+ options = options.stringify_keys
84
+ main_box_name = @argv['BOX'].blank? ? '' : @argv['BOX']
85
+ stage = options.fetch('stage', @default_stage )
86
+ if CapistranoMulticonfigParallel.configuration.development_stages.include?(stage) && main_box_name.present? && /^[a-z0-9,]+/.match(main_box_name)
87
+ execute_on_multiple_boxes(main_box_name, options)
88
+ else
89
+ prepare_job(options)
90
+ end
91
+ end
92
+
93
+ def run_async_jobs(&block)
94
+ return unless @jobs.present?
95
+ @condition = Celluloid::Condition.new
96
+ @manager = CapistranoMulticonfigParallel::CelluloidManager.new(Actor.current)
97
+ @jobs.pmap do |job|
98
+ @manager.async.delegate(job)
99
+ end
100
+ until @manager.registration_complete
101
+ sleep(0.1) # keep current thread alive
102
+ end
103
+ return unless @manager.registration_complete
104
+ @manager.process_jobs(&block)
105
+ wait_jobs_termination
106
+ end
107
+
108
+ def wait_jobs_termination
109
+ return if @jobs.blank? || CapistranoMulticonfigParallel.execute_in_sequence
110
+ result = @condition.wait
111
+ return unless result.present?
112
+ @manager.terminate
113
+ terminate
114
+ end
115
+
116
+ def prepare_job(options)
117
+ options = options.stringify_keys
118
+ branch_name = options.fetch('branch', {})
119
+ app = options.fetch('app', {})
120
+ app = app.fetch('app', '')
121
+ box = options['env_options']['BOX']
122
+ message = box.present? ? "BOX #{box}:" : "stage #{options['stage']}:"
123
+ branch_name = get_applications_branch_from_stdin(app, message) if branch_name.blank?
124
+ env_opts = get_app_additional_env_options(app, message)
125
+
126
+ options['env_options'] = options['env_options'].reverse_merge(env_opts)
127
+ env_options = {
128
+ 'BRANCH' => branch_name
129
+ }.merge(options['env_options'])
130
+
131
+ job = {
132
+ app: app,
133
+ env: options['stage'],
134
+ action: options['action'],
135
+ task_arguments: options['task_arguments'],
136
+ env_options: env_options
137
+ }
138
+
139
+ @jobs << job
140
+ end
141
+
142
+ def prepare_options(options)
143
+ @default_stage = CapistranoMulticonfigParallel.configuration.development_stages.present? ? CapistranoMulticonfigParallel.configuration.development_stages.first : 'development'
144
+ @stage = @stage.present? ? @stage : @default_stage
145
+ options = options.stringify_keys
146
+ options['app'] = options.fetch('app', @application.to_s.clone)
147
+ options['action'] = options.fetch('action', @name.to_s.clone)
148
+ options['stage'] = options.fetch('stage', @stage.to_s.clone)
149
+ options['env_options'] = options.fetch('env_options', @argv.clone)
150
+ options['task_arguments'] = options.fetch('task_arguments', @args.clone)
151
+ options
152
+ end
153
+
154
+ def parse_inputted_value(options = {})
155
+ options = options.stringify_keys
156
+ value = options['value'].present? ? options['value'] : fetch(options.fetch('key', :app_branch_name))
157
+ if value.present?
158
+ branch = value.gsub("\r\n", '')
159
+ branch = branch.gsub("\n", '') if branch.present?
160
+ branch = branch.gsub(/\s+/, ' ') if branch.present?
161
+ branch = branch.strip if branch.present?
162
+ return branch
163
+ else
164
+ return ''
165
+ end
166
+ end
167
+
168
+ def get_applications_branch_from_stdin(app, app_message)
169
+ app_name = (app.is_a?(Hash) && app[:app].present?) ? app[:app].camelcase : app
170
+ app_name = app_name.present? ? app_name : "current application"
171
+ message = "Branch name for #{app_name} for #{app_message} :"
172
+ branch = ''
173
+ if @argv['BRANCH'].blank? || (@argv['BRANCH'].present? && !custom_command?)
174
+ set :app_branch_name, ask(message, nil)
175
+ branch = parse_inputted_value
176
+ else
177
+ branch = @argv['BRANCH']
178
+ end
179
+ branch
180
+ end
181
+
182
+ def get_app_additional_env_options(app, app_message)
183
+ app_name = (app.is_a?(Hash) && app[:app].present?) ? app[:app].camelcase : app
184
+ app_name = app_name.present? ? app_name : "current application"
185
+ message = "Please write additional ENV options for #{app_name} for #{app_message} :"
186
+ set :app_additional_env_options, ask_confirm(message, nil)
187
+ fetch_app_additional_env_options
188
+ end
189
+
190
+ def fetch_app_additional_env_options
191
+ options = {}
192
+ return options if fetch(:app_additional_env_options).blank?
193
+ env_options = parse_inputted_value(key: :app_additional_env_options)
194
+ env_options = env_options.split(' ')
195
+ options = multi_fetch_argv(env_options)
196
+ options
197
+ end
198
+
199
+ def multi_fetch_argv(args)
200
+ options = {}
201
+ args.each do |arg|
202
+ if arg =~ /^(\w+)=(.*)$/m
203
+ options[Regexp.last_match(1)] = Regexp.last_match(2)
204
+ end
205
+ end
206
+ options
207
+ end
208
+
209
+ def execute_on_multiple_boxes(main_box_name, options)
210
+ boxes = parse_inputted_value(value: main_box_name).split(',').compact
211
+ boxes.each do |box_name|
212
+ options['env_options']['BOX'] = box_name
213
+ prepare_job(options)
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,84 @@
1
+ require_relative './base_manager'
2
+ require_relative '../multi_app_helpers/dependency_tracker'
3
+ module CapistranoMulticonfigParallel
4
+ # class used to find application dependencies
5
+ class MultiAppManager < CapistranoMulticonfigParallel::BaseManager
6
+ include Celluloid
7
+ include Celluloid::Logger
8
+
9
+ def initialize(cap_app, top_level_tasks, stages)
10
+ super(cap_app, top_level_tasks, stages)
11
+ @dependency_tracker = CapistranoMulticonfigParallel::DependencyTracker.new(Actor.current)
12
+ end
13
+
14
+ def run
15
+ options = {}
16
+ if custom_command?
17
+ run_custom_command(options)
18
+ else
19
+ menu_deploy_interactive(options)
20
+ end
21
+ process_jobs
22
+ end
23
+
24
+ def run_custom_command(options)
25
+ return unless custom_command?
26
+ CapistranoMulticonfigParallel.interactive_menu = true
27
+ options[:action] = @argv['ACTION'].present? ? @argv['ACTION'].present? : 'deploy'
28
+ action_name = @name
29
+ if action_name == custom_commands[:menu]
30
+ menu_deploy_interactive(options)
31
+ elsif action_name == custom_commands[:stages]
32
+ multi_stage_deploy(options)
33
+ else
34
+ raise "Custom command #{@name} not available for multi apps"
35
+ end
36
+ end
37
+
38
+ def multi_stage_deploy(options)
39
+ stages = fetch_multi_stages
40
+ return if stages.blank?
41
+ multi_collect_and_run_jobs(options) do |apps, new_options|
42
+ apps.each do |app|
43
+ stages.each do |stage|
44
+ deploy_app(new_options.merge('app' => app, 'stage' => stage))
45
+ end
46
+ end if apps.present?
47
+ end
48
+ end
49
+
50
+ def menu_deploy_interactive(options)
51
+ multi_collect_and_run_jobs(options) do |apps, new_options|
52
+ deploy_multiple_apps(apps, new_options)
53
+ deploy_app(new_options) if !custom_command? && new_options['app'].present?
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+
60
+ def multi_collect_and_run_jobs(options = {}, &block)
61
+ collect_jobs(options) do |new_options|
62
+ applications = @dependency_tracker.fetch_apps_needed_for_deployment(new_options['app'], new_options['action'])
63
+ backup_the_branch
64
+ block.call(applications, new_options) if block_given?
65
+ end
66
+ end
67
+
68
+ def backup_the_branch
69
+ return if custom_command? || @argv['BRANCH'].blank?
70
+ @branch_backup = @argv['BRANCH'].to_s
71
+ @argv['BRANCH'] = nil
72
+ end
73
+
74
+ def deploy_multiple_apps(applications, options)
75
+ options = options.stringify_keys
76
+ return unless applications.present?
77
+ applications.each do |app|
78
+ deploy_app(options.merge('app' => app))
79
+ end
80
+ end
81
+
82
+
83
+ end
84
+ end
@@ -0,0 +1,48 @@
1
+ require_relative './base_manager'
2
+ module CapistranoMulticonfigParallel
3
+ # class used to find application dependencies
4
+ class SingleAppManager < CapistranoMulticonfigParallel::BaseManager
5
+ include Celluloid
6
+ include Celluloid::Logger
7
+
8
+ def run
9
+ options = {}
10
+ if custom_command?
11
+ run_custom_command(options)
12
+ else
13
+ deploy_single_app(options)
14
+ end
15
+ process_jobs
16
+ end
17
+
18
+ def run_custom_command(options)
19
+ return unless custom_command?
20
+ action_name = @name
21
+ if action_name == custom_commands[:stages]
22
+ stage_deploy(options)
23
+ else
24
+ raise "Custom command #{@name} not available for single apps"
25
+ end
26
+ end
27
+
28
+ def stage_deploy(options)
29
+ return unless custom_command?
30
+ stages = fetch_multi_stages
31
+ return if stages.blank?
32
+ collect_jobs(options) do |new_options|
33
+ stages.each do |stage|
34
+ deploy_app(new_options.merge('stage' => stage, 'action' => 'deploy'))
35
+ end
36
+ end
37
+ end
38
+
39
+
40
+ def deploy_single_app(options)
41
+ return if custom_command?
42
+ collect_jobs(options) do |new_options|
43
+ deploy_app(new_options)
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,40 @@
1
+ require 'fileutils'
2
+ module CapistranoMulticonfigParallel
3
+ # class used to find application dependencies
4
+ class StandardDeploy
5
+ extend FileUtils
6
+
7
+ def self.setup_command_line_standard(options)
8
+ opts = ''
9
+ options.each do |key, value|
10
+ opts << "#{key}=#{value}" if value.present?
11
+ end
12
+ opts
13
+ end
14
+
15
+ def self.execute_standard_deploy(options)
16
+ app = options.fetch(:app, '')
17
+ stage = options.fetch(:env, 'development')
18
+ action_name = options.fetch(:action, 'deploy')
19
+ action = "#{action_name}[#{options.fetch(:task_arguments, []).join(',')}]"
20
+ arguments = setup_command_line_standard(options.fetch(:env_options, {}))
21
+
22
+ command = "bundle exec cap #{app}:#{stage} #{action} #{arguments}"
23
+ puts("\n\n\n Executing '#{command}' \n\n\n .")
24
+ sh("#{command}")
25
+ rescue => ex
26
+ CapistranoMulticonfigParallel.log_message(ex)
27
+ if @name == 'deploy'
28
+ begin
29
+ action = "deploy:rollback[#{options.fetch(:task_arguments, []).join(',')}]"
30
+ command = "bundle exec cap #{app}:#{stage} #{action} #{arguments}"
31
+ puts("\n\n\n Executing #{command} \n\n\n .")
32
+ sh("#{command}")
33
+ rescue => exception
34
+ CapistranoMulticonfigParallel.log_message(exception)
35
+ # nothing to do if rollback fails
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,6 @@
1
+ Configurations::StrictConfiguration.class_eval do
2
+ def property_type(property)
3
+ return unless __configurable?(property)
4
+ @__configurable__[property][:type]
5
+ end
6
+ end