capistrano_multiconfig_parallel 0.0.1

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