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.
- checksums.yaml +7 -0
- data/.coveralls.yml +2 -0
- data/.gitignore +21 -0
- data/.rspec +1 -0
- data/.rubocop.yml +68 -0
- data/.travis.yml +12 -0
- data/CONTRIBUTING.md +44 -0
- data/Gemfile +3 -0
- data/Guardfile +12 -0
- data/LICENSE +20 -0
- data/README.md +220 -0
- data/Rakefile +56 -0
- data/bin/multi_cap +7 -0
- data/capistrano_multiconfig_parallel.gemspec +51 -0
- data/img/parallel_demo.png +0 -0
- data/init.rb +1 -0
- data/lib/capistrano_multiconfig_parallel/application.rb +57 -0
- data/lib/capistrano_multiconfig_parallel/base.rb +92 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_manager.rb +178 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/celluloid_worker.rb +238 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/child_process.rb +104 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/rake_worker.rb +83 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/state_machine.rb +49 -0
- data/lib/capistrano_multiconfig_parallel/celluloid/terminal_table.rb +122 -0
- data/lib/capistrano_multiconfig_parallel/cli.rb +55 -0
- data/lib/capistrano_multiconfig_parallel/configuration.rb +70 -0
- data/lib/capistrano_multiconfig_parallel/helpers/base_manager.rb +217 -0
- data/lib/capistrano_multiconfig_parallel/helpers/multi_app_manager.rb +84 -0
- data/lib/capistrano_multiconfig_parallel/helpers/single_app_manager.rb +48 -0
- data/lib/capistrano_multiconfig_parallel/helpers/standard_deploy.rb +40 -0
- data/lib/capistrano_multiconfig_parallel/initializers/conf.rb +6 -0
- data/lib/capistrano_multiconfig_parallel/initializers/confirm_question.rb +25 -0
- data/lib/capistrano_multiconfig_parallel/initializers/i18n.rb +10 -0
- data/lib/capistrano_multiconfig_parallel/initializers/rake.rb +28 -0
- data/lib/capistrano_multiconfig_parallel/multi_app_helpers/dependency_tracker.rb +111 -0
- data/lib/capistrano_multiconfig_parallel/multi_app_helpers/interactive_menu.rb +61 -0
- data/lib/capistrano_multiconfig_parallel/version.rb +16 -0
- data/lib/capistrano_multiconfig_parallel.rb +2 -0
- data/spec/spec_helper.rb +48 -0
- 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
|