Class: MotherBrain::PluginManager
- Inherits:
-
Object
- Object
- MotherBrain::PluginManager
- Includes:
- Celluloid, Celluloid::Notifications, MB::Mixin::Services, Logging
- Defined in:
- lib/mb/plugin_manager.rb
Instance Attribute Summary collapse
- #berkshelf_path ⇒ Pathname readonly
-
#eager_load_timer ⇒ Timers::Timer?
readonly
Tracks when the plugin manager will attempt to load remote plugins from the Chef Server.
Class Method Summary collapse
Instance Method Summary collapse
-
#add(plugin, options = {}) ⇒ MB::Plugin?
Add a plugin to the set of plugins.
-
#async_change_service_state(service, plugin, environment, state, run_chef = true, options = {}) ⇒ MB::JobTicket
Runs #change_service_state asynchronously.
-
#async_loading? ⇒ Boolean
Should the plugin manager perform plugin loading operations in the background?.
-
#change_service_state(job, service, plugin, environment, state, run_chef = true, options = {}) ⇒ Object
Parses a service, creates a new instance of DynamicService and executes a Chef run to change the state of the service.
-
#clear_plugins ⇒ Set
Clear list of known plugins.
-
#eager_load_interval ⇒ Integer
The time between each poll of the remote Chef server to eagerly load discovered plugins.
-
#eager_loading? ⇒ Boolean
If enabled the plugin manager will automatically discover plugins on the remote Chef Server and load them into the plugin set.
-
#find(name, version = nil, options = {}) ⇒ MB::Plugin?
Find and return a registered plugin of the given name and version.
-
#for_environment(plugin_id, environment_id, options = {}) ⇒ MB::Plugin
Determine the best version of a plugin to use when communicating to the given environment.
-
#for_run_list_entry(run_list_entry, environment = nil, options = {}) ⇒ MB::Plugin
Finds the plugin for the cookbook specified in the run list entry.
- #has_plugin?(name, version) ⇒ Boolean
-
#initialize ⇒ PluginManager
constructor
A new instance of PluginManager.
-
#install(name, version = nil) ⇒ MB::Plugin
Download and install the cookbook containing a motherbrain plugin matching the given name and optional version into the user’s Berkshelf.
-
#install_path_for(plugin) ⇒ Pathname
The filepath that a plugin would be or should be installed to.
-
#installed_versions(name) ⇒ Array<String>
List all installed versions of a plugin with the given name of plugins.
-
#latest(name, options = {}) ⇒ MB::Plugin?
Return most current version of the plugin of the given name.
-
#list(options = {}) ⇒ Array<MB::Plugin>
A set of all the registered plugins.
- #load_all ⇒ Array<MotherBrain::Plugin>
-
#load_all_installed(options = {}) ⇒ Object
Load all of the plugins from the Berkshelf.
-
#load_all_remote(options = {}) ⇒ Object
Load all of the plugins from the remote Chef Server.
-
#load_installed(path, options = {}) ⇒ MB::Plugin?
Load a plugin from a file.
-
#load_remote(name, version, options = {}) ⇒ MB::Plugin?
Load a plugin of the given name and version from the remote Chef server.
-
#reload(plugin) ⇒ Object
Remove and Add the given plugin from the set of plugins.
-
#reload_all ⇒ Array<MotherBrain::Plugin>
Reload plugins from Chef Server and from the Berkshelf.
-
#reload_installed ⇒ Array<MotherBrain::Plugin>
Reload plugins from the Berkshelf.
-
#remote_versions(name) ⇒ Array<String>
List all versions of a plugin with the given name that are present on the remote Chef server.
-
#remove(plugin) ⇒ Object
Remove the given plugin from the set of plugins.
-
#satisfy(plugin_name, constraint, options = {}) ⇒ MB::Plugin
Return the best version of the plugin to use for the given constraint.
-
#uninstall(name, version) ⇒ MB::Plugin?
Uninstall an installed plugin.
-
#versions(name, remote = false) ⇒ Array<String>
List all of the versions of the plugin of the given name.
Methods included from Logging
add_argument_header, dev, filename, #log_exception, logger, #logger, reset, set_logger, setup
Constructor Details
#initialize ⇒ PluginManager
Returns a new instance of PluginManager.
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/mb/plugin_manager.rb', line 28 def initialize log.debug { "Plugin Manager starting..." } @berkshelf_path = MB::Berkshelf.path @plugins = Set.new MB::Berkshelf.init async_loading? ? async(:load_all) : load_all if eager_loading? @eager_load_timer = every(eager_load_interval, &method(:load_all_remote)) end subscribe(ConfigManager::UPDATE_MSG, :reconfigure) end |
Instance Attribute Details
#berkshelf_path ⇒ Pathname (readonly)
18 19 20 |
# File 'lib/mb/plugin_manager.rb', line 18 def berkshelf_path @berkshelf_path end |
#eager_load_timer ⇒ Timers::Timer? (readonly)
Tracks when the plugin manager will attempt to load remote plugins from the Chef Server. If remote loading is disabled this will return nil.
24 25 26 |
# File 'lib/mb/plugin_manager.rb', line 24 def eager_load_timer @eager_load_timer end |
Class Method Details
.instance ⇒ Celluloid::Actor(PluginManager)
7 8 9 |
# File 'lib/mb/plugin_manager.rb', line 7 def instance MB::Application[:plugin_manager] or raise Celluloid::DeadActorError, "plugin manager not running" end |
Instance Method Details
#add(plugin, options = {}) ⇒ MB::Plugin?
Add a plugin to the set of plugins
53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/mb/plugin_manager.rb', line 53 def add(plugin, = {}) if [:force] remove(plugin) end if find(plugin.name, plugin.version, remote: false) return nil end @plugins.add(plugin) plugin end |
#async_change_service_state(service, plugin, environment, state, run_chef = true, options = {}) ⇒ MB::JobTicket
Runs #change_service_state asynchronously
530 531 532 533 534 |
# File 'lib/mb/plugin_manager.rb', line 530 def async_change_service_state(service, plugin, environment, state, run_chef = true, = {}) job = Job.new(:dynamic_service_state_change) async(:change_service_state, job, service, plugin, environment, state, run_chef, ) job.ticket end |
#async_loading? ⇒ Boolean
should be disabled if running motherbrain from the CLIGateway to ensure all plugins are loaded before being accessed
Should the plugin manager perform plugin loading operations in the background?
72 73 74 |
# File 'lib/mb/plugin_manager.rb', line 72 def async_loading? Application.config.plugin_manager.async_loading end |
#change_service_state(job, service, plugin, environment, state, run_chef = true, options = {}) ⇒ Object
Parses a service, creates a new instance of DynamicService and executes a Chef run to change the state of the service.
550 551 552 553 554 |
# File 'lib/mb/plugin_manager.rb', line 550 def change_service_state(job, service, plugin, environment, state, run_chef = true, = {}) component_name, service_name = service.split('.') dynamic_service = MB::Gear::DynamicService.new(component_name, service_name) dynamic_service.state_change(job, plugin, environment, state, run_chef, ) end |
#clear_plugins ⇒ Set
Clear list of known plugins
79 80 81 |
# File 'lib/mb/plugin_manager.rb', line 79 def clear_plugins @plugins.clear end |
#eager_load_interval ⇒ Integer
to change this option set it in the Config of ConfigManager
The time between each poll of the remote Chef server to eagerly load discovered plugins
98 99 100 |
# File 'lib/mb/plugin_manager.rb', line 98 def eager_load_interval Application.config.plugin_manager.eager_load_interval end |
#eager_loading? ⇒ Boolean
to change this option set it in the Config of ConfigManager
If enabled the plugin manager will automatically discover plugins on the remote Chef Server and load them into the plugin set.
89 90 91 |
# File 'lib/mb/plugin_manager.rb', line 89 def eager_loading? Application.config.plugin_manager.eager_loading end |
#find(name, version = nil, options = {}) ⇒ MB::Plugin?
Find and return a registered plugin of the given name and version. If no version attribute is specified the latest version of the plugin is returned.
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 |
# File 'lib/mb/plugin_manager.rb', line 147 def find(name, version = nil, = {}) = .reverse_merge(remote: false) return latest(name, ) unless version installed = @plugins.find { |plugin| plugin.name == name && plugin.version.to_s == version.to_s } return installed if installed if [:remote] remote = load_remote(name, version.to_s) return remote if remote end nil end |
#for_environment(plugin_id, environment_id, options = {}) ⇒ MB::Plugin
Determine the best version of a plugin to use when communicating to the given environment
178 179 180 181 182 183 184 185 186 |
# File 'lib/mb/plugin_manager.rb', line 178 def for_environment(plugin_id, environment_id, = {}) = .reverse_merge(remote: false) environment = environment_manager.find(environment_id) constraint = environment.cookbook_versions[plugin_id] || ">= 0.0.0" satisfy(plugin_id, constraint, ) rescue MotherBrain::EnvironmentNotFound => ex abort ex end |
#for_run_list_entry(run_list_entry, environment = nil, options = {}) ⇒ MB::Plugin
Finds the plugin for the cookbook specified in the run list entry
196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/mb/plugin_manager.rb', line 196 def for_run_list_entry(run_list_entry, environment = nil, = {}) item = MotherBrain::Chef::RunListItem.new(run_list_entry) if item.version # version will be defined in run list entries such as # recipe[foo::[email protected]], which takes precidence over the # environment. find(item.cookbook_name, item.version, ) elsif !environment.nil? for_environment(item.cookbook_name, environment, ) else find(item.cookbook_name, nil, ) end end |
#has_plugin?(name, version) ⇒ Boolean
214 215 216 |
# File 'lib/mb/plugin_manager.rb', line 214 def has_plugin?(name, version) !find(name, version).nil? end |
#install(name, version = nil) ⇒ MB::Plugin
Download and install the cookbook containing a motherbrain plugin matching the given name and optional version into the user’s Berkshelf.
229 230 231 232 233 234 235 236 |
# File 'lib/mb/plugin_manager.rb', line 229 def install(name, version = nil) unless plugin = find(name, version, remote: true) abort MB::PluginNotFound.new(name, version) end chef_connection.cookbook.download(plugin.name, plugin.version, install_path_for(plugin)) reload(plugin) end |
#install_path_for(plugin) ⇒ Pathname
The filepath that a plugin would be or should be installed to
243 244 245 |
# File 'lib/mb/plugin_manager.rb', line 243 def install_path_for(plugin) Berkshelf.cookbooks_path.join("#{plugin.name}-#{plugin.version}") end |
#installed_versions(name) ⇒ Array<String>
List all installed versions of a plugin with the given name of plugins. An empty array will be returned if no versions of a plugin are installed.
357 358 359 360 361 362 363 364 365 366 |
# File 'lib/mb/plugin_manager.rb', line 357 def installed_versions(name) installed_cookbooks.collect do |path| plugin = load_installed(path) next unless plugin if plugin.name == name plugin.version.to_s end end.compact end |
#latest(name, options = {}) ⇒ MB::Plugin?
Return most current version of the plugin of the given name
256 257 258 259 260 261 262 263 264 265 266 267 268 269 |
# File 'lib/mb/plugin_manager.rb', line 256 def latest(name, = {}) = .reverse_merge(remote: false) potentials = list(name: name, remote: false).map(&:version) potentials += remote_cookbook_versions(name) if [:remote] potentials = potentials.collect { |version| Semverse::Version.new(version) }.uniq.sort.reverse potentials.find do |version| found = find(name, version.to_s, .slice(:remote)) return found if found end nil end |
#list(options = {}) ⇒ Array<MB::Plugin>
A set of all the registered plugins
376 377 378 379 380 381 382 383 384 385 |
# File 'lib/mb/plugin_manager.rb', line 376 def list( = {}) = .reverse_merge(remote: false) if [:remote] load_all_remote(.slice(:name)) end result = [:name].nil? ? @plugins : @plugins.select { |plugin| plugin.name == [:name] } result.sort.reverse end |
#load_all ⇒ Array<MotherBrain::Plugin>
272 273 274 275 |
# File 'lib/mb/plugin_manager.rb', line 272 def load_all load_all_installed load_all_remote if eager_loading? end |
#load_all_installed(options = {}) ⇒ Object
Load all of the plugins from the Berkshelf
105 106 107 108 109 110 111 |
# File 'lib/mb/plugin_manager.rb', line 105 def load_all_installed( = {}) = .reverse_merge(force: false) installed_cookbooks.each do |path| load_installed(path, ) end end |
#load_all_remote(options = {}) ⇒ Object
Load all of the plugins from the remote Chef Server. Plugins with a name and version that have already been loaded will not be loaded again unless forced.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/mb/plugin_manager.rb', line 118 def load_all_remote( = {}) = .reverse_merge(force: false) if [:name].present? remote_cookbook_versions([:name]).collect do |version| load_remote([:name], version, ) end else [].tap do |remotes| remote_cookbooks.each do |name, versions| versions.each { |version| remotes << future(:load_remote, name, version, ) } end end.map(&:value) end end |
#load_installed(path, options = {}) ⇒ MB::Plugin?
Load a plugin from a file
288 289 290 291 292 293 294 295 |
# File 'lib/mb/plugin_manager.rb', line 288 def load_installed(path, = {}) = .reverse_merge(force: true, allow_failure: true) load_file(path, ) rescue PluginSyntaxError, PluginLoadError => ex err_msg = "could not load plugin at '#{path}': #{ex.}" [:allow_failure] ? log.debug(err_msg) : abort(PluginLoadError.new(err_msg)) nil end |
#load_remote(name, version, options = {}) ⇒ MB::Plugin?
Load a plugin of the given name and version from the remote Chef server
315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 |
# File 'lib/mb/plugin_manager.rb', line 315 def load_remote(name, version, = {}) = .reverse_merge(force: false, allow_failure: true) resource = ridley.cookbook.find(name, version) return unless resource && resource.has_motherbrain_plugin? begin scratch_dir = FileSystem.tmpdir("cbplugin") = File.join(scratch_dir, CookbookMetadata::JSON_FILENAME) plugin_path = File.join(scratch_dir, Plugin::PLUGIN_FILENAME) lockfile_path = File.join(scratch_dir, Berkshelf::Lockfile::BERKSFILE_LOCK) File.write(, resource..to_json) unless resource.download_file(:root_file, Plugin::PLUGIN_FILENAME, plugin_path) raise PluginLoadError, "failure downloading plugin file for #{resource.name}" end unless resource.download_file(:root_file, Berkshelf::Lockfile::BERKSFILE_LOCK, lockfile_path) log.info "No Berksfile.lock found for #{resource.name} - won't be able to use cookbook versions from lockfile" end load_file(scratch_dir, ) rescue PluginSyntaxError, PluginLoadError => ex err_msg = "could not load remote plugin #{name} (#{version}): #{ex.}" [:allow_failure] ? log.debug(err_msg) : abort(PluginLoadError.new(err_msg)) nil ensure FileUtils.rm_rf(scratch_dir) end end |
#reload(plugin) ⇒ Object
Remove and Add the given plugin from the set of plugins
390 391 392 |
# File 'lib/mb/plugin_manager.rb', line 390 def reload(plugin) add(plugin, force: true) end |
#reload_all ⇒ Array<MotherBrain::Plugin>
Reload plugins from Chef Server and from the Berkshelf
397 398 399 400 |
# File 'lib/mb/plugin_manager.rb', line 397 def reload_all clear_plugins load_all end |
#reload_installed ⇒ Array<MotherBrain::Plugin>
Reload plugins from the Berkshelf
405 406 407 |
# File 'lib/mb/plugin_manager.rb', line 405 def reload_installed load_all_installed(force: true) end |
#remote_versions(name) ⇒ Array<String>
List all versions of a plugin with the given name that are present on the remote Chef server. An empty array will be returned if no versions are present.
509 510 511 512 513 514 515 |
# File 'lib/mb/plugin_manager.rb', line 509 def remote_versions(name) remote_cookbook_versions(name).collect do |version| (plugin = load_remote(name, version)).nil? ? nil : plugin.version.to_s end.compact rescue Ridley::Errors::HTTPNotFound [] end |
#remove(plugin) ⇒ Object
Remove the given plugin from the set of plugins
412 413 414 |
# File 'lib/mb/plugin_manager.rb', line 412 def remove(plugin) @plugins.delete(plugin) end |
#satisfy(plugin_name, constraint, options = {}) ⇒ MB::Plugin
Return the best version of the plugin to use for the given constraint
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 |
# File 'lib/mb/plugin_manager.rb', line 430 def satisfy(plugin_name, constraint, = {}) = .reverse_merge(remote: false) constraint = Semverse::Constraint.new(constraint) # Optimize for equality operator. Don't need to find all of the versions if # we only care about one. if constraint.operator == "=" find(plugin_name, constraint.version, .slice(:remote)) elsif constraint.to_s == ">= 0.0.0" latest(plugin_name, .slice(:remote)) else graph = Solve::Graph.new versions(plugin_name, [:remote]).each do |version| graph.artifact(plugin_name, version) end solution = Solve.it!(graph, [[plugin_name, constraint]]) version = solution[plugin_name] # don't search the remote for the plugin again; we would have already done that by # calling versions() and including a {remote: true} option. find(plugin_name, version, remote: false) end rescue Semverse::NoSolutionError abort PluginNotFound.new(plugin_name, constraint) end |
#uninstall(name, version) ⇒ MB::Plugin?
Uninstall an installed plugin
464 465 466 467 468 469 470 471 472 473 |
# File 'lib/mb/plugin_manager.rb', line 464 def uninstall(name, version) unless plugin = find(name, version, remote: false) return nil end FileUtils.rm_rf(install_path_for(plugin)) remove(plugin) plugin end |
#versions(name, remote = false) ⇒ Array<String>
List all of the versions of the plugin of the given name
485 486 487 488 489 490 491 492 493 494 495 496 497 |
# File 'lib/mb/plugin_manager.rb', line 485 def versions(name, remote = false) all_versions = installed_versions(name) if remote all_versions += remote_versions(name) end if all_versions.empty? abort PluginNotFound.new(name) end all_versions end |