motherbrain 0.0.0.placeholder → 0.13.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.ruby-version +1 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +196 -0
- data/COMMANDS.md +9 -0
- data/CONTRIBUTING.md +24 -0
- data/Dockerfile +26 -0
- data/Gemfile +60 -2
- data/Guardfile +30 -0
- data/LICENSE +10 -0
- data/MANIFESTS.md +90 -0
- data/OPERATORS_GUIDE.md +195 -0
- data/PLUGINS.md +268 -0
- data/README.md +304 -16
- data/Thorfile +123 -0
- data/VAGRANT.md +116 -0
- data/bin/boot +9 -0
- data/bin/mb +5 -0
- data/bin/mbsrv +5 -0
- data/config.json +32 -0
- data/features/cli/bootstrap_command/configurable_scripts.feature +32 -0
- data/features/cli/configure_command.feature +57 -0
- data/features/cli/environment_command/create_command.feature +22 -0
- data/features/cli/environment_command/destroy_command.feature +33 -0
- data/features/cli/environment_command/from_command.feature +29 -0
- data/features/cli/environment_command/list_command.feature +0 -0
- data/features/cli/node_limiting.feature +47 -0
- data/features/cli/plugin_command/list_command.feature +46 -0
- data/features/cli/service_command/service_command.feature +21 -0
- data/features/cli/template_command.feature +10 -0
- data/features/cli/validate_config.feature +46 -0
- data/features/step_definitions/bootstrap_steps.rb +57 -0
- data/features/step_definitions/chef_server_steps.rb +3 -0
- data/features/step_definitions/configuration_steps.rb +18 -0
- data/features/step_definitions/core_cli_steps.rb +33 -0
- data/features/step_definitions/environment_steps.rb +43 -0
- data/features/step_definitions/node_steps.rb +3 -0
- data/features/step_definitions/plugin_steps.rb +15 -0
- data/features/step_definitions/template_steps.rb +7 -0
- data/features/support/env.rb +68 -0
- data/lib/mb/api.rb +8 -0
- data/lib/mb/api/application.rb +7 -0
- data/lib/mb/api/endpoint.rb +5 -0
- data/lib/mb/api/helpers.rb +38 -0
- data/lib/mb/api/v1.rb +56 -0
- data/lib/mb/api/v1/config_endpoint.rb +12 -0
- data/lib/mb/api/v1/environments_endpoint.rb +174 -0
- data/lib/mb/api/v1/jobs_endpoint.rb +31 -0
- data/lib/mb/api/v1/plugins_endpoint.rb +90 -0
- data/lib/mb/api/validators.rb +5 -0
- data/lib/mb/api/validators/sem_ver.rb +18 -0
- data/lib/mb/application.rb +148 -0
- data/lib/mb/berkshelf.rb +50 -0
- data/lib/mb/bootstrap.rb +9 -0
- data/lib/mb/bootstrap/manager.rb +250 -0
- data/lib/mb/bootstrap/manifest.rb +131 -0
- data/lib/mb/bootstrap/routine.rb +199 -0
- data/lib/mb/bootstrap/template.rb +73 -0
- data/lib/mb/bootstrap/worker.rb +227 -0
- data/lib/mb/chef.rb +6 -0
- data/lib/mb/chef/config.rb +69 -0
- data/lib/mb/chef/run_list_item.rb +115 -0
- data/lib/mb/chef_mutex.rb +304 -0
- data/lib/mb/clean_room_base.rb +39 -0
- data/lib/mb/cli.rb +50 -0
- data/lib/mb/cli/base.rb +51 -0
- data/lib/mb/cli/shell.rb +29 -0
- data/lib/mb/cli/shell/basic.rb +11 -0
- data/lib/mb/cli/shell/color.rb +11 -0
- data/lib/mb/cli/shell/ext.rb +41 -0
- data/lib/mb/cli/sub_command.rb +95 -0
- data/lib/mb/cli/sub_command/component.rb +56 -0
- data/lib/mb/cli/sub_command/plugin.rb +232 -0
- data/lib/mb/cli_client.rb +178 -0
- data/lib/mb/cli_gateway.rb +426 -0
- data/lib/mb/cli_gateway/sub_commands.rb +3 -0
- data/lib/mb/cli_gateway/sub_commands/environment.rb +124 -0
- data/lib/mb/cli_gateway/sub_commands/plugin.rb +148 -0
- data/lib/mb/command.rb +88 -0
- data/lib/mb/command_invoker.rb +235 -0
- data/lib/mb/command_invoker/worker.rb +40 -0
- data/lib/mb/command_runner.rb +233 -0
- data/lib/mb/component.rb +245 -0
- data/lib/mb/config.rb +275 -0
- data/lib/mb/config_manager.rb +75 -0
- data/lib/mb/console.rb +35 -0
- data/lib/mb/cookbook_metadata.rb +73 -0
- data/lib/mb/core_ext.rb +3 -0
- data/lib/mb/core_ext/dir.rb +37 -0
- data/lib/mb/core_ext/enumerable.rb +48 -0
- data/lib/mb/core_ext/file.rb +24 -0
- data/lib/mb/core_ext/signal.rb +11 -0
- data/lib/mb/environment_manager.rb +195 -0
- data/lib/mb/error_handler.rb +212 -0
- data/lib/mb/errors.rb +693 -0
- data/lib/mb/file_system.rb +60 -0
- data/lib/mb/file_system/tempfile.rb +25 -0
- data/lib/mb/gear.rb +154 -0
- data/lib/mb/gears.rb +8 -0
- data/lib/mb/gears/dynamic_service.rb +218 -0
- data/lib/mb/gears/jmx.rb +24 -0
- data/lib/mb/gears/jmx/action.rb +46 -0
- data/lib/mb/gears/mysql.rb +20 -0
- data/lib/mb/gears/mysql/action.rb +190 -0
- data/lib/mb/gears/service.rb +163 -0
- data/lib/mb/gears/service/action.rb +58 -0
- data/lib/mb/gears/service/action_runner.rb +161 -0
- data/lib/mb/grape_ext.rb +3 -0
- data/lib/mb/grape_ext/endpoint.rb +13 -0
- data/lib/mb/group.rb +143 -0
- data/lib/mb/job.rb +183 -0
- data/lib/mb/job/state_machine.rb +34 -0
- data/lib/mb/job/states.rb +46 -0
- data/lib/mb/job_manager.rb +96 -0
- data/lib/mb/job_record.rb +67 -0
- data/lib/mb/job_ticket.rb +25 -0
- data/lib/mb/lock_manager.rb +116 -0
- data/lib/mb/logging.rb +134 -0
- data/lib/mb/logging/basic_format.rb +31 -0
- data/lib/mb/manifest.rb +128 -0
- data/lib/mb/mixin.rb +3 -0
- data/lib/mb/mixin/attribute_setting.rb +265 -0
- data/lib/mb/mixin/coded_exit.rb +49 -0
- data/lib/mb/mixin/locks.rb +54 -0
- data/lib/mb/mixin/services.rb +100 -0
- data/lib/mb/node_filter.rb +97 -0
- data/lib/mb/node_querier.rb +527 -0
- data/lib/mb/plugin.rb +300 -0
- data/lib/mb/plugin_manager.rb +589 -0
- data/lib/mb/provisioner.rb +186 -0
- data/lib/mb/provisioner/manager.rb +213 -0
- data/lib/mb/provisioner/manifest.rb +125 -0
- data/lib/mb/provisioner/provision_data.rb +96 -0
- data/lib/mb/provisioners.rb +5 -0
- data/lib/mb/provisioners/aws.rb +395 -0
- data/lib/mb/rest_gateway.rb +72 -0
- data/lib/mb/ridley_ext.rb +5 -0
- data/lib/mb/ridley_ext/cookbook_object.rb +15 -0
- data/lib/mb/srv_ctl.rb +183 -0
- data/lib/mb/test.rb +104 -0
- data/lib/mb/thor_ext.rb +49 -0
- data/lib/mb/upgrade.rb +6 -0
- data/lib/mb/upgrade/manager.rb +85 -0
- data/lib/mb/upgrade/worker.rb +149 -0
- data/lib/mb/version.rb +1 -1
- data/lib/motherbrain.rb +166 -2
- data/man/man_helper.rb +81 -0
- data/man/mb.1 +494 -0
- data/man/mb.1.html +300 -0
- data/man/mb.1.ronn.erb +62 -0
- data/motherbrain.gemspec +56 -20
- data/scripts/node_name.rb +14 -0
- data/spec/fixtures/cb_metadata.json +7 -0
- data/spec/fixtures/cb_metadata.rb +14 -0
- data/spec/fixtures/fake_id_rsa +27 -0
- data/spec/fixtures/fake_key.pem +27 -0
- data/spec/fixtures/myface-0.1.0/metadata.rb +14 -0
- data/spec/fixtures/myface-0.1.0/motherbrain.rb +0 -0
- data/spec/fixtures/test_env.json +15 -0
- data/spec/spec_helper.rb +67 -0
- data/spec/support/actor_mocking.rb +7 -0
- data/spec/support/berkshelf.rb +24 -0
- data/spec/support/chef_server.rb +102 -0
- data/spec/support/doubles.rb +11 -0
- data/spec/support/klass.rb +10 -0
- data/spec/support/matchers/each.rb +12 -0
- data/spec/support/matchers/error_codes.rb +5 -0
- data/spec/support/matchers/exit_codes.rb +57 -0
- data/spec/support/matchers/jobs.rb +11 -0
- data/spec/support/spec_helpers.rb +145 -0
- data/spec/unit/mb/api/application_spec.rb +11 -0
- data/spec/unit/mb/api/helpers_spec.rb +5 -0
- data/spec/unit/mb/api/v1/config_endpoint_spec.rb +19 -0
- data/spec/unit/mb/api/v1/environments_endpoint_spec.rb +71 -0
- data/spec/unit/mb/api/v1/jobs_endpoint_spec.rb +24 -0
- data/spec/unit/mb/api/v1/plugins_endpoint_spec.rb +298 -0
- data/spec/unit/mb/api/v1_spec.rb +37 -0
- data/spec/unit/mb/api/validators/sem_ver_spec.rb +5 -0
- data/spec/unit/mb/application_spec.rb +19 -0
- data/spec/unit/mb/berkshelf_spec.rb +38 -0
- data/spec/unit/mb/bootstrap/manager_spec.rb +347 -0
- data/spec/unit/mb/bootstrap/manifest_spec.rb +333 -0
- data/spec/unit/mb/bootstrap/routine_spec.rb +393 -0
- data/spec/unit/mb/bootstrap/template_spec.rb +100 -0
- data/spec/unit/mb/bootstrap/worker_spec.rb +194 -0
- data/spec/unit/mb/chef/config_spec.rb +33 -0
- data/spec/unit/mb/chef/run_list_item_spec.rb +34 -0
- data/spec/unit/mb/chef_mutex_spec.rb +314 -0
- data/spec/unit/mb/clean_room_base_spec.rb +31 -0
- data/spec/unit/mb/cli/base_spec.rb +43 -0
- data/spec/unit/mb/cli/shell/basic_spec.rb +5 -0
- data/spec/unit/mb/cli/shell/color_spec.rb +5 -0
- data/spec/unit/mb/cli/shell/ext_spec.rb +11 -0
- data/spec/unit/mb/cli/shell_spec.rb +38 -0
- data/spec/unit/mb/cli/sub_command/base_spec.rb +102 -0
- data/spec/unit/mb/cli/sub_command/component_spec.rb +5 -0
- data/spec/unit/mb/cli/sub_command/plugin_spec.rb +91 -0
- data/spec/unit/mb/cli/sub_command_spec.rb +43 -0
- data/spec/unit/mb/cli/ui.rb +0 -0
- data/spec/unit/mb/cli_client_spec.rb +51 -0
- data/spec/unit/mb/cli_gateway_spec.rb +386 -0
- data/spec/unit/mb/command_invoker/worker_spec.rb +43 -0
- data/spec/unit/mb/command_invoker_spec.rb +230 -0
- data/spec/unit/mb/command_runner_spec.rb +299 -0
- data/spec/unit/mb/command_spec.rb +76 -0
- data/spec/unit/mb/component_spec.rb +185 -0
- data/spec/unit/mb/config_manager_spec.rb +31 -0
- data/spec/unit/mb/config_spec.rb +408 -0
- data/spec/unit/mb/cookbook_metadata_spec.rb +89 -0
- data/spec/unit/mb/core_ext/dir_spec.rb +92 -0
- data/spec/unit/mb/core_ext/enumerable_spec.rb +104 -0
- data/spec/unit/mb/core_ext/file_spec.rb +58 -0
- data/spec/unit/mb/core_ext/signal_spec.rb +24 -0
- data/spec/unit/mb/environment_manager_spec.rb +166 -0
- data/spec/unit/mb/error_handler_spec.rb +173 -0
- data/spec/unit/mb/errors_spec.rb +132 -0
- data/spec/unit/mb/file_system/tempfile_spec.rb +14 -0
- data/spec/unit/mb/file_system_spec.rb +69 -0
- data/spec/unit/mb/gear_spec.rb +125 -0
- data/spec/unit/mb/gears/dynamic_service_spec.rb +187 -0
- data/spec/unit/mb/gears/jmx/action_spec.rb +34 -0
- data/spec/unit/mb/gears/jmx_spec.rb +32 -0
- data/spec/unit/mb/gears/mysql/action_spec.rb +118 -0
- data/spec/unit/mb/gears/mysql_spec.rb +21 -0
- data/spec/unit/mb/gears/service/action_runner_spec.rb +182 -0
- data/spec/unit/mb/gears/service/action_spec.rb +44 -0
- data/spec/unit/mb/gears/service_spec.rb +124 -0
- data/spec/unit/mb/group_spec.rb +280 -0
- data/spec/unit/mb/job_manager_spec.rb +56 -0
- data/spec/unit/mb/job_record_spec.rb +60 -0
- data/spec/unit/mb/job_spec.rb +201 -0
- data/spec/unit/mb/locks_manager_spec.rb +88 -0
- data/spec/unit/mb/logging_spec.rb +133 -0
- data/spec/unit/mb/manifest_spec.rb +105 -0
- data/spec/unit/mb/mixin/attribute_setting_spec.rb +180 -0
- data/spec/unit/mb/mixin/coded_exit_spec.rb +25 -0
- data/spec/unit/mb/mixin/locks_spec.rb +32 -0
- data/spec/unit/mb/mixin/services_spec.rb +75 -0
- data/spec/unit/mb/node_filter_spec.rb +86 -0
- data/spec/unit/mb/node_querier_spec.rb +532 -0
- data/spec/unit/mb/plugin_manager_spec.rb +724 -0
- data/spec/unit/mb/plugin_spec.rb +247 -0
- data/spec/unit/mb/provisioner/manager_spec.rb +141 -0
- data/spec/unit/mb/provisioner/manifest_spec.rb +182 -0
- data/spec/unit/mb/provisioner/provision_data_spec.rb +113 -0
- data/spec/unit/mb/provisioner_spec.rb +251 -0
- data/spec/unit/mb/provisioners/aws_spec.rb +392 -0
- data/spec/unit/mb/provisioners/environment_factory_spec.rb +108 -0
- data/spec/unit/mb/rest_gateway_spec.rb +13 -0
- data/spec/unit/mb/ridley_ext/cookbook_object_spec.rb +105 -0
- data/spec/unit/mb/srv_ctl_spec.rb +142 -0
- data/spec/unit/mb/upgrade/manager_spec.rb +37 -0
- data/spec/unit/mb/upgrade/worker_spec.rb +219 -0
- data/spec/unit/motherbrain_spec.rb +58 -0
- data/templates/bootstrap.json +8 -0
- data/templates/motherbrain.rb +44 -0
- metadata +694 -15
- data/Rakefile +0 -1
@@ -0,0 +1,178 @@
|
|
1
|
+
module MotherBrain
|
2
|
+
# A class for encapsulating view behavior for a user of the CLI.
|
3
|
+
#
|
4
|
+
# @example
|
5
|
+
#
|
6
|
+
# job = MotherBrain::Job.new
|
7
|
+
# CliClient.new(job).display
|
8
|
+
#
|
9
|
+
class CliClient
|
10
|
+
SPACE = " "
|
11
|
+
TICK = 0.1
|
12
|
+
|
13
|
+
attr_accessor :current_status, :last_spun
|
14
|
+
attr_reader :job
|
15
|
+
|
16
|
+
# @param [MotherBrain::Job] job
|
17
|
+
def initialize(job)
|
18
|
+
@job = job
|
19
|
+
end
|
20
|
+
|
21
|
+
# Block and wait for all jobs to be completed, while displaying the status
|
22
|
+
# of each job.
|
23
|
+
def display
|
24
|
+
if debugging?
|
25
|
+
wait_for_jobs
|
26
|
+
else
|
27
|
+
display_jobs
|
28
|
+
end
|
29
|
+
|
30
|
+
if job_failed?
|
31
|
+
display_log_location if log_location
|
32
|
+
abort
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def application_terminated?
|
39
|
+
JobManager.stopped?
|
40
|
+
end
|
41
|
+
|
42
|
+
def clear_line
|
43
|
+
printf "\r#{SPACE * terminal_width}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [Boolean]
|
47
|
+
def debugging?
|
48
|
+
MB.log.info?
|
49
|
+
end
|
50
|
+
|
51
|
+
def display_log_location
|
52
|
+
puts "#{left_space} [motherbrain] Log written to #{log_location}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def display_jobs
|
56
|
+
until job_completed? || application_terminated?
|
57
|
+
print_statuses
|
58
|
+
sleep TICK
|
59
|
+
end
|
60
|
+
|
61
|
+
if application_terminated?
|
62
|
+
print_final_terminated_status
|
63
|
+
else
|
64
|
+
print_final_status
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def job_completed?
|
69
|
+
job.completed?
|
70
|
+
end
|
71
|
+
|
72
|
+
def job_failed?
|
73
|
+
job.failed?
|
74
|
+
end
|
75
|
+
|
76
|
+
def job_type
|
77
|
+
@job_type || job.type
|
78
|
+
end
|
79
|
+
|
80
|
+
def log_location
|
81
|
+
MB::Logging.filename
|
82
|
+
end
|
83
|
+
|
84
|
+
def print_final_status
|
85
|
+
print_with_new_line current_status
|
86
|
+
|
87
|
+
msg = "#{left_space} [#{job_type}] #{job.state.to_s.capitalize}"
|
88
|
+
msg << ": #{job.result}" if job.result
|
89
|
+
|
90
|
+
puts msg
|
91
|
+
end
|
92
|
+
|
93
|
+
def print_final_terminated_status
|
94
|
+
puts "\nmotherbrain terminated"
|
95
|
+
end
|
96
|
+
|
97
|
+
def left_space
|
98
|
+
SPACE * spinner.peek.length
|
99
|
+
end
|
100
|
+
|
101
|
+
def print_statuses
|
102
|
+
last_status = status_buffer.pop
|
103
|
+
|
104
|
+
if last_status
|
105
|
+
if current_status && last_spun != current_status
|
106
|
+
print_with_new_line current_status
|
107
|
+
end
|
108
|
+
|
109
|
+
self.current_status = last_status
|
110
|
+
end
|
111
|
+
|
112
|
+
while status = status_buffer.shift
|
113
|
+
print_with_new_line status
|
114
|
+
end
|
115
|
+
|
116
|
+
print_with_spinner current_status
|
117
|
+
end
|
118
|
+
|
119
|
+
def print_with_spinner(text)
|
120
|
+
return unless text
|
121
|
+
|
122
|
+
string = "\r#{spinner.next} [#{job_type}] #{text}"
|
123
|
+
|
124
|
+
if string.length < terminal_width
|
125
|
+
clear_line
|
126
|
+
printf string
|
127
|
+
else
|
128
|
+
print_with_new_line(text) unless text == last_spun
|
129
|
+
self.last_spun = text
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def print_with_new_line(text)
|
134
|
+
return unless text
|
135
|
+
|
136
|
+
clear_line
|
137
|
+
|
138
|
+
printf "\r#{left_space} [#{job_type}] #{text}\n"
|
139
|
+
end
|
140
|
+
|
141
|
+
# @return [Enumerator]
|
142
|
+
def spinner
|
143
|
+
@spinner ||= Enumerator.new { |enumerator|
|
144
|
+
characters = if ENV['SPINNER'] == 'kirby'
|
145
|
+
kirby = ["(>' ')>", "(^' ')^", "<(' '<)", "^(' '^)"]
|
146
|
+
frames = [0, 0, 0, 1, 2, 2, 2, 3].inject([]) { |frames, frame| frames + [frame]*3 }
|
147
|
+
buffer = [0, 1, 2, 3, 3, 2, 1, 0].inject([]) { |frames, frame| frames + [frame]*3 }
|
148
|
+
buffer_max = buffer.max
|
149
|
+
frames.each_with_index.collect do |frame, i|
|
150
|
+
left = " " * buffer[i]
|
151
|
+
right = " " * (buffer_max - buffer[i])
|
152
|
+
left + kirby[frame] + right
|
153
|
+
end
|
154
|
+
else
|
155
|
+
%w[` ' - . , . - ']
|
156
|
+
end
|
157
|
+
|
158
|
+
loop {
|
159
|
+
characters.each do |character|
|
160
|
+
enumerator.yield character
|
161
|
+
end
|
162
|
+
}
|
163
|
+
}
|
164
|
+
end
|
165
|
+
|
166
|
+
def status_buffer
|
167
|
+
job.status_buffer
|
168
|
+
end
|
169
|
+
|
170
|
+
def terminal_width
|
171
|
+
@terminal_width ||= `tput cols`.to_i
|
172
|
+
end
|
173
|
+
|
174
|
+
def wait_for_jobs
|
175
|
+
sleep TICK until job_completed? || application_terminated?
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,426 @@
|
|
1
|
+
module MotherBrain
|
2
|
+
class CliGateway < Cli::Base
|
3
|
+
class << self
|
4
|
+
def invoked_opts
|
5
|
+
@invoked_opts ||= Hashie::Mash.new
|
6
|
+
end
|
7
|
+
|
8
|
+
# @param [Hash] options
|
9
|
+
#
|
10
|
+
# @return [MB::Config]
|
11
|
+
def configure(options)
|
12
|
+
file = options[:config] || File.expand_path(MB::Config.default_path)
|
13
|
+
|
14
|
+
begin
|
15
|
+
config = MB::Config.from_file(file)
|
16
|
+
rescue MB::InvalidConfig => ex
|
17
|
+
ui.error ex.to_s
|
18
|
+
exit_with(InvalidConfig)
|
19
|
+
rescue MB::ConfigNotFound => ex
|
20
|
+
ui.error "#{ex.message}"
|
21
|
+
ui.error "Create one with `mb configure`"
|
22
|
+
exit_with(ConfigNotFound)
|
23
|
+
end
|
24
|
+
|
25
|
+
level = Logger::WARN
|
26
|
+
level = Logger::INFO if options[:verbose]
|
27
|
+
level = Logger::DEBUG if options[:debug]
|
28
|
+
|
29
|
+
if (options[:verbose] || options[:debug]) && options[:logfile].nil?
|
30
|
+
options[:logfile] = STDOUT
|
31
|
+
end
|
32
|
+
|
33
|
+
MB::Logging.setup(level: level, location: options[:logfile])
|
34
|
+
|
35
|
+
config.rest_gateway.enable = false
|
36
|
+
config.plugin_manager.eager_loading = false
|
37
|
+
config.plugin_manager.async_loading = false
|
38
|
+
config
|
39
|
+
end
|
40
|
+
|
41
|
+
# Return the best plugin version for the given options.
|
42
|
+
#
|
43
|
+
# If no options are given, the latest version of the plugin will be loaded from either your
|
44
|
+
# Berkshelf or the remote Chef server. If no plugin is found then the CLI will exit with an error.
|
45
|
+
#
|
46
|
+
# Specifying an environment will cause this function to check with the target environment
|
47
|
+
# to see what plugin version is best suited be be returned for controlling that environment.
|
48
|
+
#
|
49
|
+
# If the specified environment does not exist then the latest plugin on the Chef server will be
|
50
|
+
# returned. If no plugin is found then the CLI will exit with an error.
|
51
|
+
#
|
52
|
+
# Specifying a plugin_version will cause this function to return only a the plugin matching that
|
53
|
+
# name and version. If no plugin is found then the CLI will exit with an error.
|
54
|
+
#
|
55
|
+
# @option options [String] :environment
|
56
|
+
# @option options [String] :plugin_version
|
57
|
+
#
|
58
|
+
# @return [MB::Plugin]
|
59
|
+
def find_plugin(name, options = {})
|
60
|
+
if options[:plugin_version]
|
61
|
+
plugin = plugin_manager.find(name, options[:plugin_version], remote: true)
|
62
|
+
|
63
|
+
unless plugin
|
64
|
+
ui.error "The cookbook #{name} (version #{options[:plugin_version]}) did not contain a motherbrain" +
|
65
|
+
" plugin or it was not found in your Berkshelf or on the remote."
|
66
|
+
exit(1)
|
67
|
+
end
|
68
|
+
|
69
|
+
plugin
|
70
|
+
elsif local_plugin?
|
71
|
+
ui.info "Loading #{name} plugin from: #{Dir.pwd}"
|
72
|
+
plugin_manager.load_installed(Dir.pwd, allow_failure: false)
|
73
|
+
elsif options[:environment]
|
74
|
+
plugin = begin
|
75
|
+
ui.info "Determining best version of the #{name} plugin to use with the #{options[:environment]}" +
|
76
|
+
" environment. This may take a few seconds..."
|
77
|
+
plugin_manager.for_environment(name, options[:environment], remote: true)
|
78
|
+
rescue MotherBrain::EnvironmentNotFound => ex
|
79
|
+
ui.warn "No environment named #{options[:environment]} was found. Finding the latest version of the" +
|
80
|
+
" #{name} plugin instead. This may take a few seconds..."
|
81
|
+
plugin_manager.latest(name, remote: true)
|
82
|
+
end
|
83
|
+
|
84
|
+
unless plugin
|
85
|
+
ui.error "No versions of the #{name} cookbook contained a motherbrain plugin that matched the" +
|
86
|
+
" requirements of the #{options[:environment]} environment."
|
87
|
+
exit(1)
|
88
|
+
end
|
89
|
+
|
90
|
+
plugin
|
91
|
+
else
|
92
|
+
ui.info "Finding the latest version of the #{name} plugin. This may take a few seconds..."
|
93
|
+
plugin = plugin_manager.latest(name, remote: true)
|
94
|
+
|
95
|
+
unless plugin
|
96
|
+
ui.error "No versions of the #{name} cookbook in your Berkshelf or on the remote contained a" +
|
97
|
+
" motherbrain plugin."
|
98
|
+
exit(1)
|
99
|
+
end
|
100
|
+
|
101
|
+
plugin
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Determines if we're running inside of a cookbook with a plugin.
|
106
|
+
#
|
107
|
+
# @return [Boolean]
|
108
|
+
def local_plugin?
|
109
|
+
Dir.has_mb_plugin?(Dir.pwd)
|
110
|
+
end
|
111
|
+
|
112
|
+
# @see {#Thor}
|
113
|
+
def start(given_args = ARGV, config = {})
|
114
|
+
config[:shell] ||= MB::Cli::Shell.shell.new
|
115
|
+
args, opts = parse_args(given_args)
|
116
|
+
invoked_opts.merge!(opts)
|
117
|
+
|
118
|
+
if requires_environment?(args)
|
119
|
+
unless opts[:environment]
|
120
|
+
ui.say "No value provided for required option '--environment'"
|
121
|
+
exit 1
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
if start_mb_application?(args)
|
126
|
+
app_config = configure(opts.dup)
|
127
|
+
app_config.validate!
|
128
|
+
|
129
|
+
MB::Application.run!(app_config)
|
130
|
+
MB::Logging.add_argument_header
|
131
|
+
|
132
|
+
# If the first argument is the name of a plugin, register that plugin and use it.
|
133
|
+
if plugin_task?(args[0])
|
134
|
+
name = args[0]
|
135
|
+
|
136
|
+
plugin = find_plugin(name, opts)
|
137
|
+
register_plugin(plugin)
|
138
|
+
|
139
|
+
ui.say "using #{plugin}"
|
140
|
+
ui.say ""
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
dispatch(nil, given_args.dup, nil, config)
|
145
|
+
ensure
|
146
|
+
Celluloid.shutdown
|
147
|
+
end
|
148
|
+
|
149
|
+
# Does the given argument array require a named argument for environment?
|
150
|
+
#
|
151
|
+
# @param [Array<String>] args the CLI arguments
|
152
|
+
#
|
153
|
+
# @return [Boolean]
|
154
|
+
def requires_environment?(args)
|
155
|
+
return false if args.count.zero?
|
156
|
+
|
157
|
+
if args.include?("help")
|
158
|
+
return false
|
159
|
+
end
|
160
|
+
|
161
|
+
if SKIP_ENVIRONMENT_TASKS.include?(args.first)
|
162
|
+
return false
|
163
|
+
end
|
164
|
+
|
165
|
+
if args.count == 1
|
166
|
+
return false
|
167
|
+
end
|
168
|
+
|
169
|
+
# All commands/subcommands require an environment unless specified in
|
170
|
+
# the {SKIP_ENVIRONMENT_TASKS} constant array.
|
171
|
+
true
|
172
|
+
end
|
173
|
+
|
174
|
+
# Did the user call a plugin task?
|
175
|
+
#
|
176
|
+
# @param [String] name
|
177
|
+
#
|
178
|
+
# @return [Boolean]
|
179
|
+
def plugin_task?(name)
|
180
|
+
non_plugin_tasks = tasks.keys.map(&:to_s)
|
181
|
+
!non_plugin_tasks.find { |task| task == name }.present?
|
182
|
+
end
|
183
|
+
|
184
|
+
# Create and register a sub command for the given plugin
|
185
|
+
#
|
186
|
+
# @param [MB::Plugin] plugin
|
187
|
+
#
|
188
|
+
# @return [MB::Cli::SubCommand]
|
189
|
+
# the sub command generated and registered
|
190
|
+
def register_plugin(plugin)
|
191
|
+
sub_command = MB::Cli::SubCommand.new(plugin)
|
192
|
+
register_subcommand(sub_command)
|
193
|
+
sub_command
|
194
|
+
end
|
195
|
+
|
196
|
+
# Check if we should start the motherbrain application stack based on the
|
197
|
+
# arguments passed to the CliGateway. The application stack won't be started
|
198
|
+
# if the first argument is a member of {SKIP_CONFIG_TASKS}.
|
199
|
+
#
|
200
|
+
# @param [Array] args
|
201
|
+
#
|
202
|
+
# @return [Boolean]
|
203
|
+
def start_mb_application?(args)
|
204
|
+
args.any? && !SKIP_CONFIG_TASKS.include?(args.first)
|
205
|
+
end
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
# Parse the given arguments into an instance of Thor::Argument and Thor::Options
|
210
|
+
#
|
211
|
+
# @param [Array] given_args
|
212
|
+
#
|
213
|
+
# @return [Array]
|
214
|
+
def parse_args(given_args)
|
215
|
+
args, opts = Thor::Options.split(given_args)
|
216
|
+
thor_opts = Thor::Options.new(self.class_options)
|
217
|
+
parsed_opts = thor_opts.parse(opts)
|
218
|
+
|
219
|
+
[ args, parsed_opts ]
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
SKIP_CONFIG_TASKS = [
|
224
|
+
"configure",
|
225
|
+
"help",
|
226
|
+
"init",
|
227
|
+
"version",
|
228
|
+
"ver"
|
229
|
+
].freeze
|
230
|
+
|
231
|
+
SKIP_ENVIRONMENT_TASKS = [
|
232
|
+
"environment",
|
233
|
+
"plugin",
|
234
|
+
"template",
|
235
|
+
"purge",
|
236
|
+
"disable",
|
237
|
+
"enable"
|
238
|
+
].freeze
|
239
|
+
|
240
|
+
CREATE_ENVIRONMENT_TASKS = [
|
241
|
+
"bootstrap",
|
242
|
+
"provision"
|
243
|
+
].freeze
|
244
|
+
|
245
|
+
def initialize(args = [], options = {}, config = {})
|
246
|
+
super
|
247
|
+
opts = self.options.dup
|
248
|
+
|
249
|
+
validate_environment
|
250
|
+
|
251
|
+
unless SKIP_CONFIG_TASKS.include?(config[:current_command].try(:name))
|
252
|
+
self.class.configure(opts)
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
map 'ver' => :version
|
257
|
+
|
258
|
+
class_option :config,
|
259
|
+
type: :string,
|
260
|
+
desc: "Path to a motherbrain JSON configuration file.",
|
261
|
+
aliases: "-c",
|
262
|
+
banner: "PATH"
|
263
|
+
class_option :verbose,
|
264
|
+
type: :boolean,
|
265
|
+
desc: "Increase verbosity of output.",
|
266
|
+
default: false,
|
267
|
+
aliases: "-v"
|
268
|
+
class_option :debug,
|
269
|
+
type: :boolean,
|
270
|
+
desc: "Output all log messages.",
|
271
|
+
default: false,
|
272
|
+
aliases: "-d"
|
273
|
+
class_option :logfile,
|
274
|
+
type: :string,
|
275
|
+
desc: "Set the log file location.",
|
276
|
+
aliases: "-L",
|
277
|
+
banner: "PATH"
|
278
|
+
class_option :plugin_version,
|
279
|
+
type: :string,
|
280
|
+
desc: "Plugin version to use",
|
281
|
+
default: nil,
|
282
|
+
aliases: "-p"
|
283
|
+
class_option :environment,
|
284
|
+
type: :string,
|
285
|
+
default: nil,
|
286
|
+
required: false,
|
287
|
+
desc: "Chef environment",
|
288
|
+
aliases: "-e"
|
289
|
+
class_option :on_environment_missing,
|
290
|
+
type: :string,
|
291
|
+
default: "prompt",
|
292
|
+
enum: %w(prompt create quit),
|
293
|
+
desc: "What to do when the environment doesn't exist"
|
294
|
+
|
295
|
+
|
296
|
+
method_option :force,
|
297
|
+
type: :boolean,
|
298
|
+
default: false,
|
299
|
+
desc: "create a new configuration file even if one already exists.",
|
300
|
+
aliases: "-f"
|
301
|
+
desc "configure", "create a new configuration file based on a set of interactive questions"
|
302
|
+
def configure(path = MB::Config.default_path)
|
303
|
+
path = File.expand_path(path)
|
304
|
+
|
305
|
+
if File.exist?(path) && !options[:force]
|
306
|
+
raise MB::ConfigExists, "A configuration file already exists. Re-run with the --force flag if you wish to overwrite it."
|
307
|
+
end
|
308
|
+
|
309
|
+
config = MB::Config.new(path)
|
310
|
+
|
311
|
+
config.chef.api_url = ui.ask "Enter a Chef API URL:", default: config.chef.api_url
|
312
|
+
config.chef.api_client = ui.ask "Enter a Chef API Client:", default: config.chef.api_client
|
313
|
+
config.chef.api_key = ui.ask "Enter the path to the client's Chef API Key:", default: config.chef.api_key
|
314
|
+
config.ssh.user = ui.ask "Enter a SSH user:", default: config.ssh.user
|
315
|
+
config.ssh.password = ui.ask "Enter a SSH password:", default: config.ssh.password
|
316
|
+
config.save
|
317
|
+
|
318
|
+
ui.say "Config written to: '#{path}'"
|
319
|
+
end
|
320
|
+
|
321
|
+
desc "console", "Start an interactive motherbrain console"
|
322
|
+
def console
|
323
|
+
require 'mb/console'
|
324
|
+
MB::Console.start
|
325
|
+
end
|
326
|
+
|
327
|
+
method_option :skip_chef,
|
328
|
+
type: :boolean,
|
329
|
+
desc: "Skip removing the Chef installation from the node",
|
330
|
+
default: false
|
331
|
+
desc "purge HOST", "Remove Chef from node and purge it's data from the Chef server"
|
332
|
+
def purge(hostname)
|
333
|
+
job = node_querier.async_purge(hostname, options.to_hash.symbolize_keys)
|
334
|
+
CliClient.new(job).display
|
335
|
+
end
|
336
|
+
|
337
|
+
method_option :force,
|
338
|
+
type: :boolean,
|
339
|
+
desc: "Perform HOST enable even if the environment is locked",
|
340
|
+
default: false,
|
341
|
+
aliases: "-f"
|
342
|
+
desc "enable HOST", "Remove stop service attributes and 'disabled' run list entry from HOST's node."
|
343
|
+
def enable(hostname)
|
344
|
+
job = node_querier.async_enable(hostname, options.to_hash.symbolize_keys)
|
345
|
+
CliClient.new(job).display
|
346
|
+
end
|
347
|
+
|
348
|
+
method_option :force,
|
349
|
+
type: :boolean,
|
350
|
+
desc: "Perform HOST disable even if the environment is locked",
|
351
|
+
default: false,
|
352
|
+
aliases: "-f"
|
353
|
+
desc "disable HOST", "Stop services on HOST and prevent chef-client from running until reenabled."
|
354
|
+
def disable(hostname)
|
355
|
+
job = node_querier.async_disable(hostname, options.to_hash.symbolize_keys)
|
356
|
+
CliClient.new(job).display
|
357
|
+
end
|
358
|
+
|
359
|
+
desc "version", "Display version and license information"
|
360
|
+
def version
|
361
|
+
ui.say version_header
|
362
|
+
ui.say "\n"
|
363
|
+
ui.say license
|
364
|
+
end
|
365
|
+
|
366
|
+
desc "template NAME PATH_OR_URL", "Download and install a bootstrap template"
|
367
|
+
def template(name, path_or_url)
|
368
|
+
MB::Bootstrap::Template.install(name, path_or_url)
|
369
|
+
ui.say "Installed template `#{name}`"
|
370
|
+
end
|
371
|
+
|
372
|
+
private
|
373
|
+
|
374
|
+
def validate_environment
|
375
|
+
return if testing?
|
376
|
+
|
377
|
+
environment_name = options[:environment]
|
378
|
+
|
379
|
+
return unless environment_name
|
380
|
+
|
381
|
+
environment_manager.find(environment_name)
|
382
|
+
rescue EnvironmentNotFound
|
383
|
+
raise unless CREATE_ENVIRONMENT_TASKS.include?(args.first)
|
384
|
+
|
385
|
+
case options[:on_environment_missing]
|
386
|
+
when 'prompt' then prompt_to_create_environment(environment_name)
|
387
|
+
when 'create' then create_environment(environment_name)
|
388
|
+
when 'quit' then quit("Environment '#{environment_name}' not found and you told me to quit when that happens")
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
def prompt_to_create_environment(environment_name)
|
393
|
+
message = "Environment '#{environment_name}' does not exist, would you like to create it?"
|
394
|
+
case ask(message, limited_to: %w[y n q], default: 'y')
|
395
|
+
when 'y' then create_environment(environment_name)
|
396
|
+
when 'n' then ui.warn "Not creating environment"
|
397
|
+
when 'q' then quit("Because you said so")
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
def testing?
|
402
|
+
MB.testing?
|
403
|
+
end
|
404
|
+
|
405
|
+
def version_header
|
406
|
+
"motherbrain (#{MB::VERSION})"
|
407
|
+
end
|
408
|
+
|
409
|
+
def license
|
410
|
+
File.read(MB.app_root.join('LICENSE'))
|
411
|
+
end
|
412
|
+
|
413
|
+
private
|
414
|
+
|
415
|
+
def create_environment(environment_name)
|
416
|
+
environment_manager.create(environment_name)
|
417
|
+
end
|
418
|
+
|
419
|
+
def quit(why)
|
420
|
+
ui.say "Quitting: #{why}"
|
421
|
+
abort
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
require 'mb/cli_gateway/sub_commands'
|