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.
Files changed (259) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +9 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +9 -0
  5. data/CHANGELOG.md +196 -0
  6. data/COMMANDS.md +9 -0
  7. data/CONTRIBUTING.md +24 -0
  8. data/Dockerfile +26 -0
  9. data/Gemfile +60 -2
  10. data/Guardfile +30 -0
  11. data/LICENSE +10 -0
  12. data/MANIFESTS.md +90 -0
  13. data/OPERATORS_GUIDE.md +195 -0
  14. data/PLUGINS.md +268 -0
  15. data/README.md +304 -16
  16. data/Thorfile +123 -0
  17. data/VAGRANT.md +116 -0
  18. data/bin/boot +9 -0
  19. data/bin/mb +5 -0
  20. data/bin/mbsrv +5 -0
  21. data/config.json +32 -0
  22. data/features/cli/bootstrap_command/configurable_scripts.feature +32 -0
  23. data/features/cli/configure_command.feature +57 -0
  24. data/features/cli/environment_command/create_command.feature +22 -0
  25. data/features/cli/environment_command/destroy_command.feature +33 -0
  26. data/features/cli/environment_command/from_command.feature +29 -0
  27. data/features/cli/environment_command/list_command.feature +0 -0
  28. data/features/cli/node_limiting.feature +47 -0
  29. data/features/cli/plugin_command/list_command.feature +46 -0
  30. data/features/cli/service_command/service_command.feature +21 -0
  31. data/features/cli/template_command.feature +10 -0
  32. data/features/cli/validate_config.feature +46 -0
  33. data/features/step_definitions/bootstrap_steps.rb +57 -0
  34. data/features/step_definitions/chef_server_steps.rb +3 -0
  35. data/features/step_definitions/configuration_steps.rb +18 -0
  36. data/features/step_definitions/core_cli_steps.rb +33 -0
  37. data/features/step_definitions/environment_steps.rb +43 -0
  38. data/features/step_definitions/node_steps.rb +3 -0
  39. data/features/step_definitions/plugin_steps.rb +15 -0
  40. data/features/step_definitions/template_steps.rb +7 -0
  41. data/features/support/env.rb +68 -0
  42. data/lib/mb/api.rb +8 -0
  43. data/lib/mb/api/application.rb +7 -0
  44. data/lib/mb/api/endpoint.rb +5 -0
  45. data/lib/mb/api/helpers.rb +38 -0
  46. data/lib/mb/api/v1.rb +56 -0
  47. data/lib/mb/api/v1/config_endpoint.rb +12 -0
  48. data/lib/mb/api/v1/environments_endpoint.rb +174 -0
  49. data/lib/mb/api/v1/jobs_endpoint.rb +31 -0
  50. data/lib/mb/api/v1/plugins_endpoint.rb +90 -0
  51. data/lib/mb/api/validators.rb +5 -0
  52. data/lib/mb/api/validators/sem_ver.rb +18 -0
  53. data/lib/mb/application.rb +148 -0
  54. data/lib/mb/berkshelf.rb +50 -0
  55. data/lib/mb/bootstrap.rb +9 -0
  56. data/lib/mb/bootstrap/manager.rb +250 -0
  57. data/lib/mb/bootstrap/manifest.rb +131 -0
  58. data/lib/mb/bootstrap/routine.rb +199 -0
  59. data/lib/mb/bootstrap/template.rb +73 -0
  60. data/lib/mb/bootstrap/worker.rb +227 -0
  61. data/lib/mb/chef.rb +6 -0
  62. data/lib/mb/chef/config.rb +69 -0
  63. data/lib/mb/chef/run_list_item.rb +115 -0
  64. data/lib/mb/chef_mutex.rb +304 -0
  65. data/lib/mb/clean_room_base.rb +39 -0
  66. data/lib/mb/cli.rb +50 -0
  67. data/lib/mb/cli/base.rb +51 -0
  68. data/lib/mb/cli/shell.rb +29 -0
  69. data/lib/mb/cli/shell/basic.rb +11 -0
  70. data/lib/mb/cli/shell/color.rb +11 -0
  71. data/lib/mb/cli/shell/ext.rb +41 -0
  72. data/lib/mb/cli/sub_command.rb +95 -0
  73. data/lib/mb/cli/sub_command/component.rb +56 -0
  74. data/lib/mb/cli/sub_command/plugin.rb +232 -0
  75. data/lib/mb/cli_client.rb +178 -0
  76. data/lib/mb/cli_gateway.rb +426 -0
  77. data/lib/mb/cli_gateway/sub_commands.rb +3 -0
  78. data/lib/mb/cli_gateway/sub_commands/environment.rb +124 -0
  79. data/lib/mb/cli_gateway/sub_commands/plugin.rb +148 -0
  80. data/lib/mb/command.rb +88 -0
  81. data/lib/mb/command_invoker.rb +235 -0
  82. data/lib/mb/command_invoker/worker.rb +40 -0
  83. data/lib/mb/command_runner.rb +233 -0
  84. data/lib/mb/component.rb +245 -0
  85. data/lib/mb/config.rb +275 -0
  86. data/lib/mb/config_manager.rb +75 -0
  87. data/lib/mb/console.rb +35 -0
  88. data/lib/mb/cookbook_metadata.rb +73 -0
  89. data/lib/mb/core_ext.rb +3 -0
  90. data/lib/mb/core_ext/dir.rb +37 -0
  91. data/lib/mb/core_ext/enumerable.rb +48 -0
  92. data/lib/mb/core_ext/file.rb +24 -0
  93. data/lib/mb/core_ext/signal.rb +11 -0
  94. data/lib/mb/environment_manager.rb +195 -0
  95. data/lib/mb/error_handler.rb +212 -0
  96. data/lib/mb/errors.rb +693 -0
  97. data/lib/mb/file_system.rb +60 -0
  98. data/lib/mb/file_system/tempfile.rb +25 -0
  99. data/lib/mb/gear.rb +154 -0
  100. data/lib/mb/gears.rb +8 -0
  101. data/lib/mb/gears/dynamic_service.rb +218 -0
  102. data/lib/mb/gears/jmx.rb +24 -0
  103. data/lib/mb/gears/jmx/action.rb +46 -0
  104. data/lib/mb/gears/mysql.rb +20 -0
  105. data/lib/mb/gears/mysql/action.rb +190 -0
  106. data/lib/mb/gears/service.rb +163 -0
  107. data/lib/mb/gears/service/action.rb +58 -0
  108. data/lib/mb/gears/service/action_runner.rb +161 -0
  109. data/lib/mb/grape_ext.rb +3 -0
  110. data/lib/mb/grape_ext/endpoint.rb +13 -0
  111. data/lib/mb/group.rb +143 -0
  112. data/lib/mb/job.rb +183 -0
  113. data/lib/mb/job/state_machine.rb +34 -0
  114. data/lib/mb/job/states.rb +46 -0
  115. data/lib/mb/job_manager.rb +96 -0
  116. data/lib/mb/job_record.rb +67 -0
  117. data/lib/mb/job_ticket.rb +25 -0
  118. data/lib/mb/lock_manager.rb +116 -0
  119. data/lib/mb/logging.rb +134 -0
  120. data/lib/mb/logging/basic_format.rb +31 -0
  121. data/lib/mb/manifest.rb +128 -0
  122. data/lib/mb/mixin.rb +3 -0
  123. data/lib/mb/mixin/attribute_setting.rb +265 -0
  124. data/lib/mb/mixin/coded_exit.rb +49 -0
  125. data/lib/mb/mixin/locks.rb +54 -0
  126. data/lib/mb/mixin/services.rb +100 -0
  127. data/lib/mb/node_filter.rb +97 -0
  128. data/lib/mb/node_querier.rb +527 -0
  129. data/lib/mb/plugin.rb +300 -0
  130. data/lib/mb/plugin_manager.rb +589 -0
  131. data/lib/mb/provisioner.rb +186 -0
  132. data/lib/mb/provisioner/manager.rb +213 -0
  133. data/lib/mb/provisioner/manifest.rb +125 -0
  134. data/lib/mb/provisioner/provision_data.rb +96 -0
  135. data/lib/mb/provisioners.rb +5 -0
  136. data/lib/mb/provisioners/aws.rb +395 -0
  137. data/lib/mb/rest_gateway.rb +72 -0
  138. data/lib/mb/ridley_ext.rb +5 -0
  139. data/lib/mb/ridley_ext/cookbook_object.rb +15 -0
  140. data/lib/mb/srv_ctl.rb +183 -0
  141. data/lib/mb/test.rb +104 -0
  142. data/lib/mb/thor_ext.rb +49 -0
  143. data/lib/mb/upgrade.rb +6 -0
  144. data/lib/mb/upgrade/manager.rb +85 -0
  145. data/lib/mb/upgrade/worker.rb +149 -0
  146. data/lib/mb/version.rb +1 -1
  147. data/lib/motherbrain.rb +166 -2
  148. data/man/man_helper.rb +81 -0
  149. data/man/mb.1 +494 -0
  150. data/man/mb.1.html +300 -0
  151. data/man/mb.1.ronn.erb +62 -0
  152. data/motherbrain.gemspec +56 -20
  153. data/scripts/node_name.rb +14 -0
  154. data/spec/fixtures/cb_metadata.json +7 -0
  155. data/spec/fixtures/cb_metadata.rb +14 -0
  156. data/spec/fixtures/fake_id_rsa +27 -0
  157. data/spec/fixtures/fake_key.pem +27 -0
  158. data/spec/fixtures/myface-0.1.0/metadata.rb +14 -0
  159. data/spec/fixtures/myface-0.1.0/motherbrain.rb +0 -0
  160. data/spec/fixtures/test_env.json +15 -0
  161. data/spec/spec_helper.rb +67 -0
  162. data/spec/support/actor_mocking.rb +7 -0
  163. data/spec/support/berkshelf.rb +24 -0
  164. data/spec/support/chef_server.rb +102 -0
  165. data/spec/support/doubles.rb +11 -0
  166. data/spec/support/klass.rb +10 -0
  167. data/spec/support/matchers/each.rb +12 -0
  168. data/spec/support/matchers/error_codes.rb +5 -0
  169. data/spec/support/matchers/exit_codes.rb +57 -0
  170. data/spec/support/matchers/jobs.rb +11 -0
  171. data/spec/support/spec_helpers.rb +145 -0
  172. data/spec/unit/mb/api/application_spec.rb +11 -0
  173. data/spec/unit/mb/api/helpers_spec.rb +5 -0
  174. data/spec/unit/mb/api/v1/config_endpoint_spec.rb +19 -0
  175. data/spec/unit/mb/api/v1/environments_endpoint_spec.rb +71 -0
  176. data/spec/unit/mb/api/v1/jobs_endpoint_spec.rb +24 -0
  177. data/spec/unit/mb/api/v1/plugins_endpoint_spec.rb +298 -0
  178. data/spec/unit/mb/api/v1_spec.rb +37 -0
  179. data/spec/unit/mb/api/validators/sem_ver_spec.rb +5 -0
  180. data/spec/unit/mb/application_spec.rb +19 -0
  181. data/spec/unit/mb/berkshelf_spec.rb +38 -0
  182. data/spec/unit/mb/bootstrap/manager_spec.rb +347 -0
  183. data/spec/unit/mb/bootstrap/manifest_spec.rb +333 -0
  184. data/spec/unit/mb/bootstrap/routine_spec.rb +393 -0
  185. data/spec/unit/mb/bootstrap/template_spec.rb +100 -0
  186. data/spec/unit/mb/bootstrap/worker_spec.rb +194 -0
  187. data/spec/unit/mb/chef/config_spec.rb +33 -0
  188. data/spec/unit/mb/chef/run_list_item_spec.rb +34 -0
  189. data/spec/unit/mb/chef_mutex_spec.rb +314 -0
  190. data/spec/unit/mb/clean_room_base_spec.rb +31 -0
  191. data/spec/unit/mb/cli/base_spec.rb +43 -0
  192. data/spec/unit/mb/cli/shell/basic_spec.rb +5 -0
  193. data/spec/unit/mb/cli/shell/color_spec.rb +5 -0
  194. data/spec/unit/mb/cli/shell/ext_spec.rb +11 -0
  195. data/spec/unit/mb/cli/shell_spec.rb +38 -0
  196. data/spec/unit/mb/cli/sub_command/base_spec.rb +102 -0
  197. data/spec/unit/mb/cli/sub_command/component_spec.rb +5 -0
  198. data/spec/unit/mb/cli/sub_command/plugin_spec.rb +91 -0
  199. data/spec/unit/mb/cli/sub_command_spec.rb +43 -0
  200. data/spec/unit/mb/cli/ui.rb +0 -0
  201. data/spec/unit/mb/cli_client_spec.rb +51 -0
  202. data/spec/unit/mb/cli_gateway_spec.rb +386 -0
  203. data/spec/unit/mb/command_invoker/worker_spec.rb +43 -0
  204. data/spec/unit/mb/command_invoker_spec.rb +230 -0
  205. data/spec/unit/mb/command_runner_spec.rb +299 -0
  206. data/spec/unit/mb/command_spec.rb +76 -0
  207. data/spec/unit/mb/component_spec.rb +185 -0
  208. data/spec/unit/mb/config_manager_spec.rb +31 -0
  209. data/spec/unit/mb/config_spec.rb +408 -0
  210. data/spec/unit/mb/cookbook_metadata_spec.rb +89 -0
  211. data/spec/unit/mb/core_ext/dir_spec.rb +92 -0
  212. data/spec/unit/mb/core_ext/enumerable_spec.rb +104 -0
  213. data/spec/unit/mb/core_ext/file_spec.rb +58 -0
  214. data/spec/unit/mb/core_ext/signal_spec.rb +24 -0
  215. data/spec/unit/mb/environment_manager_spec.rb +166 -0
  216. data/spec/unit/mb/error_handler_spec.rb +173 -0
  217. data/spec/unit/mb/errors_spec.rb +132 -0
  218. data/spec/unit/mb/file_system/tempfile_spec.rb +14 -0
  219. data/spec/unit/mb/file_system_spec.rb +69 -0
  220. data/spec/unit/mb/gear_spec.rb +125 -0
  221. data/spec/unit/mb/gears/dynamic_service_spec.rb +187 -0
  222. data/spec/unit/mb/gears/jmx/action_spec.rb +34 -0
  223. data/spec/unit/mb/gears/jmx_spec.rb +32 -0
  224. data/spec/unit/mb/gears/mysql/action_spec.rb +118 -0
  225. data/spec/unit/mb/gears/mysql_spec.rb +21 -0
  226. data/spec/unit/mb/gears/service/action_runner_spec.rb +182 -0
  227. data/spec/unit/mb/gears/service/action_spec.rb +44 -0
  228. data/spec/unit/mb/gears/service_spec.rb +124 -0
  229. data/spec/unit/mb/group_spec.rb +280 -0
  230. data/spec/unit/mb/job_manager_spec.rb +56 -0
  231. data/spec/unit/mb/job_record_spec.rb +60 -0
  232. data/spec/unit/mb/job_spec.rb +201 -0
  233. data/spec/unit/mb/locks_manager_spec.rb +88 -0
  234. data/spec/unit/mb/logging_spec.rb +133 -0
  235. data/spec/unit/mb/manifest_spec.rb +105 -0
  236. data/spec/unit/mb/mixin/attribute_setting_spec.rb +180 -0
  237. data/spec/unit/mb/mixin/coded_exit_spec.rb +25 -0
  238. data/spec/unit/mb/mixin/locks_spec.rb +32 -0
  239. data/spec/unit/mb/mixin/services_spec.rb +75 -0
  240. data/spec/unit/mb/node_filter_spec.rb +86 -0
  241. data/spec/unit/mb/node_querier_spec.rb +532 -0
  242. data/spec/unit/mb/plugin_manager_spec.rb +724 -0
  243. data/spec/unit/mb/plugin_spec.rb +247 -0
  244. data/spec/unit/mb/provisioner/manager_spec.rb +141 -0
  245. data/spec/unit/mb/provisioner/manifest_spec.rb +182 -0
  246. data/spec/unit/mb/provisioner/provision_data_spec.rb +113 -0
  247. data/spec/unit/mb/provisioner_spec.rb +251 -0
  248. data/spec/unit/mb/provisioners/aws_spec.rb +392 -0
  249. data/spec/unit/mb/provisioners/environment_factory_spec.rb +108 -0
  250. data/spec/unit/mb/rest_gateway_spec.rb +13 -0
  251. data/spec/unit/mb/ridley_ext/cookbook_object_spec.rb +105 -0
  252. data/spec/unit/mb/srv_ctl_spec.rb +142 -0
  253. data/spec/unit/mb/upgrade/manager_spec.rb +37 -0
  254. data/spec/unit/mb/upgrade/worker_spec.rb +219 -0
  255. data/spec/unit/motherbrain_spec.rb +58 -0
  256. data/templates/bootstrap.json +8 -0
  257. data/templates/motherbrain.rb +44 -0
  258. metadata +694 -15
  259. 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'