module Nucleon class Manager include Celluloid #----------------------------------------------------------------------------- @@supervisors = {} #----------------------------------------------------------------------------- # Plugin manager interface def self.init_manager(name) name = name.to_sym Manager.supervise_as name @@supervisors[name] = Celluloid::Actor[name] end #--- def self.connection(name = :core) name = name.to_sym init_manager(name) unless @@supervisors.has_key?(name) begin @@supervisors[name].test_connection rescue Celluloid::DeadActorError retry end @@supervisors[name] end #--- def initialize @logger = Nucleon.logger @namespaces = {} @types = {} @load_info = {} @plugins = {} end #----------------------------------------------------------------------------- # Property accessor / modifiers attr_reader :logger #--- def myself Actor.current end #--- def namespaces @namespaces.keys end def define_namespace(*names) names.each do |namespace| @namespaces[namespace.to_sym] = true end end #--- def types @types.keys end #--- def type_default(type) @types[type.to_sym] end #--- def loaded_plugins(type = nil, provider = nil) results = {} type = type.to_sym if type provider = provider.to_sym if provider if type && @load_info.has_key?(type) if provider && @load_info.has_key?(provider) results = @load_info[type][provider] else results = @load_info[type] end elsif ! type results = @load_info end results end #--- def plugins(type = nil, provider = nil) results = {} type = type.to_sym if type provider = provider.to_sym if provider if type && @plugins.has_key?(type) if provider && ! @plugins[type].keys.empty? @plugins[type].each do |instance_name, plugin| plugin = @plugins[type][instance_name] results[instance_name] = plugin if plugin.plugin_provider == provider end else results = @plugins[type] end elsif ! type results = @plugins end results end #----------------------------------------------------------------------------- # Operations def test_connection true end #--- def reload(core = false, &code) logger.info("Loading Nucleon plugins at #{Time.now}") if core Celluloid.logger = logger define_namespace :nucleon define_type :extension => nil, # Core :action => :update, # Core :project => :git, # Core :command => :bash, # Core :event => :regex, # Utility :template => :json, # Utility :translator => :json # Utility end # Allow block level namespace and type registration code.call(:define, myself) if code load_plugins(core, &code) logger.info("Finished loading Nucleon plugins at #{Time.now}") end #--- def define_type(type_info) if type_info.is_a?(Hash) logger.info("Defining plugin types at #{Time.now}") type_info.each do |type, default_provider| logger.debug("Mapping plugin type #{type} to default provider #{default_provider}") @types[type.to_sym] = default_provider end else logger.warn("Defined types must be specified as a hash to be registered properly") end end #--- def load_plugins(core = false, &code) if core # Register core plugins logger.info("Initializing core plugins at #{Time.now}") register(File.join(File.dirname(__FILE__), '..')) end # Register external Gem defined plugins Gems.register(true) # Register any other extension plugins exec(:register_plugins) # Catch any block level requests before autoloading code.call(:load, myself) if code # Autoload all registered plugins autoload end protected :load_plugins #--- def register(base_path, &code) namespaces.each do |namespace| namespace_path = File.join(base_path, namespace.to_s) register_namespace(namespace, namespace_path, &code) end end #--- def register_namespace(namespace, base_path, &code) if File.directory?(base_path) logger.info("Loading files from #{base_path} at #{Time.now}") Dir.glob(File.join(base_path, '*.rb')).each do |file| logger.debug("Loading file: #{file}") require file end logger.info("Loading directories from #{base_path} at #{Time.now}") Dir.entries(base_path).each do |path| unless path.match(/^\.\.?$/) register_type(namespace, base_path, path, &code) if types.include?(path.to_sym) end end end end protected :register_namespace #--- def register_type(namespace, base_path, plugin_type, &code) base_directory = File.join(base_path, plugin_type.to_s) if File.directory?(base_directory) logger.info("Registering #{base_directory} at #{Time.now}") Dir.glob(File.join(base_directory, '*.rb')).each do |file| add_build_info(namespace, plugin_type, file, &code) end end end protected :register_type #--- def add_build_info(namespace, type, file, &code) type = type.to_sym @load_info[type] = {} unless @load_info.has_key?(type) components = file.split(File::SEPARATOR) provider = components.pop.sub(/\.rb/, '').to_sym directory = components.join(File::SEPARATOR) logger.info("Loading nucleon #{type} plugin #{provider} at #{Time.now}") unless @load_info[type].has_key?(provider) data = { :namespace => namespace, :type => type, :provider => provider, :directory => directory, :file => file } code.call(data) if code logger.debug("Plugin #{type} loaded: #{data.inspect}") @load_info[type][provider] = data end end protected :add_build_info #--- def autoload logger.info("Autoloading registered plugins at #{Time.now}") @load_info.keys.each do |type| logger.debug("Autoloading type: #{type}") @load_info[type].each do |provider, plugin| logger.debug("Autoloading provider #{provider} at #{plugin[:directory]}") nucleon_require(plugin[:directory], provider) @load_info[type][provider][:class] = provider_class(plugin[:namespace], type, provider) logger.debug("Updated #{type} #{provider} load info: #{@load_info[type][provider].inspect}") # Make sure extensions are listening from the time they are loaded load(:extension, provider, { :name => provider }) if type == :extension # Create a persistent instance end end end #--- def load_base(type, provider, options = {}) logger.info("Fetching plugin #{type} provider #{provider} at #{Time.now}") config = Config.ensure(translate_type(type, options)) name = config.get(:name, nil) logger.debug("Plugin options: #{config.export.inspect}") if name logger.debug("Looking up existing instance of #{name}") existing_instance = get(type, name) logger.info("Using existing instance of #{type}, #{name}") if existing_instance end return existing_instance if existing_instance create(type, provider, options) end #--- def load(type, provider = nil, options = {}) default_provider = type_default(type) # Allow options to override provider config = Config.ensure(options) provider = config.get(:provider, provider) provider = default_provider unless provider load_base(type, provider, config) end #--- def load_multiple(type, data, build_hash = false, keep_array = false) logger.info("Fetching multiple plugins of #{type} at #{Time.now}") group = ( build_hash ? {} : [] ) klass = plugin_class(type) data = klass.build_info(type, data) if klass.respond_to?(:build_info) logger.debug("Translated plugin data: #{data.inspect}") data.each do |options| if plugin = load(type, options[:provider], options) if build_hash group[plugin.plugin_name] = plugin else group << plugin end end end return group.shift if ! build_hash && group.length == 1 && ! keep_array group end #--- def create(type, provider, options = {}) type = type.to_sym provider = provider.to_sym unless @types.has_key?(type) logger.warn("Plugin type #{type} creation requested but it has not been registered yet") return nil end info = @load_info[type][provider] if Util::Data.exists?(@load_info, [ type, provider ]) if info logger.debug("Plugin information for #{provider} #{type} found. Data: #{info.inspect}") instance_name = "#{provider}_" + Nucleon.sha1(options) options = translate(info[:namespace], type, provider, options) @plugins[type] = {} unless @plugins.has_key?(type) unless instance_name && @plugins[type].has_key?(instance_name) info[:instance_name] = instance_name options[:meta] = Config.new(info).import(Util::Data.hash(options[:meta])) logger.info("Creating new plugin #{provider} #{type} with #{options.inspect}") plugin = info[:class].new(type, provider, options) @plugins[type][instance_name] = plugin end return @plugins[type][instance_name] end logger.warn("Plugin information cannot be found for plugin #{type} #{provider}") nil end #--- def get(type, name) logger.info("Fetching plugin #{type} #{name}") if @plugins.has_key?(type) @plugins[type].each do |instance_name, plugin| if plugin.plugin_name.to_s == name.to_s logger.debug("Plugin #{type} #{name} found") return plugin end end end logger.debug("Plugin #{type} #{name} not found") nil end #--- def remove(plugin) if plugin && plugin.respond_to?(:plugin_type) && @plugins.has_key?(plugin.plugin_type) logger.debug("Removing #{plugin.plugin_type} #{plugin.plugin_name}") @plugins[plugin.plugin_type].delete(plugin.plugin_instance_name) plugin.terminate if plugin.respond_to?(:terminate) else logger.warn("Cannot remove plugin: #{plugin.inspect}") end end #----------------------------------------------------------------------------- # Extension hook execution def exec(method, options = {}) results = nil if Nucleon.log_level == :hook # To save processing on rendering logger.hook("Executing extension hook { #{method} } at #{Time.now} with:\n#{PP.pp(options, '')}\n") end extensions = plugins(:extension) extensions.each do |name, plugin| provider = plugin.plugin_provider result = nil logger.debug("Checking extension #{provider}") if plugin.respond_to?(method) results = {} if results.nil? result = plugin.send(method, options) logger.info("Completed hook #{method} at #{Time.now} with: #{result.inspect}") if block_given? results[provider] = yield(:process, result) logger.debug("Processed extension result into: #{results[provider].inspect}") end if results[provider].nil? logger.debug("Setting extension result to: #{result.inspect}") results[provider] = result end end end if ! results.nil? && block_given? results = yield(:reduce, results) logger.debug("Reducing extension results to: #{results.inspect}") else logger.debug("Final extension results: #{results.inspect}") end results end #--- def config(type, options = {}) config = Config.ensure(options) logger.debug("Generating #{type} extended configuration from: #{config.export.inspect}") exec("#{type}_config", Config.new(config.export)) do |op, data| if op == :reduce data.each do |provider, result| config.defaults(result) end nil else hash(data) end end config.delete(:extension_type) logger.debug("Final extended configuration: #{config.export.inspect}") config end #--- def check(method, options = {}) config = Config.ensure(options) logger.debug("Checking extension #{method} given: #{config.export.inspect}") success = exec(method, config.import({ :extension_type => :check })) do |op, data| if op == :reduce ! data.values.include?(false) else data ? true : false end end success = success.nil? || success ? true : false logger.debug("Extension #{method} check result: #{success.inspect}") success end #--- def value(method, value, options = {}) config = Config.ensure(options) logger.debug("Setting extension #{method} value given: #{value.inspect}") exec(method, config.import({ :value => value, :extension_type => :value })) do |op, data| if op == :process value = data unless data.nil? end end logger.debug("Extension #{method} retrieved value: #{value.inspect}") value end #--- def collect(method, options = {}) config = Config.ensure(options) values = [] logger.debug("Collecting extension #{method} values") exec(method, config.import({ :extension_type => :collect })) do |op, data| if op == :process values << data unless data.nil? end end logger.debug("Extension #{method} collected values: #{values.inspect}") values end #----------------------------------------------------------------------------- # Utilities def translate_type(type, options) klass = plugin_class(type) logger.debug("Executing option translation for: #{klass.inspect}") options = klass.send(:translate, options) if klass.respond_to?(:translate) options end #--- def translate(namespace, type, provider, options) klass = provider_class(namespace, type, provider) logger.debug("Executing option translation for: #{klass.inspect}") options = klass.send(:translate, options) if klass.respond_to?(:translate) options end #--- def class_name(name, separator = '::', want_array = FALSE) components = [] case name when String, Symbol components = name.to_s.split(separator) when Array components = name end components.collect! do |value| value = value.to_s.strip value[0] = value.capitalize[0] if value =~ /^[a-z]/ value end if want_array return components end components.join(separator) end #--- def class_const(name, separator = '::') components = class_name(name, separator, TRUE) constant = Object components.each do |component| constant = constant.const_defined?(component) ? constant.const_get(component) : constant.const_missing(component) end constant end #--- def plugin_class(type) class_const([ :nucleon, :plugin, type ]) end #--- def provider_class(namespace, type, provider) class_const([ namespace, type, provider ]) end end end