mcollective-2.6.0/0000755000175000017500000000000012375446416013047 5ustar jonasjonasmcollective-2.6.0/lib/0000755000175000017500000000000012375446416013615 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/0000755000175000017500000000000012375446416016123 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/rpc/0000755000175000017500000000000012375446416016707 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/rpc/client.rb0000644000175000017500000011432612375446407020521 0ustar jonasjonasmodule MCollective module RPC # The main component of the Simple RPC client system, this wraps around MCollective::Client # and just brings in a lot of convention and standard approached. class Client attr_accessor :timeout, :verbose, :filter, :config, :progress, :ttl, :reply_to attr_reader :client, :stats, :ddl, :agent, :limit_targets, :limit_method, :output_format, :batch_size, :batch_sleep_time, :batch_mode attr_reader :discovery_options, :discovery_method, :default_discovery_method, :limit_seed @@initial_options = nil # Creates a stub for a remote agent, you can pass in an options array in the flags # which will then be used else it will just create a default options array with # filtering enabled based on the standard command line use. # # rpc = RPC::Client.new("rpctest", :configfile => "client.cfg", :options => options) # # You typically would not call this directly you'd use MCollective::RPC#rpcclient instead # which is a wrapper around this that can be used as a Mixin def initialize(agent, flags = {}) if flags.include?(:options) initial_options = flags[:options] elsif @@initial_options initial_options = Marshal.load(@@initial_options) else oparser = MCollective::Optionparser.new({ :verbose => false, :progress_bar => true, :mcollective_limit_targets => false, :batch_size => nil, :batch_sleep_time => 1 }, "filter") initial_options = oparser.parse do |parser, opts| if block_given? yield(parser, opts) end Helpers.add_simplerpc_options(parser, opts) end @@initial_options = Marshal.dump(initial_options) end @initial_options = initial_options @config = initial_options[:config] @client = MCollective::Client.new(@config) @client.options = initial_options @stats = Stats.new @agent = agent @timeout = initial_options[:timeout] || 5 @verbose = initial_options[:verbose] @filter = initial_options[:filter] || Util.empty_filter @discovered_agents = nil @progress = initial_options[:progress_bar] @limit_targets = initial_options[:mcollective_limit_targets] @limit_method = Config.instance.rpclimitmethod @limit_seed = initial_options[:limit_seed] || nil @output_format = initial_options[:output_format] || :console @force_direct_request = false @reply_to = initial_options[:reply_to] @discovery_method = initial_options[:discovery_method] if !@discovery_method @discovery_method = Config.instance.default_discovery_method @default_discovery_method = true else @default_discovery_method = false end @discovery_options = initial_options[:discovery_options] || [] @force_display_mode = initial_options[:force_display_mode] || false @batch_size = initial_options[:batch_size] || 0 @batch_sleep_time = Float(initial_options[:batch_sleep_time] || 1) @batch_mode = determine_batch_mode(@batch_size) agent_filter agent @discovery_timeout = @initial_options.fetch(:disctimeout, nil) || Config.instance.discovery_timeout @collective = @client.collective @ttl = initial_options[:ttl] || Config.instance.ttl @publish_timeout = initial_options[:publish_timeout] || Config.instance.publish_timeout @threaded = initial_options[:threaded] || Config.instance.threaded # if we can find a DDL for the service override # the timeout of the client so we always magically # wait appropriate amounts of time. # # We add the discovery timeout to the ddl supplied # timeout as the discovery timeout tends to be tuned # for local network conditions and fact source speed # which would other wise not be accounted for and # some results might get missed. # # We do this only if the timeout is the default 5 # seconds, so that users cli overrides will still # get applied # # DDLs are required, failure to find a DDL is fatal @ddl = DDL.new(agent) @stats.ddl = @ddl @timeout = @ddl.meta[:timeout] + discovery_timeout if @timeout == 5 # allows stderr and stdout to be overridden for testing # but also for web apps that might not want a bunch of stuff # generated to actual file handles if initial_options[:stderr] @stderr = initial_options[:stderr] else @stderr = STDERR @stderr.sync = true end if initial_options[:stdout] @stdout = initial_options[:stdout] else @stdout = STDOUT @stdout.sync = true end end # Disconnects cleanly from the middleware def disconnect @client.disconnect end # Returns help for an agent if a DDL was found def help(template) @ddl.help(template) end # Creates a suitable request hash for the SimpleRPC agent. # # You'd use this if you ever wanted to take care of sending # requests on your own - perhaps via Client#sendreq if you # didn't care for responses. # # In that case you can just do: # # msg = your_rpc.new_request("some_action", :foo => :bar) # filter = your_rpc.filter # # your_rpc.client.sendreq(msg, msg[:agent], filter) # # This will send a SimpleRPC request to the action some_action # with arguments :foo = :bar, it will return immediately and # you will have no indication at all if the request was receieved or not # # Clearly the use of this technique should be limited and done only # if your code requires such a thing def new_request(action, data) callerid = PluginManager["security_plugin"].callerid raise 'callerid received from security plugin is not valid' unless PluginManager["security_plugin"].valid_callerid?(callerid) {:agent => @agent, :action => action, :caller => callerid, :data => data} end # For the provided arguments and action the input arguments get # modified by supplying any defaults provided in the DDL for arguments # that were not supplied in the request # # We then pass the modified arguments to the DDL for validation def validate_request(action, args) raise "No DDL found for agent %s cannot validate inputs" % @agent unless @ddl @ddl.set_default_input_arguments(action, args) @ddl.validate_rpc_request(action, args) end # Magic handler to invoke remote methods # # Once the stub is created using the constructor or the RPC#rpcclient helper you can # call remote actions easily: # # ret = rpc.echo(:msg => "hello world") # # This will call the 'echo' action of the 'rpctest' agent and return the result as an array, # the array will be a simplified result set from the usual full MCollective::Client#req with # additional error codes and error text: # # { # :sender => "remote.box.com", # :statuscode => 0, # :statusmsg => "OK", # :data => "hello world" # } # # If :statuscode is 0 then everything went find, if it's 1 then you supplied the correct arguments etc # but the request could not be completed, you'll find a human parsable reason in :statusmsg then. # # Codes 2 to 5 maps directly to UnknownRPCAction, MissingRPCData, InvalidRPCData and UnknownRPCError # see below for a description of those, in each case :statusmsg would be the reason for failure. # # To get access to the full result of the MCollective::Client#req calls you can pass in a block: # # rpc.echo(:msg => "hello world") do |resp| # pp resp # end # # In this case resp will the result from MCollective::Client#req. Instead of returning simple # text and codes as above you'll also need to handle the following exceptions: # # UnknownRPCAction - There is no matching action on the agent # MissingRPCData - You did not supply all the needed parameters for the action # InvalidRPCData - The data you did supply did not pass validation # UnknownRPCError - Some other error prevented the agent from running # # During calls a progress indicator will be shown of how many results we've received against # how many nodes were discovered, you can disable this by setting progress to false: # # rpc.progress = false # # This supports a 2nd mode where it will send the SimpleRPC request and never handle the # responses. It's a bit like UDP, it sends the request with the filter attached and you # only get back the requestid, you have no indication about results. # # You can invoke this using: # # puts rpc.echo(:process_results => false) # # This will output just the request id. # # Batched processing is supported: # # printrpc rpc.ping(:batch_size => 5) # # This will do everything exactly as normal but communicate to only 5 # agents at a time def method_missing(method_name, *args, &block) # set args to an empty hash if nothings given args = args[0] args = {} if args.nil? action = method_name.to_s @stats.reset validate_request(action, args) # TODO(ploubser): The logic here seems poor. It implies that it is valid to # pass arguments where batch_mode is set to false and batch_mode > 0. # If this is the case we completely ignore the supplied value of batch_mode # and do our own thing. # if a global batch size is set just use that else set it # in the case that it was passed as an argument batch_mode = args.include?(:batch_size) || @batch_mode batch_size = args.delete(:batch_size) || @batch_size batch_sleep_time = args.delete(:batch_sleep_time) || @batch_sleep_time # if we were given a batch_size argument thats 0 and batch_mode was # determined to be on via global options etc this will allow a batch_size # of 0 to disable or batch_mode for this call only batch_mode = determine_batch_mode(batch_size) # Handle single target requests by doing discovery and picking # a random node. Then do a custom request specifying a filter # that will only match the one node. if @limit_targets target_nodes = pick_nodes_from_discovered(@limit_targets) Log.debug("Picked #{target_nodes.join(',')} as limited target(s)") custom_request(action, args, target_nodes, {"identity" => /^(#{target_nodes.join('|')})$/}, &block) elsif batch_mode call_agent_batched(action, args, options, batch_size, batch_sleep_time, &block) else call_agent(action, args, options, :auto, &block) end end # Constructs custom requests with custom filters and discovery data # the idea is that this would be used in web applications where you # might be using a cached copy of data provided by a registration agent # to figure out on your own what nodes will be responding and what your # filter would be. # # This will help you essentially short circuit the traditional cycle of: # # mc discover / call / wait for discovered nodes # # by doing discovery however you like, contructing a filter and a list of # nodes you expect responses from. # # Other than that it will work exactly like a normal call, blocks will behave # the same way, stats will be handled the same way etcetc # # If you just wanted to contact one machine for example with a client that # already has other filter options setup you can do: # # puppet.custom_request("runonce", {}, ["your.box.com"], {:identity => "your.box.com"}) # # This will do runonce action on just 'your.box.com', no discovery will be # done and after receiving just one response it will stop waiting for responses # # If direct_addressing is enabled in the config file you can provide an empty # hash as a filter, this will force that request to be a directly addressed # request which technically does not need filters. If you try to use this # mode with direct addressing disabled an exception will be raise def custom_request(action, args, expected_agents, filter = {}, &block) validate_request(action, args) if filter == {} && !Config.instance.direct_addressing raise "Attempted to do a filterless custom_request without direct_addressing enabled, preventing unexpected call to all nodes" end @stats.reset custom_filter = Util.empty_filter custom_options = options.clone # merge the supplied filter with the standard empty one # we could just use the merge method but I want to be sure # we dont merge in stuff that isnt actually valid ["identity", "fact", "agent", "cf_class", "compound"].each do |ftype| if filter.include?(ftype) custom_filter[ftype] = [filter[ftype], custom_filter[ftype]].flatten end end # ensure that all filters at least restrict the call to the agent we're a proxy for custom_filter["agent"] << @agent unless custom_filter["agent"].include?(@agent) custom_options[:filter] = custom_filter # Fake out the stats discovery would have put there @stats.discovered_agents([expected_agents].flatten) # Handle fire and forget requests # # If a specific reply-to was set then from the client perspective this should # be a fire and forget request too since no response will ever reach us - it # will go to the reply-to destination if args[:process_results] == false || @reply_to return fire_and_forget_request(action, args, custom_filter) end # Now do a call pretty much exactly like in method_missing except with our own # options and discovery magic if block_given? call_agent(action, args, custom_options, [expected_agents].flatten) do |r| block.call(r) end else call_agent(action, args, custom_options, [expected_agents].flatten) end end def discovery_timeout return @discovery_timeout if @discovery_timeout return @client.discoverer.ddl.meta[:timeout] end def discovery_timeout=(timeout) @discovery_timeout = Float(timeout) # we calculate the overall timeout from the DDL of the agent and # the supplied discovery timeout unless someone specifically # specifies a timeout to the constructor # # But if we also then specifically set a discovery_timeout on the # agent that has to override the supplied timeout so we then # calculate a correct timeout based on DDL timeout and the # supplied discovery timeout @timeout = @ddl.meta[:timeout] + discovery_timeout end # Sets the discovery method. If we change the method there are a # number of steps to take: # # - set the new method # - if discovery options were provided, re-set those to initially # provided ones else clear them as they might now apply to a # different provider # - update the client options so it knows there is a new discovery # method in force # - reset discovery data forcing a discover on the next request # # The remaining item is the discovery timeout, we leave that as is # since that is the user supplied timeout either via initial options # or via specifically setting it on the client. def discovery_method=(method) @default_discovery_method = false @discovery_method = method if @initial_options[:discovery_options] @discovery_options = @initial_options[:discovery_options] else @discovery_options.clear end @client.options = options reset end def discovery_options=(options) @discovery_options = [options].flatten reset end # Sets the class filter def class_filter(klass) @filter["cf_class"] = @filter["cf_class"] | [klass] @filter["cf_class"].compact! reset end # Sets the fact filter def fact_filter(fact, value=nil, operator="=") return if fact.nil? return if fact == false if value.nil? parsed = Util.parse_fact_string(fact) @filter["fact"] = @filter["fact"] | [parsed] unless parsed == false else parsed = Util.parse_fact_string("#{fact}#{operator}#{value}") @filter["fact"] = @filter["fact"] | [parsed] unless parsed == false end @filter["fact"].compact! reset end # Sets the agent filter def agent_filter(agent) @filter["agent"] = @filter["agent"] | [agent] @filter["agent"].compact! reset end # Sets the identity filter def identity_filter(identity) @filter["identity"] = @filter["identity"] | [identity] @filter["identity"].compact! reset end # Set a compound filter def compound_filter(filter) @filter["compound"] = @filter["compound"] | [Matcher.create_compound_callstack(filter)] reset end # Resets various internal parts of the class, most importantly it clears # out the cached discovery def reset @discovered_agents = nil end # Reet the filter to an empty one def reset_filter @filter = Util.empty_filter agent_filter @agent end # Does discovery based on the filters set, if a discovery was # previously done return that else do a new discovery. # # Alternatively if identity filters are given and none of them are # regular expressions then just use the provided data as discovered # data, avoiding discovery # # Discovery can be forced if direct_addressing is enabled by passing # in an array of nodes with :nodes or JSON data like those produced # by mcollective RPC JSON output using :json # # Will show a message indicating its doing discovery if running # verbose or if the :verbose flag is passed in. # # Use reset to force a new discovery def discover(flags={}) flags.keys.each do |key| raise "Unknown option #{key} passed to discover" unless [:verbose, :hosts, :nodes, :json].include?(key) end flags.include?(:verbose) ? verbose = flags[:verbose] : verbose = @verbose verbose = false unless @output_format == :console # flags[:nodes] and flags[:hosts] are the same thing, we should never have # allowed :hosts as that was inconsistent with the established terminology flags[:nodes] = flags.delete(:hosts) if flags.include?(:hosts) reset if flags[:nodes] || flags[:json] unless @discovered_agents # if either hosts or JSON is supplied try to figure out discovery data from there # if direct_addressing is not enabled this is a critical error as the user might # not have supplied filters so raise an exception if flags[:nodes] || flags[:json] raise "Can only supply discovery data if direct_addressing is enabled" unless Config.instance.direct_addressing hosts = [] if flags[:nodes] hosts = Helpers.extract_hosts_from_array(flags[:nodes]) elsif flags[:json] hosts = Helpers.extract_hosts_from_json(flags[:json]) end raise "Could not find any hosts in discovery data provided" if hosts.empty? @discovered_agents = hosts @force_direct_request = true else identity_filter_discovery_optimization end end # All else fails we do it the hard way using a traditional broadcast unless @discovered_agents @stats.time_discovery :start @client.options = options # if compound filters are used the only real option is to use the mc # discovery plugin since its the only capable of using data queries etc # and we do not want to degrade that experience just to allow compounds # on other discovery plugins the UX would be too bad raising complex sets # of errors etc. @client.discoverer.force_discovery_method_by_filter(options[:filter]) if verbose actual_timeout = @client.discoverer.discovery_timeout(discovery_timeout, options[:filter]) if actual_timeout > 0 @stderr.print("Discovering hosts using the %s method for %d second(s) .... " % [@client.discoverer.discovery_method, actual_timeout]) else @stderr.print("Discovering hosts using the %s method .... " % [@client.discoverer.discovery_method]) end end # if the requested limit is a pure number and not a percent # and if we're configured to use the first found hosts as the # limit method then pass in the limit thus minimizing the amount # of work we do in the discover phase and speeding it up significantly if @limit_method == :first and @limit_targets.is_a?(Fixnum) @discovered_agents = @client.discover(@filter, discovery_timeout, @limit_targets) else @discovered_agents = @client.discover(@filter, discovery_timeout) end @stderr.puts(@discovered_agents.size) if verbose @force_direct_request = @client.discoverer.force_direct_mode? @stats.time_discovery :end end @stats.discovered_agents(@discovered_agents) RPC.discovered(@discovered_agents) @discovered_agents end # Provides a normal options hash like you would get from # Optionparser def options {:disctimeout => discovery_timeout, :timeout => @timeout, :verbose => @verbose, :filter => @filter, :collective => @collective, :output_format => @output_format, :ttl => @ttl, :discovery_method => @discovery_method, :discovery_options => @discovery_options, :force_display_mode => @force_display_mode, :config => @config, :publish_timeout => @publish_timeout, :threaded => @threaded} end # Sets the collective we are communicating with def collective=(c) raise "Unknown collective #{c}" unless Config.instance.collectives.include?(c) @collective = c @client.options = options reset end # Sets and sanity checks the limit_targets variable # used to restrict how many nodes we'll target # Limit targets can be reset by passing nil or false def limit_targets=(limit) if !limit @limit_targets = nil return end if limit.is_a?(String) raise "Invalid limit specified: #{limit} valid limits are /^\d+%*$/" unless limit =~ /^\d+%*$/ begin @limit_targets = Integer(limit) rescue @limit_targets = limit end else @limit_targets = Integer(limit) end end # Sets and sanity check the limit_method variable # used to determine how to limit targets if limit_targets is set def limit_method=(method) method = method.to_sym unless method.is_a?(Symbol) raise "Unknown limit method #{method} must be :random or :first" unless [:random, :first].include?(method) @limit_method = method end # Sets the batch size, if the size is set to 0 that will disable batch mode def batch_size=(limit) unless Config.instance.direct_addressing raise "Can only set batch size if direct addressing is supported" end validate_batch_size(limit) @batch_size = limit @batch_mode = determine_batch_mode(@batch_size) end def batch_sleep_time=(time) raise "Can only set batch sleep time if direct addressing is supported" unless Config.instance.direct_addressing @batch_sleep_time = Float(time) end # Pick a number of nodes from the discovered nodes # # The count should be a string that can be either # just a number or a percentage like 10% # # It will select nodes from the discovered list based # on the rpclimitmethod configuration option which can # be either :first or anything else # # - :first would be a simple way to do a distance based # selection # - anything else will just pick one at random # - if random chosen, and batch-seed set, then set srand # for the generator, and reset afterwards def pick_nodes_from_discovered(count) if count =~ /%$/ pct = Integer((discover.size * (count.to_f / 100))) pct == 0 ? count = 1 : count = pct else count = Integer(count) end return discover if discover.size <= count result = [] if @limit_method == :first return discover[0, count] else # we delete from the discovered list because we want # to be sure there is no chance that the same node will # be randomly picked twice. So we have to clone the # discovered list else this method will only ever work # once per discovery cycle and not actually return the # right nodes. haystack = discover.clone if @limit_seed haystack.sort! srand(@limit_seed) end count.times do rnd = rand(haystack.size) result << haystack.delete_at(rnd) end # Reset random number generator to fresh seed # As our seed from options is most likely short srand if @limit_seed end [result].flatten end def load_aggregate_functions(action, ddl) return nil unless ddl return nil unless ddl.action_interface(action).keys.include?(:aggregate) return Aggregate.new(ddl.action_interface(action)) rescue => e Log.error("Failed to load aggregate functions, calculating summaries disabled: %s: %s (%s)" % [e.backtrace.first, e.to_s, e.class]) return nil end def aggregate_reply(reply, aggregate) return nil unless aggregate aggregate.call_functions(reply) return aggregate rescue Exception => e Log.error("Failed to calculate aggregate summaries for reply from %s, calculating summaries disabled: %s: %s (%s)" % [reply[:senderid], e.backtrace.first, e.to_s, e.class]) return nil end def rpc_result_from_reply(agent, action, reply) Result.new(agent, action, {:sender => reply[:senderid], :statuscode => reply[:body][:statuscode], :statusmsg => reply[:body][:statusmsg], :data => reply[:body][:data]}) end # for requests that do not care for results just # return the request id and don't do any of the # response processing. # # We send the :process_results flag with to the # nodes so they can make decisions based on that. # # Should only be called via method_missing def fire_and_forget_request(action, args, filter=nil) validate_request(action, args) identity_filter_discovery_optimization req = new_request(action.to_s, args) filter = options[:filter] unless filter message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => filter, :options => options}) message.reply_to = @reply_to if @reply_to if @force_direct_request || @client.discoverer.force_direct_mode? message.discovered_hosts = discover.clone message.type = :direct_request end client.sendreq(message, nil) end # if an identity filter is supplied and it is all strings no regex we can use that # as discovery data, technically the identity filter is then redundant if we are # in direct addressing mode and we could empty it out but this use case should # only really be for a few -I's on the CLI # # For safety we leave the filter in place for now, that way we can support this # enhancement also in broadcast mode. # # This is only needed for the 'mc' discovery method, other methods might change # the concept of identity to mean something else so we should pass the full # identity filter to them def identity_filter_discovery_optimization if options[:filter]["identity"].size > 0 && @discovery_method == "mc" regex_filters = options[:filter]["identity"].select{|i| i.match("^\/")}.size if regex_filters == 0 @discovered_agents = options[:filter]["identity"].clone @force_direct_request = true if Config.instance.direct_addressing end end end # Calls an agent in a way very similar to call_agent but it supports batching # the queries to the network. # # The result sets, stats, block handling etc is all exactly like you would expect # from normal call_agent. # # This is used by method_missing and works only with direct addressing mode def call_agent_batched(action, args, opts, batch_size, sleep_time, &block) raise "Batched requests requires direct addressing" unless Config.instance.direct_addressing raise "Cannot bypass result processing for batched requests" if args[:process_results] == false validate_batch_size(batch_size) sleep_time = Float(sleep_time) Log.debug("Calling #{agent}##{action} in batches of #{batch_size} with sleep time of #{sleep_time}") @force_direct_request = true discovered = discover results = [] respcount = 0 if discovered.size > 0 req = new_request(action.to_s, args) aggregate = load_aggregate_functions(action, @ddl) if @progress && !block_given? twirl = Progress.new @stdout.puts @stdout.print twirl.twirl(respcount, discovered.size) end if (batch_size =~ /^(\d+)%$/) # determine batch_size as a percentage of the discovered array's size batch_size = (discovered.size / 100.0 * Integer($1)).ceil else batch_size = Integer(batch_size) end @stats.requestid = nil processed_nodes = 0 discovered.in_groups_of(batch_size) do |hosts| message = Message.new(req, nil, {:agent => @agent, :type => :direct_request, :collective => @collective, :filter => opts[:filter], :options => opts}) # first time round we let the Message object create a request id # we then re-use it for future requests to keep auditing sane etc @stats.requestid = message.create_reqid unless @stats.requestid message.requestid = @stats.requestid message.discovered_hosts = hosts.clone.compact @client.req(message) do |resp| respcount += 1 if block_given? aggregate = process_results_with_block(action, resp, block, aggregate) else @stdout.print twirl.twirl(respcount, discovered.size) if @progress result, aggregate = process_results_without_block(resp, action, aggregate) results << result end end if @initial_options[:sort] results.sort! end @stats.noresponsefrom.concat @client.stats[:noresponsefrom] @stats.responses += @client.stats[:responses] @stats.blocktime += @client.stats[:blocktime] + sleep_time @stats.totaltime += @client.stats[:totaltime] @stats.discoverytime += @client.stats[:discoverytime] processed_nodes += hosts.length if (discovered.length > processed_nodes) sleep sleep_time end end @stats.aggregate_summary = aggregate.summarize if aggregate @stats.aggregate_failures = aggregate.failed if aggregate else @stderr.print("\nNo request sent, we did not discover any nodes.") end @stats.finish_request RPC.stats(@stats) @stdout.print("\n") if @progress if block_given? return stats else return [results].flatten end end # Handles traditional calls to the remote agents with full stats # blocks, non blocks and everything else supported. # # Other methods of calling the nodes can reuse this code by # for example specifying custom options and discovery data def call_agent(action, args, opts, disc=:auto, &block) # Handle fire and forget requests and make sure # the :process_results value is set appropriately # # specific reply-to requests should be treated like # fire and forget since the client will never get # the responses if args[:process_results] == false || @reply_to return fire_and_forget_request(action, args) else args[:process_results] = true end # Do discovery when no specific discovery array is given # # If an array is given set the force_direct_request hint that # will tell the message object to be a direct request one if disc == :auto discovered = discover else @force_direct_request = true if Config.instance.direct_addressing discovered = disc end req = new_request(action.to_s, args) message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => opts[:filter], :options => opts}) message.discovered_hosts = discovered.clone results = [] respcount = 0 if discovered.size > 0 message.type = :direct_request if @force_direct_request if @progress && !block_given? twirl = Progress.new @stdout.puts @stdout.print twirl.twirl(respcount, discovered.size) end aggregate = load_aggregate_functions(action, @ddl) @client.req(message) do |resp| respcount += 1 if block_given? aggregate = process_results_with_block(action, resp, block, aggregate) else @stdout.print twirl.twirl(respcount, discovered.size) if @progress result, aggregate = process_results_without_block(resp, action, aggregate) results << result end end if @initial_options[:sort] results.sort! end @stats.aggregate_summary = aggregate.summarize if aggregate @stats.aggregate_failures = aggregate.failed if aggregate @stats.client_stats = @client.stats else @stderr.print("\nNo request sent, we did not discover any nodes.") end @stats.finish_request RPC.stats(@stats) @stdout.print("\n\n") if @progress if block_given? return stats else return [results].flatten end end # Handles result sets that has no block associated, sets fails and ok # in the stats object and return a hash of the response to send to the # caller def process_results_without_block(resp, action, aggregate) @stats.node_responded(resp[:senderid]) result = rpc_result_from_reply(@agent, action, resp) aggregate = aggregate_reply(result, aggregate) if aggregate if resp[:body][:statuscode] == 0 || resp[:body][:statuscode] == 1 @stats.ok if resp[:body][:statuscode] == 0 @stats.fail if resp[:body][:statuscode] == 1 else @stats.fail end [result, aggregate] end # process client requests by calling a block on each result # in this mode we do not do anything fancy with the result # objects and we raise exceptions if there are problems with # the data def process_results_with_block(action, resp, block, aggregate) @stats.node_responded(resp[:senderid]) result = rpc_result_from_reply(@agent, action, resp) aggregate = aggregate_reply(result, aggregate) if aggregate @stats.ok if resp[:body][:statuscode] == 0 @stats.fail if resp[:body][:statuscode] != 0 @stats.time_block_execution :start case block.arity when 1 block.call(resp) when 2 block.call(resp, result) end @stats.time_block_execution :end return aggregate end private def determine_batch_mode(batch_size) if (batch_size != 0 && batch_size != "0") return true end return false end # Validate the bach_size based on the following criteria # batch_size is percentage string and it's more than 0 percent # batch_size is a string of digits # batch_size is of type Integer def validate_batch_size(batch_size) if (batch_size.is_a?(Integer)) return elsif (batch_size.is_a?(String)) if ((batch_size =~ /^(\d+)%$/ && Integer($1) != 0) || batch_size =~ /^(\d+)$/) return end end raise("batch_size must be an integer or match a percentage string (e.g. '24%'") end end end end mcollective-2.6.0/lib/mcollective/rpc/agent.rb0000644000175000017500000003236412375446407020342 0ustar jonasjonasmodule MCollective module RPC # A wrapper around the traditional agent, it takes care of a lot of the tedious setup # you would do for each agent allowing you to just create methods following a naming # standard leaving the heavy lifting up to this clas. # # See http://marionette-collective.org/simplerpc/agents.html # # It only really makes sense to use this with a Simple RPC client on the other end, basic # usage would be: # # module MCollective # module Agent # class Helloworld e Log.error("Failed to load DDL for the '%s' agent, DDLs are required: %s: %s" % [@agent_name, e.class, e.to_s]) raise DDLValidationError end def handlemsg(msg, connection) @request = RPC::Request.new(msg, @ddl) @reply = RPC::Reply.new(@request.action, @ddl) begin # Incoming requests need to be validated against the DDL thus reusing # all the work users put into creating DDLs and creating a consistent # quality of input validation everywhere with the a simple once off # investment of writing a DDL @request.validate! # Calls the authorization plugin if any is defined # if this raises an exception we wil just skip processing this # message authorization_hook(@request) if respond_to?("authorization_hook") # Audits the request, currently continues processing the message # we should make this a configurable so that an audit failure means # a message wont be processed by this node depending on config audit_request(@request, connection) before_processing_hook(msg, connection) if respond_to?("#{@request.action}_action") send("#{@request.action}_action") else raise UnknownRPCAction, "Unknown action '#{@request.action}' for agent '#{@request.agent}'" end rescue RPCAborted => e @reply.fail e.to_s, 1 rescue UnknownRPCAction => e @reply.fail e.to_s, 2 rescue MissingRPCData => e @reply.fail e.to_s, 3 rescue InvalidRPCData, DDLValidationError => e @reply.fail e.to_s, 4 rescue UnknownRPCError => e Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s]) Log.error(e.backtrace.join("\n\t")) @reply.fail e.to_s, 5 rescue Exception => e Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s]) Log.error(e.backtrace.join("\n\t")) @reply.fail e.to_s, 5 end after_processing_hook if @request.should_respond? return @reply.to_hash else Log.debug("Client did not request a response, surpressing reply") return nil end end # By default RPC Agents support a toggle in the configuration that # can enable and disable them based on the agent name # # Example an agent called Foo can have: # # plugin.foo.activate_agent = false # # and this will prevent the agent from loading on this particular # machine. # # Agents can use the activate_when helper to override this for example: # # activate_when do # File.exist?("/usr/bin/puppet") # end def self.activate? agent_name = self.to_s.split("::").last.downcase config = Config.instance Log.debug("Starting default activation checks for #{agent_name}") # Check global state to determine if agent should be loaded should_activate = config.activate_agents # Check agent specific state to determine if agent should be loaded should_activate = Util.str_to_bool(config.pluginconf.fetch("#{agent_name}.activate_agent", should_activate)) unless should_activate Log.debug("Found plugin configuration '#{agent_name}.activate_agent' with value '#{should_activate}'") end return should_activate end # Returns an array of actions this agent support def self.actions public_instance_methods.sort.grep(/_action$/).map do |method| $1 if method =~ /(.+)_action$/ end end private # Runs a command via the MC::Shell wrapper, options are as per MC::Shell # # The simplest use is: # # out = "" # err = "" # status = run("echo 1", :stdout => out, :stderr => err) # # reply[:out] = out # reply[:error] = err # reply[:exitstatus] = status # # This can be simplified as: # # reply[:exitstatus] = run("echo 1", :stdout => :out, :stderr => :error) # # You can set a command specific environment and cwd: # # run("echo 1", :cwd => "/tmp", :environment => {"FOO" => "BAR"}) # # This will run 'echo 1' from /tmp with FOO=BAR in addition to a setting forcing # LC_ALL = C. To prevent LC_ALL from being set either set it specifically or: # # run("echo 1", :cwd => "/tmp", :environment => nil) # # Exceptions here will be handled by the usual agent exception handler or any # specific one you create, if you dont it will just fall through and be sent # to the client. # # If the shell handler fails to return a Process::Status instance for exit # status this method will return -1 as the exit status def run(command, options={}) shellopts = {} # force stderr and stdout to be strings as the library # will append data to them if given using the << method. # # if the data pased to :stderr or :stdin is a Symbol # add that into the reply hash with that Symbol [:stderr, :stdout].each do |k| if options.include?(k) if options[k].is_a?(Symbol) reply[ options[k] ] = "" shellopts[k] = reply[ options[k] ] else if options[k].respond_to?("<<") shellopts[k] = options[k] else reply.fail! "#{k} should support << while calling run(#{command})" end end end end [:stdin, :cwd, :environment, :timeout].each do |k| if options.include?(k) shellopts[k] = options[k] end end shell = Shell.new(command, shellopts) shell.runcommand if options[:chomp] shellopts[:stdout].chomp! if shellopts[:stdout].is_a?(String) shellopts[:stderr].chomp! if shellopts[:stderr].is_a?(String) end shell.status.exitstatus rescue -1 end # Registers meta data for the introspection hash def self.metadata(data) agent = File.basename(caller.first).split(":").first Log.warn("Setting metadata in agents has been deprecated, DDL files are now being used for this information. Please update the '#{agent}' agent") end # Creates the needed activate? class in a manner similar to the other # helpers like action, authorized_by etc # # activate_when do # File.exist?("/usr/bin/puppet") # end def self.activate_when(&block) (class << self; self; end).instance_eval do define_method("activate?", &block) end end # Creates a new action with the block passed and sets some defaults # # action "status" do # # logic here to restart service # end def self.action(name, &block) raise "Need to pass a body for the action" unless block_given? self.module_eval { define_method("#{name}_action", &block) } end # Helper that creates a method on the class that will call your authorization # plugin. If your plugin raises an exception that will abort the request def self.authorized_by(plugin) plugin = plugin.to_s.capitalize # turns foo_bar into FooBar plugin = plugin.to_s.split("_").map {|v| v.capitalize}.join pluginname = "MCollective::Util::#{plugin}" PluginManager.loadclass(pluginname) unless MCollective::Util.constants.include?(plugin) class_eval(" def authorization_hook(request) #{pluginname}.authorize(request) end ") end # Validates a data member, if validation is a regex then it will try to match it # else it supports testing object types only: # # validate :msg, String # validate :msg, /^[\w\s]+$/ # # There are also some special helper validators: # # validate :command, :shellsafe # validate :command, :ipv6address # validate :command, :ipv4address # validate :command, :boolean # validate :command, ["start", "stop"] # # It will raise appropriate exceptions that the RPC system understand def validate(key, validation) raise MissingRPCData, "please supply a #{key} argument" unless @request.include?(key) Validator.validate(@request[key], validation) rescue ValidatorError => e raise InvalidRPCData, "Input %s did not pass validation: %s" % [ key, e.message ] end # convenience wrapper around Util#shellescape def shellescape(str) Util.shellescape(str) end # handles external actions def implemented_by(command, type=:json) runner = ActionRunner.new(command, request, type) res = runner.run reply.fail! "Did not receive data from #{command}" unless res.include?(:data) reply.fail! "Reply data from #{command} is not a Hash" unless res[:data].is_a?(Hash) reply.data.merge!(res[:data]) if res[:exitstatus] > 0 reply.fail "Failed to run #{command}: #{res[:stderr]}", res[:exitstatus] end rescue Exception => e Log.warn("Unhandled #{e.class} exception during #{request.agent}##{request.action}: #{e}") reply.fail! "Unexpected failure calling #{command}: #{e.class}: #{e}" end # Called at the end of the RPC::Agent standard initialize method # use this to adjust meta parameters, timeouts and any setup you # need to do. # # This will not be called right when the daemon starts up, we use # lazy loading and initialization so it will only be called the first # time a request for this agent arrives. def startup_hook end # Called just after a message was received from the middleware before # it gets passed to the handlers. @request and @reply will already be # set, the msg passed is the message as received from the normal # mcollective runner and the connection is the actual connector. def before_processing_hook(msg, connection) end # Called at the end of processing just before the response gets sent # to the middleware. # # This gets run outside of the main exception handling block of the agent # so you should handle any exceptions you could raise yourself. The reason # it is outside of the block is so you'll have access to even status codes # set by the exception handlers. If you do raise an exception it will just # be passed onto the runner and processing will fail. def after_processing_hook end # Gets called right after a request was received and calls audit plugins # # Agents can disable auditing by just overriding this method with a noop one # this might be useful for agents that gets a lot of requests or simply if you # do not care for the auditing in a specific agent. def audit_request(msg, connection) PluginManager["rpcaudit_plugin"].audit_request(msg, connection) if @config.rpcaudit rescue Exception => e Log.warn("Audit failed - #{e} - continuing to process message") end end end end mcollective-2.6.0/lib/mcollective/rpc/stats.rb0000644000175000017500000002023512375446407020374 0ustar jonasjonasmodule MCollective module RPC # Class to wrap all the stats and to keep track of some timings class Stats attr_accessor :noresponsefrom, :starttime, :discoverytime, :blocktime, :responses, :totaltime attr_accessor :discovered, :discovered_nodes, :okcount, :failcount, :noresponsefrom, :responsesfrom attr_accessor :requestid, :aggregate_summary, :ddl, :aggregate_failures def initialize reset end # Resets stats, if discovery time is set we keep it as it was def reset @noresponsefrom = [] @responsesfrom = [] @responses = 0 @starttime = Time.now.to_f @discoverytime = 0 unless @discoverytime @blocktime = 0 @totaltime = 0 @discovered = 0 @discovered_nodes = [] @okcount = 0 @failcount = 0 @noresponsefrom = [] @requestid = nil @aggregate_summary = [] @aggregate_failures = [] end # returns a hash of our stats def to_hash {:noresponsefrom => @noresponsefrom, :starttime => @starttime, :discoverytime => @discoverytime, :blocktime => @blocktime, :responses => @responses, :totaltime => @totaltime, :discovered => @discovered, :discovered_nodes => @discovered_nodes, :noresponsefrom => @noresponsefrom, :okcount => @okcount, :requestid => @requestid, :failcount => @failcount, :aggregate_summary => @aggregate_summary, :aggregate_failures => @aggregate_failures} end # Fake hash access to keep things backward compatible def [](key) to_hash[key] rescue nil end # increment the count of ok hosts def ok @okcount += 1 rescue @okcount = 1 end # increment the count of failed hosts def fail @failcount += 1 rescue @failcount = 1 end # Re-initializes the object with stats from the basic client def client_stats=(stats) @noresponsefrom = stats[:noresponsefrom] @responses = stats[:responses] @starttime = stats[:starttime] @blocktime = stats[:blocktime] @totaltime = stats[:totaltime] @requestid = stats[:requestid] @discoverytime = stats[:discoverytime] if @discoverytime == 0 end # Utility to time discovery from :start to :end def time_discovery(action) if action == :start @discovery_start = Time.now.to_f elsif action == :end @discoverytime = Time.now.to_f - @discovery_start else raise("Uknown discovery action #{action}") end rescue @discoverytime = 0 end # helper to time block execution time def time_block_execution(action) if action == :start @block_start = Time.now.to_f elsif action == :end @blocktime += Time.now.to_f - @block_start else raise("Uknown block action #{action}") end rescue @blocktime = 0 end # Update discovered and discovered_nodes based on # discovery results def discovered_agents(agents) @discovered_nodes = agents @discovered = agents.size end # Helper to calculate total time etc def finish_request @totaltime = @blocktime + @discoverytime # figures out who we had no responses from dhosts = @discovered_nodes.clone @responsesfrom.each {|r| dhosts.delete(r)} @noresponsefrom = dhosts rescue @totaltime = 0 @noresponsefrom = [] end # Helper to keep track of who we received responses from def node_responded(node) @responsesfrom << node rescue @responsesfrom = [node] end def text_for_aggregates result = StringIO.new @aggregate_summary.each do |aggregate| output_item = aggregate.result[:output] begin action_interface = @ddl.action_interface(aggregate.action) display_as = action_interface[:output][output_item][:display_as] rescue display_as = output_item end if aggregate.is_a?(Aggregate::Result::Base) aggregate_report = aggregate.to_s else next end result.puts Util.colorize(:bold, "Summary of %s:" % display_as) result.puts unless aggregate_report == "" result.puts aggregate.to_s.split("\n").map{|x| " " + x}.join("\n") else result.puts Util.colorize(:yellow, " No aggregate summary could be computed") end result.puts end @aggregate_failures.each do |failed| case(failed[:type]) when :startup message = "exception raised while processing startup hook" when :create message = "unspecified output '#{failed[:name]}' for the action" when :process_result message = "exception raised while processing result data" when :summarize message = "exception raised while summarizing" end result.puts Util.colorize(:bold, "Summary of %s:" % failed[:name]) result.puts result.puts Util.colorize(:yellow, " Could not compute summary - %s" % message) result.puts end result.string end # Returns a blob of text representing the request status based on the # stats contained in this class def report(caption = "rpc stats", summarize = true, verbose = false) result_text = [] if verbose if @aggregate_summary.size > 0 && summarize result_text << text_for_aggregates else result_text << "" end result_text << Util.colorize(:yellow, "---- #{caption} ----") if @discovered @responses < @discovered ? color = :red : color = :reset result_text << " Nodes: %s / %s" % [ Util.colorize(color, @discovered), Util.colorize(color, @responses) ] else result_text << " Nodes: #{@responses}" end @failcount < 0 ? color = :red : color = :reset result_text << " Pass / Fail: %s / %s" % [Util.colorize(color, @okcount), Util.colorize(color, @failcount) ] result_text << " Start Time: %s" % [Time.at(@starttime)] result_text << " Discovery Time: %.2fms" % [@discoverytime * 1000] result_text << " Agent Time: %.2fms" % [@blocktime * 1000] result_text << " Total Time: %.2fms" % [@totaltime * 1000] else if @discovered @responses < @discovered ? color = :red : color = :green if @aggregate_summary.size + @aggregate_failures.size > 0 && summarize result_text << text_for_aggregates else result_text << "" end result_text << "Finished processing %s / %s hosts in %.2f ms" % [Util.colorize(color, @responses), Util.colorize(color, @discovered), @blocktime * 1000] else result_text << "Finished processing %s hosts in %.2f ms" % [Util.colorize(:bold, @responses), @blocktime * 1000] end end if no_response_report != "" result_text << "" << no_response_report end result_text.join("\n") end # Returns a blob of text indicating what nodes did not respond def no_response_report result_text = StringIO.new if @noresponsefrom.size > 0 result_text.puts result_text.puts Util.colorize(:red, "No response from:") result_text.puts field_size = Util.field_size(@noresponsefrom, 30) fields_num = Util.field_number(field_size) format = " " + ( " %-#{field_size}s" * fields_num ) @noresponsefrom.sort.in_groups_of(fields_num) do |c| result_text.puts format % c end result_text.puts end result_text.string end end end end mcollective-2.6.0/lib/mcollective/rpc/request.rb0000644000175000017500000000331612375446407020727 0ustar jonasjonasmodule MCollective module RPC # Simple class to manage compliant requests for MCollective::RPC agents class Request attr_accessor :time, :action, :data, :sender, :agent, :uniqid, :caller, :ddl def initialize(msg, ddl) @time = msg[:msgtime] @action = msg[:body][:action] @data = msg[:body][:data] @sender = msg[:senderid] @agent = msg[:body][:agent] @uniqid = msg[:requestid] @caller = msg[:callerid] || "unknown" @ddl = ddl end # If data is a hash, quick helper to get access to it's include? method # else returns false def include?(key) return false unless @data.is_a?(Hash) return @data.include?(key) end # If no :process_results is specified always respond else respond # based on the supplied property def should_respond? return @data[:process_results] if @data.include?(:process_results) return true end # If data is a hash, gives easy access to its members, else returns nil def [](key) return nil unless @data.is_a?(Hash) return @data[key] end def fetch(key, default) return nil unless @data.is_a?(Hash) return @data.fetch(key, default) end def to_hash return {:agent => @agent, :action => @action, :data => @data} end # Validate the request against the DDL def validate! @ddl.validate_rpc_request(@action, @data) end def to_json to_hash.merge!({:sender => @sender, :callerid => @callerid, :uniqid => @uniqid}).to_json end end end end mcollective-2.6.0/lib/mcollective/rpc/actionrunner.rb0000644000175000017500000001021712375446407021744 0ustar jonasjonasmodule MCollective module RPC # A helper used by RPC::Agent#implemented_by to delegate an action to # an external script. At present only JSON based serialization is # supported in future ones based on key=val pairs etc will be added # # It serializes the request object into an input file and creates an # empty output file. It then calls the external command reading the # output file at the end. # # any STDERR gets logged at error level and any STDOUT gets logged at # info level. # # It will interpret the exit code from the application the same way # RPC::Reply#fail! and #fail handles their codes creating a consistent # interface, the message part of the fail message will come from STDERR # # Generally externals should just exit with code 1 on failure and print to # STDERR, this is exactly what Perl die() does and translates perfectly # to our model # # It uses the MCollective::Shell wrapper to call the external application class ActionRunner attr_reader :command, :agent, :action, :format, :stdout, :stderr, :request def initialize(command, request, format=:json) @agent = request.agent @action = request.action @format = format @request = request @command = path_to_command(command) @stdout = "" @stderr = "" end def run unless canrun?(command) Log.warn("Cannot run #{to_s}") raise RPCAborted, "Cannot execute #{to_s}" end Log.debug("Running #{to_s}") request_file = saverequest(request) reply_file = tempfile("reply") reply_file.close runner = shell(command, request_file.path, reply_file.path) runner.runcommand Log.debug("#{command} exited with #{runner.status.exitstatus}") stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty? stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty? {:exitstatus => runner.status.exitstatus, :stdout => runner.stdout, :stderr => runner.stderr, :data => load_results(reply_file.path)} ensure request_file.close! if request_file.respond_to?("close!") reply_file.close! if reply_file.respond_to?("close") end def shell(command, infile, outfile) env = {"MCOLLECTIVE_REQUEST_FILE" => infile, "MCOLLECTIVE_REPLY_FILE" => outfile} Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env) end def load_results(file) Log.debug("Attempting to load results in #{format} format from #{file}") data = {} if respond_to?("load_#{format}_results") tempdata = send("load_#{format}_results", file) tempdata.each_pair do |k,v| data[k.to_sym] = v end end data rescue Exception => e {} end def load_json_results(file) return {} unless File.readable?(file) JSON.load(File.read(file)) || {} rescue JSON::ParserError {} end def saverequest(req) Log.debug("Attempting to save request in #{format} format") if respond_to?("save_#{format}_request") data = send("save_#{format}_request", req) request_file = tempfile("request") request_file.puts data request_file.close end request_file end def save_json_request(req) req.to_json end def canrun?(command) File.executable?(command) end def to_s "%s#%s command: %s" % [ agent, action, command ] end def tempfile(prefix) Tempfile.new("mcollective_#{prefix}", Dir.tmpdir) end def path_to_command(command) unless command[0,1] == File::SEPARATOR Config.instance.libdir.each do |libdir| command_file = File.join(libdir, "agent", agent, command) return command_file if File.exist?(command_file) end end return command end end end end mcollective-2.6.0/lib/mcollective/rpc/reply.rb0000644000175000017500000000424612375446407020375 0ustar jonasjonasmodule MCollective module RPC # Simple class to manage compliant replies to MCollective::RPC class Reply attr_accessor :statuscode, :statusmsg, :data def initialize(action, ddl) @data = {} @statuscode = 0 @statusmsg = "OK" @ddl = ddl @action = action begin initialize_data rescue Exception => e Log.warn("Could not pre-populate reply data from the DDL: %s: %s" % [e.class, e.to_s ]) end end def initialize_data unless @ddl.actions.include?(@action) raise "No action '%s' defined for agent '%s' in the DDL" % [@action, @ddl.pluginname] end interface = @ddl.action_interface(@action) interface[:output].keys.each do |output| # must deep clone this data to avoid accidental updates of the DDL in cases where the # default is for example a string and someone does << on it @data[output] = Marshal.load(Marshal.dump(interface[:output][output].fetch(:default, nil))) end end # Helper to fill in statusmsg and code on failure def fail(msg, code=1) @statusmsg = msg @statuscode = code end # Helper that fills in statusmsg and code but also raises an appropriate error def fail!(msg, code=1) @statusmsg = msg @statuscode = code case code when 1 raise RPCAborted, msg when 2 raise UnknownRPCAction, msg when 3 raise MissingRPCData, msg when 4 raise InvalidRPCData, msg else raise UnknownRPCError, msg end end # Write to the data hash def []=(key, val) @data[key] = val end # Read from the data hash def [](key) @data[key] end def fetch(key, default) @data.fetch(key, default) end # Returns a compliant Hash of the reply that should be sent # over the middleware def to_hash return {:statuscode => @statuscode, :statusmsg => @statusmsg, :data => @data} end end end end mcollective-2.6.0/lib/mcollective/rpc/audit.rb0000644000175000017500000000244512375446407020347 0ustar jonasjonasmodule MCollective module RPC # Auditing of requests is done only for SimpleRPC requests, you provide # a plugin in the MCollective::Audit::* namespace which the SimpleRPC # framework calls for each message # # We provide a simple one that logs to a logfile in the class # MCollective::Audit::Logfile you can create your own: # # Create a class in plugins/mcollective/audit/.rb # # You must inherit from MCollective::RPC::Audit which will take # care of registering you with the plugin system. # # Your plugin must provide audit_request(request, connection) # the request parameter will be an instance of MCollective::RPC::Request # # To enable auditing you should set: # # rpcaudit = 1 # rpcauditprovider = Logfile # # in the config file this will enable logging using the # MCollective::Audit::Logile class # # The Audit class acts as a base for audit plugins and takes care of registering them # with the plugin manager class Audit def self.inherited(klass) PluginManager << {:type => "rpcaudit_plugin", :class => klass.to_s} end def audit_request(request, connection) @log.error("audit_request is not implimented in #{this.class}") end end end end mcollective-2.6.0/lib/mcollective/rpc/result.rb0000644000175000017500000000217612375446407020560 0ustar jonasjonasmodule MCollective module RPC # Simple class to manage compliant results from MCollective::RPC agents # # Currently it just fakes Hash behaviour to the result to remain backward # compatible but it also knows which agent and action produced it so you # can associate results to a DDL class Result attr_reader :agent, :action, :results include Enumerable def initialize(agent, action, result={}) @agent = agent @action = action @results = result end def [](idx) @results[idx] end def []=(idx, item) @results[idx] = item end def fetch(key, default) @results.fetch(key, default) end def each @results.each_pair {|k,v| yield(k,v) } end def to_json(*a) {:agent => @agent, :action => @action, :sender => @results[:sender], :statuscode => @results[:statuscode], :statusmsg => @results[:statusmsg], :data => @results[:data]}.to_json(*a) end def <=>(other) self[:sender] <=> other[:sender] end end end end mcollective-2.6.0/lib/mcollective/rpc/progress.rb0000644000175000017500000000326312375446407021104 0ustar jonasjonasmodule MCollective module RPC # Class that shows a progress bar, currently only supports a twirling # progress bar. # # You can specify a size for the progress bar if you want if you dont # it will use the helper functions to figure out terminal dimensions # and draw an appropriately sized bar # # p = Progress.new # 100.times {|i| print p.twirl(i+1, 100) + "\r"};puts # # * [ ==================================================> ] 100 / 100 class Progress def initialize(size=nil) @twirl = ['|', '/', '-', "\\", '|', '/', '-', "\\"] @twirldex = 0 if size @size = size else cols = Util.terminal_dimensions[0] - 22 # Defaults back to old behavior if it # couldn't figure out the size or if # its more than 60 wide if cols <= 0 @size = 0 elsif cols > 60 @size = 60 else @size = cols end end end def twirl(current, total) # if the size is negative there is just not enough # space on the terminal, return a simpler version return "\r#{current} / #{total}" if @size == 0 if current == total txt = "\r %s [ " % Util.colorize(:green, "*") else txt = "\r %s [ " % Util.colorize(:red, @twirl[@twirldex]) end dashes = ((current.to_f / total) * @size).round dashes.times { txt << "=" } txt << ">" (@size - dashes).times { txt << " " } txt << " ] #{current} / #{total}" @twirldex == 7 ? @twirldex = 0 : @twirldex += 1 return txt end end end end mcollective-2.6.0/lib/mcollective/rpc/helpers.rb0000644000175000017500000002533012375446407020701 0ustar jonasjonasmodule MCollective module RPC # Various utilities for the RPC system class Helpers # Parse JSON output as produced by printrpc and extract # the "sender" of each rpc response # # The simplist valid JSON based data would be: # # [ # {"sender" => "example.com"}, # {"sender" => "another.com"} # ] def self.extract_hosts_from_json(json) hosts = JSON.parse(json) raise "JSON hosts list is not an array" unless hosts.is_a?(Array) hosts.map do |host| raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash) raise "JSON host list does not have senders in it" unless host.include?("sender") host["sender"] end.uniq end # Given an array of something, make sure each is a string # chomp off any new lines and return just the array of hosts def self.extract_hosts_from_array(hosts) [hosts].flatten.map do |host| raise "#{host} should be a string" unless host.is_a?(String) host.chomp end end # Returns a blob of text representing the results in a standard way # # It tries hard to do sane things so you often # should not need to write your own display functions # # If the agent you are getting results for has a DDL # it will use the hints in there to do the right thing specifically # it will look at the values of display in the DDL to choose # when to show results # # If you do not have a DDL you can pass these flags: # # printrpc exim.mailq, :flatten => true # printrpc exim.mailq, :verbose => true # # If you've asked it to flatten the result it will not print sender # hostnames, it will just print the result as if it's one huge result, # handy for things like showing a combined mailq. def self.rpcresults(result, flags = {}) flags = {:verbose => false, :flatten => false, :format => :console, :force_display_mode => false}.merge(flags) result_text = "" ddl = nil # if running in verbose mode, just use the old style print # no need for all the DDL helpers obfuscating the result if flags[:format] == :json if STDOUT.tty? result_text = JSON.pretty_generate(result) else result_text = result.to_json end else if flags[:verbose] result_text = old_rpcresults(result, flags) else [result].flatten.each do |r| begin ddl ||= DDL.new(r.agent).action_interface(r.action.to_s) sender = r[:sender] status = r[:statuscode] message = r[:statusmsg] result = r[:data] if flags[:force_display_mode] display = flags[:force_display_mode] else display = ddl[:display] end # appand the results only according to what the DDL says case display when :ok if status == 0 result_text << text_for_result(sender, status, message, result, ddl) end when :failed if status > 0 result_text << text_for_result(sender, status, message, result, ddl) end when :always result_text << text_for_result(sender, status, message, result, ddl) when :flatten Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release") result_text << text_for_flattened_result(status, result) end rescue Exception => e # no DDL so just do the old style print unchanged for # backward compat result_text = old_rpcresults(result, flags) end end end end result_text end # Return text representing a result def self.text_for_result(sender, status, msg, result, ddl) statusses = ["", Util.colorize(:red, "Request Aborted"), Util.colorize(:yellow, "Unknown Action"), Util.colorize(:yellow, "Missing Request Data"), Util.colorize(:yellow, "Invalid Request Data"), Util.colorize(:red, "Unknown Request Status")] result_text = "%-40s %s\n" % [sender, statusses[status]] result_text << " %s\n" % [Util.colorize(:yellow, msg)] unless msg == "OK" # only print good data, ignore data that results from failure if status == 0 if result.is_a?(Hash) # figure out the lengths of the display as strings, we'll use # it later to correctly justify the output lengths = result.keys.map do |k| begin ddl[:output][k][:display_as].size rescue k.to_s.size end end result.keys.sort_by{|k| k}.each do |k| # get all the output fields nicely lined up with a # 3 space front padding begin display_as = ddl[:output][k][:display_as] rescue display_as = k.to_s end display_length = display_as.size padding = lengths.max - display_length + 3 result_text << " " * padding result_text << "#{display_as}:" if [String, Numeric].include?(result[k].class) lines = result[k].to_s.split("\n") if lines.empty? result_text << "\n" else lines.each_with_index do |line, i| i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2) result_text << "#{padtxt}#{line}\n" end end else padding = " " * (lengths.max + 5) result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n" end end elsif status == 1 # for status 1 we dont want to show half baked # data by default since the DDL will supply all the defaults # it just doesnt look right else result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t") end end result_text << "\n" result_text end # Returns text representing a flattened result of only good data def self.text_for_flattened_result(status, result) result_text = "" if status <= 1 unless result.is_a?(String) result_text << result.pretty_inspect else result_text << result end end end # Backward compatible display block for results without a DDL def self.old_rpcresults(result, flags = {}) result_text = "" if flags[:flatten] result.each do |r| if r[:statuscode] <= 1 data = r[:data] unless data.is_a?(String) result_text << data.pretty_inspect else result_text << data end else result_text << r.pretty_inspect end end result_text << "" else [result].flatten.each do |r| if flags[:verbose] result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]] if r[:statuscode] <= 1 r[:data].pretty_inspect.split("\n").each {|m| result_text += " #{m}"} result_text << "\n\n" elsif r[:statuscode] == 2 # dont print anything, no useful data to display # past what was already shown elsif r[:statuscode] == 3 # dont print anything, no useful data to display # past what was already shown elsif r[:statuscode] == 4 # dont print anything, no useful data to display # past what was already shown else result_text << " #{r[:statusmsg]}" end else unless r[:statuscode] == 0 result_text << "%-40s %s\n" % [r[:sender], Util.colorize(:red, r[:statusmsg])] end end end end result_text << "" end # Add SimpleRPC common options def self.add_simplerpc_options(parser, options) parser.separator "" parser.separator "RPC Options" # add SimpleRPC specific options to all clients that use our library parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v| options[:progress_bar] = false end parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v| options[:mcollective_limit_targets] = 1 end parser.on('--batch SIZE', 'Do requests in batches') do |v| # validate batch string. Is it x% where x > 0 or is it an integer if ((v =~ /^(\d+)%$/ && Integer($1) != 0) || v =~ /^(\d+)$/) options[:batch_size] = v else raise(::OptionParser::InvalidArgument.new(v)) end end parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v| options[:batch_sleep_time] = v end parser.on('--limit-seed NUMBER', Integer, 'Seed value for deterministic random batching') do |v| options[:limit_seed] = v end parser.on('--limit-nodes COUNT', '--ln', '--limit', 'Send request to only a subset of nodes, can be a percentage') do |v| raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/ if v =~ /^\d+$/ options[:mcollective_limit_targets] = v.to_i else options[:mcollective_limit_targets] = v end end parser.on('--json', '-j', 'Produce JSON output') do |v| options[:progress_bar] = false options[:output_format] = :json end parser.on('--display MODE', 'Influence how results are displayed. One of ok, all or failed') do |v| if v == "all" options[:force_display_mode] = :always else options[:force_display_mode] = v.intern end raise "--display has to be one of 'ok', 'all' or 'failed'" unless [:ok, :failed, :always].include?(options[:force_display_mode]) end end end end end mcollective-2.6.0/lib/mcollective/client.rb0000644000175000017500000002265012375446407017733 0ustar jonasjonasmodule MCollective # Helpers for writing clients that can talk to agents, do discovery and so forth class Client attr_accessor :options, :stats, :discoverer def initialize(configfile) @config = Config.instance @config.loadconfig(configfile) unless @config.configured @connection = PluginManager["connector_plugin"] @security = PluginManager["security_plugin"] @security.initiated_by = :client @options = nil @subscriptions = {} @discoverer = Discovery.new(self) @connection.connect end @@request_sequence = 0 def self.request_sequence @@request_sequence end # Returns the configured main collective if no # specific collective is specified as options def collective if @options[:collective].nil? @config.main_collective else @options[:collective] end end # Disconnects cleanly from the middleware def disconnect Log.debug("Disconnecting from the middleware") @connection.disconnect end # Sends a request and returns the generated request id, doesn't wait for # responses and doesn't execute any passed in code blocks for responses def sendreq(msg, agent, filter = {}) request = createreq(msg, agent, filter) Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}") request.publish request.requestid end def createreq(msg, agent, filter ={}) if msg.is_a?(Message) request = msg agent = request.agent else ttl = @options[:ttl] || @config.ttl request = Message.new(msg, nil, {:agent => agent, :type => :request, :collective => collective, :filter => filter, :ttl => ttl}) request.reply_to = @options[:reply_to] if @options[:reply_to] end @@request_sequence += 1 request.encode! subscribe(agent, :reply) unless request.reply_to request end def subscribe(agent, type) unless @subscriptions.include?(agent) subscription = Util.make_subscriptions(agent, type, collective) Log.debug("Subscribing to #{type} target for agent #{agent}") Util.subscribe(subscription) @subscriptions[agent] = 1 end end def unsubscribe(agent, type) if @subscriptions.include?(agent) subscription = Util.make_subscriptions(agent, type, collective) Log.debug("Unsubscribing #{type} target for #{agent}") Util.unsubscribe(subscription) @subscriptions.delete(agent) end end # Blocking call that waits for ever for a message to arrive. # # If you give it a requestid this means you've previously send a request # with that ID and now you just want replies that matches that id, in that # case the current connection will just ignore all messages not directed at it # and keep waiting for more till it finds a matching message. def receive(requestid = nil) reply = nil begin reply = @connection.receive reply.type = :reply reply.expected_msgid = requestid reply.decode! unless reply.requestid == requestid raise(MsgDoesNotMatchRequestID, "Message reqid #{reply.requestid} does not match our reqid #{requestid}") end rescue SecurityValidationFailed => e Log.warn("Ignoring a message that did not pass security validations") retry rescue MsgDoesNotMatchRequestID => e Log.debug("Ignoring a message for some other client : #{e.message}") retry end reply end # Performs a discovery of nodes matching the filter passed # returns an array of nodes # # An integer limit can be supplied this will have the effect # of the discovery being cancelled soon as it reached the # requested limit of hosts def discover(filter, timeout, limit=0) discovered = @discoverer.discover(filter, timeout, limit) end # Send a request, performs the passed block for each response # # times = req("status", "mcollectived", options, client) {|resp| # pp resp # } # # It returns a hash of times and timeouts for discovery and total run is taken from the options # hash which in turn is generally built using MCollective::Optionparser def req(body, agent=nil, options=false, waitfor=0, &block) if body.is_a?(Message) agent = body.agent waitfor = body.discovered_hosts.size || 0 @options = body.options end @options = options if options threaded = @options[:threaded] timeout = @discoverer.discovery_timeout(@options[:timeout], @options[:filter]) request = createreq(body, agent, @options[:filter]) publish_timeout = @options[:publish_timeout] stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0} STDOUT.sync = true hosts_responded = 0 begin if threaded hosts_responded = threaded_req(request, publish_timeout, timeout, waitfor, &block) else hosts_responded = unthreaded_req(request, publish_timeout, timeout, waitfor, &block) end rescue Interrupt => e ensure unsubscribe(agent, :reply) end return update_stat(stat, hosts_responded, request.requestid) end # Starts the client receiver and publisher unthreaded. # This is the default client behaviour. def unthreaded_req(request, publish_timeout, timeout, waitfor, &block) start_publisher(request, publish_timeout) start_receiver(request.requestid, waitfor, timeout, &block) end # Starts the client receiver and publisher in threads. # This is activated when the 'threader_client' configuration # option is set. def threaded_req(request, publish_timeout, timeout, waitfor, &block) Log.debug("Starting threaded client") publisher = Thread.new do start_publisher(request, publish_timeout) end # When the client is threaded we add the publishing timeout to # the agent timeout so that the receiver doesn't time out before # publishing has finished in cases where publish_timeout >= timeout. total_timeout = publish_timeout + timeout hosts_responded = 0 receiver = Thread.new do hosts_responded = start_receiver(request.requestid, waitfor, total_timeout, &block) end receiver.join hosts_responded end # Starts the request publishing routine def start_publisher(request, publish_timeout) Log.debug("Starting publishing with publish timeout of #{publish_timeout}") begin Timeout.timeout(publish_timeout) do Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}") request.publish end rescue Timeout::Error => e Log.warn("Could not publish all messages. Publishing timed out.") end end # Starts the response receiver routine # Expected to return the amount of received responses. def start_receiver(requestid, waitfor, timeout, &block) Log.debug("Starting response receiver with timeout of #{timeout}") hosts_responded = 0 begin Timeout.timeout(timeout) do begin resp = receive(requestid) yield resp.payload hosts_responded += 1 end while (waitfor == 0 || hosts_responded < waitfor) end rescue Timeout::Error => e if (waitfor > hosts_responded) Log.warn("Could not receive all responses. Expected : #{waitfor}. Received : #{hosts_responded}") end end hosts_responded end def update_stat(stat, hosts_responded, requestid) stat[:totaltime] = Time.now.to_f - stat[:starttime] stat[:blocktime] = stat[:totaltime] - stat[:discoverytime] stat[:responses] = hosts_responded stat[:noresponsefrom] = [] stat[:requestid] = requestid @stats = stat end def discovered_req(body, agent, options=false) raise "Client#discovered_req has been removed, please port your agent and client to the SimpleRPC framework" end # Prints out the stats returns from req and discovered_req in a nice way def display_stats(stats, options=false, caption="stomp call summary") options = @options unless options if options[:verbose] puts("\n---- #{caption} ----") if stats[:discovered] puts(" Nodes: #{stats[:discovered]} / #{stats[:responses]}") else puts(" Nodes: #{stats[:responses]}") end printf(" Start Time: %s\n", Time.at(stats[:starttime])) printf(" Discovery Time: %.2fms\n", stats[:discoverytime] * 1000) printf(" Agent Time: %.2fms\n", stats[:blocktime] * 1000) printf(" Total Time: %.2fms\n", stats[:totaltime] * 1000) else if stats[:discovered] printf("\nFinished processing %d / %d hosts in %.2f ms\n\n", stats[:responses], stats[:discovered], stats[:blocktime] * 1000) else printf("\nFinished processing %d hosts in %.2f ms\n\n", stats[:responses], stats[:blocktime] * 1000) end end if stats[:noresponsefrom].size > 0 puts("\nNo response from:\n") stats[:noresponsefrom].each do |c| puts if c % 4 == 1 printf("%30s", c) end puts end end end end mcollective-2.6.0/lib/mcollective/agents.rb0000644000175000017500000001121112375446407017725 0ustar jonasjonasmodule MCollective # A collection of agents, loads them, reloads them and dispatches messages to them. # It uses the PluginManager to store, load and manage instances of plugins. class Agents def initialize(agents = {}) @config = Config.instance raise ("Configuration has not been loaded, can't load agents") unless @config.configured @@agents = agents loadagents end # Deletes all agents def clear! @@agents.each_key do |agent| PluginManager.delete "#{agent}_agent" Util.unsubscribe(Util.make_subscriptions(agent, :broadcast)) end @@agents = {} end # Loads all agents from disk def loadagents Log.debug("Reloading all agents from disk") clear! @config.libdir.each do |libdir| agentdir = "#{libdir}/mcollective/agent" next unless File.directory?(agentdir) Dir.new(agentdir).grep(/\.rb$/).each do |agent| agentname = File.basename(agent, ".rb") loadagent(agentname) unless PluginManager.include?("#{agentname}_agent") end end end # Loads a specified agent from disk if available def loadagent(agentname) agentfile = findagentfile(agentname) return false unless agentfile classname = class_for_agent(agentname) PluginManager.delete("#{agentname}_agent") begin single_instance = ["registration", "discovery"].include?(agentname) PluginManager.loadclass(classname) if activate_agent?(agentname) PluginManager << {:type => "#{agentname}_agent", :class => classname, :single_instance => single_instance} # Attempt to instantiate the agent once so any validation and hooks get run # this does a basic sanity check on the agent as a whole, if this fails it # will be removed from the plugin list PluginManager["#{agentname}_agent"] Util.subscribe(Util.make_subscriptions(agentname, :broadcast)) unless @@agents.include?(agentname) @@agents[agentname] = {:file => agentfile} return true else Log.debug("Not activating agent #{agentname} due to agent policy in activate? method") return false end rescue Exception => e Log.error("Loading agent #{agentname} failed: #{e}") PluginManager.delete("#{agentname}_agent") return false end end # Builds a class name string given a Agent name def class_for_agent(agent) "MCollective::Agent::#{agent.capitalize}" end # Checks if a plugin should be activated by # calling #activate? on it if it responds to # that method else always activate it def activate_agent?(agent) klass = Kernel.const_get("MCollective").const_get("Agent").const_get(agent.capitalize) if klass.respond_to?("activate?") return klass.activate? else Log.debug("#{klass} does not have an activate? method, activating as default") return true end rescue Exception => e Log.warn("Agent activation check for #{agent} failed: #{e.class}: #{e}") return false end # searches the libdirs for agents def findagentfile(agentname) @config.libdir.each do |libdir| agentfile = File.join([libdir, "mcollective", "agent", "#{agentname}.rb"]) if File.exist?(agentfile) Log.debug("Found #{agentname} at #{agentfile}") return agentfile end end return false end # Determines if we have an agent with a certain name def include?(agentname) PluginManager.include?("#{agentname}_agent") end # Dispatches a message to an agent, accepts a block that will get run if there are # any replies to process from the agent def dispatch(request, connection) Log.debug("Dispatching a message to agent #{request.agent}") Thread.new do begin agent = PluginManager["#{request.agent}_agent"] Timeout::timeout(agent.timeout) do replies = agent.handlemsg(request.payload, connection) # Agents can decide if they wish to reply or not, # returning nil will mean nothing goes back to the # requestor unless replies == nil yield(replies) end end rescue Timeout::Error => e Log.warn("Timeout while handling message for #{request.agent}") rescue Exception => e Log.error("Execution of #{request.agent} failed: #{e}") Log.error(e.backtrace.join("\n\t\t")) end end end # Get a list of agents that we have def self.agentlist @@agents.keys end end end mcollective-2.6.0/lib/mcollective/security.rb0000644000175000017500000000174512375446407020326 0ustar jonasjonasmodule MCollective # Security is implimented using a module structure and installations # can configure which module they want to use. # # Security modules deal with various aspects of authentication and authorization: # # - Determines if a filter excludes this host from dealing with a request # - Serialization and Deserialization of messages # - Validation of messages against keys, certificates or whatever the class choose to impliment # - Encoding and Decoding of messages # # To impliment a new security class using SSL for example you would inherit from the base # class and only impliment: # # - decodemsg # - encodereply # - encoderequest # - validrequest? # # Each of these methods should increment various stats counters, see the default MCollective::Security::Psk module for examples of this # # Filtering can be extended by providing a new validate_filter? method. module Security autoload :Base, "mcollective/security/base" end end mcollective-2.6.0/lib/mcollective/vendor/0000755000175000017500000000000012417211236017403 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/vendor/require_vendored.rb0000644000175000017500000000004112375446407023302 0ustar jonasjonasrequire 'systemu' require 'json' mcollective-2.6.0/lib/mcollective/validator.rb0000644000175000017500000000435012375446407020437 0ustar jonasjonasmodule MCollective module Validator @last_load = nil @@validator_mutex = Mutex.new # Loads the validator plugins. Validators will only be loaded every 5 minutes def self.load_validators begin @@validator_mutex.lock if load_validators? @last_load = Time.now.to_i PluginManager.find_and_load("validator") end ensure @@validator_mutex.unlock end end # Returns and instance of the Plugin class from which objects can be created. # Valid plugin names are # :valplugin # "valplugin" # "ValpluginValidator" def self.[](klass) if klass.is_a?(Symbol) klass = validator_class(klass) elsif !(klass.match(/.*Validator$/)) klass = validator_class(klass) end const_get(klass) end # Allows validation plugins to be called like module methods : Validator.validate() def self.method_missing(method, *args, &block) if has_validator?(method) validator = Validator[method].validate(*args) else raise ValidatorError, "Unknown validator: '#{method}'." end end def self.has_validator?(validator) const_defined?(validator_class(validator)) end def self.validator_class(validator) "#{validator.to_s.capitalize}Validator" end def self.load_validators? return true if @last_load.nil? (@last_load - Time.now.to_i) > 300 end # Generic validate method that will call the correct validator # plugin based on the type of the validation parameter def self.validate(validator, validation) Validator.load_validators begin if [:integer, :boolean, :float, :number, :string].include?(validation) Validator.typecheck(validator, validation) else case validation when Regexp,String Validator.regex(validator, validation) when Symbol Validator.send(validation, validator) when Array Validator.array(validator, validation) when Class Validator.typecheck(validator, validation) end end rescue => e raise ValidatorError, e.to_s end end end end mcollective-2.6.0/lib/mcollective/pluginpackager.rb0000644000175000017500000000470512375446407021452 0ustar jonasjonasmodule MCollective module PluginPackager # Plugin definition classes autoload :AgentDefinition, "mcollective/pluginpackager/agent_definition" autoload :StandardDefinition, "mcollective/pluginpackager/standard_definition" # Package implementation plugins def self.load_packagers PluginManager.find_and_load("pluginpackager") end def self.[](klass) const_get("#{klass}") end # Fetch and return metadata from plugin DDL def self.get_metadata(path, type) ddl = DDL.new("package", type.to_sym, false) begin ddl_file = File.read(Dir.glob(File.join(path, type, "*.ddl")).first) rescue Exception raise "failed to load ddl file in plugin directory : #{File.join(path, type)}" end ddl.instance_eval ddl_file return ddl.meta, ddl.requirements[:mcollective] end # Checks if a directory is present and not empty def self.check_dir_present(path) (File.directory?(path) && !Dir.glob(File.join(path, "*")).empty?) end # Quietly calls a block if verbose parameter is false def self.execute_verbosely(verbose, &block) unless verbose old_stdout = $stdout.clone $stdout.reopen(File.new("/dev/null", "w")) begin block.call rescue Exception => e $stdout.reopen old_stdout raise e ensure $stdout.reopen old_stdout end else block.call end end # Checks if a build tool is present on the system def self.command_available?(build_tool) ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| builder = File.join(path, build_tool) if File.exists?(builder) return true end end false end def self.safe_system(*args) raise(RuntimeError, "Failed: #{args.join(' ')}") unless system *args end # Filter out platform specific dependencies # Given a list of dependencies named - # debian::foo # redhat::bar # PluginPackager.filter_dependencies('debian', dependencies) # will return foo. def self.filter_dependencies(prefix, dependencies) dependencies.map do |dependency| if dependency[:name] =~ /^(\w+)::(\w+)/ if prefix == $1 dependency[:name] = $2 dependency else nil end else dependency end end.reject{ |dependency| dependency == nil } end end end mcollective-2.6.0/lib/mcollective/connector.rb0000644000175000017500000000130012375446407020434 0ustar jonasjonasmodule MCollective # Connectors take care of transporting messages between clients and agents, # the design doesn't your middleware to be very rich in features. All it # really needs is the ability to send and receive messages to named queues/topics. # # At present there are assumptions about the naming of topics and queues that is # compatible with Stomp, ie. # # /topic/foo.bar/baz # /queue/foo.bar/baz # # This is the only naming format that is supported, but you could replace Stomp # with something else that supports the above, see MCollective::Connector::Stomp # for the default connector. module Connector autoload :Base, "mcollective/connector/base" end end mcollective-2.6.0/lib/mcollective/pluginpackager/0000755000175000017500000000000012375446416021117 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/pluginpackager/agent_definition.rb0000644000175000017500000000754012375446407024760 0ustar jonasjonasmodule MCollective module PluginPackager # MCollective Agent Plugin package class AgentDefinition attr_accessor :path, :packagedata, :metadata, :target_path, :vendor, :revision, :preinstall attr_accessor :plugintype, :dependencies, :postinstall, :mcname, :mcversion def initialize(configuration, mcdependency, plugintype) @plugintype = plugintype @path = configuration[:target] @packagedata = {} @revision = configuration[:revision] || 1 @preinstall = configuration[:preinstall] @postinstall = configuration[:postinstall] @vendor = configuration[:vendor] || "Puppet Labs" @dependencies = configuration[:dependency] || [] @target_path = File.expand_path(@path) @metadata, mcversion = PluginPackager.get_metadata(@path, "agent") @mcname = mcdependency[:mcname] || "mcollective" @mcversion = mcdependency[:mcversion] || mcversion @metadata[:version] = (configuration[:version] || @metadata[:version]) @dependencies << {:name => "#{@mcname}-common", :version => @mcversion} @metadata[:name] = (configuration[:pluginname] || @metadata[:name]).downcase.gsub(/\s+|_/, "-") identify_packages end # Identify present packages and populate packagedata hash. def identify_packages common_package = common @packagedata[:common] = common_package if common_package agent_package = agent @packagedata[:agent] = agent_package if agent_package client_package = client @packagedata[:client] = client_package if client_package end # Obtain Agent package files and dependencies. def agent agent = {:files => [], :dependencies => @dependencies.clone, :description => "Agent plugin for #{@metadata[:name]}"} agentdir = File.join(@path, "agent") if PluginPackager.check_dir_present agentdir ddls = Dir.glob(File.join(agentdir, "*.ddl")) agent[:files] = (Dir.glob(File.join(agentdir, "*")) - ddls) else return nil end agent[:plugindependency] = {:name => "#{@mcname}-#{@metadata[:name]}-common", :version => @metadata[:version], :revision => @revision} agent end # Obtain client package files and dependencies. def client client = {:files => [], :dependencies => @dependencies.clone, :description => "Client plugin for #{@metadata[:name]}"} clientdir = File.join(@path, "application") aggregatedir = File.join(@path, "aggregate") client[:files] += Dir.glob(File.join(clientdir, "*")) if PluginPackager.check_dir_present clientdir client[:files] += Dir.glob(File.join(aggregatedir, "*")) if PluginPackager.check_dir_present aggregatedir client[:plugindependency] = {:name => "#{@mcname}-#{@metadata[:name]}-common", :version => @metadata[:version], :revision => @revision} client[:files].empty? ? nil : client end # Obtain common package files and dependencies. def common common = {:files =>[], :dependencies => @dependencies.clone, :description => "Common libraries for #{@metadata[:name]}"} datadir = File.join(@path, "data", "**") utildir = File.join(@path, "util", "**", "**") ddldir = File.join(@path, "agent", "*.ddl") validatordir = File.join(@path, "validator", "**") [datadir, utildir, validatordir, ddldir].each do |directory| common[:files] += Dir.glob(directory) end # We fail if there is no ddl file present if common[:files].grep(/^.*\.ddl$/).empty? raise "cannot create package - No ddl file found in #{File.join(@path, "agent")}" end common[:files].empty? ? nil : common end end end end mcollective-2.6.0/lib/mcollective/pluginpackager/standard_definition.rb0000644000175000017500000000534612375446407025464 0ustar jonasjonasmodule MCollective module PluginPackager class StandardDefinition attr_accessor :path, :packagedata, :metadata, :target_path, :vendor, :revision attr_accessor :plugintype, :preinstall, :postinstall, :dependencies, :mcname, :mcversion def initialize(configuration, mcdependency, plugintype) @plugintype = plugintype @path = configuration[:target] @packagedata = {} @revision = configuration[:revision] || 1 @preinstall = configuration[:preinstall] @postinstall = configuration[:postinstall] @vendor = configuration[:vendor] || "Puppet Labs" @dependencies = configuration[:dependency] || [] @target_path = File.expand_path(@path) @metadata, mcversion = PluginPackager.get_metadata(@path, @plugintype) @mcname = mcdependency[:mcname] || "mcollective" @mcversion = mcdependency[:mcversion] || mcversion @dependencies << {:name => "#{mcname}-common", :version => @mcversion} @metadata[:name] = (configuration[:pluginname] || @metadata[:name]).downcase.gsub(/\s+|_/, "-") @metadata[:version] = (configuration[:version] || @metadata[:version]) identify_packages end # Identify present packages and populate the packagedata hash def identify_packages common_package = common @packagedata[:common] = common_package if common_package plugin_package = plugin @packagedata[@plugintype.to_sym] = plugin_package if plugin_package end # Obtain standard plugin files and dependencies def plugin plugindata = {:files => [], :dependencies => @dependencies.clone, :description => "#{@name} #{@plugintype} plugin for the Marionette Collective."} plugindir = File.join(@path, @plugintype.to_s) if PluginPackager.check_dir_present plugindir plugindata[:files] = Dir.glob(File.join(plugindir, "*")) else return nil end plugindata[:plugindependency] = {:name => "#{@mcname}-#{@metadata[:name]}-common", :version => @metadata[:version], :revision => @revision} if @packagedata[:common] plugindata end # Obtain list of common files def common common = {:files => [], :dependencies => @dependencies.clone, :description => "Common libraries for #{@name} connector plugin"} commondir = File.join(@path, "util") if PluginPackager.check_dir_present commondir common[:files] = Dir.glob(File.join(commondir, "*")) return common else return nil end end end end end mcollective-2.6.0/lib/mcollective/matcher/0000755000175000017500000000000012375446416017546 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/matcher/parser.rb0000644000175000017500000001104312375446407021366 0ustar jonasjonasmodule MCollective module Matcher class Parser attr_reader :scanner, :execution_stack def initialize(args) @scanner = Scanner.new(args) @execution_stack = [] @parse_errors = [] @token_errors = [] @paren_errors = [] parse exit_with_token_errors if @token_errors.size > 0 exit_with_parse_errors if @parse_errors.size > 0 exit_with_paren_errors if @paren_errors.size > 0 end # Exit and highlight any malformed tokens def exit_with_token_errors @token_errors.each do |error_range| (error_range[0]..error_range[1]).each do |i| @scanner.arguments[i] = Util.colorize(:red, @scanner.arguments[i]) end end raise "Malformed token(s) found while parsing -S input #{@scanner.arguments.join}" end def exit_with_parse_errors @parse_errors.each do |error_range| (error_range[0]..error_range[1]).each do |i| @scanner.arguments[i] = Util.colorize(:red, @scanner.arguments[i]) end end raise "Parse errors found while parsing -S input #{ @scanner.arguments.join}" end def exit_with_paren_errors @paren_errors.each do |i| @scanner.arguments[i] = Util.colorize(:red, @scanner.arguments[i]) end raise "Missing parenthesis found while parsing -S input #{@scanner.arguments.join}" end # Parse the input string, one token at a time a contruct the call stack def parse pre_index = @scanner.token_index p_token,p_token_value = nil c_token,c_token_value = @scanner.get_token parenth = 0 while (c_token != nil) @scanner.token_index += 1 n_token, n_token_value = @scanner.get_token unless n_token == " " case c_token when "bad_token" @token_errors << c_token_value when "and" unless (n_token =~ /not|fstatement|statement|\(/) || (scanner.token_index == scanner.arguments.size) && !(n_token == nil) @parse_errors << [pre_index, scanner.token_index] end if p_token == nil @parse_errors << [pre_index - c_token.size, scanner.token_index] elsif (p_token == "and" || p_token == "or") @parse_errors << [pre_index - 1 - p_token.size, pre_index - 1] end when "or" unless (n_token =~ /not|fstatement|statement|\(/) || (scanner.token_index == scanner.arguments.size) && !(n_token == nil) @parse_errors << [pre_index, scanner.token_index] end if p_token == nil @parse_errors << [pre_index - c_token.size, scanner.token_index] elsif (p_token == "and" || p_token == "or") @parse_errors << [pre_index - 1 - p_token.size, pre_index - 1] end when "not" unless n_token =~ /fstatement|statement|\(|not/ && !(n_token == nil) @parse_errors << [pre_index, scanner.token_index] end when "statement","fstatement" unless n_token =~ /and|or|\)/ unless scanner.token_index == scanner.arguments.size @parse_errors << [pre_index, scanner.token_index] end end when ")" unless (n_token =~ /|and|or|not|\(/) unless(scanner.token_index == scanner.arguments.size) @parse_errors << [pre_index, scanner.token_index] end end unless @paren_errors.empty? @paren_errors.pop else @paren_errors.push((n_token.nil?) ? scanner.token_index - 1: scanner.token_index - n_token_value.size) end when "(" unless n_token =~ /fstatement|statement|not|\(/ @parse_errors << [pre_index, scanner.token_index] end @paren_errors.push((n_token.nil?) ? scanner.token_index - 1: scanner.token_index - n_token_value.size) else @parse_errors << [pre_index, scanner.token_index] end unless n_token == " " ||c_token == "bad_token" @execution_stack << {c_token => c_token_value} end p_token, p_token_value = c_token, c_token_value c_token, c_token_value = n_token, n_token_value end pre_index = @scanner.token_index end end end end end mcollective-2.6.0/lib/mcollective/matcher/scanner.rb0000644000175000017500000001453112375446407021530 0ustar jonasjonasmodule MCollective module Matcher class Scanner attr_accessor :arguments, :token_index def initialize(arguments) @token_index = 0 @arguments = arguments.split("") @seperation_counter = 0 @white_spaces = 0 end # Scans the input string and identifies single language tokens def get_token if @token_index >= @arguments.size return nil end case @arguments[@token_index] when "(" return "(", "(" when ")" return ")", ")" when "n" if (@arguments[@token_index + 1] == "o") && (@arguments[@token_index + 2] == "t") && ((@arguments[@token_index + 3] == " ") || (@arguments[@token_index + 3] == "(")) @token_index += 2 return "not", "not" else gen_statement end when "!" return "not", "not" when "a" if (@arguments[@token_index + 1] == "n") && (@arguments[@token_index + 2] == "d") && ((@arguments[@token_index + 3] == " ") || (@arguments[@token_index + 3] == "(")) @token_index += 2 return "and", "and" else gen_statement end when "o" if (@arguments[@token_index + 1] == "r") && ((@arguments[@token_index + 2] == " ") || (@arguments[@token_index + 2] == "(")) @token_index += 1 return "or", "or" else gen_statement end when " " return " ", " " else gen_statement end end private # Helper generates a statement token def gen_statement func = false current_token_value = "" j = @token_index begin if (@arguments[j] == "/") begin current_token_value << @arguments[j] j += 1 end until (j >= @arguments.size) || (@arguments[j] =~ /\s/) elsif (@arguments[j] =~ /=|<|>/) while !(@arguments[j] =~ /=|<|>/) current_token_value << @arguments[j] j += 1 end current_token_value << @arguments[j] j += 1 if @arguments[j] == "/" begin current_token_value << @arguments[j] j += 1 if @arguments[j] == "/" current_token_value << "/" break end end until (j >= @arguments.size) || (@arguments[j] =~ /\//) else while (j < @arguments.size) && ((@arguments[j] != " ") && (@arguments[j] != ")")) current_token_value << @arguments[j] j += 1 end end else begin # Identify and tokenize regular expressions by ignoring everything between /'s if @arguments[j] == '/' current_token_value << '/' j+=1 while(j < @arguments.size && @arguments[j] != '/') current_token_value << @arguments[j] j += 1 end current_token_value << @arguments[j] if @arguments[j] break end if @arguments[j+1] == "(" func = true be_greedy = true end current_token_value << @arguments[j] if be_greedy while !(j+1 >= @arguments.size) && @arguments[j] != ')' j += 1 current_token_value << @arguments[j] end j += 1 be_greedy = false else j += 1 end if(@arguments[j] == ' ') break if(is_klass?(j) && !(@arguments[j-1] =~ /=|<|>/)) end if( (@arguments[j] == ' ') && (@seperation_counter < 2) && !(current_token_value.match(/^.+(=|<|>).+$/)) ) if((index = lookahead(j))) j = index end end end until (j >= @arguments.size) || (@arguments[j] =~ /\s|\)/) @seperation_counter = 0 end rescue Exception => e raise "An exception was raised while trying to tokenize '#{current_token_value} - #{e}'" end @token_index += current_token_value.size + @white_spaces - 1 @white_spaces = 0 # bar( if current_token_value.match(/.+?\($/) return "bad_token", [@token_index - current_token_value.size + 1, @token_index] # /foo/=bar elsif current_token_value.match(/^\/.+?\/(<|>|=).+/) return "bad_token", [@token_index - current_token_value.size + 1, @token_index] elsif current_token_value.match(/^.+?\/(<|>|=).+/) return "bad_token", [@token_index - current_token_value.size + 1, @token_index] else if func if current_token_value.match(/^.+?\((\s*(')[^']*(')\s*(,\s*(')[^']*('))*)?\)(\.[a-zA-Z0-9_]+)?((!=|<=|>=|=|>|<).+)?$/) || current_token_value.match(/^.+?\((\s*(")[^"]*(")\s*(,\s*(")[^"]*("))*)?\)(\.[a-zA-Z0-9_]+)?((!=|<=|>=|=|>|<).+)?$/) return "fstatement", current_token_value else return "bad_token", [@token_index - current_token_value.size + 1, @token_index] end else slash_err = false current_token_value.split('').each do |c| if c == '/' slash_err = !slash_err end end return "bad_token", [@token_index - current_token_value.size + 1, @token_index] if slash_err return "statement", current_token_value end end end # Deal with special puppet class statement def is_klass?(j) while(j < @arguments.size && @arguments[j] == ' ') j += 1 end if @arguments[j] =~ /=|<|>/ return false else return true end end # Eat spaces while looking for the next comparison symbol def lookahead(index) index += 1 while(index <= @arguments.size) @white_spaces += 1 unless(@arguments[index] =~ /\s/) @seperation_counter +=1 return index end index += 1 end return nil end end end end mcollective-2.6.0/lib/mcollective/pluginmanager.rb0000644000175000017500000001337112375446407021306 0ustar jonasjonasmodule MCollective # A simple plugin manager, it stores one plugin each of a specific type # the idea is that we can only have one security provider, one connector etc. module PluginManager @plugins = {} # Adds a plugin to the list of plugins, we expect a hash like: # # {:type => "base", # :class => foo.new} # # or like: # {:type => "base", # :class => "Foo::Bar"} # # In the event that we already have a class with the given type # an exception will be raised. # # If the :class passed is a String then we will delay instantiation # till the first time someone asks for the plugin, this is because most likely # the registration gets done by inherited() hooks, at which point the plugin class is not final. # # If we were to do a .new here the Class initialize method would get called and not # the plugins, we there for only initialize the classes when they get requested via [] # # By default all plugin instances are cached and returned later so there's # always a single instance. You can pass :single_instance => false when # calling this to instruct it to always return a new instance when a copy # is requested. This only works with sending a String for :class. def self.<<(plugin) plugin[:single_instance] = true unless plugin.include?(:single_instance) type = plugin[:type] klass = plugin[:class] single = plugin[:single_instance] raise("Plugin #{type} already loaded") if @plugins.include?(type) # If we get a string then store 'nil' as the instance, signalling that we'll # create the class later on demand. if klass.is_a?(String) @plugins[type] = {:loadtime => Time.now, :class => klass, :instance => nil, :single => single} Log.debug("Registering plugin #{type} with class #{klass} single_instance: #{single}") else @plugins[type] = {:loadtime => Time.now, :class => klass.class, :instance => klass, :single => true} Log.debug("Registering plugin #{type} with class #{klass.class} single_instance: true") end end # Removes a plugim the list def self.delete(plugin) @plugins.delete(plugin) if @plugins.include?(plugin) end # Finds out if we have a plugin with the given name def self.include?(plugin) @plugins.include?(plugin) end # Provides a list of plugins we know about def self.pluginlist @plugins.keys.sort end # deletes all registered plugins def self.clear @plugins.clear end # Gets a plugin by type def self.[](plugin) raise("No plugin #{plugin} defined") unless @plugins.include?(plugin) klass = @plugins[plugin][:class] if @plugins[plugin][:single] # Create an instance of the class if one hasn't been done before if @plugins[plugin][:instance] == nil Log.debug("Returning new plugin #{plugin} with class #{klass}") @plugins[plugin][:instance] = create_instance(klass) else Log.debug("Returning cached plugin #{plugin} with class #{klass}") end @plugins[plugin][:instance] else Log.debug("Returning new plugin #{plugin} with class #{klass}") create_instance(klass) end end # use eval to create an instance of a class def self.create_instance(klass) begin eval("#{klass}.new") rescue Exception => e raise("Could not create instance of plugin #{klass}: #{e}") end end # Finds plugins in all configured libdirs # # find("agent") # # will return an array of just agent names, for example: # # ["puppetd", "package"] # # Can also be used to find files of other extensions: # # find("agent", "ddl") # # Will return the same list but only of files with extension .ddl # in the agent subdirectory def self.find(type, extension="rb") extension = ".#{extension}" unless extension.match(/^\./) plugins = [] Config.instance.libdir.each do |libdir| plugdir = File.join([libdir, "mcollective", type.to_s]) next unless File.directory?(plugdir) Dir.new(plugdir).grep(/#{extension}$/).map do |plugin| plugins << File.basename(plugin, extension) end end plugins.sort.uniq end # Finds and loads from disk all plugins from all libdirs that match # certain criteria. # # find_and_load("pluginpackager") # # Will find all .rb files in the libdir/mcollective/pluginpackager/ # directory in all libdirs and load them from disk. # # You can influence what plugins get loaded using a block notation: # # find_and_load("pluginpackager") do |plugin| # plugin.match(/puppet/) # end # # This will load only plugins matching /puppet/ def self.find_and_load(type, extension="rb") extension = ".#{extension}" unless extension.match(/^\./) klasses = find(type, extension).map do |plugin| if block_given? next unless yield(plugin) end "%s::%s::%s" % [ "MCollective", type.capitalize, plugin.capitalize ] end.compact klasses.sort.uniq.each {|klass| loadclass(klass, true)} end # Loads a class from file by doing some simple search/replace # on class names and then doing a require. def self.loadclass(klass, squash_failures=false) fname = klass.gsub("::", "/").downcase + ".rb" Log.debug("Loading #{klass} from #{fname}") load fname rescue Exception => e Log.error("Failed to load #{klass}: #{e}") raise unless squash_failures end # Grep's over the plugin list and returns the list found def self.grep(regex) @plugins.keys.grep(regex) end end end mcollective-2.6.0/lib/mcollective/log.rb0000644000175000017500000000542212375446407017234 0ustar jonasjonasmodule MCollective # A simple class that allows logging at various levels. class Log class << self @logger = nil # Obtain the class name of the currently configured logger def logger @logger.class end # Logs at info level def info(msg) log(:info, msg) end # Logs at warn level def warn(msg) log(:warn, msg) end # Logs at debug level def debug(msg) log(:debug, msg) end # Logs at fatal level def fatal(msg) log(:fatal, msg) end # Logs at error level def error(msg) log(:error, msg) end # handle old code that relied on this class being a singleton def instance self end # increments the active log level def cycle_level @logger.cycle_level if @configured end # reopen log files def reopen if @configured @logger.reopen end end # logs a message at a certain level def log(level, msg) configure unless @configured raise "Unknown log level" unless [:error, :fatal, :debug, :warn, :info].include?(level) if @logger @logger.log(level, from, msg) else t = Time.new.strftime("%H:%M:%S") STDERR.puts "#{t}: #{level}: #{from}: #{msg}" end end # sets the logger class to use def set_logger(logger) @logger = logger end # configures the logger class, if the config has not yet been loaded # we default to the console logging class and do not set @configured # so that future calls to the log method will keep attempting to configure # the logger till we eventually get a logging preference from the config # module def configure(logger=nil) unless logger logger_type = "console" config = Config.instance if config.configured logger_type = config.logger_type @configured = true end require "mcollective/logger/#{logger_type.downcase}_logger" logger_class = MCollective::Logger.const_get("#{logger_type.capitalize}_logger") set_logger(logger_class.new) else set_logger(logger) @configured = true end @logger.start rescue Exception => e @configured = false STDERR.puts "Could not start logger: #{e.class} #{e}" end # figures out the filename that called us def from path, line, method = execution_stack[3].split(/:(\d+)/) "%s:%s%s" % [File.basename(path), line, method] end # this method is here to facilitate testing and from def execution_stack caller end end end end mcollective-2.6.0/lib/mcollective/cache.rb0000644000175000017500000001026612375446407017520 0ustar jonasjonasmodule MCollective # A class to manage a number of named caches. Each cache can have a unique # timeout for keys in it and each cache has an independent Mutex protecting # access to it. # # This cache is setup early in the process of loading the mcollective # libraries, before any threads are created etc making it suitable as a # cross thread cache or just a store for Mutexes you need to use across # threads like in an Agent or something. # # # sets up a new cache, noop if it already exist # Cache.setup(:ddl, 600) # => true # # # writes an item to the :ddl cache, this item will # # be valid on the cache for 600 seconds # Cache.write(:ddl, :something, "value") # => "value" # # # reads from the cache, read failures due to non existing # # data or expired data will raise an exception # Cache.read(:ddl, :something) # => "value" # # # time left till expiry, raises if nothing is found # Cache.ttl(:ddl, :something) # => 500 # # # forcibly evict something from the cache # Cache.invalidate!(:ddl, :something) # => "value" # # # deletes an entire named cache and its mutexes # Cache.delete!(:ddl) # => true # # # you can also just use this cache as a global mutex store # Cache.setup(:mylock) # # Cache.synchronize(:mylock) do # do_something() # end # module Cache # protects access to @cache_locks and top level @cache @locks_mutex = Mutex.new # stores a mutex per named cache @cache_locks = {} # the named caches protected by items in @cache_locks @cache = {} def self.setup(cache_name, ttl=300) @locks_mutex.synchronize do break if @cache_locks.include?(cache_name) @cache_locks[cache_name] = Mutex.new @cache_locks[cache_name].synchronize do @cache[cache_name] = {:max_age => Float(ttl)} end end true end def self.has_cache?(cache_name) @locks_mutex.synchronize do @cache.include?(cache_name) end end def self.delete!(cache_name) @locks_mutex.synchronize do raise("No cache called '%s'" % cache_name) unless @cache_locks.include?(cache_name) @cache_locks.delete(cache_name) @cache.delete(cache_name) end true end def self.write(cache_name, key, value) raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name) @cache_locks[cache_name].synchronize do @cache[cache_name][key] ||= {} @cache[cache_name][key][:cache_create_time] = Time.now @cache[cache_name][key][:value] = value end value end def self.read(cache_name, key) raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name) unless ttl(cache_name, key) > 0 Log.debug("Cache expired on '%s' key '%s'" % [cache_name, key]) raise("Cache for item '%s' on cache '%s' has expired" % [key, cache_name]) end Log.debug("Cache hit on '%s' key '%s'" % [cache_name, key]) @cache_locks[cache_name].synchronize do @cache[cache_name][key][:value] end end def self.ttl(cache_name, key) raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name) @cache_locks[cache_name].synchronize do unless @cache[cache_name].include?(key) Log.debug("Cache miss on '%s' key '%s'" % [cache_name, key]) raise("No item called '%s' for cache '%s'" % [key, cache_name]) end @cache[cache_name][:max_age] - (Time.now - @cache[cache_name][key][:cache_create_time]) end end def self.synchronize(cache_name) raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name) raise ("No block supplied to synchronize") unless block_given? @cache_locks[cache_name].synchronize do yield end end def self.invalidate!(cache_name, key) raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name) @cache_locks[cache_name].synchronize do return false unless @cache[cache_name].include?(key) @cache[cache_name].delete(key) end end end end mcollective-2.6.0/lib/mcollective/agent.rb0000644000175000017500000000005512375446407017546 0ustar jonasjonasmodule MCollective module Agent end end mcollective-2.6.0/lib/mcollective/logger.rb0000644000175000017500000000013312375446407017724 0ustar jonasjonasmodule MCollective module Logger autoload :Base, "mcollective/logger/base" end end mcollective-2.6.0/lib/mcollective/config.rb0000644000175000017500000002121012375446407017711 0ustar jonasjonasmodule MCollective # A pretty sucky config class, ripe for refactoring/improving class Config include Singleton attr_accessor :mode attr_reader :daemonize, :pluginconf, :libdir, :configured attr_reader :logfile, :keeplogs, :max_log_size, :loglevel, :logfacility attr_reader :identity, :daemonize, :connector, :securityprovider, :factsource attr_reader :registration, :registerinterval, :classesfile attr_reader :rpcauditprovider, :rpcaudit, :configdir, :rpcauthprovider attr_reader :rpcauthorization, :color, :configfile attr_reader :rpclimitmethod, :logger_type, :fact_cache_time, :collectives attr_reader :main_collective, :ssl_cipher, :registration_collective attr_reader :direct_addressing, :direct_addressing_threshold, :ttl attr_reader :default_discovery_method, :default_discovery_options attr_reader :publish_timeout, :threaded, :soft_shutdown, :activate_agents attr_reader :registration_splay, :discovery_timeout, :soft_shutdown_timeout def initialize @configured = false end def loadconfig(configfile) set_config_defaults(configfile) if File.exists?(configfile) File.readlines(configfile).each do |line| # strip blank spaces, tabs etc off the end of all lines line.gsub!(/\s*$/, "") unless line =~ /^#|^$/ if (line =~ /(.+?)\s*=\s*(.+)/) key = $1.strip val = $2 begin case key when "registration" @registration = val.capitalize when "registration_collective" @registration_collective = val when "registerinterval" @registerinterval = Integer(val) when "registration_splay" @registration_splay = Util.str_to_bool(val) when "collectives" @collectives = val.split(",").map {|c| c.strip} when "main_collective" @main_collective = val when "logfile" @logfile = val when "keeplogs" @keeplogs = Integer(val) when "max_log_size" @max_log_size = Integer(val) when "loglevel" @loglevel = val when "logfacility" @logfacility = val when "libdir" paths = val.split(File::PATH_SEPARATOR) paths.each do |path| raise("libdir paths should be absolute paths but '%s' is relative" % path) unless Util.absolute_path?(path) @libdir << path unless $LOAD_PATH.include?(path) $LOAD_PATH << path end end when "identity" @identity = val when "direct_addressing" @direct_addressing = Util.str_to_bool(val) when "direct_addressing_threshold" @direct_addressing_threshold = Integer(val) when "color" @color = Util.str_to_bool(val) when "daemonize" @daemonize = Util.str_to_bool(val) when "securityprovider" @securityprovider = val.capitalize when "factsource" @factsource = val.capitalize when "connector" @connector = val.capitalize when "classesfile" @classesfile = val when /^plugin.(.+)$/ @pluginconf[$1] = val when "discovery_timeout" @discovery_timeout = Integer(val) when "publish_timeout" @publish_timeout = Integer(val) when "rpcaudit" @rpcaudit = Util.str_to_bool(val) when "rpcauditprovider" @rpcauditprovider = val.capitalize when "rpcauthorization" @rpcauthorization = Util.str_to_bool(val) when "rpcauthprovider" @rpcauthprovider = val.capitalize when "rpclimitmethod" @rpclimitmethod = val.to_sym when "logger_type" @logger_type = val when "fact_cache_time" @fact_cache_time = Integer(val) when "ssl_cipher" @ssl_cipher = val when "threaded" @threaded = Util.str_to_bool(val) when "ttl" @ttl = Integer(val) when "default_discovery_options" @default_discovery_options << val when "default_discovery_method" @default_discovery_method = val when "soft_shutdown" @soft_shutdown = Util.str_to_bool(val) when "soft_shutdown_timeout" @soft_shutdown_timeout = Integer(val) when "activate_agents" @activate_agents = Util.str_to_bool(val) when "topicprefix", "topicsep", "queueprefix", "rpchelptemplate", "helptemplatedir" Log.warn("Use of deprecated '#{key}' option. This option is ignored and should be removed from '#{configfile}'") else raise("Unknown config parameter '#{key}'") end rescue ArgumentError => e raise "Could not parse value for configuration option '#{key}' with value '#{val}'" end end end end raise('The %s config file does not specify a libdir setting, cannot continue' % configfile) if @libdir.empty? read_plugin_config_dir("#{@configdir}/plugin.d") raise 'Identities can only match /\w\.\-/' unless @identity.match(/^[\w\.\-]+$/) @configured = true @libdir.each {|dir| Log.warn("Cannot find libdir: #{dir}") unless File.directory?(dir)} if @logger_type == "syslog" raise "The sylog logger is not usable on the Windows platform" if Util.windows? end PluginManager.loadclass("Mcollective::Facts::#{@factsource}_facts") PluginManager.loadclass("Mcollective::Connector::#{@connector}") PluginManager.loadclass("Mcollective::Security::#{@securityprovider}") PluginManager.loadclass("Mcollective::Registration::#{@registration}") PluginManager.loadclass("Mcollective::Audit::#{@rpcauditprovider}") if @rpcaudit PluginManager << {:type => "global_stats", :class => RunnerStats.new} Log.info("The Marionette Collective version #{MCollective::VERSION} started by #{$0} using config file #{configfile}") else raise("Cannot find config file '#{configfile}'") end end def set_config_defaults(configfile) @stomp = Hash.new @subscribe = Array.new @pluginconf = Hash.new @connector = "activemq" @securityprovider = "Psk" @factsource = "Yaml" @identity = Socket.gethostname @registration = "Agentlist" @registerinterval = 0 @registration_collective = nil @registration_splay = false @classesfile = "/var/lib/puppet/state/classes.txt" @rpcaudit = false @rpcauditprovider = "" @rpcauthorization = false @rpcauthprovider = "" @configdir = File.dirname(configfile) @color = !Util.windows? @configfile = configfile @logger_type = "file" @keeplogs = 5 @max_log_size = 2097152 @rpclimitmethod = :first @libdir = Array.new @fact_cache_time = 300 @loglevel = "info" @logfacility = "user" @collectives = ["mcollective"] @main_collective = @collectives.first @ssl_cipher = "aes-256-cbc" @direct_addressing = true @direct_addressing_threshold = 10 @default_discovery_method = "mc" @default_discovery_options = [] @ttl = 60 @mode = :client @publish_timeout = 2 @threaded = false @soft_shutdown = false @soft_shutdown_timeout = nil @activate_agents = true end def read_plugin_config_dir(dir) return unless File.directory?(dir) Dir.new(dir).each do |pluginconfigfile| next unless pluginconfigfile =~ /^([\w]+).cfg$/ plugin = $1 File.open("#{dir}/#{pluginconfigfile}", "r").each do |line| # strip blank lines line.gsub!(/\s*$/, "") next if line =~ /^#|^$/ if (line =~ /(.+?)\s*=\s*(.+)/) key = $1.strip val = $2 @pluginconf["#{plugin}.#{key}"] = val end end end end end end mcollective-2.6.0/lib/mcollective/registration/0000755000175000017500000000000012375446416020635 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/registration/base.rb0000644000175000017500000000505012375446407022074 0ustar jonasjonasmodule MCollective module Registration # This is a base class that other registration plugins can use # to handle regular announcements to the mcollective # # The configuration file determines how often registration messages # gets sent using the _registerinterval_ option, the plugin runs in the # background in a thread. class Base # Register plugins that inherits base def self.inherited(klass) PluginManager << {:type => "registration_plugin", :class => klass.to_s} end # Creates a background thread that periodically send a registration notice. # # The actual registration notices comes from the 'body' method of the registration # plugins. def run(connection) return false if interval == 0 Thread.new do publish_thread(connection) end end def config Config.instance end def msg_filter filter = Util.empty_filter filter["agent"] << "registration" filter end def target_collective main_collective = config.main_collective collective = config.registration_collective || main_collective unless config.collectives.include?(collective) Log.warn("Sending registration to #{main_collective}: #{collective} is not a valid collective") collective = main_collective end return collective end def interval config.registerinterval end def publish(message) unless message Log.debug("Skipping registration due to nil body") else req = Message.new(message, nil, {:type => :request, :agent => "registration", :collective => target_collective, :filter => msg_filter}) req.encode! Log.debug("Sending registration #{req.requestid} to collective #{req.collective}") req.publish end end def body raise "Registration Plugins must implement the #body method" end private def publish_thread(connnection) if config.registration_splay splay_delay = rand(interval) Log.debug("registration_splay enabled. Registration will start in #{splay_delay} seconds") sleep splay_delay end loop do begin publish(body) sleep interval rescue Exception => e Log.error("Sending registration message failed: #{e}") sleep interval end end end end end end mcollective-2.6.0/lib/mcollective/ddl.rb0000644000175000017500000001064712375446407017223 0ustar jonasjonasmodule MCollective # A set of classes that helps create data description language files # for plugins. You can define meta data, actions, input and output # describing the behavior of your agent or other plugins # # DDL files are used for input validation, constructing outputs, # producing online help, informing the various display routines and # so forth. # # A sample DDL for an agent be seen below, you'd put this in your agent # dir as .ddl # # metadata :name => "SimpleRPC Service Agent", # :description => "Agent to manage services using the Puppet service provider", # :author => "R.I.Pienaar", # :license => "GPLv2", # :version => "1.1", # :url => "http://mcollective-plugins.googlecode.com/", # :timeout => 60 # # action "status", :description => "Gets the status of a service" do # display :always # # input :service, # :prompt => "Service Name", # :description => "The service to get the status for", # :type => :string, # :validation => '^[a-zA-Z\-_\d]+$', # :optional => true, # :maxlength => 30 # # output :status, # :description => "The status of service", # :display_as => "Service Status" # end # # There are now many types of DDL and ultimately all pugins should have # DDL files. The code is organized so that any plugin type will magically # just work - they will be an instane of Base which has #metadata and a few # common cases. # # For plugin types that require more specific behaviors they can just add a # class here that inherits from Base and add their specific behavior. # # Base defines a specific behavior for input, output and metadata which we'd # like to keep standard across plugin types so do not completely override the # behavior of input. The methods are written that they will gladly store extra # content though so you add, do not remove. See the AgentDDL class for an example # where agents want a :required argument to be always set. module DDL autoload :Base, "mcollective/ddl/base" autoload :AgentDDL, "mcollective/ddl/agentddl" autoload :DataDDL, "mcollective/ddl/dataddl" autoload :DiscoveryDDL, "mcollective/ddl/discoveryddl" # There used to be only one big nasty DDL class with a bunch of mashed # together behaviors. It's been around for ages and we would rather not # ask all the users to change their DDL.new calls to some other factory # method that would have this exact same behavior. # # So we override the behavior of #new which is a hugely sucky thing to do # but ultimately it's what would be least disrupting to code out there # today. We did though change DDL to a module to make it possibly a # little less suprising, possibly. def self.new(*args, &blk) load_and_cache(*args) end def self.load_and_cache(*args) Cache.setup(:ddl, 300) plugin = args.first args.size > 1 ? type = args[1].to_s : type = "agent" path = "%s/%s" % [type, plugin] begin ddl = Cache.read(:ddl, path) rescue begin klass = DDL.const_get("%sDDL" % type.capitalize) rescue NameError klass = Base end ddl = Cache.write(:ddl, path, klass.new(*args)) end return ddl end # As we're taking arguments on the command line we need a # way to input booleans, true on the cli is a string so this # method will take the ddl, find all arguments that are supposed # to be boolean and if they are the strings "true"/"yes" or "false"/"no" # turn them into the matching boolean def self.string_to_boolean(val) return true if ["true", "t", "yes", "y", "1"].include?(val.downcase) return false if ["false", "f", "no", "n", "0"].include?(val.downcase) raise "#{val} does not look like a boolean argument" end # a generic string to number function, if a number looks like a float # it turns it into a float else an int. This is naive but should be sufficient # for numbers typed on the cli in most cases def self.string_to_number(val) return val.to_f if val =~ /^\d+\.\d+$/ return val.to_i if val =~ /^\d+$/ raise "#{val} does not look like a number" end end end mcollective-2.6.0/lib/mcollective/windows_daemon.rb0000644000175000017500000000227512375446407021473 0ustar jonasjonasrequire 'win32/daemon' module MCollective class WindowsDaemon < Win32::Daemon def self.daemonize_runner(pid=nil) raise "Writing pid files are not supported on the Windows Platform" if pid raise "The Windows Daemonizer should only be used on the Windows Platform" unless Util.windows? WindowsDaemon.mainloop end def service_main Log.debug("Starting Windows Service Daemon") @runner = Runner.new(nil) @runner.main_loop # On shut down there may be threads outside of the runner's context that are # in a sleeping state, causing the stop action to wait for them to cleanly exit. # We get around this by iterating the list of threads and killing everything that # isn't the main thread, letting us shut down cleanly. Thread.list.each do |t| if t != Thread.current t.kill end end end def service_stop Log.info("Windows service stopping") @runner.stop end def service_pause Log.info("Pausing MCollective Windows server") @runner.pause end def service_resume Log.info("Resuming MCollective Windows server") @runner.resume end end end mcollective-2.6.0/lib/mcollective/message.rb0000644000175000017500000002223212375446407020075 0ustar jonasjonasmodule MCollective # container for a message, its headers, agent, collective and other meta data class Message attr_reader :message, :request, :validated, :msgtime, :payload, :type, :expected_msgid, :reply_to attr_accessor :headers, :agent, :collective, :filter attr_accessor :requestid, :discovered_hosts, :options, :ttl VALIDTYPES = [:message, :request, :direct_request, :reply] # payload - the message body without headers etc, just the text # message - the original message received from the middleware # options[:base64] - if the body base64 encoded? # options[:agent] - the agent the message is for/from # options[:collective] - the collective its for/from # options[:headers] - the message headers # options[:type] - an indicator about the type of message, :message, :request, :direct_request or :reply # options[:request] - if this is a reply this should old the message we are replying to # options[:filter] - for requests, the filter to encode into the message # options[:options] - the normal client options hash # options[:ttl] - the maximum amount of seconds this message can be valid for # options[:expected_msgid] - in the case of replies this is the msgid it is expecting in the replies # options[:requestid] - specific request id to use else one will be generated def initialize(payload, message, options = {}) options = {:base64 => false, :agent => nil, :headers => {}, :type => :message, :request => nil, :filter => Util.empty_filter, :options => {}, :ttl => 60, :expected_msgid => nil, :requestid => nil, :collective => nil}.merge(options) @payload = payload @message = message @requestid = options[:requestid] @discovered_hosts = nil @reply_to = nil @type = options[:type] @headers = options[:headers] @base64 = options[:base64] @filter = options[:filter] @expected_msgid = options[:expected_msgid] @options = options[:options] @ttl = @options[:ttl] || Config.instance.ttl @msgtime = 0 @validated = false if options[:request] @request = options[:request] @agent = request.agent @collective = request.collective @type = :reply else @agent = options[:agent] @collective = options[:collective] end base64_decode! end # Sets the message type to one of the known types. In the case of :direct_request # the list of hosts to communicate with should have been set with #discovered_hosts # else an exception will be raised. This is for extra security, we never accidentally # want to send a direct request without a list of hosts or something weird like that # as it might result in a filterless broadcast being sent. # # Additionally you simply cannot set :direct_request if direct_addressing was not enabled # this is to force a workflow that doesnt not yield in a mistake when someone might assume # direct_addressing is enabled when its not. def type=(type) raise "Unknown message type #{type}" unless VALIDTYPES.include?(type) if type == :direct_request raise "Direct requests is not enabled using the direct_addressing config option" unless Config.instance.direct_addressing unless @discovered_hosts && !@discovered_hosts.empty? raise "Can only set type to :direct_request if discovered_hosts have been set" end # clear out the filter, custom discovery sources might interpret the filters # different than the remote mcollectived and in directed mode really the only # filter that matters is the agent filter @filter = Util.empty_filter @filter["agent"] << @agent end @type = type end # Sets a custom reply-to target for requests. The connector plugin should inspect this # when constructing requests and set this header ensuring replies will go to the custom target # otherwise the connector should just do what it usually does def reply_to=(target) raise "Custom reply targets can only be set on requests" unless [:request, :direct_request].include?(@type) @reply_to = target end # in the case of reply messages we are expecting replies to a previously # created message. This stores a hint to that previously sent message id # and can be used by other classes like the security plugins as a means # of optimizing their behavior like by ignoring messages not directed # at us. def expected_msgid=(msgid) raise "Can only store the expected msgid for reply messages" unless @type == :reply @expected_msgid = msgid end def base64_decode! return unless @base64 @payload = SSL.base64_decode(@payload) @base64 = false end def base64_encode! return if @base64 @payload = SSL.base64_encode(@payload) @base64 = true end def base64? @base64 end def encode! case type when :reply raise "Cannot encode a reply message if no request has been associated with it" unless request raise 'callerid in original request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(request.payload[:callerid]) @requestid = request.payload[:requestid] @payload = PluginManager["security_plugin"].encodereply(agent, payload, requestid, request.payload[:callerid]) when :request, :direct_request validate_compound_filter(@filter["compound"]) unless @filter["compound"].empty? @requestid = create_reqid unless @requestid @payload = PluginManager["security_plugin"].encoderequest(Config.instance.identity, payload, requestid, filter, agent, collective, ttl) else raise "Cannot encode #{type} messages" end end def validate_compound_filter(compound_filter) compound_filter.each do |filter| filter.each do |statement| if statement["fstatement"] functionname = statement["fstatement"]["name"] pluginname = Data.pluginname(functionname) value = statement["fstatement"]["value"] ddl = DDL.new(pluginname, :data) # parses numbers and booleans entered as strings into proper # types of data so that DDL validation will pass statement["fstatement"]["params"] = Data.ddl_transform_input(ddl, statement["fstatement"]["params"]) Data.ddl_validate(ddl, statement["fstatement"]["params"]) unless value && Data.ddl_has_output?(ddl, value) DDL.validation_fail!(:PLMC41, "Data plugin '%{functionname}()' does not return a '%{value}' value", :error, {:functionname => functionname, :value => value}) end end end end end def decode! raise "Cannot decode message type #{type}" unless [:request, :reply].include?(type) @payload = PluginManager["security_plugin"].decodemsg(self) if type == :request raise 'callerid in request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(payload[:callerid]) end [:collective, :agent, :filter, :requestid, :ttl, :msgtime].each do |prop| instance_variable_set("@#{prop}", payload[prop]) if payload.include?(prop) end end # Perform validation against the message by checking filters and ttl def validate raise "Can only validate request messages" unless type == :request msg_age = Time.now.utc.to_i - msgtime if msg_age > ttl cid = "" cid += payload[:callerid] + "@" if payload.include?(:callerid) cid += payload[:senderid] if msg_age > ttl PluginManager["global_stats"].ttlexpired raise(MsgTTLExpired, "message #{requestid} from #{cid} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}. Rejecting message.") end end raise(NotTargettedAtUs, "Received message is not targetted to us") unless PluginManager["security_plugin"].validate_filter?(payload[:filter]) @validated = true end # publish a reply message by creating a target name and sending it def publish # If we've been specificaly told about hosts that were discovered # use that information to do P2P calls if appropriate else just # send it as is. config = Config.instance if @discovered_hosts && config.direct_addressing && (@discovered_hosts.size <= config.direct_addressing_threshold) self.type = :direct_request Log.debug("Handling #{requestid} as a direct request") end PluginManager['connector_plugin'].publish(self) end def create_reqid # we gsub out the -s so that the format of the id does not # change from previous versions, these should just be more # unique than previous ones SSL.uuid.gsub("-", "") end end end mcollective-2.6.0/lib/mcollective/discovery.rb0000644000175000017500000001032312375446407020456 0ustar jonasjonasmodule MCollective class Discovery def initialize(client) @known_methods = find_known_methods @default_method = Config.instance.default_discovery_method @client = client end def find_known_methods PluginManager.find("discovery") end def has_method?(method) @known_methods.include?(method) end def force_direct_mode? discovery_method != "mc" end def discovery_method method = "mc" if @client.options[:discovery_method] method = @client.options[:discovery_method] else method = @default_method end raise "Unknown discovery method %s" % method unless has_method?(method) unless method == "mc" raise "Custom discovery methods require direct addressing mode" unless Config.instance.direct_addressing end return method end def discovery_class method = discovery_method.capitalize PluginManager.loadclass("MCollective::Discovery::#{method}") unless self.class.const_defined?(method) self.class.const_get(method) end def ddl @ddl ||= DDL.new(discovery_method, :discovery) # if the discovery method got changed we might have an old DDL cached # this will detect that and reread the correct DDL from disk unless @ddl.meta[:name] == discovery_method @ddl = DDL.new(discovery_method, :discovery) end return @ddl end # Agent filters are always present no matter what, so we cant raise an error if the capabilities # suggest the discovery method cant do agents we just have to rely on the discovery plugin to not # do stupid things in the presense of a agent filter def check_capabilities(filter) capabilities = ddl.discovery_interface[:capabilities] unless capabilities.include?(:classes) raise "Cannot use class filters while using the '%s' discovery method" % discovery_method unless filter["cf_class"].empty? end unless capabilities.include?(:facts) raise "Cannot use fact filters while using the '%s' discovery method" % discovery_method unless filter["fact"].empty? end unless capabilities.include?(:identity) raise "Cannot use identity filters while using the '%s' discovery method" % discovery_method unless filter["identity"].empty? end unless capabilities.include?(:compound) raise "Cannot use compound filters while using the '%s' discovery method" % discovery_method unless filter["compound"].empty? end end # checks if compound filters are used and then forces the 'mc' discovery plugin def force_discovery_method_by_filter(filter) unless discovery_method == "mc" unless filter["compound"].empty? Log.info "Switching to mc discovery method because compound filters are used" @client.options[:discovery_method] = "mc" return true end end return false end # if a compound filter is specified and it has any function # then we read the DDL for each of those plugins and sum up # the timeout declared in the DDL def timeout_for_compound_filter(compound_filter) return 0 if compound_filter.nil? || compound_filter.empty? timeout = 0 compound_filter.each do |filter| filter.each do |statement| if statement["fstatement"] pluginname = Data.pluginname(statement["fstatement"]["name"]) ddl = DDL.new(pluginname, :data) timeout += ddl.meta[:timeout] end end end timeout end def discovery_timeout(timeout, filter) timeout = ddl.meta[:timeout] unless timeout unless (filter["compound"] && filter["compound"].empty?) timeout + timeout_for_compound_filter(filter["compound"]) else timeout end end def discover(filter, timeout, limit) raise "Limit has to be an integer" unless limit.is_a?(Fixnum) force_discovery_method_by_filter(filter) check_capabilities(filter) discovered = discovery_class.discover(filter, discovery_timeout(timeout, filter), limit, @client) if limit > 0 return discovered[0,limit] else return discovered end end end end mcollective-2.6.0/lib/mcollective/generators/0000755000175000017500000000000012375446416020274 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/generators/data_generator.rb0000644000175000017500000000320012375446407023573 0ustar jonasjonasmodule MCollective module Generators class DataGenerator \"%DESCRIPTION%\",\n"] query_text += "%9s%s" % [" ", ":display_as => \"%DESCRIPTION%\"\n"] query_text += "\n" unless @outputs.size == (i + 1) end query_text += "end" # Use inherited method to create metadata part of the ddl create_metadata_string + query_text end def create_plugin_content content_text = "%6s%s" % [" ", "query do |what|\n"] @outputs.each do |output| content_text += "%8s%s" % [" ", "result[:#{output}] = nil\n"] end content_text += "%6s%s" % [" ", "end\n"] # Add actions to agent file content_text end end end end mcollective-2.6.0/lib/mcollective/generators/templates/0000755000175000017500000000000012375446416022272 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/generators/templates/ddl.erb0000644000175000017500000000064712375446407023536 0ustar jonasjonasmetadata :name => "<%= meta[:name] || "%FULLNANE%" -%>", :description => "<%= meta[:description] || "%DESCRIPTION%" -%>", :author => "<%= meta[:author] || "%AUTHOR%" -%>", :license => "<%= meta[:license] || "%LICENSE%" -%>", :version => "<%= meta[:version] || "%VERSION%" -%>", :url => "<%= meta[:url] || "%URL%" -%>", :timeout => <%= meta[:timeout] || "%TIMEOUT%" %> mcollective-2.6.0/lib/mcollective/generators/templates/plugin.erb0000644000175000017500000000020512375446407024257 0ustar jonasjonasmodule MCollective module <%= @mod_name%> class <%= @plugin_name.capitalize -%><<%= @pclass%> <%= @content%> end end end mcollective-2.6.0/lib/mcollective/generators/templates/action_snippet.erb0000644000175000017500000000060412375446407026003 0ustar jonasjonas # Example Input input :name, :prompt => "%PROMPT%", :description => "%DESCRIPTION%", :type => %TYPE%, :validation => '%VALIDATION%', :optional => %OPTIONAL%, :maxlength => %MAXLENGTH% # Example output output :name, :description => "%DESCRIPTION%", :display_as => "%DISPLAYAS%" mcollective-2.6.0/lib/mcollective/generators/templates/data_input_snippet.erb0000644000175000017500000000027012375446407026655 0ustar jonasjonas input :query, :prompt => "%PROMP%", :description => "%DESCRIPTION%", :type => %TYPE%, :validation => %VALIDATION%, :maxlength => %MAXLENGTH% mcollective-2.6.0/lib/mcollective/generators/base.rb0000644000175000017500000000326712375446407021543 0ustar jonasjonasmodule MCollective module Generators class Base attr_accessor :meta, :plugin_name, :mod_name def initialize(name, description, author, license, version, url, timeout) @meta = {:name => name, :description => description, :author => author, :license => license, :version => version, :url => url, :timeout => timeout} end def create_metadata_string ddl_template = File.read(File.join(File.dirname(__FILE__), "templates", "ddl.erb")) ERB.new(ddl_template, nil, "-").result(binding) end def create_plugin_string plugin_template = File.read(File.join(File.dirname(__FILE__), "templates", "plugin.erb")) ERB.new(plugin_template, nil, "-").result(binding) end def write_plugins begin Dir.mkdir @plugin_name dirname = File.join(@plugin_name, @mod_name.downcase) Dir.mkdir dirname puts "Created plugin directory : #{@plugin_name}" File.open(File.join(dirname, "#{@plugin_name}.ddl"), "w"){|f| f.puts @ddl} puts "Created DDL file : #{File.join(dirname, "#{@plugin_name}.ddl")}" File.open(File.join(dirname, "#{@plugin_name}.rb"), "w"){|f| f.puts @plugin} puts "Created #{@mod_name} file : #{File.join(dirname, "#{@plugin_name}.rb")}" rescue Errno::EEXIST raise "cannot generate '#{@plugin_name}' : plugin directory already exists." rescue Exception => e FileUtils.rm_rf(@plugin_name) if File.directory?(@plugin_name) raise "cannot generate plugin - #{e}" end end end end end mcollective-2.6.0/lib/mcollective/generators/agent_generator.rb0000644000175000017500000000316212375446407023767 0ustar jonasjonasmodule MCollective module Generators class AgentGenerator \"%ACTIONDESCRIPTION%\" do\n" action_text += action_help if i == 0 action_text += "end\n" action_text += "\n" unless @actions.size == (i + 1) end # Use inherited method to create metadata part of the ddl create_metadata_string + action_text end def create_plugin_content content_text = "" # Add actions to agent file @actions.each_with_index do |action, i| content_text += "%6s%s" % [" ", "action \"#{action}\" do\n"] content_text += "%6s%s" % [" ", "end\n"] content_text += "\n" unless @actions.size == (i + 1) end content_text end def action_help action_snippet = File.read(File.join(File.dirname(__FILE__), "templates", "action_snippet.erb")) ERB.new(action_snippet).result end end end end mcollective-2.6.0/lib/mcollective/ddl/0000755000175000017500000000000012375446416016666 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/ddl/validatorddl.rb0000644000175000017500000000011612375446407021662 0ustar jonasjonasmodule MCollective module DDL class ValidatorDDL "mc", # :description => "MCollective Broadcast based discovery", # :author => "R.I.Pienaar ", # :license => "ASL 2.0", # :version => "0.1", # :url => "http://marionette-collective.org/", # :timeout => 2 # # discovery do # capabilities [:classes, :facts, :identity, :agents, :compound] # end class DiscoveryDDL []} @current_entity = :discovery block.call if block_given? @current_entity = nil end end end end mcollective-2.6.0/lib/mcollective/ddl/agentddl.rb0000644000175000017500000002056712375446407021007 0ustar jonasjonasmodule MCollective module DDL # A DDL class specific to agent plugins. # # A full DDL can be seen below with all the possible bells and whistles present. # # metadata :name => "Utilities and Helpers for SimpleRPC Agents", # :description => "General helpful actions that expose stats and internals to SimpleRPC clients", # :author => "R.I.Pienaar ", # :license => "Apache License, Version 2.0", # :version => "1.0", # :url => "http://marionette-collective.org/", # :timeout => 10 # # action "get_fact", :description => "Retrieve a single fact from the fact store" do # display :always # # input :fact, # :prompt => "The name of the fact", # :description => "The fact to retrieve", # :type => :string, # :validation => '^[\w\-\.]+$', # :optional => false, # :maxlength => 40, # :default => "fqdn" # # output :fact, # :description => "The name of the fact being returned", # :display_as => "Fact" # # output :value, # :description => "The value of the fact", # :display_as => "Value", # :default => "" # # summarize do # aggregate summary(:value) # end # end class AgentDDL nil}) raise(DDLValidationError, "Formats supplied to aggregation functions should be a hash") unless format.is_a?(Hash) raise(DDLValidationError, "Formats supplied to aggregation functions must have a :format key") unless format.keys.include?(:format) raise(DDLValidationError, "Functions supplied to aggregate should be a hash") unless function.is_a?(Hash) unless (function.keys.include?(:args)) && function[:args] raise DDLValidationError, "aggregate method for action '%s' missing a function parameter" % entities[@current_entity][:action] end entities[@current_entity][:aggregate] ||= [] entities[@current_entity][:aggregate] << (format[:format].nil? ? function : function.merge(format)) end # Sets the display preference to either :ok, :failed, :flatten or :always # operates on action level def display(pref) if pref == :flatten Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release.") end # defaults to old behavior, complain if its supplied and invalid unless [:ok, :failed, :flatten, :always].include?(pref) raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always" end action = @current_entity @entities[action][:display] = pref end # Creates the definition for an action, you can nest input definitions inside the # action to attach inputs and validation to the actions # # action "status", :description => "Restarts a Service" do # display :always # # input "service", # :prompt => "Service Action", # :description => "The action to perform", # :type => :list, # :optional => true, # :list => ["start", "stop", "restart", "status"] # # output "status", # :description => "The status of the service after the action" # # end def action(name, input, &block) raise "Action needs a :description property" unless input.include?(:description) unless @entities.include?(name) @entities[name] = {} @entities[name][:action] = name @entities[name][:input] = {} @entities[name][:output] = {} @entities[name][:display] = :failed @entities[name][:description] = input[:description] end # if a block is passed it might be creating input methods, call it # we set @current_entity so the input block can know what its talking # to, this is probably an epic hack, need to improve. @current_entity = name block.call if block_given? @current_entity = nil end # If the method name matches a # aggregate function, we return the function # with args as a hash. This will only be active if the @process_aggregate_functions # is set to true which only happens in the #summarize block def method_missing(name, *args, &block) unless @process_aggregate_functions || is_function?(name) raise NoMethodError, "undefined local variable or method `#{name}'", caller end return {:function => name, :args => args} end # Checks if a method name matches a aggregate plugin. # This is used by method missing so that we dont greedily assume that # every method_missing call in an agent ddl has hit a aggregate function. def is_function?(method_name) PluginManager.find("aggregate").include?(method_name.to_s) end # For a given action and arguments look up the DDL interface to that action # and if any arguments in the DDL have a :default value assign that to any # input that does not have an argument in the input arguments # # This is intended to only be called on clients and not on servers as the # clients should never be able to publish non compliant requests and the # servers should really not tamper with incoming requests since doing so # might raise validation errors that were not raised on the client breaking # our fail-fast approach to input validation def set_default_input_arguments(action, arguments) input = action_interface(action)[:input] return unless input input.keys.each do |key| if !arguments.include?(key) && !input[key][:default].nil? && !input[key][:optional] Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]]) arguments[key] = input[key][:default] end end end # Helper to use the DDL to figure out if the remote call to an # agent should be allowed based on action name and inputs. def validate_rpc_request(action, arguments) # is the action known? unless actions.include?(action) raise DDLValidationError, "Attempted to call action #{action} for #{@pluginname} but it's not declared in the DDL" end input = action_interface(action)[:input] || {} input.keys.each do |key| unless input[key][:optional] unless arguments.keys.include?(key) raise DDLValidationError, "Action #{action} needs a #{key} argument" end end if arguments.keys.include?(key) validate_input_argument(input, key, arguments[key]) end end true end # Returns the interface for a specific action def action_interface(name) @entities[name] || {} end # Returns an array of actions this agent support def actions @entities.keys end end end end mcollective-2.6.0/lib/mcollective/ddl/base.rb0000644000175000017500000002004212375446407020123 0ustar jonasjonasmodule MCollective module DDL # The base class for all kinds of DDL files. DDL files when # run gets parsed and builds up a hash of the basic primitive # types, ideally restricted so it can be converted to JSON though # today there are some Ruby Symbols in them which might be fixed # laster on. # # The Hash being built should be stored in @entities, the format # is generally not prescribed but there's a definite feel to how # DDL files look so study the agent and discovery ones to see how # the structure applies to very different use cases. # # For every plugin type you should have a single word name - that # corresponds to the directory in the libdir where these plugins # live. If you need anything above and beyond 'metadata' in your # plugin DDL then add a PlugintypeDDL class here and add your # specific behaviors to those. class Base attr_reader :meta, :entities, :pluginname, :plugintype, :usage, :requirements def initialize(plugin, plugintype=:agent, loadddl=true) @entities = {} @meta = {} @usage = "" @config = Config.instance @pluginname = plugin @plugintype = plugintype.to_sym @requirements = {} loadddlfile if loadddl end # Generates help using the template based on the data # created with metadata and input. # # If no template name is provided one will be chosen based # on the plugin type. If the provided template path is # not absolute then the template will be loaded either from # the config dir and if that does not exist, default to # /etc/mcollective def help(template=nil) template = template_for_plugintype unless template template = Util.templatepath(template) unless Util.absolute_path?(template) template = File.read(template) meta = @meta entities = @entities unless template == "metadata-help.erb" metadata_template = Util.templatepath("metadata-help.erb") metadata_template = File.read(metadata_template) metastring = ERB.new(metadata_template, 0, '%') metastring = metastring.result(binding) end erb = ERB.new(template, 0, '%') erb.result(binding) end def usage(usage_text) @usage = usage_text end def template_for_plugintype case @plugintype when :agent return "rpc-help.erb" else if File.exists?(Util.templatepath("#{@plugintype}-help.erb")) return "#{@plugintype}-help.erb" else # Default help template gets loaded if plugintype-help does not exist. return "metadata-help.erb" end end end def loadddlfile if ddlfile = findddlfile instance_eval(File.read(ddlfile), ddlfile, 1) else raise("Can't find DDL for #{@plugintype} plugin '#{@pluginname}'") end end def findddlfile(ddlname=nil, ddltype=nil) ddlname = @pluginname unless ddlname ddltype = @plugintype unless ddltype @config.libdir.each do |libdir| ddlfile = File.join([libdir, "mcollective", ddltype.to_s, "#{ddlname}.ddl"]) if File.exist?(ddlfile) Log.debug("Found #{ddlname} ddl at #{ddlfile}") return ddlfile end end return false end def validate_requirements if requirement = @requirements[:mcollective] if Util.mcollective_version == "@DEVELOPMENT_VERSION@" Log.warn("DDL requirements validation being skipped in development") return true end if Util.versioncmp(Util.mcollective_version, requirement) < 0 raise DDLValidationError, "%s plugin '%s' requires MCollective version %s or newer" % [@plugintype.to_s.capitalize, @pluginname, requirement] end end true end # validate strings, lists and booleans, we'll add more types of validators when # all the use cases are clear # # only does validation for arguments actually given, since some might # be optional. We validate the presense of the argument earlier so # this is a safe assumption, just to skip them. # # :string can have maxlength and regex. A maxlength of 0 will bypasss checks # :list has a array of valid values def validate_input_argument(input, key, argument) Validator.load_validators case input[key][:type] when :string Validator.validate(argument, :string) Validator.length(argument, input[key][:maxlength].to_i) Validator.validate(argument, input[key][:validation]) when :list Validator.validate(argument, input[key][:list]) else Validator.validate(argument, input[key][:type]) end return true rescue => e raise DDLValidationError, "Cannot validate input %s: %s" % [key, e.to_s] end # Registers an input argument for a given action # # See the documentation for action for how to use this def input(argument, properties) raise "Cannot figure out what entity input #{argument} belongs to" unless @current_entity entity = @current_entity [:prompt, :description, :type].each do |arg| raise "Input needs a :#{arg} property" unless properties.include?(arg) end @entities[entity][:input][argument] = {:prompt => properties[:prompt], :description => properties[:description], :type => properties[:type], :default => properties[:default], :optional => properties[:optional]} case properties[:type] when :string raise "Input type :string needs a :validation argument" unless properties.include?(:validation) raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength) @entities[entity][:input][argument][:validation] = properties[:validation] @entities[entity][:input][argument][:maxlength] = properties[:maxlength] when :list raise "Input type :list needs a :list argument" unless properties.include?(:list) @entities[entity][:input][argument][:list] = properties[:list] end end # Registers an output argument for a given action # # See the documentation for action for how to use this def output(argument, properties) raise "Cannot figure out what action input #{argument} belongs to" unless @current_entity raise "Output #{argument} needs a description argument" unless properties.include?(:description) raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as) action = @current_entity @entities[action][:output][argument] = {:description => properties[:description], :display_as => properties[:display_as], :default => properties[:default]} end def requires(requirement) raise "Requirement should be a hash in the form :item => 'requirement'" unless requirement.is_a?(Hash) valid_requirements = [:mcollective] requirement.keys.each do |key| unless valid_requirements.include?(key) raise "Requirement %s is not a valid requirement, only %s is supported" % [key, valid_requirements.join(", ")] end @requirements[key] = requirement[key] end validate_requirements end # Registers meta data for the introspection hash def metadata(meta) [:name, :description, :author, :license, :version, :url, :timeout].each do |arg| raise "Metadata needs a :#{arg} property" unless meta.include?(arg) end @meta = meta end end end end mcollective-2.6.0/lib/mcollective/ddl/dataddl.rb0000644000175000017500000000362712375446407020620 0ustar jonasjonasmodule MCollective module DDL # A DDL file for the data query plugins. # # Query plugins can today take only one input by convention in the DDL that # is called :query, otherwise the input is identical to the standard input. # # metadata :name => "Agent", # :description => "Meta data about installed MColletive Agents", # :author => "R.I.Pienaar ", # :license => "ASL 2.0", # :version => "1.0", # :url => "http://marionette-collective.org/", # :timeout => 1 # # dataquery :description => "Agent Meta Data" do # input :query, # :prompt => "Agent Name", # :description => "Valid agent name", # :type => :string, # :validation => /^[\w\_]+$/, # :maxlength => 20 # # [:license, :timeout, :description, :url, :version, :author].each do |item| # output item, # :description => "Agent #{item}", # :display_as => item.to_s.capitalize # end # end class DataDDL input[:description], :input => {}, :output => {}} @current_entity = :data block.call if block_given? @current_entity = nil end def input(argument, properties) raise "The only valid input name for a data query is 'query'" if argument != :query super end # Returns the interface for the data query def dataquery_interface @entities[:data] || {} end end end end mcollective-2.6.0/lib/mcollective/vendor.rb0000644000175000017500000000230012375446407017740 0ustar jonasjonasmodule MCollective # Simple module to manage vendored code. # # To vendor a library simply download its whole git repo or untar # into vendor/libraryname and create a load_libraryname.rb file # to add its libdir into the $:. # # Once you have that file, add a require line in vendor/require_vendored.rb # which will run after all the load_* files. # # The intention is to not change vendored libraries and to eventually # make adding them in optional so that distros can simply adjust their # packaging to exclude this directory and the various load_xxx.rb scripts # if they wish to install these gems as native packages. class Vendor class << self def vendor_dir File.join([File.dirname(File.expand_path(__FILE__)), "vendor"]) end def load_entry(entry) Log.debug("Loading vendored #{$1}") load "#{vendor_dir}/#{entry}" end def require_libs require 'mcollective/vendor/require_vendored' end def load_vendored Dir.entries(vendor_dir).each do |entry| if entry.match(/load_(\w+?)\.rb$/) load_entry entry end end require_libs end end end end mcollective-2.6.0/lib/mcollective/monkey_patches.rb0000644000175000017500000000560312375446407021465 0ustar jonasjonas# start_with? was introduced in 1.8.7, we need to support # 1.8.5 and 1.8.6 class String def start_with?(str) return self[0 .. (str.length-1)] == str end unless method_defined?("start_with?") end # Make arrays of Symbols sortable class Symbol include Comparable def <=>(other) self.to_s <=> other.to_s end unless method_defined?("<=>") end # This provides an alias for RbConfig to Config for versions of Ruby older then # # version 1.8.5. This allows us to use RbConfig in place of the older Config in # # our code and still be compatible with at least Ruby 1.8.1. # require 'rbconfig' unless defined? ::RbConfig ::RbConfig = ::Config end # a method # that walks an array in groups, pass a block to # call the block on each sub array class Array def in_groups_of(chunk_size, padded_with=nil, &block) arr = self.clone # how many to add padding = chunk_size - (arr.size % chunk_size) # pad at the end arr.concat([padded_with] * padding) unless padding == chunk_size # how many chunks we'll make count = arr.size / chunk_size # make that many arrays result = [] count.times {|s| result << arr[s * chunk_size, chunk_size]} if block_given? result.each_with_index do |a, i| case block.arity when 1 yield(a) when 2 yield(a, (i == result.size - 1)) else raise "Expected 1 or 2 arguments, got #{block.arity}" end end else result end end unless method_defined?(:in_groups_of) end class String def bytes(&block) # This should not be necessary, really ... require 'enumerator' return to_enum(:each_byte) unless block_given? each_byte(&block) end unless method_defined?(:bytes) end class Dir def self.mktmpdir(prefix_suffix=nil, tmpdir=nil) case prefix_suffix when nil prefix = "d" suffix = "" when String prefix = prefix_suffix suffix = "" when Array prefix = prefix_suffix[0] suffix = prefix_suffix[1] else raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}" end tmpdir ||= Dir.tmpdir t = Time.now.strftime("%Y%m%d") n = nil begin path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}" path << "-#{n}" if n path << suffix Dir.mkdir(path, 0700) rescue Errno::EEXIST n ||= 0 n += 1 retry end if block_given? begin yield path ensure FileUtils.remove_entry_secure path end else path end end unless method_defined?(:mktmpdir) def self.tmpdir tmp = '.' for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], '/tmp'] if dir and stat = File.stat(dir) and stat.directory? and stat.writable? tmp = dir break end rescue nil end File.expand_path(tmp) end unless method_defined?(:tmpdir) end mcollective-2.6.0/lib/mcollective/exceptions.rb0000644000175000017500000000142212375446407020630 0ustar jonasjonasmodule MCollective # Exceptions for the RPC system class DDLValidationError "security_plugin", :class => klass.to_s} end # Initializes configuration and logging as well as prepare a zero'd hash of stats # various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample def initialize @config = Config.instance @log = Log @stats = PluginManager["global_stats"] end # Takes a Hash with a filter in it and validates it against host information. # # At present this supports filter matches against the following criteria: # # - puppet_class|cf_class - Presence of a configuration management class in # the file configured with classesfile # - agent - Presence of a MCollective agent with a supplied name # - fact - The value of a fact avout this system # - identity - the configured identity of the system # # TODO: Support REGEX and/or multiple filter keys to be AND'd def validate_filter?(filter) failed = 0 passed = 0 passed = 1 if Util.empty_filter?(filter) filter.keys.each do |key| case key when /puppet_class|cf_class/ filter[key].each do |f| Log.debug("Checking for class #{f}") if Util.has_cf_class?(f) then Log.debug("Passing based on configuration management class #{f}") passed += 1 else Log.debug("Failing based on configuration management class #{f}") failed += 1 end end when "compound" filter[key].each do |compound| result = false truth_values = [] begin compound.each do |expression| case expression.keys.first when "statement" truth_values << Matcher.eval_compound_statement(expression).to_s when "fstatement" truth_values << Matcher.eval_compound_fstatement(expression.values.first) when "and" truth_values << "&&" when "or" truth_values << "||" when "(" truth_values << "(" when ")" truth_values << ")" when "not" truth_values << "!" end end result = eval(truth_values.join(" ")) rescue DDLValidationError result = false end if result Log.debug("Passing based on class and fact composition") passed +=1 else Log.debug("Failing based on class and fact composition") failed +=1 end end when "agent" filter[key].each do |f| if Util.has_agent?(f) || f == "mcollective" Log.debug("Passing based on agent #{f}") passed += 1 else Log.debug("Failing based on agent #{f}") failed += 1 end end when "fact" filter[key].each do |f| if Util.has_fact?(f[:fact], f[:value], f[:operator]) Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}") passed += 1 else Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}") failed += 1 end end when "identity" unless filter[key].empty? # Identity filters should not be 'and' but 'or' as each node can only have one identity matched = filter[key].select{|f| Util.has_identity?(f)}.size if matched == 1 Log.debug("Passing based on identity") passed += 1 else Log.debug("Failed based on identity") failed += 1 end end end end if failed == 0 && passed > 0 Log.debug("Message passed the filter checks") @stats.passed return true else Log.debug("Message failed the filter checks") @stats.filtered return false end end def create_reply(reqid, agent, body) Log.debug("Encoded a message for request #{reqid}") {:senderid => @config.identity, :requestid => reqid, :senderagent => agent, :msgtime => Time.now.utc.to_i, :body => body} end def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60) Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}") {:body => msg, :senderid => @config.identity, :requestid => reqid, :filter => filter, :collective => target_collective, :agent => target_agent, :callerid => callerid, :ttl => ttl, :msgtime => Time.now.utc.to_i} end # Give a MC::Message instance and a message id this will figure out if you the incoming # message id matches the one the Message object is expecting and raise if its not # # Mostly used by security plugins to figure out if they should do the hard work of decrypting # etc messages that would only later on be ignored def should_process_msg?(msg, msgid) if msg.expected_msgid unless msg.expected_msgid == msgid msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid] Log.debug msgtext raise MsgDoesNotMatchRequestID, msgtext end end true end # Validates a callerid. We do not want to allow things like \ and / in # callerids since other plugins make assumptions that these are safe strings. # # callerids are generally in the form uid=123 or cert=foo etc so we do that # here but security plugins could override this for some complex uses def valid_callerid?(id) !!id.match(/^[\w]+=[\w\.\-]+$/) end # Returns a unique id for the caller, by default we just use the unix # user id, security plugins can provide their own means of doing ids. def callerid "uid=#{Process.uid}" end # Security providers should provide this, see MCollective::Security::Psk def validrequest?(req) Log.error("validrequest? is not implemented in #{self.class}") end # Security providers should provide this, see MCollective::Security::Psk def encoderequest(sender, msg, filter={}) Log.error("encoderequest is not implemented in #{self.class}") end # Security providers should provide this, see MCollective::Security::Psk def encodereply(sender, msg, requestcallerid=nil) Log.error("encodereply is not implemented in #{self.class}") end # Security providers should provide this, see MCollective::Security::Psk def decodemsg(msg) Log.error("decodemsg is not implemented in #{self.class}") end end end end mcollective-2.6.0/lib/mcollective/ssl.rb0000644000175000017500000002140712375446407017255 0ustar jonasjonasrequire 'openssl' require 'base64' require 'digest/sha1' module MCollective # A class that assists in encrypting and decrypting data using a # combination of RSA and AES # # Data will be AES encrypted for speed, the Key used in # the AES # stage will be encrypted using RSA # # ssl = SSL.new(public_key, private_key, passphrase) # # data = File.read("largefile.dat") # # crypted_data = ssl.encrypt_with_private(data) # # pp crypted_data # # This will result in a hash of data like: # # crypted = {:key => "crd4NHvG....=", # :data => "XWXlqN+i...=="} # # The key and data will all be base 64 encoded already by default # you can pass a 2nd parameter as false to encrypt_with_private and # counterparts that will prevent the base 64 encoding # # You can pass the data hash into ssl.decrypt_with_public which # should return your original data # # There are matching methods for using a public key to encrypt # data to be decrypted using a private key class SSL attr_reader :public_key_file, :private_key_file, :ssl_cipher def initialize(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil) @public_key_file = pubkey @private_key_file = privkey @public_key = read_key(:public, pubkey) @private_key = read_key(:private, privkey, passphrase) @ssl_cipher = "aes-256-cbc" @ssl_cipher = Config.instance.ssl_cipher if Config.instance.ssl_cipher @ssl_cipher = cipher if cipher raise "The supplied cipher '#{@ssl_cipher}' is not supported" unless OpenSSL::Cipher.ciphers.include?(@ssl_cipher) end # Encrypts supplied data using AES and then encrypts using RSA # the key and IV # # Return a hash with everything optionally base 64 encoded def encrypt_with_public(plain_text, base64=true) crypted = aes_encrypt(plain_text) if base64 key = base64_encode(rsa_encrypt_with_public(crypted[:key])) data = base64_encode(crypted[:data]) else key = rsa_encrypt_with_public(crypted[:key]) data = crypted[:data] end {:key => key, :data => data} end # Encrypts supplied data using AES and then encrypts using RSA # the key and IV # # Return a hash with everything optionally base 64 encoded def encrypt_with_private(plain_text, base64=true) crypted = aes_encrypt(plain_text) if base64 key = base64_encode(rsa_encrypt_with_private(crypted[:key])) data = base64_encode(crypted[:data]) else key = rsa_encrypt_with_private(crypted[:key]) data = crypted[:data] end {:key => key, :data => data} end # Decrypts data, expects a hash as create with crypt_with_public def decrypt_with_private(crypted, base64=true) raise "Crypted data should include a key" unless crypted.include?(:key) raise "Crypted data should include data" unless crypted.include?(:data) if base64 key = rsa_decrypt_with_private(base64_decode(crypted[:key])) aes_decrypt(key, base64_decode(crypted[:data])) else key = rsa_decrypt_with_private(crypted[:key]) aes_decrypt(key, crypted[:data]) end end # Decrypts data, expects a hash as create with crypt_with_private def decrypt_with_public(crypted, base64=true) raise "Crypted data should include a key" unless crypted.include?(:key) raise "Crypted data should include data" unless crypted.include?(:data) if base64 key = rsa_decrypt_with_public(base64_decode(crypted[:key])) aes_decrypt(key, base64_decode(crypted[:data])) else key = rsa_decrypt_with_public(crypted[:key]) aes_decrypt(key, crypted[:data]) end end # Use the public key to RSA encrypt data def rsa_encrypt_with_public(plain_string) raise "No public key set" unless @public_key @public_key.public_encrypt(plain_string) end # Use the private key to RSA decrypt data def rsa_decrypt_with_private(crypt_string) raise "No private key set" unless @private_key @private_key.private_decrypt(crypt_string) end # Use the private key to RSA encrypt data def rsa_encrypt_with_private(plain_string) raise "No private key set" unless @private_key @private_key.private_encrypt(plain_string) end # Use the public key to RSA decrypt data def rsa_decrypt_with_public(crypt_string) raise "No public key set" unless @public_key @public_key.public_decrypt(crypt_string) end # encrypts a string, returns a hash of key, iv and data def aes_encrypt(plain_string) cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher) cipher.encrypt key = cipher.random_key cipher.key = key cipher.pkcs5_keyivgen(key) encrypted_data = cipher.update(plain_string) + cipher.final {:key => key, :data => encrypted_data} end # decrypts a string given key, iv and data def aes_decrypt(key, crypt_string) cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher) cipher.decrypt cipher.key = key cipher.pkcs5_keyivgen(key) decrypted_data = cipher.update(crypt_string) + cipher.final end # Signs a string using the private key def sign(string, base64=false) sig = @private_key.sign(OpenSSL::Digest::SHA1.new, string) base64 ? base64_encode(sig) : sig end # Using the public key verifies that a string was signed using the private key def verify_signature(signature, string, base64=false) signature = base64_decode(signature) if base64 @public_key.verify(OpenSSL::Digest::SHA1.new, signature, string) end # base 64 encode a string def base64_encode(string) SSL.base64_encode(string) end def self.base64_encode(string) Base64.encode64(string) end # base 64 decode a string def base64_decode(string) SSL.base64_decode(string) end def self.base64_decode(string) # The Base 64 character set is A-Z a-z 0-9 + / = # Also allow for whitespace, but raise if we get anything else if string !~ /^[A-Za-z0-9+\/=\s]+$/ raise ArgumentError, 'invalid base64' end Base64.decode64(string) end def md5(string) SSL.md5(string) end def self.md5(string) Digest::MD5.hexdigest(string) end # Creates a RFC 4122 version 5 UUID. If string is supplied it will produce repeatable # UUIDs for that string else a random 128bit string will be used from OpenSSL::BN # # Code used with permission from: # https://github.com/kwilczynski/puppet-functions/blob/master/lib/puppet/parser/functions/uuid.rb # def self.uuid(string=nil) string ||= OpenSSL::Random.random_bytes(16).unpack('H*').shift uuid_name_space_dns = "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8" sha1 = Digest::SHA1.new sha1.update(uuid_name_space_dns) sha1.update(string) # first 16 bytes.. bytes = sha1.digest[0, 16].bytes.to_a # version 5 adjustments bytes[6] &= 0x0f bytes[6] |= 0x50 # variant is DCE 1.1 bytes[8] &= 0x3f bytes[8] |= 0x80 bytes = [4, 2, 2, 2, 6].collect do |i| bytes.slice!(0, i).pack('C*').unpack('H*') end bytes.join('-') end # Reads either a :public or :private key from disk, uses an # optional passphrase to read the private key def read_key(type, key=nil, passphrase=nil) return key if key.nil? raise "Could not find key #{key}" unless File.exist?(key) raise "#{type} key file '#{key}' is empty" if File.zero?(key) if type == :public begin key = OpenSSL::PKey::RSA.new(File.read(key)) rescue OpenSSL::PKey::RSAError key = OpenSSL::X509::Certificate.new(File.read(key)).public_key end # Ruby < 1.9.3 had a bug where it does not correctly clear the # queue of errors while reading a key. It tries various ways # to read the key and each failing attempt pushes an error onto # the queue. With pubkeys only the 3rd attempt pass leaving 2 # stale errors on the error queue. # # In 1.9.3 they fixed this by simply discarding the errors after # every attempt. So we simulate this fix here for older rubies # as without it we get SSL_read errors from the Stomp+TLS sessions # # We do this only on 1.8 relying on 1.9.3 to do the right thing # and we do not support 1.9 less than 1.9.3 # # See http://bugs.ruby-lang.org/issues/4550 OpenSSL.errors if Util.ruby_version =~ /^1.8/ return key elsif type == :private return OpenSSL::PKey::RSA.new(File.read(key), passphrase) else raise "Can only load :public or :private keys" end end end end mcollective-2.6.0/lib/mcollective/applications.rb0000644000175000017500000000774212375446407021150 0ustar jonasjonasmodule MCollective class Applications def self.[](appname) load_application(appname) PluginManager["#{appname}_application"] end def self.run(appname) load_config begin load_application(appname) rescue Exception => e e.backtrace.first << Util.colorize(:red, " <----") STDERR.puts "Application '#{appname}' failed to load:" STDERR.puts STDERR.puts Util.colorize(:red, " #{e} (#{e.class})") STDERR.puts STDERR.puts " %s" % [e.backtrace.join("\n ")] exit 1 end PluginManager["#{appname}_application"].run end def self.load_application(appname) return if PluginManager.include?("#{appname}_application") load_config PluginManager.loadclass "MCollective::Application::#{appname.capitalize}" PluginManager << {:type => "#{appname}_application", :class => "MCollective::Application::#{appname.capitalize}"} end # Returns an array of applications found in the lib dirs def self.list load_config PluginManager.find("application") rescue SystemExit exit 1 rescue Exception => e STDERR.puts "Failed to generate application list: #{e.class}: #{e}" exit 1 end # Filters a string of opts out using Shellwords # keeping only things related to --config and -c def self.filter_extra_options(opts) res = "" words = Shellwords.shellwords(opts) words.each_with_index do |word,idx| if word == "-c" return "--config=#{words[idx + 1]}" elsif word == "--config" return "--config=#{words[idx + 1]}" elsif word =~ /\-c=/ return word elsif word =~ /\-\-config=/ return word end end return "" end # We need to know the config file in order to know the libdir # so that we can find applications. # # The problem is the CLI might be stuffed with options only the # app in the libdir might understand so we have a chicken and # egg situation. # # We're parsing and filtering MCOLLECTIVE_EXTRA_OPTS removing # all but config related options and parsing the options looking # just for the config file. # # We're handling failures gracefully and finally restoring the # ARG and MCOLLECTIVE_EXTRA_OPTS to the state they were before # we started parsing. # # This is mostly a hack, when we're redoing how config works # this stuff should be made less sucky def self.load_config return if Config.instance.configured original_argv = ARGV.clone original_extra_opts = ENV["MCOLLECTIVE_EXTRA_OPTS"].clone rescue nil configfile = nil parser = OptionParser.new parser.on("--config CONFIG", "-c", "Config file") do |f| configfile = f end parser.program_name = $0 parser.on("--help") # avoid option parsers own internal version handling that sux parser.on("-v", "--verbose") if original_extra_opts begin # optparse will parse the whole ENV in one go and refuse # to play along with the retry trick I do below so in # order to handle unknown options properly I parse out # only -c and --config deleting everything else and # then restore the environment variable later when I # am done with it ENV["MCOLLECTIVE_EXTRA_OPTS"] = filter_extra_options(ENV["MCOLLECTIVE_EXTRA_OPTS"].clone) parser.environment("MCOLLECTIVE_EXTRA_OPTS") rescue Exception => e Log.error("Failed to parse MCOLLECTIVE_EXTRA_OPTS: #{e}") end ENV["MCOLLECTIVE_EXTRA_OPTS"] = original_extra_opts.clone end begin parser.parse! rescue OptionParser::InvalidOption => e retry end ARGV.clear original_argv.each {|a| ARGV << a} configfile = Util.config_file_for_user unless configfile Config.instance.loadconfig(configfile) end end end mcollective-2.6.0/lib/mcollective/matcher.rb0000644000175000017500000001602512375446407020077 0ustar jonasjonasmodule MCollective # A parser and scanner that creates a stack machine for a simple # fact and class matching language used on the CLI to facilitate # a rich discovery language # # Language EBNF # # compound = ["("] expression [")"] {["("] expression [")"]} # expression = [!|not]statement ["and"|"or"] [!|not] statement # char = A-Z | a-z | < | > | => | =< | _ | - |* | / { A-Z | a-z | < | > | => | =< | _ | - | * | / | } # int = 0|1|2|3|4|5|6|7|8|9{|0|1|2|3|4|5|6|7|8|9|0} module Matcher autoload :Parser, "mcollective/matcher/parser" autoload :Scanner, "mcollective/matcher/scanner" # Helper creates a hash from a function call string def self.create_function_hash(function_call) func_hash = {} f = "" func_parts = function_call.split(/(!=|>=|<=|<|>|=)/) func_hash["r_compare"] = func_parts.pop func_hash["operator"] = func_parts.pop func = func_parts.join # Deal with dots in function parameters and functions without dot values if func.match(/^.+\(.*\)$/) f = func else func_parts = func.split(".") func_hash["value"] = func_parts.pop f = func_parts.join(".") end # Deal with regular expression matches if func_hash["r_compare"] =~ /^\/.*\/$/ func_hash["operator"] = "=~" if func_hash["operator"] == "=" func_hash["operator"] = "!=~" if func_hash["operator"] == "!=" func_hash["r_compare"] = Regexp.new(func_hash["r_compare"].gsub(/^\/|\/$/, "")) # Convert = operators to == so they can be propperly evaluated elsif func_hash["operator"] == "=" func_hash["operator"] = "==" end # Grab function name and parameters from left compare string func_hash["name"], func_hash["params"] = f.split("(") if func_hash["params"] == ")" func_hash["params"] = nil else # Walk the function parameters from the front and from the # back removing the first and last instances of single of # double qoutes. We do this to handle the case where params # contain escaped qoutes. func_hash["params"] = func_hash["params"].gsub(")", "") func_quotes = func_hash["params"].split(/('|")/) func_quotes.each_with_index do |item, i| if item.match(/'|"/) func_quotes.delete_at(i) break end end func_quotes.reverse.each_with_index do |item,i| if item.match(/'|"/) func_quotes.delete_at(func_quotes.size - i - 1) break end end func_hash["params"] = func_quotes.join end func_hash end # Returns the result of an executed function def self.execute_function(function_hash) # In the case where a data plugin isn't present there are two ways we can handle # the raised exception. The function result can either be false or the entire # expression can fail. # # In the case where we return the result as false it opens us op to unexpected # negation behavior. # # !foo('bar').name = bar # # In this case the user would expect discovery to match on all machines where # the name value of the foo function does not equal bar. If a non existent function # returns false then it is posible to match machines where the name value of the # foo function is bar. # # Instead we raise a DDLValidationError to prevent this unexpected behavior from # happening. result = Data.send(function_hash["name"], function_hash["params"]) if function_hash["value"] begin eval_result = result.send(function_hash["value"]) rescue # If data field has not been set we set the comparison result to nil eval_result = nil end return eval_result else return result end rescue NoMethodError Log.debug("cannot execute discovery function '#{function_hash["name"]}'. data plugin not found") raise DDLValidationError end # Evaluates a compound statement def self.eval_compound_statement(expression) if expression.values.first =~ /^\// return Util.has_cf_class?(expression.values.first) elsif expression.values.first =~ />=|<=|=|<|>/ optype = expression.values.first.match(/>=|<=|=|<|>/) name, value = expression.values.first.split(optype[0]) unless value.split("")[0] == "/" optype[0] == "=" ? optype = "==" : optype = optype[0] else optype = "=~" end return Util.has_fact?(name,value, optype).to_s else return Util.has_cf_class?(expression.values.first) end end # Returns the result of an evaluated compound statement that # includes a function def self.eval_compound_fstatement(function_hash) l_compare = execute_function(function_hash) # Break out early and return false if the function returns nil return false unless l_compare # Prevent unwanted discovery by limiting comparison operators # on Strings and Booleans if((l_compare.is_a?(String) || l_compare.is_a?(TrueClass) || l_compare.is_a?(FalseClass)) && function_hash["operator"].match(/<|>/)) Log.debug "Cannot do > and < comparison on Booleans and Strings '#{l_compare} #{function_hash["operator"]} #{function_hash["r_compare"]}'" return false end # Prevent backticks in function parameters if function_hash["params"] =~ /`/ Log.debug("Cannot use backticks in function parameters") return false end # Escape strings for evaluation function_hash["r_compare"] = "\"#{function_hash["r_compare"]}\"" if(l_compare.is_a?(String) && !(function_hash["operator"] =~ /=~|!=~/)) # Do a regex comparison if right compare string is a regex if function_hash["operator"] =~ /(=~|!=~)/ # Fail if left compare value isn't a string unless l_compare.is_a?(String) Log.debug("Cannot do a regex check on a non string value.") return false else compare_result = l_compare.match(function_hash["r_compare"]) # Flip return value for != operator if function_hash["operator"] == "!=~" !((compare_result.nil?) ? false : true) else (compare_result.nil?) ? false : true end end # Otherwise evaluate the logical comparison else l_compare = "\"#{l_compare}\"" if l_compare.is_a?(String) result = eval("#{l_compare} #{function_hash["operator"]} #{function_hash["r_compare"]}") (result.nil?) ? false : result end end # Creates a callstack to be evaluated from a compound evaluation string def self.create_compound_callstack(call_string) callstack = Matcher::Parser.new(call_string).execution_stack callstack.each_with_index do |statement, i| if statement.keys.first == "fstatement" callstack[i]["fstatement"] = create_function_hash(statement.values.first) end end callstack end end end mcollective-2.6.0/lib/mcollective/facts/0000755000175000017500000000000012375446416017223 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/facts/base.rb0000644000175000017500000000532412375446407020466 0ustar jonasjonasmodule MCollective module Facts # A base class for fact providers, to make a new fully functional fact provider # inherit from this and simply provide a self.get_facts method that returns a # hash like: # # {"foo" => "bar", # "bar" => "baz"} class Base def initialize @facts = {} @last_good_facts = {} @last_facts_load = 0 end # Registers new fact sources into the plugin manager def self.inherited(klass) PluginManager << {:type => "facts_plugin", :class => klass.to_s} end # Returns the value of a single fact def get_fact(fact=nil) config = Config.instance cache_time = config.fact_cache_time || 300 Thread.exclusive do begin if (Time.now.to_i - @last_facts_load > cache_time.to_i ) || force_reload? Log.debug("Resetting facter cache, now: #{Time.now.to_i} last-known-good: #{@last_facts_load}") tfacts = load_facts_from_source # Force reset to last known good state on empty facts raise "Got empty facts" if tfacts.empty? @facts = normalize_facts(tfacts) @last_good_facts = @facts.clone @last_facts_load = Time.now.to_i else Log.debug("Using cached facts now: #{Time.now.to_i} last-known-good: #{@last_facts_load}") end rescue Exception => e Log.error("Failed to load facts: #{e.class}: #{e}") # Avoid loops where failing fact loads cause huge CPU # loops, this way it only retries once every cache_time # seconds @last_facts_load = Time.now.to_i # Revert to last known good state @facts = @last_good_facts.clone end end # If you do not supply a specific fact all facts will be returned if fact.nil? return @facts else @facts.include?(fact) ? @facts[fact] : nil end end # Returns all facts def get_facts get_fact(nil) end # Returns true if we know about a specific fact, false otherwise def has_fact?(fact) get_fact(nil).include?(fact) end # Plugins can override this to provide forced fact invalidation def force_reload? false end private def normalize_facts(value) case value when Array return value.map { |v| normalize_facts(v) } when Hash new_hash = {} value.each do |k,v| new_hash[k.to_s] = normalize_facts(v) end return new_hash else return value.to_s end end end end end mcollective-2.6.0/lib/mcollective/generators.rb0000644000175000017500000000037012375446407020621 0ustar jonasjonasmodule MCollective module Generators autoload :Base, "mcollective/generators/base.rb" autoload :DataGenerator, "mcollective/generators/data_generator.rb" autoload :AgentGenerator, "mcollective/generators/agent_generator.rb" end end mcollective-2.6.0/lib/mcollective/data/0000755000175000017500000000000012375446416017034 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/data/base.rb0000644000175000017500000000343312375446407020276 0ustar jonasjonasmodule MCollective module Data class Base attr_reader :name, :result, :ddl, :timeout # Register plugins that inherits base def self.inherited(klass) type = klass.to_s.split("::").last.downcase PluginManager << {:type => type, :class => klass.to_s, :single_instance => false} end def initialize @name = self.class.to_s.split("::").last.downcase @ddl = DDL.new(@name, :data) @result = Result.new(@ddl.dataquery_interface[:output]) @timeout = @ddl.meta[:timeout] || 1 startup_hook end def lookup(what) ddl_validate(what) Log.debug("Doing data query %s for '%s'" % [ @name, what ]) Timeout::timeout(@timeout) do query_data(what) end @result rescue Timeout::Error # Timeout::Error is a inherited from Interrupt which seems a really # strange choice, making it an equivelant of ^C and such. Catch it # and raise something less critical that will not the runner to just # give up the ghost msg = "Data plugin %s timed out on query '%s'" % [@name, what] Log.error(msg) raise MsgTTLExpired, msg end def self.query(&block) self.module_eval { define_method("query_data", &block) } end def ddl_validate(what) Data.ddl_validate(@ddl, what) end # activate_when do # file.exist?("/usr/bin/puppet") # end def self.activate_when(&block) (class << self; self; end).instance_eval do define_method("activate?", &block) end end # Always be active unless a specific block is given with activate_when def self.activate? return true end def startup_hook;end end end end mcollective-2.6.0/lib/mcollective/data/result.rb0000644000175000017500000000211112375446407020672 0ustar jonasjonasmodule MCollective module Data class Result # remove some methods that might clash with commonly # used return data to improve the effectiveness of the # method_missing lookup strategy undef :type if method_defined?(:type) def initialize(outputs) @data = {} outputs.keys.each do |output| @data[output] = Marshal.load(Marshal.dump(outputs[output].fetch(:default, nil))) end end def include?(key) @data.include?(key.to_sym) end def [](key) @data[key.to_sym] end def []=(key, val) raise "Can only store String, Integer, Float or Boolean data but got #{val.class} for key #{key}" unless [String, Fixnum, Bignum, Float, TrueClass, FalseClass].include?(val.class) @data[key.to_sym] = val end def keys @data.keys end def method_missing(method, *args) key = method.to_sym raise NoMethodError, "undefined local variable or method `%s'" % key unless include?(key) @data[key] end end end end mcollective-2.6.0/lib/mcollective/util.rb0000644000175000017500000004057212375446407017435 0ustar jonasjonasmodule MCollective # Some basic utility helper methods useful to clients, agents, runner etc. module Util # Finds out if this MCollective has an agent by the name passed # # If the passed name starts with a / it's assumed to be regex # and will use regex to match def self.has_agent?(agent) agent = Regexp.new(agent.gsub("\/", "")) if agent.match("^/") if agent.is_a?(Regexp) if Agents.agentlist.grep(agent).size > 0 return true else return false end else return Agents.agentlist.include?(agent) end false end # On windows ^c can't interrupt the VM if its blocking on # IO, so this sets up a dummy thread that sleeps and this # will have the end result of being interruptable at least # once a second. This is a common pattern found in Rails etc def self.setup_windows_sleeper Thread.new { loop { sleep 1 } } if Util.windows? end # Checks if this node has a configuration management class by parsing the # a text file with just a list of classes, recipes, roles etc. This is # ala the classes.txt from puppet. # # If the passed name starts with a / it's assumed to be regex # and will use regex to match def self.has_cf_class?(klass) klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/") cfile = Config.instance.classesfile Log.debug("Looking for configuration management classes in #{cfile}") begin File.readlines(cfile).each do |k| if klass.is_a?(Regexp) return true if k.chomp.match(klass) else return true if k.chomp == klass end end rescue Exception => e Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}") end false end # Gets the value of a specific fact, mostly just a duplicate of MCollective::Facts.get_fact # but it kind of goes with the other classes here def self.get_fact(fact) Facts.get_fact(fact) end # Compares fact == value, # # If the passed value starts with a / it's assumed to be regex # and will use regex to match def self.has_fact?(fact, value, operator) Log.debug("Comparing #{fact} #{operator} #{value}") Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'") fact = Facts[fact] return false if fact.nil? fact = fact.clone case fact when Array return fact.any? { |element| test_fact_value(element, value, operator)} when Hash return fact.keys.any? { |element| test_fact_value(element, value, operator)} else return test_fact_value(fact, value, operator) end end def self.test_fact_value(fact, value, operator) if operator == '=~' # to maintain backward compat we send the value # as /.../ which is what 1.0.x needed. this strips # off the /'s which is what we need here if value =~ /^\/(.+)\/$/ value = $1 end return true if fact.match(Regexp.new(value)) elsif operator == "==" return true if fact == value elsif ['<=', '>=', '<', '>', '!='].include?(operator) # Yuk - need to type cast, but to_i and to_f are overzealous if value =~ /^[0-9]+$/ && fact =~ /^[0-9]+$/ fact = Integer(fact) value = Integer(value) elsif value =~ /^[0-9]+.[0-9]+$/ && fact =~ /^[0-9]+.[0-9]+$/ fact = Float(fact) value = Float(value) end return true if eval("fact #{operator} value") end false end private_class_method :test_fact_value # Checks if the configured identity matches the one supplied # # If the passed name starts with a / it's assumed to be regex # and will use regex to match def self.has_identity?(identity) identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/") if identity.is_a?(Regexp) return Config.instance.identity.match(identity) else return true if Config.instance.identity == identity end false end # Checks if the passed in filter is an empty one def self.empty_filter?(filter) filter == empty_filter || filter == {} end # Creates an empty filter def self.empty_filter {"fact" => [], "cf_class" => [], "agent" => [], "identity" => [], "compound" => []} end # Returns the PuppetLabs mcollective path for windows def self.windows_prefix require 'win32/dir' prefix = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "mcollective") end # Picks a config file defaults to ~/.mcollective # else /etc/mcollective/client.cfg def self.config_file_for_user # expand_path is pretty lame, it relies on HOME environment # which isnt't always there so just handling all exceptions # here as cant find reverting to default begin config = File.expand_path("~/.mcollective") unless File.readable?(config) && File.file?(config) if self.windows? config = File.join(self.windows_prefix, "etc", "client.cfg") else config = "/etc/mcollective/client.cfg" end end rescue Exception => e if self.windows? config = File.join(self.windows_prefix, "etc", "client.cfg") else config = "/etc/mcollective/client.cfg" end end return config end # Creates a standard options hash def self.default_options {:verbose => false, :disctimeout => nil, :timeout => 5, :config => config_file_for_user, :collective => nil, :discovery_method => nil, :discovery_options => Config.instance.default_discovery_options, :filter => empty_filter} end def self.make_subscriptions(agent, type, collective=nil) config = Config.instance raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type) if collective.nil? config.collectives.map do |c| {:agent => agent, :type => type, :collective => c} end else raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective) [{:agent => agent, :type => type, :collective => collective}] end end # Helper to subscribe to a topic on multiple collectives or just one def self.subscribe(targets) connection = PluginManager["connector_plugin"] targets = [targets].flatten targets.each do |target| connection.subscribe(target[:agent], target[:type], target[:collective]) end end # Helper to unsubscribe to a topic on multiple collectives or just one def self.unsubscribe(targets) connection = PluginManager["connector_plugin"] targets = [targets].flatten targets.each do |target| connection.unsubscribe(target[:agent], target[:type], target[:collective]) end end # Wrapper around PluginManager.loadclass def self.loadclass(klass) PluginManager.loadclass(klass) end # Parse a fact filter string like foo=bar into the tuple hash thats needed def self.parse_fact_string(fact) if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/ return {:fact => $1, :value => $2, :operator => '>=' } elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/ return {:fact => $1, :value => $2, :operator => '<=' } elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/ return {:fact => $1, :value => $3, :operator => $2 } elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/ return {:fact => $1, :value => "/#{$2}/", :operator => '=~' } elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/ return {:fact => $1, :value => $2, :operator => '==' } else raise "Could not parse fact #{fact} it does not appear to be in a valid format" end end # Escapes a string so it's safe to use in system() or backticks # # Taken from Shellwords#shellescape since it's only in a few ruby versions def self.shellescape(str) return "''" if str.empty? str = str.dup # Process as a single byte sequence because not all shell # implementations are multibyte aware. str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/n, "\\\\\\1") # A LF cannot be escaped with a backslash because a backslash + LF # combo is regarded as line continuation and simply ignored. str.gsub!(/\n/, "'\n'") return str end def self.windows? !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i) end # Return color codes, if the config color= option is false # just return a empty string def self.color(code) colorize = Config.instance.color colors = {:red => "", :green => "", :yellow => "", :cyan => "", :bold => "", :reset => ""} if colorize return colors[code] || "" else return "" end end # Helper to return a string in specific color def self.colorize(code, msg) "%s%s%s" % [ color(code), msg, color(:reset) ] end # Returns the current ruby version as per RUBY_VERSION, mostly # doing this here to aid testing def self.ruby_version RUBY_VERSION end def self.mcollective_version MCollective::VERSION end # Returns an aligned_string of text relative to the size of the terminal # window. If a line in the string exceeds the width of the terminal window # the line will be chopped off at the whitespace chacter closest to the # end of the line and prepended to the next line, keeping all indentation. # # The terminal size is detected by default, but custom line widths can # passed. All strings will also be left aligned with 5 whitespace characters # by default. def self.align_text(text, console_cols = nil, preamble = 5) unless console_cols console_cols = terminal_dimensions[0] # if unknown size we default to the typical unix default console_cols = 80 if console_cols == 0 end console_cols -= preamble # Return unaligned text if console window is too small return text if console_cols <= 0 # If console is 0 this implies unknown so we assume the common # minimal unix configuration of 80 characters console_cols = 80 if console_cols <= 0 text = text.split("\n") piece = '' whitespace = 0 text.each_with_index do |line, i| whitespace = 0 while whitespace < line.length && line[whitespace].chr == ' ' whitespace += 1 end # If the current line is empty, indent it so that a snippet # from the previous line is aligned correctly. if line == "" line = (" " * whitespace) end # If text was snipped from the previous line, prepend it to the # current line after any current indentation. if piece != '' # Reset whitespaces to 0 if there are more whitespaces than there are # console columns whitespace = 0 if whitespace >= console_cols # If the current line is empty and being prepended to, create a new # empty line in the text so that formatting is preserved. if text[i + 1] && line == (" " * whitespace) text.insert(i + 1, "") end # Add the snipped text to the current line line.insert(whitespace, "#{piece} ") end piece = '' # Compare the line length to the allowed line length. # If it exceeds it, snip the offending text from the line # and store it so that it can be prepended to the next line. if line.length > (console_cols + preamble) reverse = console_cols while line[reverse].chr != ' ' reverse -= 1 end piece = line.slice!(reverse, (line.length - 1)).lstrip end # If a snippet exists when all the columns in the text have been # updated, create a new line and append the snippet to it, using # the same left alignment as the last line in the text. if piece != '' && text[i+1].nil? text[i+1] = "#{' ' * (whitespace)}#{piece}" piece = '' end # Add the preamble to the line and add it to the text line = ((' ' * preamble) + line) text[i] = line end text.join("\n") end # Figures out the columns and lines of the current tty # # Returns [0, 0] if it can't figure it out or if you're # not running on a tty def self.terminal_dimensions(stdout = STDOUT, environment = ENV) return [0, 0] unless stdout.tty? return [80, 40] if Util.windows? if environment["COLUMNS"] && environment["LINES"] return [environment["COLUMNS"].to_i, environment["LINES"].to_i] elsif environment["TERM"] && command_in_path?("tput") return [`tput cols`.to_i, `tput lines`.to_i] elsif command_in_path?('stty') return `stty size`.scan(/\d+/).map {|s| s.to_i } else return [0, 0] end rescue [0, 0] end # Checks in PATH returns true if the command is found def self.command_in_path?(command) found = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p| File.exist?(File.join(p, command)) end found.include?(true) end # compare two software versions as commonly found in # package versions. # # returns 0 if a == b # returns -1 if a < b # returns 1 if a > b # # Code originally from Puppet def self.versioncmp(version_a, version_b) vre = /[-.]|\d+|[^-.\d]+/ ax = version_a.scan(vre) bx = version_b.scan(vre) while (ax.length>0 && bx.length>0) a = ax.shift b = bx.shift if( a == b ) then next elsif (a == '-' && b == '-') then next elsif (a == '-') then return -1 elsif (b == '-') then return 1 elsif (a == '.' && b == '.') then next elsif (a == '.' ) then return -1 elsif (b == '.' ) then return 1 elsif (a =~ /^\d+$/ && b =~ /^\d+$/) then if( a =~ /^0/ or b =~ /^0/ ) then return a.to_s.upcase <=> b.to_s.upcase end return a.to_i <=> b.to_i else return a.upcase <=> b.upcase end end version_a <=> version_b; end # we should really use Pathname#absolute? but it's not in all the # ruby versions we support and it comes down to roughly this def self.absolute_path?(path, separator=File::SEPARATOR, alt_separator=File::ALT_SEPARATOR) if alt_separator path_matcher = /^([a-zA-Z]:){0,1}[#{Regexp.quote alt_separator}#{Regexp.quote separator}]/ else path_matcher = /^#{Regexp.quote separator}/ end !!path.match(path_matcher) end # Converts a string into a boolean value # Strings matching 1,y,yes,true or t will return TrueClass # Any other value will return FalseClass def self.str_to_bool(val) clean_val = val.to_s.strip if clean_val =~ /^(1|yes|true|y|t)$/i return true elsif clean_val =~ /^(0|no|false|n|f)$/i return false else raise("Cannot convert string value '#{clean_val}' into a boolean.") end end # Looks up the template directory and returns its full path def self.templatepath(template_file) config_dir = File.dirname(Config.instance.configfile) template_path = File.join(config_dir, template_file) return template_path if File.exists?(template_path) template_path = File.join("/etc/mcollective", template_file) return template_path end # subscribe to the direct addressing queue def self.subscribe_to_direct_addressing_queue subscribe(make_subscriptions("mcollective", :directed)) end # Get field size for printing def self.field_size(elements, min_size=40) max_length = elements.max_by { |e| e.length }.length max_length > min_size ? max_length : min_size end # Calculate number of fields for printing def self.field_number(field_size, max_size=90) number = (max_size/field_size).to_i (number == 0) ? 1 : number end end end mcollective-2.6.0/lib/mcollective/aggregate.rb0000644000175000017500000000565312375446407020407 0ustar jonasjonasmodule MCollective class Aggregate autoload :Result, 'mcollective/aggregate/result' autoload :Base, 'mcollective/aggregate/base' attr_accessor :ddl, :functions, :action, :failed def initialize(ddl) @functions = [] @ddl = ddl @action = ddl[:action] @failed = [] create_functions end # Creates instances of the Aggregate functions and stores them in the function array. # All aggregate call and summarize method calls operate on these function as a batch. def create_functions @ddl[:aggregate].each_with_index do |agg, i| output = agg[:args][0] if contains_output?(output) arguments = agg[:args][1] format = (arguments.delete(:format) if arguments) || nil begin @functions << load_function(agg[:function]).new(output, arguments, format, @action) rescue Exception => e Log.error("Cannot create aggregate function '#{output}'. #{e.to_s}") @failed << {:name => output, :type => :startup} end else Log.error("Cannot create aggregate function '#{output}'. '#{output}' has not been specified as a valid ddl output.") @failed << {:name => output, :type => :create} end end end # Check if the function param is defined as an output for the action in the ddl def contains_output?(output) @ddl[:output].keys.include?(output) end # Call all the appropriate functions with the reply data received from RPC::Client def call_functions(reply) @functions.each do |function| Log.debug("Calling aggregate function #{function} for result") begin function.process_result(reply[:data][function.output_name], reply) rescue Exception => e Log.error("Could not process aggregate function for '#{function.output_name}'. #{e.to_s}") @failed << {:name => function.output_name, :type => :process_result} @functions.delete(function) end end end # Finalizes the function returning a result object def summarize summary = @functions.map do |function| begin function.summarize rescue Exception => e Log.error("Could not summarize aggregate result for '#{function.output_name}'. #{e.to_s}") @failed << {:name => function.output_name, :type => :summarize} nil end end summary.reject{|x| x.nil?}.sort do |x,y| x.result[:output] <=> y.result[:output] end end # Loads function from disk for use def load_function(function_name) function_name = function_name.to_s.capitalize PluginManager.loadclass("MCollective::Aggregate::#{function_name}") unless Aggregate.const_defined?(function_name) Aggregate.const_get(function_name) rescue Exception raise "Aggregate function file '#{function_name.downcase}.rb' cannot be loaded" end end end mcollective-2.6.0/lib/mcollective/application.rb0000644000175000017500000002607312375446407020763 0ustar jonasjonasmodule MCollective class Application include RPC class << self # Intialize a blank set of options if its the first time used # else returns active options def application_options intialize_application_options unless @application_options @application_options end # set an option in the options hash def []=(option, value) intialize_application_options unless @application_options @application_options[option] = value end # retrieves a specific option def [](option) intialize_application_options unless @application_options @application_options[option] end # Sets the application description, there can be only one # description per application so multiple calls will just # change the description def description(descr) self[:description] = descr end # Supplies usage information, calling multiple times will # create multiple usage lines in --help output def usage(usage) self[:usage] << usage end def exclude_argument_sections(*sections) sections = [sections].flatten sections.each do |s| raise "Unknown CLI argument section #{s}" unless ["rpc", "common", "filter"].include?(s) end intialize_application_options unless @application_options self[:exclude_arg_sections] = sections end # Wrapper to create command line options # # - name: varaible name that will be used to access the option value # - description: textual info shown in --help # - arguments: a list of possible arguments that can be used # to activate this option # - type: a data type that ObjectParser understand of :bool or :array # - required: true or false if this option has to be supplied # - validate: a proc that will be called with the value used to validate # the supplied value # # option :foo, # :description => "The foo option" # :arguments => ["--foo ARG"] # # after this the value supplied will be in configuration[:foo] def option(name, arguments) opt = {:name => name, :description => nil, :arguments => [], :type => String, :required => false, :validate => Proc.new { true }} arguments.each_pair{|k,v| opt[k] = v} self[:cli_arguments] << opt end # Creates an empty set of options def intialize_application_options @application_options = {:description => nil, :usage => [], :cli_arguments => [], :exclude_arg_sections => []} end end # The application configuration built from CLI arguments def configuration @application_configuration ||= {} @application_configuration end # The active options hash used for MC::Client and other configuration def options @options end # Calls the supplied block in an option for validation, an error raised # will log to STDERR and exit the application def validate_option(blk, name, value) validation_result = blk.call(value) unless validation_result == true STDERR.puts "Validation of #{name} failed: #{validation_result}" exit 1 end end # Creates a standard options hash, pass in a block to add extra headings etc # see Optionparser def clioptions(help) oparser = Optionparser.new({:verbose => false, :progress_bar => true}, "filter", application_options[:exclude_arg_sections]) options = oparser.parse do |parser, options| if block_given? yield(parser, options) end RPC::Helpers.add_simplerpc_options(parser, options) unless application_options[:exclude_arg_sections].include?("rpc") end return oparser.parser.help if help validate_cli_options post_option_parser(configuration) if respond_to?(:post_option_parser) return options rescue Exception => e application_failure(e) end # Builds an ObjectParser config, parse the CLI options and validates based # on the option config def application_parse_options(help=false) @options ||= {:verbose => false} @options = clioptions(help) do |parser, options| parser.define_head application_description if application_description parser.banner = "" if application_usage parser.separator "" application_usage.each do |u| parser.separator "Usage: #{u}" end parser.separator "" end parser.separator "Application Options" unless application_cli_arguments.empty? parser.define_tail "" parser.define_tail "The Marionette Collective #{MCollective.version}" application_cli_arguments.each do |carg| opts_array = [] opts_array << :on # if a default is set from the application set it up front if carg.include?(:default) configuration[carg[:name]] = carg[:default] end # :arguments are multiple possible ones if carg[:arguments].is_a?(Array) carg[:arguments].each {|a| opts_array << a} else opts_array << carg[:arguments] end # type was given and its not one of our special types, just pass it onto optparse opts_array << carg[:type] if carg[:type] && ![:boolean, :bool, :array].include?(carg[:type]) opts_array << carg[:description] # Handle our special types else just rely on the optparser to handle the types if [:bool, :boolean].include?(carg[:type]) parser.send(*opts_array) do |v| validate_option(carg[:validate], carg[:name], v) configuration[carg[:name]] = v end elsif carg[:type] == :array parser.send(*opts_array) do |v| validate_option(carg[:validate], carg[:name], v) configuration[carg[:name]] = [] unless configuration.include?(carg[:name]) configuration[carg[:name]] << v end else parser.send(*opts_array) do |v| validate_option(carg[:validate], carg[:name], v) configuration[carg[:name]] = v end end end end end def validate_cli_options # Check all required parameters were set validation_passed = true application_cli_arguments.each do |carg| # Check for required arguments if carg[:required] unless configuration[ carg[:name] ] validation_passed = false STDERR.puts "The #{carg[:name]} option is mandatory" end end end unless validation_passed STDERR.puts "\nPlease run with --help for detailed help" exit 1 end end # Retrieves the full hash of application options def application_options self.class.application_options end # Retrieve the current application description def application_description application_options[:description] end # Return the current usage text false if nothing is set def application_usage usage = application_options[:usage] usage.empty? ? false : usage end # Returns an array of all the arguments built using # calls to optin def application_cli_arguments application_options[:cli_arguments] end # Handles failure, if we're far enough in the initialization # phase it will log backtraces if its in verbose mode only def application_failure(e, err_dest=STDERR) # peole can use exit() anywhere and not get nasty backtraces as a result if e.is_a?(SystemExit) disconnect raise(e) end if options[:verbose] err_dest.puts "\nThe %s application failed to run: %s\n" % [ Util.colorize(:bold, $0), Util.colorize(:red, e.to_s)] else err_dest.puts "\nThe %s application failed to run, use -v for full error backtrace details: %s\n" % [ Util.colorize(:bold, $0), Util.colorize(:red, e.to_s)] end if options.nil? || options[:verbose] e.backtrace.first << Util.colorize(:red, " <----") err_dest.puts "\n%s %s" % [ Util.colorize(:red, e.to_s), Util.colorize(:bold, "(#{e.class.to_s})")] e.backtrace.each{|l| err_dest.puts "\tfrom #{l}"} end disconnect exit 1 end def help application_parse_options(true) end # The main logic loop, builds up the options, validate configuration and calls # the main as supplied by the user. Disconnects when done and pass any exception # onto the application_failure helper def run application_parse_options validate_configuration(configuration) if respond_to?(:validate_configuration) Util.setup_windows_sleeper if Util.windows? main disconnect rescue Exception => e application_failure(e) end def disconnect MCollective::PluginManager["connector_plugin"].disconnect rescue end # Fake abstract class that logs if the user tries to use an application without # supplying a main override method. def main STDERR.puts "Applications need to supply a 'main' method" exit 1 end def halt_code(stats) request_stats = {:discoverytime => 0, :discovered => 0, :okcount => 0, :failcount => 0}.merge(stats.to_hash) if (request_stats[:discoverytime] == 0 && request_stats[:responses] == 0) return 4 end if (request_stats[:discovered] > 0) if (request_stats[:responses] == 0) return 3 elsif (request_stats[:failcount] > 0) return 2 end end if (request_stats[:discovered] == 0) if (request_stats[:responses] && request_stats[:responses] > 0) return 0 else return 1 end end return 0 end # A helper that creates a consistent exit code for applications by looking at an # instance of MCollective::RPC::Stats # # Exit with 0 if nodes were discovered and all passed # Exit with 0 if no discovery were done and > 0 responses were received, all ok # Exit with 1 if no nodes were discovered # Exit with 2 if nodes were discovered but some RPC requests failed # Exit with 3 if nodes were discovered, but no responses received # Exit with 4 if no discovery were done and no responses were received def halt(stats) exit(halt_code(stats)) end # Wrapper around MC::RPC#rpcclient that forcably supplies our options hash # if someone forgets to pass in options in an application the filters and other # cli options wouldnt take effect which could have a disasterous outcome def rpcclient(agent, flags = {}) flags[:options] = options unless flags.include?(:options) flags[:exit_on_failure] = false super end end end mcollective-2.6.0/lib/mcollective/optionparser.rb0000644000175000017500000001465312375446407021206 0ustar jonasjonasmodule MCollective # A simple helper to build cli tools that supports a uniform command line # layout. class Optionparser attr_reader :parser # Creates a new instance of the parser, you can supply defaults and include named groups of options. # # Starts a parser that defaults to verbose and that includs the filter options: # # oparser = MCollective::Optionparser.new({:verbose => true}, "filter") # # Stats a parser in non verbose mode that does support discovery # # oparser = MCollective::Optionparser.new() # # Starts a parser in verbose mode that does not show the common options: # # oparser = MCollective::Optionparser.new({:verbose => true}, "filter", "common") def initialize(defaults = {}, include_sections = nil, exclude_sections = nil) @parser = ::OptionParser.new @include = [include_sections].flatten @exclude = [exclude_sections].flatten @options = Util.default_options @options.merge!(defaults) end # Parse the options returning the options, you can pass a block that adds additional options # to the Optionparser. # # The sample below starts a parser that also prompts for --arguments in addition to the defaults. # It also sets the description and shows a usage message specific to this app. # # options = oparser.parse{|parser, options| # parser.define_head "Control the mcollective controller daemon" # parser.banner = "Usage: sh-mcollective [options] command" # # parser.on('--arg', '--argument ARGUMENT', 'Argument to pass to agent') do |v| # options[:argument] = v # end # } # # Users can set default options that get parsed in using the MCOLLECTIVE_EXTRA_OPTS environemnt # variable def parse(&block) yield(@parser, @options) if block_given? add_required_options add_common_options unless @exclude.include?("common") @include.each do |i| next if @exclude.include?(i) options_name = "add_#{i}_options" send(options_name) if respond_to?(options_name) end @parser.environment("MCOLLECTIVE_EXTRA_OPTS") @parser.parse! @options[:collective] = Config.instance.main_collective unless @options[:collective] @options end # These options will be added if you pass 'filter' into the include list of the # constructor. def add_filter_options @parser.separator "" @parser.separator "Host Filters" @parser.on('-W', '--with FILTER', 'Combined classes and facts filter') do |f| f.split(" ").each do |filter| begin fact_parsed = parse_fact(filter) @options[:filter]["fact"] << fact_parsed rescue @options[:filter]["cf_class"] << filter end end end @parser.on('-S', '--select FILTER', 'Compound filter combining facts and classes') do |f| @options[:filter]["compound"] << Matcher.create_compound_callstack(f) end @parser.on('-F', '--wf', '--with-fact fact=val', 'Match hosts with a certain fact') do |f| fact_parsed = parse_fact(f) @options[:filter]["fact"] << fact_parsed if fact_parsed end @parser.on('-C', '--wc', '--with-class CLASS', 'Match hosts with a certain config management class') do |f| @options[:filter]["cf_class"] << f end @parser.on('-A', '--wa', '--with-agent AGENT', 'Match hosts with a certain agent') do |a| @options[:filter]["agent"] << a end @parser.on('-I', '--wi', '--with-identity IDENT', 'Match hosts with a certain configured identity') do |a| @options[:filter]["identity"] << a end end # These options should always be present def add_required_options @parser.on('-c', '--config FILE', 'Load configuratuion from file rather than default') do |f| @options[:config] = f end @parser.on('-v', '--verbose', 'Be verbose') do |v| @options[:verbose] = v end @parser.on('-h', '--help', 'Display this screen') do puts @parser exit! 1 end end # These options will be added to most cli tools def add_common_options @parser.separator "" @parser.separator "Common Options" @parser.on('-T', '--target COLLECTIVE', 'Target messages to a specific sub collective') do |f| @options[:collective] = f end @parser.on('--dt', '--discovery-timeout SECONDS', Integer, 'Timeout for doing discovery') do |t| @options[:disctimeout] = t end @parser.on('-t', '--timeout SECONDS', Integer, 'Timeout for calling remote agents') do |t| @options[:timeout] = t end @parser.on('-q', '--quiet', 'Do not be verbose') do |v| @options[:verbose] = false end @parser.on('--ttl TTL', 'Set the message validity period') do |v| @options[:ttl] = v.to_i end @parser.on('--reply-to TARGET', 'Set a custom target for replies') do |v| @options[:reply_to] = v end @parser.on('--dm', '--disc-method METHOD', 'Which discovery method to use') do |v| raise "Discovery method is already set by a competing option" if @options[:discovery_method] && @options[:discovery_method] != v @options[:discovery_method] = v end @parser.on('--do', '--disc-option OPTION', 'Options to pass to the discovery method') do |a| @options[:discovery_options] << a end @parser.on("--nodes FILE", "List of nodes to address") do |v| raise "Cannot mix --disc-method, --disc-option and --nodes" if @options[:discovery_method] || @options[:discovery_options].size > 0 raise "Cannot read the discovery file #{v}" unless File.readable?(v) @options[:discovery_method] = "flatfile" @options[:discovery_options] << v end @parser.on("--publish_timeout TIMEOUT", Integer, "Timeout for publishing requests to remote agents.") do |pt| @options[:publish_timeout] = pt end @parser.on("--threaded", "Start publishing requests and receiving responses in threaded mode.") do |v| @options[:threaded] = true end @parser.on("--sort", "Sort the output of an RPC call before processing.") do |v| @options[:sort] = true end end private # Parse a fact filter string like foo=bar into the tuple hash thats needed def parse_fact(fact) Util.parse_fact_string(fact) end end end mcollective-2.6.0/lib/mcollective/runnerstats.rb0000644000175000017500000000400412375446407021036 0ustar jonasjonasmodule MCollective # Class to store stats about the mcollectived, it should live in the PluginManager # so that agents etc can get hold of it and return the stats to callers class RunnerStats def initialize @starttime = Time.now.to_i @validated = 0 @unvalidated = 0 @filtered = 0 @passed = 0 @total = 0 @replies = 0 @ttlexpired = 0 @mutex = Mutex.new end # Records a message that failed TTL checks def ttlexpired Log.debug("Incrementing ttl expired stat") @ttlexpired += 1 end # Records a message that passed the filters def passed Log.debug("Incrementing passed stat") @passed += 1 end # Records a message that didnt pass the filters def filtered Log.debug("Incrementing filtered stat") @filtered += 1 end # Records a message that validated ok def validated Log.debug("Incrementing validated stat") @validated += 1 end def unvalidated Log.debug("Incrementing unvalidated stat") @unvalidated += 1 end # Records receipt of a message def received Log.debug("Incrementing total stat") @total += 1 end # Records sending a message def sent @mutex.synchronize do Log.debug("Incrementing replies stat") @replies += 1 end end # Returns a hash with all stats def to_hash stats = {:validated => @validated, :unvalidated => @unvalidated, :passed => @passed, :filtered => @filtered, :starttime => @starttime, :total => @total, :ttlexpired => @ttlexpired, :replies => @replies} reply = {:stats => stats, :threads => [], :pid => Process.pid, :times => {} } ::Process.times.each_pair{|k,v| k = k.to_sym reply[:times][k] = v } Thread.list.each do |t| reply[:threads] << "#{t.inspect}" end reply[:agents] = Agents.agentlist reply end end end mcollective-2.6.0/lib/mcollective/unix_daemon.rb0000644000175000017500000000160312375446407020756 0ustar jonasjonasmodule MCollective class UnixDaemon # Daemonize the current process def self.daemonize fork do Process.setsid exit if fork Dir.chdir('/tmp') STDIN.reopen('/dev/null') STDOUT.reopen('/dev/null', 'a') STDERR.reopen('/dev/null', 'a') yield end end def self.daemonize_runner(pid=nil) raise "The Unix Daemonizer can not be used on the Windows Platform" if Util.windows? UnixDaemon.daemonize do if pid begin File.open(pid, 'w') {|f| f.write(Process.pid) } rescue Exception => e end end begin runner = Runner.new(nil) runner.main_loop rescue => e Log.warn(e.backtrace) Log.warn(e) ensure File.unlink(pid) if pid && File.exist?(pid) end end end end end mcollective-2.6.0/lib/mcollective/facts.rb0000644000175000017500000000211612375446407017550 0ustar jonasjonasmodule MCollective # This is a class that gives access to the configured fact provider # such as MCollectives::Facts::Facter that uses Reductive Labs facter # # The actual provider is pluggable and configurable using the 'factsource' # configuration option. # # To develop a new factsource simply create a class under MCollective::Facts:: # and provide the following classes: # # self.get_fact(fact) # self.has_fact?(fact) # # You can also just inherit from MCollective::Facts::Base and provide just the # # self.get_facts # # method that should return a hash of facts. module Facts autoload :Base, "mcollective/facts/base" @@config = nil # True if we know of a specific fact else false def self.has_fact?(fact, value) PluginManager["facts_plugin"].get_fact(fact) == value ? true : false end # Get the value of a fact def self.get_fact(fact) PluginManager["facts_plugin"].get_fact(fact) end # Get the value of a fact def self.[](fact) PluginManager["facts_plugin"].get_fact(fact) end end end mcollective-2.6.0/lib/mcollective/runner.rb0000644000175000017500000002053712375446407017770 0ustar jonasjonasmodule MCollective class Runner attr_reader :state def initialize(configfile) begin @config = Config.instance @config.loadconfig(configfile) unless @config.configured @config.mode = :server @stats = PluginManager["global_stats"] @connection = PluginManager["connector_plugin"] # @state describes the current contextual state of the MCollective runner. # Valid states are: # :running - MCollective is alive and receiving messages from the middleware # :stopping - MCollective is shutting down and in the process of terminating # :stopped - MCollective is not running # :pausing - MCollective is going into it's paused state # :unpausing - MCollective is waking up from it's paused state # :paused - MCollective is paused and not receiving messages but can be woken up @state = :stopped @exit_receiver_thread = false @registration_thread = nil @agent_threads = [] @security = PluginManager["security_plugin"] @security.initiated_by = :node unless Util.windows? Signal.trap("USR1") do Thread.new do Log.info("Reloading all agents after receiving USR1 signal") @agents.loadagents end end Signal.trap("USR2") do Thread.new do Log.info("Cycling logging level due to USR2 signal") Log.cycle_level end end Signal.trap("WINCH") do Thread.new do Log.info("Reopening logfiles due to WINCH signal") Log.reopen Log.info("Reopened logfiles due to WINCH signal") end end else Util.setup_windows_sleeper end rescue => e Log.error("Failed to initialize MCollective runner.") Log.error(e) Log.error(e.backtrace.join("\n\t")) raise e end end # Deprecated def run Log.warn("The #run method has been deprecated. Use #main_loop instead.") main_loop end # The main runner loop def main_loop # Enter the main context @receiver_thread = start_receiver_thread loop do begin case @state when :stopping Log.debug("Stopping MCollective server") # If soft_shutdown has been enabled we wait for all running agents to # finish, one way or the other. if @config.soft_shutdown soft_shutdown end stop_threads @state = :stopped return # When pausing we stop the receiver thread but keep everything else alive # This means that running agents also run to completion. when :pausing Log.debug("Pausing MCollective server") stop_threads @state = :paused when :unpausing Log.debug("Unpausing MCollective server") start_receiver_thread end # prune dead threads from the agent_threads array @agent_threads.reject! { |t| !t.alive? } sleep 0.1 rescue SignalException => e Log.info("Exiting after signal: #{e}") stop rescue => e Log.error("A failure occurred in the MCollective runner.") Log.error(e) Log.error(e.backtrace.join("\n\t")) stop end end end def stop @state = :stopping end def pause if @state == :running @state = :pausing else Log.error("Cannot pause MCollective while not in a running state") end end def resume if @state == :paused @state = :unpausing else Log.error("Cannot unpause MCollective when it is not paused") end end private def start_receiver_thread @state = :running Thread.new { receiver_thread } end def stop_threads @receiver_thread.kill if @receiver_thread.alive? # Kill the registration thread if it was made and alive if @registration_thread && @registration_thread.alive? @registration_thread.kill end end def receiver_thread # Create internal connection in Connector @connection.connect # Subscribe to the direct addressing queue if direct_addressing is enabled if @config.direct_addressing Util.subscribe_to_direct_addressing_queue end # Create the agents and let them create their subscriptions @agents ||= Agents.new # Load data sources Data.load_data_sources # Start the registration plugin if interval isn't 0 begin if @config.registerinterval != 0 @registration_thread = PluginManager["registration_plugin"].run(@connection) end rescue Exception => e Log.error("Failed to start registration plugin: #{e}") end # Start the receiver loop loop do begin request = receive @agent_threads << agentmsg(request) rescue MsgTTLExpired => e Log.warn(e) rescue NotTargettedAtUs => e Log.debug("Message does not pass filters, ignoring") rescue MessageNotReceived, UnexpectedMessageType => e Log.warn(e) if e.backoff && @state != :stopping Log.info("sleeping for suggested #{e.backoff} seconds") sleep e.backoff end rescue Exception => e Log.warn("Failed to handle message: #{e} - #{e.class}\n") Log.warn(e.backtrace.join("\n\t")) end return if @exit_receiver_thread end end # Deals with messages directed to agents def agentmsg(request) Log.debug("Handling message for agent '#{request.agent}' on collective '#{request.collective}'") @agents.dispatch(request, @connection) do |reply_message| reply(reply_message, request) if reply_message end end # Receive a message from the connection handler def receive request = @connection.receive request.type = :request @stats.received request.decode! request.validate request end # Sends a reply to a specific target topic def reply(msg, request) msg = Message.new(msg, nil, :request => request) msg.encode! msg.publish @stats.sent end # Waits for all agent threads to complete # If soft_shutdown_timeout has been defined it will wait for the # configured grace period before killing all the threads def soft_shutdown timeout = @config.soft_shutdown_timeout if timeout && timeout <= 0 Log.warn("soft_shutdown_timeout has been set to '#{timeout}'. soft_shutdown_timeout must be > 0") Log.warn("Shutting down normally.") return end if Util.windows? windows_soft_shutdown(timeout) return end posix_soft_shutdown(timeout) end # Implements soft shutdown on the Windows platform # Logs and returns without doing anything if a timeout # hasn't been specified since waiting for long running threads # to exit on Windows can put the MCollective service in a broken state def windows_soft_shutdown(timeout) if !timeout Log.warn("soft_shutdown specified but not soft_shutdown_timeout specified.") Log.warn("To enable soft_shutdown on windows a soft_shutdown_timeout must be specified.") Log.warn("Shutting down normally.") return end shutdown_with_timeout(timeout) end # Implements soft shutdown on posix systems def posix_soft_shutdown(timeout) if timeout shutdown_with_timeout(timeout) return end stop_agent_threads end def shutdown_with_timeout(timeout) Log.debug("Shutting down agents with a timeout of '#{timeout}' seconds") begin Timeout.timeout(timeout) do stop_agent_threads end rescue Timeout::Error Log.warn("soft_shutdown_timeout reached. Terminating all running agent threads.") end end def stop_agent_threads Log.debug("Waiting for all running agents to finish or timeout.") @agent_threads.each do |t| if t.alive? t.join end end Log.debug("All running agents have completed. Stopping.") end end end mcollective-2.6.0/lib/mcollective/logger/0000755000175000017500000000000012375446416017402 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/logger/base.rb0000644000175000017500000000437412375446407020651 0ustar jonasjonasmodule MCollective module Logger # A base class for logging providers. # # Logging providers should provide the following: # # * start - all you need to do to setup your logging # * set_logging_level - set your logging to :info, :warn, etc # * valid_levels - a hash of maps from :info to your internal level name # * log - what needs to be done to log a specific message class Base attr_reader :active_level def initialize @known_levels = [:debug, :info, :warn, :error, :fatal] # Sanity check the class that impliments the logging @known_levels.each do |lvl| raise "Logger class did not specify a map for #{lvl}" unless valid_levels.include?(lvl) end end # Figures out the next level and sets it def cycle_level lvl = get_next_level set_level(lvl) log(lvl, "", "Logging level is now #{lvl.to_s.upcase}") end # Sets a new level and record it in @active_level def set_level(level) set_logging_level(level) @active_level = level.to_sym end def start raise "The logging class did not supply a start method" end def log(level, from, msg) raise "The logging class did not supply a log method" end def reopen # reopen may not make sense to all Loggers, but we expect it of the API end private def map_level(level) raise "Logger class do not know how to handle #{level} messages" unless valid_levels.include?(level.to_sym) valid_levels[level.to_sym] end # Gets the next level in the list, cycles down to the firt once it reaches the end def get_next_level # if all else fails, always go to debug mode nextlvl = :debug if @known_levels.index(@active_level) == (@known_levels.size - 1) nextlvl = @known_levels.first else idx = @known_levels.index(@active_level) + 1 nextlvl = @known_levels[idx] end nextlvl end # Abstract methods to ensure the logging implimentations supply what they should def valid_levels raise "The logging class did not supply a valid_levels method" end end end end mcollective-2.6.0/lib/mcollective/logger/syslog_logger.rb0000644000175000017500000000246512375446407022615 0ustar jonasjonasmodule MCollective module Logger # Implements a syslog based logger using the standard ruby syslog class class Syslog_logger e STDERR.puts "Invalid syslog facility #{facility} supplied, reverting to USER" Syslog::LOG_USER end end def set_logging_level(level) # noop end def valid_levels {:info => :info, :warn => :warning, :debug => :debug, :fatal => :crit, :error => :err} end def log(level, from, msg) if @known_levels.index(level) >= @known_levels.index(@active_level) Syslog.send(map_level(level), "#{from} #{msg}") end rescue # if this fails we probably cant show the user output at all, # STDERR it as last resort STDERR.puts("#{level}: #{msg}") end end end end mcollective-2.6.0/lib/mcollective/logger/console_logger.rb0000644000175000017500000000341012375446407022726 0ustar jonasjonasmodule MCollective module Logger # Implements a syslog based logger using the standard ruby syslog class class Console_logger :info, :warn => :warning, :debug => :debug, :fatal => :crit, :error => :err} end def log(level, from, msg, normal_output=STDERR, last_resort_output=STDERR) if @known_levels.index(level) >= @known_levels.index(@active_level) time = Time.new.strftime("%Y/%m/%d %H:%M:%S") normal_output.puts("%s %s: %s %s" % [colorize(level, level), time, from, msg]) end rescue # if this fails we probably cant show the user output at all, # STDERR it as last resort last_resort_output.puts("#{level}: #{msg}") end # Set some colors for various logging levels, will honor the # color configuration option and return nothing if its configured # not to def color(level) colorize = Config.instance.color colors = {:error => Util.color(:red), :fatal => Util.color(:red), :warn => Util.color(:yellow), :info => Util.color(:green), :reset => Util.color(:reset)} if colorize return colors[level] || "" else return "" end end # Helper to return a string in specific color def colorize(level, msg) "%s%s%s" % [ color(level), msg, color(:reset) ] end end end end mcollective-2.6.0/lib/mcollective/logger/file_logger.rb0000644000175000017500000000261712375446407022213 0ustar jonasjonasrequire 'logger' module MCollective module Logger # Impliments a file based logger using the standard ruby logger class # # To configure you should set: # # - config.logfile # - config.keeplogs defaults to 2097152 # - config.max_log_size defaults to 5 class File_logger e @logger.level = ::Logger::DEBUG log(:error, "", "Could not set logging to #{level} using debug instead: #{e.class} #{e}") end def valid_levels {:info => ::Logger::INFO, :warn => ::Logger::WARN, :debug => ::Logger::DEBUG, :fatal => ::Logger::FATAL, :error => ::Logger::ERROR} end def log(level, from, msg) @logger.add(map_level(level)) { "#{from} #{msg}" } rescue # if this fails we probably cant show the user output at all, # STDERR it as last resort STDERR.puts("#{level}: #{msg}") end def reopen level = @logger.level @logger.close start @logger.level = level end end end end mcollective-2.6.0/lib/mcollective/shell.rb0000644000175000017500000001030212375446407017553 0ustar jonasjonasmodule MCollective # Wrapper around systemu that handles executing of system commands # in a way that makes stdout, stderr and status available. Supports # timeouts and sets a default sane environment. # # s = Shell.new("date", opts) # s.runcommand # puts s.stdout # puts s.stderr # puts s.status.exitstatus # # Options hash can have: # # cwd - the working directory the command will be run from # stdin - a string that will be sent to stdin of the program # stdout - a variable that will receive stdout, must support << # stderr - a variable that will receive stdin, must support << # environment - the shell environment, defaults to include LC_ALL=C # set to nil to clear the environment even of LC_ALL # timeout - a timeout in seconds after which the subprocess is killed, # the special value :on_thread_exit kills the subprocess # when the invoking thread (typically the agent) has ended # class Shell attr_reader :environment, :command, :status, :stdout, :stderr, :stdin, :cwd, :timeout def initialize(command, options={}) @environment = {"LC_ALL" => "C"} @command = command @status = nil @stdout = "" @stderr = "" @stdin = nil @cwd = Dir.tmpdir @timeout = nil options.each do |opt, val| case opt.to_s when "stdout" raise "stdout should support <<" unless val.respond_to?("<<") @stdout = val when "stderr" raise "stderr should support <<" unless val.respond_to?("<<") @stderr = val when "stdin" raise "stdin should be a String" unless val.is_a?(String) @stdin = val when "cwd" raise "Directory #{val} does not exist" unless File.directory?(val) @cwd = val when "environment" if val.nil? @environment = {} else @environment.merge!(val.dup) @environment = @environment.delete_if { |k,v| v.nil? } end when "timeout" raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Fixnum) && val>0 ) @timeout = val end end end # Actually does the systemu call passing in the correct environment, stdout and stderr def runcommand opts = {"env" => @environment, "stdout" => @stdout, "stderr" => @stderr, "cwd" => @cwd} opts["stdin"] = @stdin if @stdin thread = Thread.current # Start a double fork and exec with systemu which implies a guard thread. # If a valid timeout is configured the guard thread will terminate the # executing process and reap the pid. # If no timeout is specified the process will run to completion with the # guard thread reaping the pid on completion. @status = systemu(@command, opts) do |cid| begin if timeout.is_a?(Fixnum) # wait for the specified timeout sleep timeout else # sleep while the agent thread is still alive while(thread.alive?) sleep 0.1 end end # if the process is still running if (Process.kill(0, cid)) # and a timeout was specified if timeout if Util.windows? Process.kill('KILL', cid) else # Kill the process Process.kill('TERM', cid) sleep 2 Process.kill('KILL', cid) if (Process.kill(0, cid)) end end # only wait if the parent thread is dead Process.waitpid(cid) unless thread.alive? end rescue SystemExit rescue Errno::ESRCH rescue Errno::ECHILD Log.warn("Could not reap process '#{cid}'.") rescue Exception => e Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}") end end @status.thread.kill @status end end end mcollective-2.6.0/lib/mcollective/rpc.rb0000644000175000017500000001322412375446407017236 0ustar jonasjonasrequire 'pp' module MCollective # Toolset to create a standard interface of client and agent using # an RPC metaphor, standard compliant agents will make it easier # to create generic clients like web interfaces etc module RPC autoload :ActionRunner, "mcollective/rpc/actionrunner" autoload :Agent, "mcollective/rpc/agent" autoload :Audit, "mcollective/rpc/audit" autoload :Client, "mcollective/rpc/client" autoload :Helpers, "mcollective/rpc/helpers" autoload :Progress, "mcollective/rpc/progress" autoload :Reply, "mcollective/rpc/reply" autoload :Request, "mcollective/rpc/request" autoload :Result, "mcollective/rpc/result" autoload :Stats, "mcollective/rpc/stats" # Creates a standard options hash, pass in a block to add extra headings etc # see Optionparser def rpcoptions oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true}, "filter") options = oparser.parse do |parser, options| if block_given? yield(parser, options) end Helpers.add_simplerpc_options(parser, options) end return options end # Wrapper to create clients, supposed to be used as # a mixin: # # include MCollective::RPC # # exim = rpcclient("exim") # printrpc exim.mailq # # or # # rpcclient("exim") do |exim| # printrpc exim.mailq # end # # It will take a few flags: # :configfile => "etc/client.cfg" # :options => options # :exit_on_failure => true # # Options would be a build up options hash from the Optionparser # you can use the rpcoptions helper to create this # # :exit_on_failure is true by default, and causes the application to # exit if there is a failure constructing the RPC client. Set this flag # to false to cause an Exception to be raised instead. def rpcclient(agent, flags = {}) configfile = flags[:configfile] || "/etc/mcollective/client.cfg" options = flags[:options] || nil if flags.key?(:exit_on_failure) exit_on_failure = flags[:exit_on_failure] else # We exit on failure by default for CLI-friendliness exit_on_failure = true end begin if options rpc = Client.new(agent, :configfile => options[:config], :options => options) @options = rpc.options else rpc = Client.new(agent, :configfile => configfile) @options = rpc.options end rescue Exception => e if exit_on_failure puts("Could not create RPC client: #{e}") exit! else raise e end end if block_given? yield(rpc) else return rpc end end # means for other classes to drop stats into this module # its a bit hacky but needed so that the mixin methods like # printrpcstats can easily get access to it without # users having to pass it around in params. def self.stats(stats) @@stats = stats end # means for other classes to drop discovered hosts into this module # its a bit hacky but needed so that the mixin methods like # printrpcstats can easily get access to it without # users having to pass it around in params. def self.discovered(discovered) @@discovered = discovered end # Prints stats, requires stats to be saved from elsewhere # using the MCollective::RPC.stats method. # # If you've passed -v on the command line a detailed stat block # will be printed, else just a one liner. # # You can pass flags into it: # # printrpcstats :caption => "Foo", :summarize => true # # This will use "Foo" as the caption to the stats in verbose # mode and print out any aggregate summary information if present def printrpcstats(flags={}) return unless @options[:output_format] == :console flags = {:summarize => false, :caption => "rpc stats"}.merge(flags) verbose = @options[:verbose] rescue verbose = false begin stats = @@stats rescue puts("no stats to display") return end puts stats.report(flags[:caption], flags[:summarize], verbose) end # Prints the result of an RPC call. # # In the default quiet mode - no flattening or verbose - only results # that produce an error will be printed # # To get details of each result run with the -v command line option. def printrpc(result, flags = {}) verbose = @options[:verbose] rescue verbose = false verbose = flags[:verbose] || verbose flatten = flags[:flatten] || false format = @options[:output_format] forced_mode = @options[:force_display_mode] || false result_text = Helpers.rpcresults(result, {:verbose => verbose, :flatten => flatten, :format => format, :force_display_mode => forced_mode}) if result.is_a?(Array) && format == :console puts "\n%s\n" % [ result_text ] else # when we get just one result to print dont pad them all with # blank spaces etc, just print the individual result with no # padding puts result_text unless result_text == "" end end # Wrapper for MCollective::Util.empty_filter? to make clients less fugly # to write - ticket #18 def empty_filter?(options) if options.include?(:filter) Util.empty_filter?(options[:filter]) else Util.empty_filter?(options) end end def self.const_missing(const_name) super unless const_name == :DDL Log.warn("MCollective::RPC::DDL is deprecatd, please use MCollective::DDL instead") MCollective::DDL end end end mcollective-2.6.0/lib/mcollective/connector/0000755000175000017500000000000012375446416020115 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/connector/base.rb0000644000175000017500000000263012375446407021355 0ustar jonasjonasmodule MCollective # Connector plugins handle the communications with the middleware, you can provide your own to speak # to something other than Stomp, your plugins must inherit from MCollective::Connector::Base and should # provide the following methods: # # connect - Creates a connection to the middleware, no arguments should get its parameters from the config # receive - Receive data from the middleware, should act like a blocking call only returning if/when data # was received. It should get data from all subscribed channels/topics. Individual messages # should be returned as MCollective::Request objects with the payload provided # publish - Takes a target and msg, should send the message to the supplied target topic or destination # subscribe - Adds a subscription to a specific message source # unsubscribe - Removes a subscription to a specific message source # disconnect - Disconnects from the middleware # # These methods are all that's needed for a new connector protocol and should hopefully be simple # enough to not have tied us to Stomp. module Connector class Base def self.inherited(klass) plugin_name = klass.to_s.split("::").last.downcase ddl = DDL.new(plugin_name, :connector) PluginManager << {:type => "connector_plugin", :class => klass.to_s} end end end end mcollective-2.6.0/lib/mcollective/aggregate/0000755000175000017500000000000012375446416020051 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/aggregate/base.rb0000644000175000017500000000260612375446407021314 0ustar jonasjonasmodule MCollective class Aggregate class Base attr_accessor :name, :result, :output_name, :action, :aggregate_format, :arguments def initialize(output_name, arguments, aggregate_format, action) @name = self.class.to_s @output_name = output_name # Any additional arguments passed in the ddl after the output field will # be stored in the arguments array which can be used in the function @arguments = arguments @aggregate_format = aggregate_format @action = action @result = {:value => nil, :type => nil, :output => output_name} startup_hook end ['startup_hook', 'process_result'].each do |method| define_method method do raise RuntimeError, "'#{method}' method of function class #{@name} has not yet been implemented" end end # Stops execution of the function and returns a specific ResultObject, # aggregate functions will most likely override this but this is the simplest # case so we might as well default to that def summarize raise "Result type is not set while trying to summarize aggregate function results" unless @result[:type] result_class(@result[:type]).new(@result, @aggregate_format, @action) end def result_class(type) Result.const_get("#{type.to_s.capitalize}Result") end end end end mcollective-2.6.0/lib/mcollective/aggregate/result/0000755000175000017500000000000012375446416021367 5ustar jonasjonasmcollective-2.6.0/lib/mcollective/aggregate/result/collection_result.rb0000644000175000017500000000065012375446407025446 0ustar jonasjonasmodule MCollective class Aggregate module Result class CollectionResult y[1]}.reverse.each do |value| result.puts @aggregate_format % [value[0], value[1]] end result.string.chomp end end end end end mcollective-2.6.0/lib/mcollective/aggregate/result/base.rb0000644000175000017500000000113212375446407022623 0ustar jonasjonasmodule MCollective class Aggregate module Result class Base attr_accessor :result, :aggregate_format, :action def initialize(result, aggregate_format, action) raise "No aggregate_format defined in ddl or aggregate function" unless aggregate_format @result = result @aggregate_format = aggregate_format @action = action end def to_s raise "'to_s' method not implemented for result class '#{self.class}'" end def result_type @result[:type] end end end end end mcollective-2.6.0/lib/mcollective/aggregate/result/numeric_result.rb0000644000175000017500000000036112375446407024754 0ustar jonasjonasmodule MCollective class Aggregate module Result class NumericResult.rb # # You can inherit from MCollective::Registration::Base in which case you just need # to supply a _body_ method, whatever this method returns will be send to the # middleware connection for an agent called _registration_ module Registration autoload :Base, "mcollective/registration/base" end end mcollective-2.6.0/lib/mcollective/data.rb0000644000175000017500000000540212375446407017362 0ustar jonasjonasmodule MCollective module Data autoload :Base, "mcollective/data/base" autoload :Result, "mcollective/data/result" def self.load_data_sources PluginManager.find_and_load("data") PluginManager.grep(/_data$/).each do |plugin| begin unless PluginManager[plugin].class.activate? Log.debug("Disabling data plugin %s due to plugin activation policy" % plugin) PluginManager.delete(plugin) end rescue Exception => e Log.debug("Disabling data plugin %s due to exception #{e.class}: #{e}" % plugin) PluginManager.delete(plugin) end end end def self.pluginname(plugin) plugin.to_s =~ /_data$/i ? plugin.to_s.downcase : "%s_data" % plugin.to_s.downcase end def self.[](plugin) PluginManager[pluginname(plugin)] end # Data.package("httpd").architecture def self.method_missing(method, *args) super unless PluginManager.include?(pluginname(method)) PluginManager[pluginname(method)].lookup(args.first) end def self.ddl(plugin) DDL.new(pluginname(plugin), :data) end def self.ddl_validate(ddl, argument) name = ddl.meta[:name] query = ddl.entities[:data] raise DDLValidationError, "No dataquery has been defined in the DDL for data plugin #{name}" unless query input = query.fetch(:input, {}) output = query.fetch(:output, {}) raise DDLValidationError, "No output has been defined in the DDL for data plugin #{name}" if output.keys.empty? if input[:query] return true if argument.nil? && input[:query][:optional] ddl.validate_input_argument(input, :query, argument) else raise("No data plugin argument was declared in the %s DDL but an input was supplied" % name) if argument return true end end def self.ddl_has_output?(ddl, output) ddl.entities[:data][:output].include?(output.to_sym) rescue false end # For an input where the DDL requests a boolean or some number # this will convert the input to the right type where possible # else just returns the origin input unedited # # if anything here goes wrong just return the input value # this is not really the end of the world or anything since # all that will happen is that DDL validation will fail and # the user will get an error, no need to be too defensive here def self.ddl_transform_input(ddl, input) begin type = ddl.entities[:data][:input][:query][:type] case type when :boolean return DDL.string_to_boolean(input) when :number, :integer, :float return DDL.string_to_number(input) end rescue end return input end end end mcollective-2.6.0/lib/mcollective.rb0000644000175000017500000000432612375446416016455 0ustar jonasjonasrequire 'rubygems' require 'stomp' require 'timeout' require 'digest/md5' require 'optparse' require 'singleton' require 'socket' require 'erb' require 'shellwords' require 'stringio' require 'rbconfig' require 'tempfile' require 'tmpdir' require 'mcollective/monkey_patches' require 'mcollective/cache' require 'mcollective/exceptions' # == The Marionette Collective # # Framework to build and run Systems Administration agents running on a # publish/subscribe middleware system. The system allows you to treat your # network as the only true source of the state of your platform via discovery agents # and allow you to run agents matching discovery criteria. # # For an overview of the idea behind this and what it enables please see: # http://www.devco.net/archives/2009/10/18/middleware_for_systems_administration.php module MCollective autoload :Agent, "mcollective/agent" autoload :Agents, "mcollective/agents" autoload :Aggregate, "mcollective/aggregate" autoload :Application, "mcollective/application" autoload :Applications, "mcollective/applications" autoload :Client, "mcollective/client" autoload :Config, "mcollective/config" autoload :Connector, "mcollective/connector" autoload :Data, "mcollective/data" autoload :DDL, "mcollective/ddl" autoload :Discovery, "mcollective/discovery" autoload :Facts, "mcollective/facts" autoload :Logger, "mcollective/logger" autoload :Log, "mcollective/log" autoload :Matcher, "mcollective/matcher" autoload :Message, "mcollective/message" autoload :Optionparser, "mcollective/optionparser" autoload :Generators, "mcollective/generators" autoload :PluginManager, "mcollective/pluginmanager" autoload :PluginPackager, "mcollective/pluginpackager" autoload :Registration, "mcollective/registration" autoload :RPC, "mcollective/rpc" autoload :Runner, "mcollective/runner" autoload :RunnerStats, "mcollective/runnerstats" autoload :Security, "mcollective/security" autoload :Shell, "mcollective/shell" autoload :SSL, "mcollective/ssl" autoload :Util, "mcollective/util" autoload :Validator, "mcollective/validator" autoload :Vendor, "mcollective/vendor" MCollective::Vendor.load_vendored VERSION="2.6.0" def self.version VERSION end end mcollective-2.6.0/ext/0000755000175000017500000000000012375446416013647 5ustar jonasjonasmcollective-2.6.0/ext/bash/0000755000175000017500000000000012375446416014564 5ustar jonasjonasmcollective-2.6.0/ext/bash/mco_completion.sh0000644000175000017500000000357012375446407020134 0ustar jonasjonas_mco() { local agents options COMPREPLY=() local cur=${COMP_WORDS[COMP_CWORD]} local prev=${COMP_WORDS[COMP_CWORD-1]} # Where are the plugins? local libdir=$(sed -n 's@libdir = @@p' /etc/mcollective/client.cfg) # All arguments by options noopt=($(tr ' ' '\n' <<<${COMP_WORDS[@]} | \ grep -v "^$cur$" | grep -v -- '^-')) local count_noopt=${#noopt[@]} local cmd=${noopt[0]} local app=${noopt[1]} # A bug in the output of --help prevents # from parsing all options, so we list the common ones here local common_options="-T --target -c --config --dt --discovery-timeout \ -t --timeout -q --quiet -v --verbose -h --help -W --with -F \ --wf --with-fact -C --wc --with-class -A --wa --with-agent -I \ --wi --with-identity" if [ $COMP_CWORD -eq 1 ]; then apps=$($cmd completion --list-applications) COMPREPLY=($(compgen -W "$apps" -- "$cur")) elif [ $COMP_CWORD -gt 1 ]; then options="${common_options} $($cmd $app --help | grep -o -- '-[^, ]\+')" if [ "x${app}" = "xrpc" ]; then if [[ $count_noopt -eq 2 || "x${prev}" = "x--agent" ]]; then # Complete with agents agents=$($cmd completion --list-agents) options="$options $agents" elif [[ $count_noopt -eq 3 || "x${prev}" = "x--action" ]]; then # Complete with agent actions rpcagent=${noopt[2]} actions=$($cmd completion --list-actions \ --agent "$rpcagent") options="$options $actions" elif [ $count_noopt -gt 3 ]; then # Complete with key=value rpcagent=${noopt[2]} rpcaction=${noopt[3]} inputs=$($cmd completion --list-inputs \ --agent "$rpcagent" --action "$rpcaction") options="$options $inputs" fi fi COMPREPLY=($(compgen -W "$options" -S ' ' -- "$cur")) fi } [ -n "${have:-}" ] && complete -o nospace -F _mco mco mcollective-2.6.0/ext/zsh/0000755000175000017500000000000012375446416014453 5ustar jonasjonasmcollective-2.6.0/ext/zsh/_mco0000644000175000017500000000623012375446407015314 0ustar jonasjonas#compdef mco # completion for the mcollective cli. # # for the main mco application it will complete # the list of available applications # # for the rpc application it will complete first # the list of agents, then actions and then each # input. # # For all other applications it will just complete # the common command line options, to add another # application simply define a function called # _mco_application_foo() for the foo application _mco() { if (( CURRENT > 2 )); then local application=${words[2]} shift words args=({-W,--with}'[Combined class and fact filter]' \ {-S,--select}'[Select filter]' \ {-F,--wf,--with-fact}'[Fact filter]' \ {-C,--wc,--with-class}'[Class filter]' \ {-A,--wa,--with-agent}'[Agent filter]' \ {-I,--wi,--with-identity}'[Identity filter]' \ {-T,--target}'[Target collective]' \ {--dm,--disc-method}'[Which discovery method to use]' \ {--do,--disc-option}'[Options to pass to the discovery method]' \ {--dt,--discovery-timeout}'[Discovery timeout]' \ {-t,--timeout}'[Command Timeout]' \ {-q,--quiet}'[Surpress verbose output]' \ {-c,--config}'[Path to the config file]' \ {-v,--verbose}'[Be verbose]' \ {-h,--help}'[Show complete help message]' \ '--nodes[List of nodes to address]' \ '--ttl[Time To Live for the request]' \ '--reply-to[Custom reply target]') curcontext="${curcontext%:*:*}:mco-${application}" if (( $+functions[_mco_application_$application] > 0 ));then _mco_application_$application fi _arguments -s : $args else local -a cmdlist _call_program mco-list-applications mco completion --list-applications -v | while read -A hline; do cmdlist=($cmdlist "${hline}") done curcontext="${curcontext%:*:*}:mco-applications" _describe -t mco-application 'MCollective applications' cmdlist fi } _mco_application_rpc() { local -a clist if (( CURRENT == 3 )); then _call_program mco-list-agents mco completion --list-agents -v | while read -A hline; do clist=($clist "${hline}") done _describe -t mco-agents "MCollective agents" clist elif (( CURRENT == 4 )); then _call_program mco-list-actions mco completion --list-actions --agent=${words[2]} -v | while read -A hline; do clist=($clist "${hline}") done _describe -t mco-actions "${words[2]} actions" clist elif (( CURRENT > 4 )); then _call_program mco-list-inputs mco completion --list-inputs --action=${words[3]} --agent=${words[2]} -v | while read hline; do clist=($clist $hline) done _describe -t mco-inputs "${words[3]} inputs" clist -S = fi args+=( {--np,--no-progress}'[Do not show the progress bar]' \ {--nr,--no-results}'[Do not process results, just send request]' \ {-1,--one}'[Send request to only one discovered node]' \ '--batch[Do request in batches]' \ '--batch-sleep[Sleep time between batches]' \ {--ln,--limit-nodes}'[Only send the request to a certain number of discovered nodes]' \ {-j,--json}'[Output result as JSON data]' ) } mcollective-2.6.0/ext/perl/0000755000175000017500000000000012375446416014611 5ustar jonasjonasmcollective-2.6.0/ext/perl/mc-find-hosts.pl0000644000175000017500000000450112375446407017621 0ustar jonasjonas#!/usr/bin/perl # A simple Perl client for mcollective that just demonstrates # how to construct requests, send them and process results. # # This is in effect a mc-find-hosts equivelant, you can fill in # filters in the request and only the matching ones will reply. # # For this to work you need the SSL security plugin in MCollective # 1.0.0 set to operate in YAML mode. use YAML::Syck; use Digest::MD5 qw(md5 md5_hex md5_base64); use Crypt::OpenSSL::RSA; use MIME::Base64; use Net::STOMP::Client; use Data::Dumper; # The topics from your activemq, /topic/mcollective_dev/... $mcollective_prefix = "mcollective_dev"; # Path to your SSL private key and what it's called so the # mcollectived will load yours $ssl_private_key = "/path/to/your.pem"; $ssl_private_key_name = "you"; # A string representing your sending host $mcollective_client_identity = "devel.your.com-perl"; # Stomp connection parameters $stomp_host = "localhost"; $stomp_port = 6163; $stomp_user = "your"; $stomp_password = "secret"; $YAML::Syck::ImplicitTyping = 1; $request{":msgtime"} = time(); $request{":filter"}{"identity"} = []; $request{":filter"}{"fact"} = []; $request{":filter"}{"agent"} = []; $request{":filter"}{"cf_class"} = []; $request{":requestid"} = md5_hex(time() . $$); $request{":callerid"} = "cert=${ssl_private_key_name}"; $request{":senderid"} = $mcollective_client_identity; $request{":body"} = Dump("ping"); $request{":msgtarget"} = "/topic/${mcollective_prefix}.discovery.command"; $key = ""; open(SSL, $ssl_private_key); while() { $key = $key . $_; } close(SSL); $rsa = Crypt::OpenSSL::RSA->new_private_key($key); $request{":hash"} = encode_base64($rsa->sign($request{":body"})); $mcrequest = Dump(\%request); $stomp = Net::STOMP::Client->new(host => $stomp_host, port => $stomp_port); $stomp->connect(login => $stomp_user, passcode => $stomp_password); $stomp->message_callback(sub { my ($self, $frame) = @_; $mc_reply = Load($frame->body); $mc_body = Load($mc_reply->{":body"}); print $mc_reply->{":senderid"} . "> " . $mc_body . "\n"; return($self); }); $stomp->subscribe(destination => "/topic/${mcollective_prefix}.discovery.reply"); $stomp->send(destination => "/topic/${mcollective_prefix}.discovery.command", body => $mcrequest); $stomp->wait_for_frames(callback => sub { return(0) }, timeout => 5); $stomp->disconnect(); mcollective-2.6.0/ext/redhat/0000755000175000017500000000000012375446416015116 5ustar jonasjonasmcollective-2.6.0/ext/redhat/mcollective.init0000755000175000017500000000620712375446407020321 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # chkconfig: - 24 76 # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # ### BEGIN INIT INFO # Provides: mcollective # Required-Start: $remote_fs # Required-Stop: $remote_fs # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO mcollectived="/usr/sbin/mcollectived" pidfile="/var/run/mcollectived.pid" if [ -d /var/lock/subsys ]; then # RedHat/CentOS/etc who use subsys lockfile="/var/lock/subsys/mcollective" else # The rest of them lockfile="/var/lock/mcollective" fi # Check that binary exists if ! [ -f $mcollectived ]; then echo "mcollectived binary not found" exit 5 fi # Source function library. . /etc/init.d/functions if [ -f /etc/sysconfig/mcollective ]; then . /etc/sysconfig/mcollective fi # Determine if we can use the -p option to daemon, killproc, and status. # RHEL < 5 can't. if status | grep -q -- '-p' 2>/dev/null; then daemonopts="--pidfile $pidfile" pidopts="-p $pidfile" fi start() { echo -n "Starting mcollective: " # Only try to start if not already started if ! rh_status_q; then daemon ${daemonopts} ${mcollectived} --pid=${pidfile} --config="/etc/mcollective/server.cfg" --daemonize fi # This will be 0 if mcollective is already running RETVAL=$? echo [ $RETVAL -eq 0 ] && touch ${lockfile} return $RETVAL } stop() { echo -n "Shutting down mcollective: " # If running, try to stop it if rh_status_q; then killproc ${pidopts} -d 10 ${mcollectived} else # Non-zero status either means lockfile and pidfile need cleanup (1 and 2) # or the process is already stopped (3), so we can just call true to # trigger the cleanup that happens below. true fi RETVAL=$? echo [ $RETVAL = 0 ] && rm -f ${lockfile} ${pidfile} return $RETVAL } restart() { stop start } reload_agents() { echo -n "Reloading mcollective agents: " killproc ${pidopts} ${mcollectived} -USR1 RETVAL=$? echo return $RETVAL } reload_loglevel() { echo -n "Cycling mcollective logging level: " killproc ${pidopts} ${mcollectived} -USR2 RETVAL=$? echo return $RETVAL } rh_status() { status ${pidopts} ${mcollectived} RETVAL=$? return $RETVAL } rh_status_q() { rh_status >/dev/null 2>&1 } # See how we were called. case "$1" in start) start ;; stop) stop ;; restart) restart ;; condrestart) rh_status_q || exit 0 restart ;; reload-agents) reload_agents ;; reload-loglevel) reload_loglevel ;; status) rh_status ;; *) echo "Usage: mcollectived {start|stop|restart|condrestart|reload-agents|reload-loglevel|status}" RETVAL=2 ;; esac exit $RETVAL mcollective-2.6.0/ext/redhat/mcollective.spec0000644000175000017500000001162212375446416020302 0ustar jonasjonas# Fedora 17 and later use vendorlibdir instead of sitelibdir (see https://fedoraproject.org/wiki/Packaging:Ruby?rd=Packaging/Ruby#Pure_Ruby_packages) %if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 %global ruby_libdir %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]') %else %global ruby_libdir %(ruby -rrbconfig -e "puts RbConfig::CONFIG['sitelibdir']") %endif %if 0%{?fedora} >= 17 || 0%{?rhel} >= 7 %global _with_systemd 1 %else %global _with_systemd 0 %endif # VERSION is subbed out during rake package:srpm process %global realversion 2.6.0 %global rpmversion 2.6.0 Summary: Application Server for hosting Ruby code on any capable middleware Name: mcollective Version: %{rpmversion} Release: 1%{?dist} Group: System Environment/Daemons License: ASL 2.0 URL: http://puppetlabs.com/mcollective/introduction/ Source0: http://downloads.puppetlabs.com/mcollective/%{name}-%{realversion}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRequires: ruby >= 1.8 Requires: mcollective-common = %{version}-%{release} Packager: Puppet Labs BuildArch: noarch %if 0%{?_with_systemd} # Required for %%post, %%preun, %%postun Requires: systemd %if 0%{?fedora} >= 18 || 0%{?rhel} >= 7 BuildRequires: systemd %else BuildRequires: systemd-units %endif %else # Required for %%post and %%preun Requires: chkconfig # Required for %%preun and %%postun Requires: initscripts %endif %description The Marionette Collective: Server for the mcollective Application Server %package common Summary: Common libraries for the mcollective clients and servers Group: System Environment/Libraries Requires: ruby >= 1.8 Requires: rubygems >= 1.3.7 Requires: rubygem-stomp %description common The Marionette Collective: Common libraries for the mcollective clients and servers %package client Summary: Client tools for the mcollective Application Server Requires: mcollective-common = %{version}-%{release} Group: Applications/System %description client The Marionette Collective: Client tools for the mcollective Application Server %prep %setup -q -n %{name}-%{realversion} %build %install rm -rf %{buildroot} ruby install.rb --destdir=%{buildroot} --no-rdoc --sitelibdir=%{ruby_libdir} --plugindir=%{_libexecdir}/mcollective %if 0%{?_with_systemd} %{__install} -d -m0755 %{buildroot}%{_unitdir} %{__install} -m0644 ext/redhat/mcollective.service %{buildroot}%{_unitdir}/mcollective.service %else %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/init.d %if 0%{?suse_version} %{__install} -m0755 mcollective.init %{buildroot}%{_sysconfdir}/init.d/mcollective %else %{__install} -m0755 ext/redhat/mcollective.init %{buildroot}%{_sysconfdir}/init.d/mcollective %endif %endif %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/mcollective/plugin.d %{__install} -d -m0755 %{buildroot}%{_sysconfdir}/mcollective/ssl/clients %clean rm -rf %{buildroot} %post %if 0%{?_with_systemd} if [ $1 -eq 1 ] ; then /bin/systemctl daemon-reload >/dev/null 2>&1 || : fi %else /sbin/chkconfig --add mcollective || : %endif %postun %if 0%{?_with_systemd} if [ $1 -ge 1 ] ; then # Package upgrade, not uninstall /bin/systemctl try-restart mcollective.service >/dev/null 2>&1 || : fi %else if [ "$1" -ge 1 ]; then /sbin/service mcollective condrestart &>/dev/null || : fi %endif %preun %if 0%{?_with_systemd} if [ $1 -eq 0 ] ; then # Package removal, not upgrade /bin/systemctl --no-reload disable mcollective.service > /dev/null 2>&1 || : /bin/systemctl stop mcollective.service > /dev/null 2>&1 || : fi %else if [ "$1" = 0 ] ; then /sbin/service mcollective stop > /dev/null 2>&1 /sbin/chkconfig --del mcollective || : fi %endif %files common %defattr(-, root, root, 0755) %doc COPYING %doc doc %{ruby_libdir}/mcollective.rb %{ruby_libdir}/mcollective %{_libexecdir}/mcollective/mcollective %dir %{_sysconfdir}/mcollective %dir %{_sysconfdir}/mcollective/ssl %config %{_sysconfdir}/mcollective/*.erb %files client %defattr(-, root, root, 0755) %attr(0755, root, root)%{_bindir}/mco %doc COPYING %config(noreplace)%{_sysconfdir}/mcollective/client.cfg %{_libexecdir}/mcollective/mcollective/application %{_libexecdir}/mcollective/mcollective/pluginpackager %files %defattr(-, root, root, 0755) %doc COPYING %attr(0755, root, root)%{_sbindir}/mcollectived %if 0%{?_with_systemd} %{_unitdir}/mcollective.service %else %{_sysconfdir}/init.d/mcollective %endif %config(noreplace)%{_sysconfdir}/mcollective/server.cfg %config(noreplace)%{_sysconfdir}/mcollective/facts.yaml %dir %{_sysconfdir}/mcollective/ssl/clients %config(noreplace)%{_sysconfdir}/mcollective/plugin.d %changelog * Thu Aug 21 2014 Puppet Labs Release - 2.6.0-1 - Build for 2.6.0 * Tue Nov 03 2009 R.I.Pienaar - First release mcollective-2.6.0/ext/redhat/mcollective.service0000644000175000017500000000054512375446407021012 0ustar jonasjonas[Unit] Description=The Marionette Collective After=network.target [Service] Type=forking StandardOutput=syslog StandardError=syslog ExecStart=/usr/sbin/mcollectived --config=/etc/mcollective/server.cfg --pidfile=/var/run/mcollective.pid --daemonize ExecReload=/bin/kill -USR1 $MAINPID PIDFile=/var/run/mcollective.pid [Install] WantedBy=multi-user.target mcollective-2.6.0/ext/activemq/0000755000175000017500000000000012417211236015443 5ustar jonasjonasmcollective-2.6.0/ext/activemq/examples/0000755000175000017500000000000012375446416017276 5ustar jonasjonasmcollective-2.6.0/ext/activemq/examples/single-broker/0000755000175000017500000000000012375446416022041 5ustar jonasjonasmcollective-2.6.0/ext/activemq/examples/single-broker/README0000644000175000017500000000027312375446407022723 0ustar jonasjonasSimple single broker setup for ActiveMQ 5.5.0. Provides 2 users, one admin and one for mcollective. Admin user can create all sorts of queues and topics, mcollective user is restricted. mcollective-2.6.0/ext/activemq/examples/single-broker/activemq.xml0000644000175000017500000001421012375446407024372 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.6.0/ext/activemq/examples/multi-broker/0000755000175000017500000000000012375446416021712 5ustar jonasjonasmcollective-2.6.0/ext/activemq/examples/multi-broker/README0000644000175000017500000000053612375446407022576 0ustar jonasjonas3 ActiveMQ servers clustered together in a star foramt: broker2 <-----> broker1 <----> broker3 Pay attention to the names in the config file as well as the users. This is identical to the simple single broker example except with the aded amq user for the clustering and the connection setups in broker1 Tested to work with ActiveMQ 5.5.0 mcollective-2.6.0/ext/activemq/examples/multi-broker/broker1-activemq.xml0000755000175000017500000002262612375446407025623 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.6.0/ext/activemq/examples/multi-broker/broker3-activemq.xml0000755000175000017500000001547012375446407025624 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.6.0/ext/activemq/examples/multi-broker/broker2-activemq.xml0000755000175000017500000001547012375446407025623 0ustar jonasjonas file:${activemq.base}/conf/credentials.properties mcollective-2.6.0/ext/Makefile0000644000175000017500000000211012375446407015301 0ustar jonasjonas#!/usr/bin/make -f DESTDIR= build: clean: install: install-bin install-lib install-conf install-plugins install-doc install-bin: install -d $(DESTDIR)/usr/sbin install -d $(DESTDIR)/usr/bin cp bin/mco $(DESTDIR)/usr/bin cp bin/mcollectived $(DESTDIR)/usr/sbin/mcollectived install-lib: install -d $(DESTDIR)/usr/lib/ruby/1.8/ cp -a lib/* $(DESTDIR)/usr/lib/ruby/1.8/ install-conf: install -d $(DESTDIR)/etc/mcollective/ install -d $(DESTDIR)/etc/init.d cp -r etc/* $(DESTDIR)/etc/mcollective/ cp mcollective.init $(DESTDIR)/etc/init.d/mcollective rm $(DESTDIR)/etc/mcollective/ssl/PLACEHOLDER rm $(DESTDIR)/etc/mcollective/ssl/clients/PLACEHOLDER install-plugins: install -d $(DESTDIR)/usr/share/mcollective/ cp -a plugins $(DESTDIR)/usr/share/mcollective/ install-doc: install -d $(DESTDIR)/usr/share/doc/ cp -a doc $(DESTDIR)/usr/share/doc/mcollective uninstall: rm -f $(DESTDIR)/usr/sbin/mcollectived rm -rf $(DESTDIR)/usr/lib/ruby/1.8/mcollective* rm -rf $(DESTDIR)/usr/share/mcollective rm -rf $(DESTDIR)/etc/mcollective .PHONY: build clean install uninstall mcollective-2.6.0/ext/packaging.rake0000644000175000017500000000233712375446407016444 0ustar jonasjonasbuild_defs_file = File.join(RAKE_ROOT, 'ext', 'build_defaults.yaml') if File.exist?(build_defs_file) begin require 'yaml' @build_defaults ||= YAML.load_file(build_defs_file) rescue Exception => e STDERR.puts "Unable to load yaml from #{build_defs_file}:" raise e end @packaging_url = @build_defaults['packaging_url'] @packaging_repo = @build_defaults['packaging_repo'] raise "Could not find packaging url in #{build_defs_file}" if @packaging_url.nil? raise "Could not find packaging repo in #{build_defs_file}" if @packaging_repo.nil? namespace :package do desc "Bootstrap packaging automation, e.g. clone into packaging repo" task :bootstrap do if File.exist?(File.join(RAKE_ROOT, "ext", @packaging_repo)) puts "It looks like you already have ext/#{@packaging_repo}. If you don't like it, blow it away with package:implode." else cd File.join(RAKE_ROOT, 'ext') do %x{git clone #{@packaging_url}} end end end desc "Remove all cloned packaging automation" task :implode do rm_rf File.join(RAKE_ROOT, "ext", @packaging_repo) end end end begin load File.join(RAKE_ROOT, 'ext', 'packaging', 'packaging.rake') rescue LoadError end mcollective-2.6.0/ext/osx/0000755000175000017500000000000012375446416014460 5ustar jonasjonasmcollective-2.6.0/ext/osx/README0000644000175000017500000000132012375446407015334 0ustar jonasjonasThis script will automatically build Mac packages based on your source tarball. The only argument is the path to the untarred source directory. It automatically determines the MCollective version so it will only work with 0.4.9 and forward. It loads mcollective in ruby so it requires that the stomp rubygem be installed. I could just grep the mcollective.rb script but I wanted it to be future proof. It also requires XCode be installed to have PackageMaker available. It sets the server.cfg file daemonize setting to 0 since my launchd plist will handle that. I also have launchd set to restart mcollectived if it stops running. Let me know if you run in to trouble with it. Carl Caum - carl _at_ carlcaum.com mcollective-2.6.0/ext/osx/bldmacpkg0000644000175000017500000001211312375446407016325 0ustar jonasjonas#!/bin/bash MPATH="$1" BETCDIR='/etc/mcollective' BRUBYDIR='/Library/Ruby/Site/1.8' BSBINDIR='/usr/sbin' BBINDIR='/usr/bin' BLIBEXECDIR='/usr/libexec/mcollective' BDOCDIR='/usr/share/doc/mcollective' BLAUNCHDIR='/Library/LaunchDaemons' BLOGDIR='/var/log/mcollective' PACKAGEMAKER='/Applications/PackageMaker.app/Contents/MacOS/PackageMaker' if [ -z $MPATH ]; then echo 'Please give the path to the MCollective source directory' exit 1 fi msg_stomp() { echo "It is recommended to install stomp on this system using ruby gems" exit 2 } msg_xcode() { echo 'It is required to have the latest XCode installed' exit 3 } # Make sure we have stomp so we can load mcollective /usr/bin/ruby < $tmpdir/$BLAUNCHDIR/org.marionette-collective.mcollective.plist < EnvironmentVariables PATH /sbin:/usr/sbin:/bin:/usr/bin RUBYLIB /Library/Ruby/Site/1.8 Label org.marionette-collective.mcollective OnDemand KeepAlive ProgramArguments /usr/sbin/mcollectived --config=/etc/mcollective/server.cfg RunAtLoad ServiceDescription MCollective Server ServiceIPC EOF #Make our Packages. This requires XCode be installed $PACKAGEMAKER -r $tmpdir --version $mcversion --title "MCollective" -l / -o $pkgdir/MCollective_$mcversion.pkg -i org.marionette-collective.mcollective $PACKAGEMAKER -r $common_tmpdir --version $mcversion --title "MCollective Common" -l / -o $pkgdir/MCollective-Common_$mcversion.pkg -i org.marionette-collective.mcollective-common $PACKAGEMAKER -r $client_tmpdir --version $mcversion --title "MCollective Client" -l / -o $pkgdir/MCollective-Client_$mcversion.pkg -i org.marionette-collective.mcollective-client # Make sure that we install the stomp gem, this is ugly and should be part of the package cat - > $pkgdir/MCollective-Common_$mcversion.pkg/Contents/Resources/postflight < $pkgdir/MCollective_$mcversion.pkg/Contents/Resources/postflight </dev/null || /bin/true /usr/sbin/svccfg delete svc:network/cswmcollectived 2>/dev/null || /bin/true rm /etc/mcollective || /bin/true mcollective-2.6.0/ext/solaris/prototype.head0000644000175000017500000000006112375446407020210 0ustar jonasjonasi pkginfo i postinstall i preremove i postremove mcollective-2.6.0/ext/solaris/build0000755000175000017500000000603212375446407016351 0ustar jonasjonas#!/opt/csw/bin/gmake -f -d # -*- makefile -*- BUILDDIR = solaris/tmp PKG = solaris/pkg DESTDIR = ${CURDIR}/${BUILDDIR} PKGDIR = ${CURDIR}/${PKG} PKGNAME = CSWmcollective VERSION = $(shell cd ../.. ; RUBYLIB=./lib /opt/csw/bin/ruby18 -r mcollective -e 'puts MCollective::VERSION' ) # If we checked out from git: ifeq ($(VERSION),@DEVELOPMENT_VERSION@) VERSION = $(shell ggrep "PROJ_VERSION = " ../../Rakefile | cut -d' ' -f3 | sed -e 's/"//g') endif RELEASE = 1 PKGVERSION = ${VERSION}\,REV=$(shell date +%Y.%m.%d) RUBY_VERSION = 1.8 RUBY_SITE = ${DESTDIR}/opt/csw/lib/ruby/site_ruby/${RUBY_VERSION} install: # install directories ginstall -d $(DESTDIR) ginstall -g root -d $(DESTDIR)/opt ginstall -g sys -d $(DESTDIR)/var $(DESTDIR)/var/lock $(DESTDIR)/etc $(DESTDIR)/etc/opt ginstall -g bin -d $(DESTDIR)/var/opt $(DESTDIR)/var/opt/csw $(DESTDIR)/var/opt/csw/svc $(DESTDIR)/var/opt/csw/svc/manifest $(DESTDIR)/var/opt/csw/svc/manifest/network ginstall -g bin -d $(DESTDIR)/opt/csw/lib $(DESTDIR)/opt/csw/lib/svc $(DESTDIR)/opt/csw/lib/svc/method ginstall -g bin -d $(DESTDIR)/opt/csw $(DESTDIR)/opt/csw/lib $(DESTDIR)/opt/csw/sbin $(DESTDIR)/opt/csw/bin ginstall -g bin -d $(DESTDIR)/opt/csw/lib/ruby $(DESTDIR)/opt/csw/lib/ruby/site_ruby $(DESTDIR)/opt/csw/lib/ruby/site_ruby/$(RUBY_VERSION) ginstall -g bin -d $(DESTDIR)/etc/opt/csw $(DESTDIR)/etc/opt/csw/mcollective ginstall -g bin -d $(DESTDIR)/opt/csw/share $(DESTDIR)/opt/csw/share/mcollective # install binaries ginstall -g bin $(CURDIR)/../../bin/mc-* $(DESTDIR)/opt/csw/sbin/ ginstall -g bin $(CURDIR)/../../bin/mco $(DESTDIR)/opt/csw/sbin/ ginstall -g bin $(CURDIR)/../../bin/mcollectived $(DESTDIR)/opt/csw/sbin/mcollectived # install libraries gcp -a $(CURDIR)/../../lib/* $(RUBY_SITE)/ chgrp -R bin $(RUBY_SITE)/ # install example config files gcp -a $(CURDIR)/../../etc/* $(DESTDIR)/etc/opt/csw/mcollective/ grm $(DESTDIR)/etc/opt/csw/mcollective/ssl/PLACEHOLDER grm $(DESTDIR)/etc/opt/csw/mcollective/ssl/clients/PLACEHOLDER chgrp -R bin $(DESTDIR)/etc/opt/csw/mcollective/ # install plugins gcp -a $(CURDIR)/../../plugins $(DESTDIR)/opt/csw/share/mcollective/ # install docs #ginstall -d $(DESTDIR)/opt/csw/doc $(DESTDIR)/opt/csw/doc/mcollective/ #gcp -a $(CURDIR)/../../doc/ $(DESTDIR)/opt/cs/doc/mcollective ginstall -g bin $(CURDIR)/mcollective.init $(DESTDIR)/opt/csw/lib/svc/method/svc-cswmcollectived ginstall -g bin $(CURDIR)/cswmcollectived.xml $(DESTDIR)/var/opt/csw/svc/manifest/network (cat prototype.head; pkgproto $(DESTDIR)=/ ) > solaris/prototype mkdir $(PKGDIR) || true ginstall postinstall solaris/ ginstall postremove solaris/ ginstall preremove solaris/ ginstall pkginfo solaris/ (echo PKG=${PKGNAME} ) >> solaris/pkginfo (echo VERSION=${PKGVERSION} ) >> solaris/pkginfo (cd solaris/ ; pkgmk -o -d $(PKGDIR)) pkgtrans -s $(PKGDIR) $(CURDIR)/$(PKGNAME)-$(PKGVERSION)-`uname -s``uname -r`-all-CSW.pkg $(PKGNAME) clean: grm -rf $(DESTDIR) grm -rf $(PKGDIR) grm -f solaris/prototype grm -f $(PKGNAME)-$(SOLARIS_VERSION)-`uname -s``uname -r`-all-CSW.pkg mcollective-2.6.0/ext/solaris/postinstall0000644000175000017500000000050712375446407017624 0ustar jonasjonas#! /bin/bash PKG_INSTALL_ROOT=${PKG_INSTALL_ROOT:-/} /usr/bin/test -d $PKG_INSTALL_ROOT/etc/mcollective || /usr/sbin/chroot $PKG_INSTALL_ROOT /usr/bin/ln -s /etc/opt/csw/mcollective /etc/mcollective /usr/sbin/chroot $PKG_INSTALL_ROOT /usr/sbin/svccfg import /var/opt/csw/svc/manifest/network/cswmcollectived.xml || /bin/true mcollective-2.6.0/ext/solaris/setversion0000755000175000017500000000025612375446407017455 0ustar jonasjonas#! /bin/bash IN=$1 OUT=$2 SOLARIS_VERSION=`ggrep ^VERSION solaris/pkginfo | cut -d = -f2` sed 's/VERSION="none"/VERSION="'"$SOLARIS_VERSION"'"/' $IN > $OUT chmod 755 $OUT mcollective-2.6.0/ext/solaris/README0000644000175000017500000000115212375446407016202 0ustar jonasjonasBuilding -------- Requirements, you can get them from opencsw: - coreuitls (CSWcoreutils) - gmake (CSWgmake) - ggrep (CSWggrep) Just run ./build on your solaris system. Running ------- Requirements, get them from opencsw: - ruby (CSWruby) - rubygems (CSWrubygems) Run requirements - rubystomp library https://rubygems.org/gems/stomp Up and till version 1.0.4 it is a single file. Put in /opt/csw/lib/ruby/site_ruby/1.8/ Configuration ------------- /etc/mcollective/server.cfg Put the plugins in: libdir = /opt/csw/share/mcollective/plugins Credits ------- Rudy Gevaert mcollective-2.6.0/ext/solaris/mcollective.init0000755000175000017500000000356212375446407020527 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # RUBYLIB=/opt/csw/lib/ruby/site_ruby/1.8:$RUBYLIB export RUBYLIB mcollectived="/opt/csw/sbin/mcollectived" lock="/var/lock/mcollective" # PID directory pidfile="/var/run/mcollectived.pid" # Check that binary exists if [ ! -f $mcollectived ] then echo "mcollectived binary not found" exit 1 fi # See how we were called. case "$1" in start) if [ -f ${lock} ]; then # we were not shut down correctly if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} rm -f ${lock} sleep 2 fi rm -f ${pidfile} ${mcollectived} --pid=${pidfile} --config="/etc/mcollective/server.cfg" --daemonize if [ $? = 0 ]; then touch $lock exit 0 else exit 1 fi ;; stop) if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} rm -f $lock ;; restart) $0 stop sleep 2 $0 start ;; condrestart) if [ -f $lock ]; then $0 stop # avoid race sleep 2 $0 start fi ;; status) if [ -f ${lock} ]; then if [ -s ${pidfile} ]; then if [ -d /proc/`cat ${pidfile}` ]; then echo "mcollectived (`cat ${pidfile}`) is running" exit 0 else echo "mcollectived (`cat ${pidfile}`) is NOT running" exit 1 fi fi else echo "mcollectived: service not started" exit 1 fi ;; force-reload) echo "not implemented" ;; *) echo "Usage: $0 {start|stop|restart|condrestart|status}" exit 1 ;; esac exit 0 mcollective-2.6.0/ext/solaris/cswmcollectived.xml0000644000175000017500000000467012375446407021243 0ustar jonasjonas mcollective-2.6.0/ext/solaris/pkginfo0000644000175000017500000000033112375446407016700 0ustar jonasjonasNAME=mcollective CATEGORY=network DESC=The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. ARCH=all EMAIL=rudy.gevaert@ugent.be VENDOR=Puppetlabs mcollective-2.6.0/ext/solaris/preremove0000644000175000017500000000023312375446407017250 0ustar jonasjonas/usr/sbin/svcadm disable svc:network/cswmcollectived 2>/dev/null || /bin/true /usr/sbin/svccfg delete svc:network/cswmcollectived 2>/dev/null || /bin/true mcollective-2.6.0/ext/solaris11/0000755000175000017500000000000012375446416015465 5ustar jonasjonasmcollective-2.6.0/ext/solaris11/Makefile0000644000175000017500000000230012375446407017120 0ustar jonasjonas#!/usr/bin/make -f DESTDIR= build: clean: install: install-bin install-lib install-conf install-plugins # install-doc install-bin: install -d $(DESTDIR)/usr/sbin install -d $(DESTDIR)/usr/bin cp bin/mc-* $(DESTDIR)/usr/sbin cp bin/mco $(DESTDIR)/usr/bin cp bin/mcollectived $(DESTDIR)/usr/sbin/mcollectived cp COPYING $(DESTDIR)/ install-lib: install -d $(DESTDIR)/usr/ruby/1.8/lib/ruby/site_ruby/1.8/ cp -rp lib/* $(DESTDIR)/usr/ruby/1.8/lib/ruby/site_ruby/1.8/ install-conf: install -d $(DESTDIR)/etc/mcollective/ install -d $(DESTDIR)/etc/init.d cp -r etc/* $(DESTDIR)/etc/mcollective/ cp mcollective.init $(DESTDIR)/etc/init.d/mcollective rm $(DESTDIR)/etc/mcollective/ssl/PLACEHOLDER rm $(DESTDIR)/etc/mcollective/ssl/clients/PLACEHOLDER install-plugins: install -d $(DESTDIR)/usr/share/mcollective/ cp -rp plugins $(DESTDIR)/usr/share/mcollective/ install-doc: install -d $(DESTDIR)/usr/share/doc/ cp -rp doc $(DESTDIR)/usr/share/doc/mcollective uninstall: rm -f $(DESTDIR)/usr/sbin/mcollectived rm -rf $(DESTDIR)/usr/ruby/1.8/lib/ruby/site_ruby/1.8/mcollective* rm -rf $(DESTDIR)/usr/share/mcollective rm -rf $(DESTDIR)/etc/mcollective .PHONY: build clean install uninstall mcollective-2.6.0/ext/solaris11/README.md0000644000175000017500000000643312375446407016752 0ustar jonasjonasRequirements ------------ - Ruby (pkg install pkg:/runtime/ruby-18) - Header files (pkg install system/header) - GCC to install JSON (pkg install developer/gcc-3) - Stomp: gem install stomp - Ruby-JSON: gem install json Installation ------------ Clone the github repository and install as root: $ cd marionette-collective $ make -f ext/solaris11/Makefile install This will use / as a default destination root directory. IPS package ----------- To create an IPS package, follow the excellent guide at: http://www.neuhalfen.name/blog/2011/07/02/Solaris11-Packaging-IPS_simple_packages/ To create a basic IPS repository (and start the associated services): # zfs create rpool/IPS # zfs set atime=off rpool/IPS # zfs set mountpoint=/IPS rpool/IPS # mkdir /IPS/Solaris11 # svcadm enable application/pkg/server # svccfg -s application/pkg/server setprop pkg/inst_root=/IPS/Solaris11 # svccfg -s application/pkg/server setprop pkg/readonly=false # pkgrepo create /IPS/Solaris11/ # pkgrepo set -s /IPS/Solaris11 publisher/prefix=legrand.im # pkgrepo -s /IPS/Solaris11 refresh # svcadm refresh application/pkg/server # svcadm enable application/pkg/server # pkg set-publisher -O http://localhost:80 legrand.im To create and send the package itself, from the guide above: # mkdir ~/package # cd /marionette-collective # cat Makefile | sed 's/DESTDIR=$/DESTDIR=~\/package/' > Makefile.package # make -f ext/solaris11/Makefile.package install # pkg install pkg:/file/gnu-findutils # export ROOT=/ # export description="MCollective" # export user="root" # export group="root" # cd ~/package # cat > ../send.sh << "EOF" #!/bin/sh export PKGSEND="pkgsend -s http://localhost:80" eval `$PKGSEND open mcollective@1.1-1` $PKGSEND add license ./COPYING license=lorem_ipsum $PKGSEND add set name=description value="${description}" EOF # gfind . -type d -not -name . -printf "\$PKGSEND add dir mode=%m owner=${user} group=${group} path=$ROOT/%h/%f \n" >> ../send.sh # gfind . -type f -not -name LICENSE -printf "\$PKGSEND add file %h/%f mode=%m owner=${user} group=${group} path=$ROOT/%h/%f \n" >> ../send.sh # gfind . -type l -not -name LICENSE -printf "\$PKGSEND add link path=%h/%f target=%l \n" >> ../send.sh # echo '$PKGSEND close' >> ../send.sh # sh -x ../send.sh The package can then be installed with: # pkg install pkg://legrand.im/mcollective Configuration ------------- There is no packaged configuration; you can use the following example: # cat > /etc/mcollective/client.cfg << "EOF" main_collective = mcollective collectives = mcollective libdir = /usr/share/mcollective/plugins logfile = /dev/null loglevel = info # Plugins securityprovider = psk plugin.psk = unset connector = stomp plugin.stomp.host = mqserver plugin.stomp.port = 6163 plugin.stomp.user = mcollective plugin.stomp.password = changeme # Facts factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml EOF License ------ http://creativecommons.org/publicdomain/zero/1.0/ To the extent possible under law, Mathieu Legrand has waived all copyright and related or neighboring rights to this work. This work is published from: Singapore. mcollective-2.6.0/ext/windows/0000755000175000017500000000000012375446416015341 5ustar jonasjonasmcollective-2.6.0/ext/windows/daemon.bat0000644000175000017500000000017212375446407017274 0ustar jonasjonas@echo off SETLOCAL call "%~dp0..\bin\environment.bat" %0 %* ruby -rubygems "%MCOLLECTIVE_DIR%\bin\mcollectived" %* mcollective-2.6.0/ext/windows/service_manager.rb0000644000175000017500000000452112375446407021022 0ustar jonasjonasrequire 'optparse' opt = OptionParser.new ruby_path = ENV["RUBY"].gsub('"','') basedir = ENV["BASEDIR"] libdir = ENV["RUBYLIB"] mcollectived = ENV["MCOLLECTIVED"] configfile = ENV["SERVER_CONFIG"] unless File.exist?(ruby_path) ENV["PATH"].split(File::PATH_SEPARATOR).each do |path| ruby = File.join(path, "ruby.exe") if File.exist?(ruby) ruby_path = ruby break end end end abort("Can't find ruby.ext in the path") unless ruby_path options = {:name => "mcollectived", :display_name => "The Marionette Collective", :description => "Puppet Labs server orchestration framework", :command => '%s -I"%s" -- "%s" --config "%s"' % [ ruby_path, libdir, mcollectived, configfile ]} action = false opt.on("--install", "Install service") do action = :install end opt.on("--uninstall", "Remove service") do action = :uninstall end opt.on("--name NAME", String, "Service name (#{options[:name]})") do |n| options[:name] = n end opt.on("--description DESCRIPTION", String, "Service description (#{options[:description]})") do |v| options[:description] = v end opt.on("--display NAME", String, "Service display name (#{options[:display_name]})") do |n| options[:display_name] = n end opt.on("--command COMMAND", String, "Service command (#{options[:command]})") do |c| options[:command] = c end opt.parse! abort "Please choose an action with --install or --uninstall" unless action require 'rubygems' require 'win32/service' include Win32 case action when :install if ENV["MC_STARTTYPE"] =~ /auto/i start_type = Service::AUTO_START else start_type = Service::DEMAND_START end Service.new( :service_name => options[:name], :display_name => options[:display_name], :description => options[:description], :binary_path_name => options[:command], :service_type => Service::SERVICE_WIN32_OWN_PROCESS, :start_type => start_type ) puts "Service %s installed" % [options[:name]] when :uninstall Service.stop(options[:name]) unless Service.status(options[:name]).current_state == 'stopped' while Service.status(options[:name]).current_state != 'stopped' puts "Waiting for service %s to stop" % [options[:name]] sleep 1 end Service.delete(options[:name]) puts "Service %s removed" % [options[:name]] end mcollective-2.6.0/ext/windows/README.md0000644000175000017500000000454112375446407016624 0ustar jonasjonasThese files support installing and using MCollective on MS Windows. Here are a few instructions for people who wish to do early adopter testing. At some point we hope to have this packaged into an MSI installer, but your early feedback will help. This guide doesn't include installing a message broker and the process may or may not work as described. Some additional troubleshooting/experimentation will probably be necessary. Assuming you are installing MCollective into `C:\marionette-collective`: * Install Ruby 1.9.3 from * check the boxes for "Add Ruby executables to your PATH" and "Associate .rb and .rbw files with the Ruby installation" * Run the following commands to install the required gems: * `gem install --no-rdoc --no-ri stomp win32-service sys-admin windows-api` * `gem install --no-rdoc --no-ri win32-dir -v 0.3.7` * `gem install --no-rdoc --no-ri win32-process -v 0.5.5` * Extract the zip file or clone the git repository into `C:\marionette-collective` * Copy the files from `C:\marionette-collective\ext\windows\` into `C:\marionette-collective\bin` * Install any plugins and their dependencies into `C:\marionette-collective\plugins` specifically for the package and service agents. You can install Puppet via gems. * Edit the configuration files in `C:\marionette-collective\etc\`: * Rename `server.cfg.dist` to `server.cfg` and change the following settings: * `libdir = C:\marionette-collective\plugins` * `logfile = C:\marionette-collective\mcollective.log` * `plugin.yaml = C:\marionette-collective\etc\facts.yaml` * Rename `client.cfg.dist` to `client.cfg` and rename `facts.yaml.dist` to `facts.yaml` * Register and start the service * Enter the `C:\marionette-collective\bin\` directory and run `register_service.bat` * Right click on "My Computer," select "Manage" * Under "Services and Applications," expand "Services" * Find "The Marionette Collective" and start the service If it does not run: * Look in the log files. Edit `server.cfg` to set `loglevel` to `debug`. * If the log files are empty, look at the command the service wrapper runs and run it by hand. This will show you any early exceptions preventing it from running. It wont succesfully start, but you should see why it does not get far enough to start writing logs. mcollective-2.6.0/ext/windows/environment.bat0000644000175000017500000000055012375446407020375 0ustar jonasjonasSET BASEDIR=%~dp0.. SET BASEDIR=%BASEDIR:\bin\..=% SET SERVER_CONFIG=%BASEDIR%\etc\server.cfg SET CLIENT_CONFIG=%BASEDIR%\etc\client.cfg SET MCOLLECTIVED=%BASEDIR%\bin\mcollectived SET MC_STARTTYPE=manual REM SET MC_STARTTYPE=auto SET PATH=%BASEDIR%\bin;%PATH% SET RUBYLIB=%BASEDIR%\lib;%RUBYLIB% SET RUBYLIB=%RUBYLIB:\=/% SET RUBY="ruby" mcollective-2.6.0/ext/windows/mco.bat0000644000175000017500000000015612375446407016611 0ustar jonasjonas@echo off SETLOCAL call "%~dp0environment.bat" %0 %* %RUBY% -S -- mco %* --config "%CLIENT_CONFIG%" mcollective-2.6.0/ext/windows/register_service.bat0000644000175000017500000000015112375446407021372 0ustar jonasjonas@echo off SETLOCAL call "%~dp0environment.bat" %0 %* %RUBY% -S -- service_manager.rb --install mcollective-2.6.0/ext/windows/unregister_service.bat0000644000175000017500000000015312375446407021737 0ustar jonasjonas@echo off SETLOCAL call "%~dp0environment.bat" %0 %* %RUBY% -S -- service_manager.rb --uninstall mcollective-2.6.0/ext/mc-rpc-restserver.rb0000755000175000017500000000153212375446407017563 0ustar jonasjonas#!/usr/bin/env ruby # A very simple demonstration of writing a REST server # for Simple RPC clients that takes requests over HTTP # and returns results as JSON structures. require 'rubygems' require 'sinatra' require 'mcollective' require 'json' include MCollective::RPC # http:///mcollective/rpctest/echo/msg=hello%20world # # Creates a new Simple RPC client for the 'rpctest' agent, calls # the echo action with a message 'hello world'. # # Returns all the answers as a JSON data block get '/mcollective/:agent/:action/*' do mc = rpcclient(params[:agent]) mc.discover arguments = {} # split up the wildcard params into key=val pairs and # build the arguments hash params[:splat].each do |arg| arguments[$1.to_sym] = $2 if arg =~ /^(.+?)=(.+)$/ end JSON.dump(mc.send(params[:action], arguments).map{|r| r.results}) end mcollective-2.6.0/ext/mc-irb0000755000175000017500000001460012375446407014747 0ustar jonasjonas#!/usr/bin/env ruby # Simple IRB shell for mcollective # # mc-irb nrpe # Determining the amount of hosts matching filter for 2 seconds .... 47 # >> rpc :runcommand, :command => "check_disks" # # * [ ============================================================> ] 47 / 47 # # # dev1.your.net Request Aborted # CRITICAL # Output: DISK CRITICAL - free space: / 176 MB (4% inode=86%); # Exit Code: 2 # Performance Data: /=3959MB;3706;3924;0;4361 /boot=26MB;83;88;0;98 /dev/shm=0MB;217;230;0;256 # # => true # >> mchelp # # => true # >> rpc(:runcommand, :command => "check_disks") do |resp| # ?> puts resp[:sender] + ": " + resp[:data][:output] # >> end # # * [ ============================================================> ] 47 / 47 # # dev1.your.net: DISK OK # # => true # >> # # You can access the agent variable via @agent from where you can do the usual manipulation of filters etc, # if you wish to switch to a different agent mid run just do newagent("some_other_agent") # # If you install the Bond gem you'll get some DDL assisted completion in the rpc method require 'rubygems' require 'irb' def consolize &block yield IRB.setup(nil) irb = IRB::Irb.new IRB.conf[:MAIN_CONTEXT] = irb.context irb.context.evaluate("require 'irb/completion'", 0) begin require 'bond' Bond.start Bond.complete(:method => "rpc") do |e| begin if e.argument == 1 if e.arguments.last == "?" puts "\n\nActions for #{@agent_name}:\n" @agent.ddl.actions.each do |action| puts "%20s - %s" % [ ":#{action}", @agent.ddl.action_interface(action)[:description] ] end print "\n" + e.line end @agent.ddl.actions elsif e.argument > 1 action = eval(e.arguments[0]).to_s ddl = @agent.ddl.action_interface(action) if e.arguments.last == "?" puts "\n\nArguments for #{action}:\n" ddl[:input].keys.each do |input| puts "%20s - %s" % [ ":#{input}", ddl[:input][input][:description] ] end print "\n" + e.line end [ddl[:input].keys, :verbose].flatten end rescue Exception [] end end rescue Exception end trap("SIGINT") do irb.signal_handle end catch(:IRB_EXIT) do irb.eval_input end end def mchelp system("mc-rpc --agent-help #{@agent_name}|less") true end def rpc(method_name, *args, &block) unless block_given? if args.size > 0 args = args.first else args = {} end if args[:verbose] args.delete(:verbose) printrpc(@agent.send(method_name, args), :verbose => true) printrpcstats else printrpc @agent.send(method_name, args) printrpcstats end else @agent.send(method_name, args.first).each do |resp| yield resp end printrpcstats end true rescue MCollective::DDLValidationError => e puts "Request did not pass DDL validation: #{e}" end def print_filter puts "Active Filter matched #{discover.size} hosts:" puts "\tIdentity: #{@agent.filter['identity'].pretty_inspect}" puts "\t Classes: #{@agent.filter['cf_class'].pretty_inspect}" puts "\t Facts: #{@agent.filter['fact'].pretty_inspect}" puts "\t Agents: #{@agent.filter['agent'].pretty_inspect}" discover.size > 0 ? true : false end def newagent(agent) @agent_name = agent @options[:filter]["agent"] = [] @agent = rpcclient(@agent_name, :options => @options) discover @agent.progress = true print_filter end def identity_filter(*args) @agent.identity_filter(*args) print_filter end def fact_filter(*args) @agent.fact_filter(*args) print_filter end def agent_filter(*args) @agent.agent_filter(*args) print_filter end def class_filter(*args) @agent.class_filter(*args) print_filter end def reset_filter @agent.reset_filter print_filter end def reset @agent.reset print_filter end def discover @agent.discover end def mc? puts < for a list of actions or arguments, do simple : to get completion on action names and arguments without description of each EOF true end consolize do require 'mcollective' include MCollective::RPC @options = rpcoptions unless ARGV.size == 1 puts "Please specify an agent name on the command line" exit 1 end puts "The Marionette Collective Interactive Ruby Shell version #{MCollective.version}" puts newagent(ARGV[0]) puts puts "Use mc? to get help on using this shell" end mcollective-2.6.0/ext/project_data.yaml0000644000175000017500000000272612375446407017201 0ustar jonasjonas--- project: 'mcollective' author: 'Puppet Labs' email: 'info@puppetlabs.com' homepage: 'https://docs.puppetlabs.com/mcollective/' summary: 'Application Server for hosting Ruby code on any capable middleware' description: 'The Marionette Collective, e.g. mcollective, is a framework for building server orchestration or parallel job execution systems.' version_file: 'lib/mcollective.rb' update_version_file: true # files and gem_files are space separated lists files: 'mcollective.init COPYING doc etc lib plugins ext bin install.rb' # List of packaging related templates to evaluate before the tarball is packed templates: - "ext/redhat/mcollective.spec.erb" - "ext/debian/changelog.erb" # The gem is only built for the mcollective client gem_name: 'mcollective-client' gem_summary: 'Client libraries for the Mcollective Application Server' gem_description: 'Client libraries for the Mcollective Application Server' gem_files: 'lib bin' gem_test_files: 'spec/**/*' gem_require_path: 'lib' gem_executables: 'mco' gem_default_executables: 'mco' gem_excludes: "bin/mcollectived lib/mcollective/runner.rb lib/mcollective/vendor/json lib/mcollective/vendor/load_json.rb lib/mcollective/vendor/systemu lib/mcollective/vendor/load_systemu.rb" gem_runtime_dependencies: systemu: json: stomp: gem_rdoc_options: - --line-numbers - --main - Mcollective - --exclude - mcollective/vendor - --exclude - spec - --exclude - ext - --exclude - website - --exclude - plugins mcollective-2.6.0/ext/debian/0000755000175000017500000000000012375446416015071 5ustar jonasjonasmcollective-2.6.0/ext/debian/mcollective.dirs0000644000175000017500000000006512375446407020263 0ustar jonasjonasetc/mcollective/ssl/clients etc/mcollective/plugin.d mcollective-2.6.0/ext/debian/mcollective.install0000644000175000017500000000011412375446407020763 0ustar jonasjonasusr/sbin/mcollectived etc/mcollective/facts.yaml etc/mcollective/server.cfg mcollective-2.6.0/ext/debian/changelog0000644000175000017500000000031412375446416016741 0ustar jonasjonasmcollective (2.6.0-1puppetlabs1) unstable sid squeeze wheezy precise; urgency=low * Update to version 2.6.0-1puppetlabs1 -- Puppet Labs Release Thu, 21 Aug 2014 12:48:30 -0700 mcollective-2.6.0/ext/debian/rules0000755000175000017500000000107512375446407016154 0ustar jonasjonas#!/usr/bin/make -f include /usr/share/cdbs/1/rules/debhelper.mk BUILD_ROOT=$(CURDIR)/debian/tmp RUBY_LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendordir"]') PLUGINDIR=/usr/share/mcollective/plugins DOCDIR=$(BUILD_ROOT)/usr/share/doc/mcollective install/mcollective:: /usr/bin/ruby install.rb --destdir=$(CURDIR)/debian/tmp --no-rdoc --sitelibdir=$(RUBY_LIBDIR) --ruby=/usr/bin/ruby --plugindir=$(PLUGINDIR) mkdir -p $(DOCDIR) cp -a doc/* $(DOCDIR) binary-fixup/mcollective:: chmod 640 $(CURDIR)/debian/mcollective/etc/mcollective/server.cfg mcollective-2.6.0/ext/debian/mcollective-common.install0000644000175000017500000000163212375446407022257 0ustar jonasjonasusr/lib/ruby/vendor_ruby/* usr/lib/ruby/vendor_ruby/ etc/mcollective/*.erb etc/mcollective usr/share/mcollective/plugins/mcollective/agent usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/audit usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/connector usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/data usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/facts usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/registration usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/security usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/validator usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/discovery usr/share/mcollective/plugins/mcollective mcollective-2.6.0/ext/debian/mcollective-client.install0000644000175000017500000000054112375446407022243 0ustar jonasjonasusr/bin/mco usr/bin/ etc/mcollective/client.cfg etc/mcollective usr/share/mcollective/plugins/mcollective/application usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/aggregate usr/share/mcollective/plugins/mcollective usr/share/mcollective/plugins/mcollective/pluginpackager usr/share/mcollective/plugins/mcollective mcollective-2.6.0/ext/debian/mcollective-doc.install0000644000175000017500000000007312375446407021532 0ustar jonasjonasusr/share/doc/mcollective/* usr/share/doc/mcollective-doc/ mcollective-2.6.0/ext/debian/control0000644000175000017500000000306312375446407016476 0ustar jonasjonasSource: mcollective Section: utils Priority: extra Maintainer: Riccardo Setti Build-Depends: debhelper (>= 7), quilt, cdbs, ruby Standards-Version: 3.8.0 Homepage: http://marionette-collective.org/ Package: mcollective Architecture: all Depends: ruby (>= 1.8.1), mcollective-common (= ${source:Version}) Description: build server orchestration or parallel job execution systems The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. Package: mcollective-client Architecture: all Depends: ruby (>= 1.8.1), mcollective-common (= ${source:Version}) Description: build server orchestration or parallel job execution systems The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution system Package: mcollective-common Replaces: mcollective (<< 2.0.0-1) Breaks: mcollective (<< 2.0.0-1), mcollective-client (<< 2.0.0-1) Architecture: all Depends: ruby (>= 1.8.1) , rubygems1.8 | rubygems1.9.1 | libruby (>= 1:1.9.3.4), ruby-stomp | libstomp-ruby Description: build server orchestration or parallel job execution systems The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. . Common files for mcollective packages. Package: mcollective-doc Architecture: all Section: doc Description: Documentation for mcollective The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. . Documentation package. mcollective-2.6.0/ext/debian/patches/0000755000175000017500000000000012375446416016520 5ustar jonasjonasmcollective-2.6.0/ext/debian/patches/pluginsdir.patch0000755000175000017500000000125412375446407021726 0ustar jonasjonasdiff --git a/etc/client.cfg.dist b/etc/client.cfg.dist index 1acffee..5c36486 100644 --- a/etc/client.cfg.dist +++ b/etc/client.cfg.dist @@ -1,6 +1,6 @@ main_collective = mcollective collectives = mcollective -libdir = /usr/libexec/mcollective +libdir = /usr/share/mcollective/plugins logger_type = console loglevel = warn diff --git a/etc/server.cfg.dist b/etc/server.cfg.dist index 2038324..c28a826 100644 --- a/etc/server.cfg.dist +++ b/etc/server.cfg.dist @@ -1,6 +1,6 @@ main_collective = mcollective collectives = mcollective -libdir = /usr/libexec/mcollective +libdir = /usr/share/mcollective/plugins logfile = /var/log/mcollective.log loglevel = info daemonize = 1 mcollective-2.6.0/ext/debian/patches/series0000644000175000017500000000002112375446407017726 0ustar jonasjonaspluginsdir.patch mcollective-2.6.0/ext/debian/mcollective.init0000755000175000017500000000462712375446407020300 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # ### BEGIN INIT INFO # Provides: mcollective # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by mcollective. ### END INIT INFO # check permissions uid=`id -u` [ $uid -gt 0 ] && { echo "You need to be root to run file" ; exit 4 ; } # PID directory pidfile="/var/run/mcollectived.pid" name="mcollective" mcollectived=/usr/sbin/mcollectived daemonopts="--pid=${pidfile} --config=/etc/mcollective/server.cfg" # Source function library. . /lib/lsb/init-functions if [ -f /etc/default/mcollective ]; then . /etc/default/mcollective fi # Check that binary exists if ! [ -f $mcollectived ] then echo "mcollectived binary not found" exit 5 fi # create pid file if it does not exist [ ! -f ${pidfile} ] && { touch ${pidfile} ; } # See how we were called. case "$1" in start) echo "Starting daemon: " $name # start the program start-stop-daemon -S -p ${pidfile} --oknodo -q -a ${mcollectived} -- ${daemonopts} --daemonize [ $? = 0 ] && { exit 0 ; } || { exit 1 ; } log_success_msg "mcollective started" touch $lock ;; stop) echo "Stopping daemon: " $name start-stop-daemon -K -R 5 -s "TERM" --oknodo -q -p ${pidfile} [ $? = 0 ] && { exit 0 ; } || { exit 1 ; } log_success_msg "mcollective stopped" ;; restart) echo "Restarting daemon: " $name $0 stop sleep 2 $0 start [ $? = 0 ] && { echo "mcollective restarted" ; exit 0 ; } ;; condrestart) if [ -f $lock ]; then $0 stop # avoid race sleep 2 $0 start fi ;; force-reload) echo "not implemented" ;; status) status_of_proc -p ${pidfile} ${mcollectived} ${name} && exit 0 || exit $? ;; *) echo "Usage: mcollectived {start|stop|restart|condrestart|status}" exit 2 ;; esac mcollective-2.6.0/ext/debian/compat0000644000175000017500000000000212375446407016267 0ustar jonasjonas7 mcollective-2.6.0/ext/debian/source/0000755000175000017500000000000012375446416016371 5ustar jonasjonasmcollective-2.6.0/ext/debian/source/format0000644000175000017500000000001412375446407017577 0ustar jonasjonas3.0 (quilt) mcollective-2.6.0/ext/debian/copyright0000644000175000017500000000167612375446407017036 0ustar jonasjonasThis package was debianized by Riccardo Setti on Mon, 04 Jan 2010 17:09:50 +0000. It was downloaded from http://code.google.com/p/mcollective Upstream Author: R.I.Pienaar Copyright: Copyright 2009 R.I.Pienaar License: Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. The Debian packaging is (C) 2010, Riccardo Setti and is licensed under the Apache License v2. mcollective-2.6.0/ext/vim/0000755000175000017500000000000012375446416014442 5ustar jonasjonasmcollective-2.6.0/ext/vim/mcollective_ddl.snippets0000644000175000017500000000500312375446407021360 0ustar jonasjonas# Snippets for use with VIM and http://www.vim.org/scripts/script.php?script_id=2540 # # These snippets help you write Agent DDLs. Install the VIM Snippets system # and copy this to your snippets directory. # # Create a file .vim/ftdetect/mcollective_ddl.vim with the following: # # au BufRead,BufNewFile *.ddl setfiletype mcollective_ddl # # Your file type should now be correctly set automatically and editing # DDLs should be easier. # # Please contact R.I.Pienaar for additions and feedback, snippet meta metadata :name => "${1:`Filename('', 'name')`}", :description => "${2:description}", :author => "${3:`g:snips_author`}", :license => "${4:license}", :version => "${5:version}", :url => "${6:homepage}", :timeout => ${7:run timeout} ${8} snippet discovery discovery do capabilities ${1:capability list} end snippet dataquery dataquery :description => "${1:data query description}" do ${2} end snippet action action "${1:action name}", :description => "${2:action description}" do ${3} end snippet input String input :${1:input name}, :prompt => "${2:prompt when asking for information}", :description => "${3:description of the input}", :type => :string, :validation => '${4:^.+$}', :optional => ${5:false}, :maxlength => ${6:20} ${7} snippet input List input :${1:input name}, :prompt => "${2:prompt when asking for information}", :description => "${3:description of the input}", :type => :list, :optional => ${4:false}, :list => [${5:list members}] ${6} snippet input Numeric input :${1:input name}, :prompt => "${2:prompt when asking for information}", :description => "${3:description of the input}", :type => :number, :optional => ${4:false} ${5} snippet input Boolean input :${1:input name}, :prompt => "${2:prompt when asking for information}", :description => "${3:description of the input}", :type => :boolean, :optional => ${4:false} ${5} snippet output output ${1:output name}, :description => "${2:description of this output data}", :display_as => "${3:what do display}", :default => ${4:nil} ${5} snippet display Always display :always snippet display Only OK results display :ok snippet display Only failed results display :failed mcollective-2.6.0/ext/vim/_.snippets0000644000175000017500000000025312375446407016447 0ustar jonasjonassnippet mcagent module MCollective module Agent class ${1:Agentname} more information at www.devco.net # # Licensed under the Apache License, Version 2.0 require 'rubygems' require 'stomp' require 'readline' require 'thread' require 'getoptlong' opts = GetoptLong.new( [ '--server', '-s', GetoptLong::REQUIRED_ARGUMENT], [ '--port', '-p', GetoptLong::REQUIRED_ARGUMENT], [ '--user', '-u', GetoptLong::REQUIRED_ARGUMENT], [ '--password', '-P', GetoptLong::REQUIRED_ARGUMENT], [ '--help', '-h', GetoptLong::NO_ARGUMENT] ) @user = ENV["STOMP_USER"]; @password = ENV["STOMP_PASSWORD"] @server = ENV["STOMP_SERVER"] @port = ENV["STOMP_PORT"] || 6163 opts.each { |opt, arg| case opt when '--help' begin require 'rdoc/ri/ri_paths' require 'rdoc/usage' RDoc::usage exit rescue Exception => e puts("Install RDoc::usage or view the comments in the top of the script to get detailed help") if e.to_str != "exit" end exit when '--server' @server = arg when '--port' @port = arg when '--user' @user = arg when '--password' @password = arg end } @conn = Stomp::Connection.open(@user, @password, @server, @port, true) STDOUT.sync = true def showhelp puts("List of commands:") puts("\n\t- subscribe /(topic|queue)/foo subscribes to topic of queue 'foo'") puts("\t- /(topic|queue|/foo bar sends msg 'bar' to topic of queue 'foo'") puts("\t- quit|exit|q|^d exit") puts("\t- detail show/dont show time and topic a msg was received on") puts("\t- help show this help") end @showdetails = true Thread.new(@conn) do |amq| while true msg = amq.receive dest = msg.headers["destination"] time = Time.now.strftime('%H:%M:%S') if @showdetails msg = "\r#{time}:#{dest} > #{msg.body.chomp}\n" else msg = "\r#{msg.body.chomp}\n" end puts (msg) end end loop do line = Readline::readline('AMQ> ') if line Readline::HISTORY.push(line) if line != "" else exit end if (line =~ /^(\/(topic|queue)\/\S+)\s+(.+)$/) puts("Sending '#{$3}' to #{$1}") if @conn.respond_to?("publish") @conn.publish($1, $3) else @conn.send($1, $3) end elsif (line =~ /^sub\S* (\/(topic|queue)\/\S+)$/) puts("Subscribing to #{$1}") @conn.subscribe($1) elsif (line =~ /^det(ail)*$/) if @showdetails @showdetails = false puts("No longer showing details") else @showdetails = true puts("Showing time and topic for each msg") end elsif (line =~ /^(quit|exit|q)$/) exit elsif (line =~ /^(help|h|\?)$/) showhelp elsif (line =~ /^$/) else puts("ERROR: unrecognised input: #{line}") end end mcollective-2.6.0/ext/action_helpers/0000755000175000017500000000000012375446416016646 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/perl/0000755000175000017500000000000012375446416017610 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/perl/lib/0000755000175000017500000000000012375446416020356 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/perl/lib/MCollective/0000755000175000017500000000000012375446416022564 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/perl/lib/MCollective/Action.pm0000644000175000017500000000501212375446407024335 0ustar jonasjonaspackage MCollective::Action; use strict; use warnings; use JSON; =head1 NAME MCollective::Action - helper class for writing mcollective actions in perl =head1 SYNOPSIS In your mcollective agent action "echo" do validate :message, String implemented by "/tmp/echo.perl" end And C #!/usr/bin/env perl use strict; use MCollective::Action; my $mc = MCollective::Action->new; $mc->reply->{message} = $mc->request->{message}; $mc->reply->{timestamp} = time; $mc->info("some text to log on the server"); =head1 DESCRIPTION mcollective version 1.X introduced a mechanism for writing agent actions as external commands. This module provides a convenient api for writing them in perl which performs some of the boilerplate for you. =head2 METHODS =over =item new create a new MCollection::Action helper object =cut sub new { my $class = shift; my $self = bless { request => {}, reply => {}, }, $class; $self->_load; return $self; } =item request returns a hash reference containing the request =cut sub request { $_[0]->{request} } =item reply returns a hash reference you should populate with your reply =cut sub reply { $_[0]->{reply} } sub _load { my $self = shift; my $file = $ENV{MCOLLECTIVE_REQUEST_FILE}; open my $fh, "<$file" or die "Can't open '$file': $!"; my $json = do { local $/; <$fh> }; $self->{request} = JSON->new->decode( $json ); delete $self->request->{data}{process_results}; } sub DESTROY { my $self = shift; $self->_save; } sub _save { my $self = shift; my $file = $ENV{MCOLLECTIVE_REPLY_FILE}; open my $fh, ">$file" or die "Can't open '$file': $!"; print $fh JSON->new->encode( $self->reply ); } =item info($message) report a message into the server log =cut sub info { my ($self, $message) = @_; print STDOUT $message, "\n"; } =item error($message) report an error into the server log =cut sub error { my ($self, $message) = @_; print STDERR $message, "\n"; } =item fail reports an error and exits immediately =cut sub fail { my ($self, $message) = @_; $self->error( $message ); exit 1; } 1; __END__ =back =head1 AUTHOR Richard Clamp =head1 COPYRIGHT Copyright 2011, Richard Clamp. All Rights Reserved. This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =head1 SEE ALSO http://docs.puppetlabs.com/mcollective/ =cut mcollective-2.6.0/ext/action_helpers/perl/t/0000755000175000017500000000000012375446416020053 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/perl/t/basic.t0000644000175000017500000000132012375446407021315 0ustar jonasjonas#!perl use strict; use Test::More; use JSON; use File::Temp; my $class = "MCollective::Action"; use_ok( $class ); my $infile = File::Temp->new; my $outfile = File::Temp->new; $ENV{MCOLLECTIVE_REQUEST_FILE} = $infile->filename; $ENV{MCOLLECTIVE_REPLY_FILE} = $outfile->filename; print $infile JSON->new->encode({ red => "apples", blue => "moon" }); close $infile; { my $mc = $class->new; isa_ok( $mc, $class ); is( $mc->request->{red}, "apples", "apples are red" ); $mc->reply->{potato} = "chips"; } my $json = do { local $/; <$outfile> }; ok( $json, "Got some JSON" ); my $reply = JSON->new->decode( $json ); is( $reply->{potato}, "chips", "Got the reply that potato = chips" ); done_testing(); mcollective-2.6.0/ext/action_helpers/perl/Makefile.PL0000644000175000017500000000022012375446407021554 0ustar jonasjonas#!perl use strict; use ExtUtils::MakeMaker; WriteMakefile( NAME => "MCollective::Action", PREREQ_PM => { "JSON" => 0, }, ); mcollective-2.6.0/ext/action_helpers/python/0000755000175000017500000000000012375446416020167 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/python/kwilczynski/0000755000175000017500000000000012375446416022550 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/python/kwilczynski/README.markdown0000644000175000017500000000203512375446407025251 0ustar jonasjonasA simple helper to assist with writing MCollective actions in Python. Given an action as below:
action "echo" do
   validate :message, String

   implemented_by "/tmp/echo.py"
end
The following Python script will implement the echo action externally replying with _message_ and current _time_.
#!/usr/bin/env python

import sys
import time
import mcollective_action as mc

if __name__ == '__main__':
    mc = mc.MCollectiveAction()
    request = mc.request()
    mc.message = request['data']['message']
    mc.time = time.strftime('%c')
    mc.info("Some text to info log in the server")

    sys.exit(0)
Calling it with _mco rpc_ results in:
$ mco rpc test echo message="Hello World"
Determining the amount of hosts matching filter for 2 seconds .... 1

 * [ ============================================================> ] 1 / 1


host.example.com              : OK
    {:message=>"Hello World", :time=>"Tue Mar 15 19:20:53 +0000 2011"}
This implementation was successfully tested with Python 2.4 and 2.6. mcollective-2.6.0/ext/action_helpers/python/kwilczynski/mcollective_action.py0000644000175000017500000000623012375446407026766 0ustar jonasjonas#!/usr/bin/env python import os import sys class Error(Exception): pass class MissingModule(Error): pass class MissingFiles(Error): pass class MissingEnvironemntVariable(Error): pass class FileReadError(Error): pass class JSONParsingError(Error): pass try: import simplejson as json except ImportError: raise MissingModule('Unable to load JSON module. Missing module?') class MCollectiveAction(object): _environment_variables = [ 'MCOLLECTIVE_REQUEST_FILE', 'MCOLLECTIVE_REPLY_FILE' ] def __init__(self): self._info = sys.__stdout__ self._error = sys.__stderr__ for entry in '_reply', '_request': self.__dict__[entry] = {} self._arguments = sys.argv[1:] if len(self._arguments) < 2: try: for variable in self._environment_variables: self._arguments.append(os.environ[variable]) except KeyError: raise MissingEnvironemntVariable("Environment variable `%s' " "is not set." % variable) self._request_file, self._reply_file = self._arguments if len(self._request_file) == 0 or len(self._reply_file) == 0: raise MissingFiles("Both request and reply files have to be set.") def __setattr__(self, name, value): if name.startswith('_'): object.__setattr__(self, name, value) else: self.__dict__['_reply'][name] = value def __getattr__(self, name): if name.startswith('_'): return self.__dict__.get(name, None) else: return self.__dict__['_reply'].get(name, None) def __del__(self): if self._reply: try: file = open(self._reply_file, 'w') json.dump(self._reply, file) file.close() except IOError, error: raise FileReadError("Unable to open reply file `%s': %s" % (self._reply_file, error)) def info(self, message): print >> self._info, message self._info.flush() def error(self, message): print >> self._error, message self._error.flush() def fail(self, message, exit_code=1): self.error(message) sys.exit(exit_code) def reply(self): return self._reply def request(self): if self._request: return self._request else: try: file = open(self._request_file, 'r') self._request = json.load(file) file.close() except IOError, error: raise FileReadError("Unable to open request file `%s': %s" % (self._request_file, error)) except json.JSONDecodeError, error: raise JSONParsingError("An error occurred during parsing of " "the JSON data in the file `%s': %s" % (self._request_file, error)) file.close() return self._request # vim: set ts=4 sw=4 et : mcollective-2.6.0/ext/action_helpers/python/kwilczynski/echo.py0000644000175000017500000000050412375446407024037 0ustar jonasjonas#!/usr/bin/env python import sys import time import mcollective_action as mc if __name__ == '__main__': mc = mc.MCollectiveAction() request = mc.request() mc.message = request['data']['message'] mc.time = time.strftime('%c') mc.info("An example echo agent") sys.exit(0) # vim: set ts=4 sw=4 et : mcollective-2.6.0/ext/action_helpers/python/romke/0000755000175000017500000000000012375446416021304 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/python/romke/test.py0000644000175000017500000000325512375446407022642 0ustar jonasjonas#!/bin/env python # -*- coding: utf-8 -*- vim: set ts=4 et sw=4 fdm=indent : import unittest, tempfile, simplejson, os, random import mcollectiveah class TestFunctions(unittest.TestCase): def test_raise_environ(self): try: del os.environ['MCOLLECTIVE_REQUEST_FILE'] del os.environ['MCOLLECTIVE_REPLY_FILE'] except: pass self.assertRaises(mcollectiveah.MCollectiveActionNoEnv, mcollectiveah.MCollectiveAction) def test_raise_file_error(self): os.environ['MCOLLECTIVE_REQUEST_FILE'] = '/tmp/mcollectiveah-test-request.%d' % random.randrange(100000) os.environ['MCOLLECTIVE_REPLY_FILE'] = '/tmp/mcollectiveah-test-reply.%d' % random.randrange(100000) self.assertRaises(mcollectiveah.MCollectiveActionFileError, mcollectiveah.MCollectiveAction) os.unlink(os.environ['MCOLLECTIVE_REPLY_FILE']) def test_echo(self): tin = tempfile.NamedTemporaryFile(mode='w', delete=False) self.data = {'message': 'test'} simplejson.dump(self.data, tin) os.environ['MCOLLECTIVE_REQUEST_FILE'] = tin.name tin.close() tout = tempfile.NamedTemporaryFile(mode='w') os.environ['MCOLLECTIVE_REPLY_FILE'] = tout.name tout.close() mc = mcollectiveah.MCollectiveAction() mc.reply['message'] = mc.request['message'] del mc tout = open(os.environ['MCOLLECTIVE_REPLY_FILE'], 'r') data = simplejson.load(tout) tout.close() self.assertEqual(data, self.data) os.unlink(os.environ['MCOLLECTIVE_REQUEST_FILE']) os.unlink(os.environ['MCOLLECTIVE_REPLY_FILE']) if __name__ == '__main__': unittest.main() mcollective-2.6.0/ext/action_helpers/python/romke/README.markdown0000644000175000017500000000162212375446407024006 0ustar jonasjonasA simple helper to assist with writing MCollective actions in Python. Given an action as below:
action "echo" do
   validate :message, String

   implemented_by "/tmp/echo.py"
end
The following Python script will implement the echo action externally replying with _message_ and _timestamp_
#!/bin/env python
import mcollectiveah
import time

mc = mcollectiveah.MCollectiveAction()
mc.reply['message'] = mc.request['message']
mc.reply['timestamp'] = time.strftime("%c")
mc.reply['info'] = "some text to info log in the server"
Calling it with _mco rpc_ results in:
$ mco rpc test echo message="hello world"
Determining the amount of hosts matching filter for 2 seconds .... 1

 * [ ============================================================> ] 1 / 1


nephilim.ml.org                         : OK
    {:message=>"hello world", :time=>"Tue Mar 15 19:20:53 +0000 2011"}
mcollective-2.6.0/ext/action_helpers/python/romke/mcollectiveah.py0000644000175000017500000000441012375446407024474 0ustar jonasjonas#!/bin/env python # -*- coding: utf-8 -*- vim: set ts=4 et sw=4 fdm=indent : import os, sys try: import simplejson except ImportError: sys.stderr.write('Unable to load simplejson python module.') sys.exit(1) class MCollectiveActionNoEnv(Exception): pass class MCollectiveActionFileError(Exception): pass class MCollectiveAction(object): def __init__(self, *args, **kwargs): try: self.infile = os.environ['MCOLLECTIVE_REQUEST_FILE'] except KeyError: raise MCollectiveActionNoEnv("No MCOLLECTIVE_REQUEST_FILE environment variable") try: self.outfile = os.environ['MCOLLECTIVE_REPLY_FILE'] except KeyError: raise MCollectiveActionNoEnv("No MCOLLECTIVE_REPLY_FILE environment variable") self.request = {} self.reply = {} self.load() def load(self): if not self.infile: return False try: infile = open(self.infile, 'r') self.request = simplejson.load(infile) infile.close() except IOError, e: raise MCollectiveActionFileError("Could not read request file `%s`: %s" % (self.infile, e)) except simplejson.JSONDecodeError, e: infile.close() raise MCollectiveActionFileError("Could not parse JSON data in file `%s`: %s", (self.infile, e)) def send(self): if not getattr(self, 'outfile', None): # if exception was raised during or before setting self.outfile return False try: outfile = open(self.outfile, 'w') simplejson.dump(self.reply, outfile) outfile.close() except IOError, e: raise MCollectiveActionFileError("Could not write reply file `%s`: %s" % (self.outfile, e)) def error(self, msg): """Prints line to STDERR that will be logged at error level in the mcollectived log file""" sys.stderr.write("%s\n" % msg) def fail(self, msg): """Logs error message and exitst with RPCAborted""" self.error(msg) sys.exit(1) def info(self, msg): """Prints line to STDOUT that will be logged at info level in the mcollectived log file""" sys.stdout.write("%s\n" % msg) def __del__(self): self.send() mcollective-2.6.0/ext/action_helpers/php/0000755000175000017500000000000012375446416017435 5ustar jonasjonasmcollective-2.6.0/ext/action_helpers/php/mcollective_action.php0000644000175000017500000000326412375446407024016 0ustar jonasjonasinfile = $_ENV["MCOLLECTIVE_REQUEST_FILE"]; $this->outfile = $_ENV["MCOLLECTIVE_REPLY_FILE"]; $this->readJSON(); } function __destruct() { $this->save(); } function readJSON() { $this->request = json_decode(file_get_contents($this->infile), true); unset($this->request["data"]["process_results"]); } function save() { file_put_contents($this->outfile, json_encode($this->request["data"])); } // prints a line to STDERR that will log at error level in the // mcollectived log file function error($msg) { fwrite(STDERR, "$msg\n"); } // prints a line to STDOUT that will log at info level in the // mcollectived log file function info($msg) { fwrite(STDOUT, "$msg\n"); } // logs an error message and exits with RPCAborted function fail($msg) { $this->error($msg); exit(1); } function __get($property) { if (isSet($this->request[$property])) { return $this->request[$property]; } else { throw new Exception("No $property in request"); } } function __set($property, $value) { $this->request["data"][$property] = $value; } } ?> mcollective-2.6.0/ext/action_helpers/php/README.markdown0000644000175000017500000000157412375446407022145 0ustar jonasjonasA simple helper to assist with writing MCollective actions in PHP. Given an action as below:
action "echo" do
   validate :message, String

   implemented_by "/tmp/echo.php"
end
The following PHP script will implement the echo action externally replying with _message_ and _timestamp_
<?php
    require("mcollective_action.php");

    $mc = new MCollectiveAction();
    $mc->message = $mc->data["message"];
    $mc->timestamp = strftime("%c");
    $mc->info("some text to info log on the server");
?>
Calling it with _mco rpc_ results in:
$ mco rpc test echo message="hello world"
Determining the amount of hosts matching filter for 2 seconds .... 1

 * [ ============================================================> ] 1 / 1


nephilim.ml.org                         : OK
    {:message=>"hello world", :time=>"Tue Mar 15 19:20:53 +0000 2011"}
mcollective-2.6.0/ext/help-templates/0000755000175000017500000000000012375446416016573 5ustar jonasjonasmcollective-2.6.0/ext/help-templates/README0000644000175000017500000000007612375446407017456 0ustar jonasjonasA number of templates for the SimpleRPC DDL based help system mcollective-2.6.0/ext/help-templates/rpc-help-markdown.erb0000644000175000017500000000302012375446407022612 0ustar jonasjonas<%= meta[:name].upcase %> AGENT <% (meta[:name].size + 7).times do %>=<% end %> <%= meta[:description] %> Author: <%= meta[:author] %> Version: <%= meta[:version] %> License: <%= meta[:license] %> Timeout: <%= meta[:timeout] %> Home Page: <%= meta[:url] %> ACTIONS: ======== % actions.keys.sort.each do |action| * <%= action %> % end % actions.keys.sort.each do |action| _<%= action %>_ action: <% (action.size + 8).times do %>-<% end %> <%= actions[action][:description] %> % if actions[action][:input].keys.size > 0 INPUT: % end % actions[action][:input].keys.sort.each do |input| <%= input %>: Description: <%= actions[action][:input][input][:description] %> Prompt: <%= actions[action][:input][input][:prompt] %> Type: <%= actions[action][:input][input][:type] %> % if actions[action][:input][input][:type] == :string Validation: <%= actions[action][:input][input][:validation] %> Length: <%= actions[action][:input][input][:maxlength] %> % elsif actions[action][:input][input][:type] == :list Valid Values: <%= actions[action][:input][input][:list].join(", ") %> % end % end OUTPUT: % actions[action][:output].keys.sort.each do |output| <%= output %>: Description: <%= actions[action][:output][output][:description] %> Display As: <%= actions[action][:output][output][:display_as] %> % end % end mcollective-2.6.0/ext/build_defaults.yaml0000644000175000017500000000144612375446407017526 0ustar jonasjonas--- packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' default_cow: 'base-squeeze-i386.cow' cows: 'base-lucid-i386.cow base-precise-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-trusty-i386.cow base-wheezy-i386.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' gpg_key: '4BD6EC30' sign_tar: FALSE # a space separated list of mock configs final_mocks: 'pl-el-5-i386 pl-el-6-i386 pl-el-7-x86_64 pl-fedora-19-i386 pl-fedora-20-i386' yum_host: 'yum.puppetlabs.com' yum_repo_path: '/opt/repository/yum/' build_gem: TRUE build_dmg: FALSE build_doc: TRUE build_ips: FALSE apt_host: 'apt.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com' apt_repo_path: '/opt/repository/incoming' mcollective-2.6.0/ext/openbsd/0000755000175000017500000000000012375446416015301 5ustar jonasjonasmcollective-2.6.0/ext/openbsd/port-files/0000755000175000017500000000000012375446416017365 5ustar jonasjonasmcollective-2.6.0/ext/openbsd/port-files/mcollective/0000755000175000017500000000000012375446416021673 5ustar jonasjonasmcollective-2.6.0/ext/openbsd/port-files/mcollective/Makefile0000644000175000017500000000104512375446407023333 0ustar jonasjonasPKG_ARCH= * COMMENT= The Marionette Collective DISTNAME= mcollective-2.2.1 CATEGORIES= sysutils HOMEPAGE= http://puppetlabs.com/mcollective/ MASTER_SITES= http://puppetlabs.com/downloads/mcollective/ EXTRACT_SUFX= .tgz # GFDL PERMIT_PACKAGE_CDROM= Yes PERMIT_PACKAGE_FTP= Yes PERMIT_DISTFILES_CDROM= Yes PERMIT_DISTFILES_FTP= Yes NO_BUILD= Yes NO_REGRESS= Yes # makefile is in ext/ MAKE_FILE=ext/Makefile post-patch: ${SUBST_CMD} ${WRKSRC}/etc/server.cfg.dist rm -f ${WRKSRC}/etc/server.cfg.dist.{orig,beforesubst} .include mcollective-2.6.0/ext/openbsd/port-files/mcollective/patches/0000755000175000017500000000000012375446416023322 5ustar jonasjonasmcollective-2.6.0/ext/openbsd/port-files/mcollective/patches/patch-etc_server_cfg_dist0000644000175000017500000000042612375446407030347 0ustar jonasjonas$OpenBSD$ --- etc/server.cfg.dist.orig Thu Jun 24 15:57:17 2010 +++ etc/server.cfg.dist Thu Jun 24 15:57:25 2010 @@ -1,5 +1,5 @@ -libdir = /usr/libexec/mcollective +libdir = ${PREFIX}/share/mcollective/plugins logfile = /var/log/mcollective.log loglevel = info daemonize = 1 mcollective-2.6.0/ext/openbsd/port-files/mcollective/patches/patch-ext_Makefile0000644000175000017500000000406212375446407026741 0ustar jonasjonasdiff --git ext/Makefile ext/Makefile index 029fda4..638d7a5 100644 --- ext/Makefile +++ ext/Makefile @@ -1,6 +1,5 @@ #!/usr/bin/make -f -DESTDIR= build: @@ -9,36 +8,34 @@ clean: install: install-bin install-lib install-conf install-plugins install-doc install-bin: - install -d $(DESTDIR)/usr/sbin - install -d $(DESTDIR)/usr/bin - cp bin/mc-* $(DESTDIR)/usr/sbin - cp bin/mco $(DESTDIR)/usr/bin - cp bin/mcollectived $(DESTDIR)/usr/sbin/mcollectived + install -d $(PREFIX)/sbin + install -d $(PREFIX)/bin + cp bin/mc-* $(PREFIX)/sbin + cp bin/mco $(PREFIX)/bin + cp bin/mcollectived $(PREFIX)/sbin/mcollectived install-lib: - install -d $(DESTDIR)/usr/lib/ruby/1.8/ - cp -a lib/* $(DESTDIR)/usr/lib/ruby/1.8/ + install -d $(PREFIX)/lib/ruby/1.8/ + cp -R lib/* $(PREFIX)/lib/ruby/1.8/ install-conf: - install -d $(DESTDIR)/etc/mcollective/ - install -d $(DESTDIR)/etc/init.d - cp -r etc/* $(DESTDIR)/etc/mcollective/ - cp mcollective.init $(DESTDIR)/etc/init.d/mcollective - rm $(DESTDIR)/etc/mcollective/ssl/PLACEHOLDER - rm $(DESTDIR)/etc/mcollective/ssl/clients/PLACEHOLDER + install -d $(PREFIX)/share/examples/mcollective/ + cp -R etc/* $(PREFIX)/share/examples/mcollective/ + rm $(PREFIX)/share/examples/mcollective/ssl/PLACEHOLDER + rm $(PREFIX)/share/examples/mcollective/ssl/clients/PLACEHOLDER install-plugins: - install -d $(DESTDIR)/usr/share/mcollective/ - cp -a plugins $(DESTDIR)/usr/share/mcollective/ + install -d $(PREFIX)/share/mcollective/ + cp -R plugins $(PREFIX)/share/mcollective/ install-doc: - install -d $(DESTDIR)/usr/share/doc/ - cp -a doc $(DESTDIR)/usr/share/doc/mcollective + install -d $(PREFIX)/share/doc/ + cp -R doc $(PREFIX)/share/doc/mcollective uninstall: - rm -f $(DESTDIR)/usr/sbin/mcollectived - rm -rf $(DESTDIR)/usr/lib/ruby/1.8/mcollective* - rm -rf $(DESTDIR)/usr/share/mcollective - rm -rf $(DESTDIR)/etc/mcollective + rm -f $(PREFIX)/sbin/mcollectived + rm -rf $(PREFIX)/lib/ruby/1.8/mcollective* + rm -rf $(PREFIX)/share/mcollective + rm -rf $(PREFIX)/share/examples/mcollective .PHONY: build clean install uninstall mcollective-2.6.0/ext/openbsd/port-files/mcollective/distinfo0000644000175000017500000000044612375446407023441 0ustar jonasjonasMD5 (mcollective-2.2.1.tgz) = 82t1Sk+HRSXR9L4x4eTaSg== RMD160 (mcollective-2.2.1.tgz) = PMTcw0VMqrr7rSOYApCW0q8SXok= SHA1 (mcollective-2.2.1.tgz) = 1yPH0CgmQa5J4VeiDEAExNbn3K4= SHA256 (mcollective-2.2.1.tgz) = 2KaZk2KrMXPJ7aycM+Uy+0E8hyUsONoP9HkdjpAZ4YM= SIZE (mcollective-2.2.1.tgz) = 1123400 mcollective-2.6.0/ext/openbsd/port-files/mcollective/pkg/0000755000175000017500000000000012375446416022454 5ustar jonasjonasmcollective-2.6.0/ext/openbsd/port-files/mcollective/pkg/mcollectived.rc0000644000175000017500000000023512375446407025454 0ustar jonasjonas#!/bin/sh daemon="${TRUEPREFIX}/sbin/mcollectived" . /etc/rc.d/rc.subr pexp=".*ruby.* ${daemon}${daemon_flags:+ ${daemon_flags}}" rc_reload=NO rc_cmd $1 mcollective-2.6.0/ext/openbsd/port-files/mcollective/pkg/PLIST0000644000175000017500000007774512375446407023316 0ustar jonasjonas@comment $OpenBSD$ bin/mco lib/ruby/ lib/ruby/1.8/ lib/ruby/1.8/mcollective/ lib/ruby/1.8/mcollective.rb lib/ruby/1.8/mcollective/agent.rb lib/ruby/1.8/mcollective/agents.rb lib/ruby/1.8/mcollective/aggregate/ lib/ruby/1.8/mcollective/aggregate.rb lib/ruby/1.8/mcollective/aggregate/base.rb lib/ruby/1.8/mcollective/aggregate/result/ lib/ruby/1.8/mcollective/aggregate/result.rb lib/ruby/1.8/mcollective/aggregate/result/base.rb lib/ruby/1.8/mcollective/aggregate/result/collection_result.rb lib/ruby/1.8/mcollective/aggregate/result/numeric_result.rb lib/ruby/1.8/mcollective/application.rb lib/ruby/1.8/mcollective/applications.rb lib/ruby/1.8/mcollective/cache.rb lib/ruby/1.8/mcollective/client.rb lib/ruby/1.8/mcollective/config.rb lib/ruby/1.8/mcollective/connector/ lib/ruby/1.8/mcollective/connector.rb lib/ruby/1.8/mcollective/connector/base.rb lib/ruby/1.8/mcollective/data/ lib/ruby/1.8/mcollective/data.rb lib/ruby/1.8/mcollective/data/base.rb lib/ruby/1.8/mcollective/data/result.rb lib/ruby/1.8/mcollective/ddl/ lib/ruby/1.8/mcollective/ddl.rb lib/ruby/1.8/mcollective/ddl/agentddl.rb lib/ruby/1.8/mcollective/ddl/base.rb lib/ruby/1.8/mcollective/ddl/dataddl.rb lib/ruby/1.8/mcollective/ddl/discoveryddl.rb lib/ruby/1.8/mcollective/ddl/validatorddl.rb lib/ruby/1.8/mcollective/discovery.rb lib/ruby/1.8/mcollective/facts/ lib/ruby/1.8/mcollective/facts.rb lib/ruby/1.8/mcollective/facts/base.rb lib/ruby/1.8/mcollective/generators/ lib/ruby/1.8/mcollective/generators.rb lib/ruby/1.8/mcollective/generators/agent_generator.rb lib/ruby/1.8/mcollective/generators/base.rb lib/ruby/1.8/mcollective/generators/data_generator.rb lib/ruby/1.8/mcollective/generators/templates/ lib/ruby/1.8/mcollective/generators/templates/action_snippet.erb lib/ruby/1.8/mcollective/generators/templates/data_input_snippet.erb lib/ruby/1.8/mcollective/generators/templates/ddl.erb lib/ruby/1.8/mcollective/generators/templates/plugin.erb lib/ruby/1.8/mcollective/log.rb lib/ruby/1.8/mcollective/logger/ lib/ruby/1.8/mcollective/logger.rb lib/ruby/1.8/mcollective/logger/base.rb lib/ruby/1.8/mcollective/logger/console_logger.rb lib/ruby/1.8/mcollective/logger/file_logger.rb lib/ruby/1.8/mcollective/logger/syslog_logger.rb lib/ruby/1.8/mcollective/matcher/ lib/ruby/1.8/mcollective/matcher.rb lib/ruby/1.8/mcollective/matcher/parser.rb lib/ruby/1.8/mcollective/matcher/scanner.rb lib/ruby/1.8/mcollective/message.rb lib/ruby/1.8/mcollective/monkey_patches.rb lib/ruby/1.8/mcollective/optionparser.rb lib/ruby/1.8/mcollective/pluginmanager.rb lib/ruby/1.8/mcollective/pluginpackager/ lib/ruby/1.8/mcollective/pluginpackager.rb lib/ruby/1.8/mcollective/pluginpackager/agent_definition.rb lib/ruby/1.8/mcollective/pluginpackager/standard_definition.rb lib/ruby/1.8/mcollective/registration/ lib/ruby/1.8/mcollective/registration.rb lib/ruby/1.8/mcollective/registration/base.rb lib/ruby/1.8/mcollective/rpc/ lib/ruby/1.8/mcollective/rpc.rb lib/ruby/1.8/mcollective/rpc/actionrunner.rb lib/ruby/1.8/mcollective/rpc/agent.rb lib/ruby/1.8/mcollective/rpc/audit.rb lib/ruby/1.8/mcollective/rpc/client.rb lib/ruby/1.8/mcollective/rpc/helpers.rb lib/ruby/1.8/mcollective/rpc/progress.rb lib/ruby/1.8/mcollective/rpc/reply.rb lib/ruby/1.8/mcollective/rpc/request.rb lib/ruby/1.8/mcollective/rpc/result.rb lib/ruby/1.8/mcollective/rpc/stats.rb lib/ruby/1.8/mcollective/runner.rb lib/ruby/1.8/mcollective/runnerstats.rb lib/ruby/1.8/mcollective/security/ lib/ruby/1.8/mcollective/security.rb lib/ruby/1.8/mcollective/security/base.rb lib/ruby/1.8/mcollective/shell.rb lib/ruby/1.8/mcollective/ssl.rb lib/ruby/1.8/mcollective/unix_daemon.rb lib/ruby/1.8/mcollective/util.rb lib/ruby/1.8/mcollective/validator.rb lib/ruby/1.8/mcollective/vendor/ lib/ruby/1.8/mcollective/vendor.rb lib/ruby/1.8/mcollective/vendor/json/ lib/ruby/1.8/mcollective/vendor/json/.gitignore lib/ruby/1.8/mcollective/vendor/json/CHANGES lib/ruby/1.8/mcollective/vendor/json/COPYING lib/ruby/1.8/mcollective/vendor/json/COPYING-json-jruby lib/ruby/1.8/mcollective/vendor/json/GPL lib/ruby/1.8/mcollective/vendor/json/README lib/ruby/1.8/mcollective/vendor/json/README-json-jruby.markdown lib/ruby/1.8/mcollective/vendor/json/Rakefile lib/ruby/1.8/mcollective/vendor/json/TODO lib/ruby/1.8/mcollective/vendor/json/VERSION lib/ruby/1.8/mcollective/vendor/json/benchmarks/ lib/ruby/1.8/mcollective/vendor/json/benchmarks/data/ lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/.keep lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkComparison.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_fast.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_pretty.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt#generator_safe.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkExt.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_fast.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_pretty.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure#generator_safe.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkPure.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails#generator.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/GeneratorBenchmarkRails.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkComparison.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt#parser.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkExt.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure#parser.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkPure.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails#parser.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkRails.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser-autocorrelation.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML#parser.dat lib/ruby/1.8/mcollective/vendor/json/benchmarks/data-p4-3GHz-ruby18/ParserBenchmarkYAML.log lib/ruby/1.8/mcollective/vendor/json/benchmarks/data/.keep lib/ruby/1.8/mcollective/vendor/json/benchmarks/generator2_benchmark.rb lib/ruby/1.8/mcollective/vendor/json/benchmarks/generator_benchmark.rb lib/ruby/1.8/mcollective/vendor/json/benchmarks/ohai.json lib/ruby/1.8/mcollective/vendor/json/benchmarks/ohai.ruby lib/ruby/1.8/mcollective/vendor/json/benchmarks/parser2_benchmark.rb lib/ruby/1.8/mcollective/vendor/json/benchmarks/parser_benchmark.rb lib/ruby/1.8/mcollective/vendor/json/bin/ lib/ruby/1.8/mcollective/vendor/json/bin/edit_json.rb lib/ruby/1.8/mcollective/vendor/json/bin/prettify_json.rb lib/ruby/1.8/mcollective/vendor/json/data/ lib/ruby/1.8/mcollective/vendor/json/data/example.json lib/ruby/1.8/mcollective/vendor/json/data/index.html lib/ruby/1.8/mcollective/vendor/json/data/prototype.js lib/ruby/1.8/mcollective/vendor/json/diagrams/ lib/ruby/1.8/mcollective/vendor/json/diagrams/.keep lib/ruby/1.8/mcollective/vendor/json/ext/ lib/ruby/1.8/mcollective/vendor/json/ext/json/ lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/ lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/generator/ lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/generator/extconf.rb lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/generator/generator.c lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/generator/generator.h lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/parser/ lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/parser/extconf.rb lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/parser/parser.c lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/parser/parser.h lib/ruby/1.8/mcollective/vendor/json/ext/json/ext/parser/parser.rl lib/ruby/1.8/mcollective/vendor/json/install.rb lib/ruby/1.8/mcollective/vendor/json/java/ lib/ruby/1.8/mcollective/vendor/json/java/lib/ lib/ruby/1.8/mcollective/vendor/json/java/lib/bytelist-1.0.6.jar lib/ruby/1.8/mcollective/vendor/json/java/lib/jcodings.jar lib/ruby/1.8/mcollective/vendor/json/java/src/ lib/ruby/1.8/mcollective/vendor/json/java/src/json/ lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/ lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/ByteListTranscoder.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/Generator.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/GeneratorMethods.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/GeneratorService.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/GeneratorState.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/OptionsReader.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/Parser.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/Parser.rl lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/ParserService.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/RuntimeInfo.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/StringDecoder.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/StringEncoder.java lib/ruby/1.8/mcollective/vendor/json/java/src/json/ext/Utils.java lib/ruby/1.8/mcollective/vendor/json/json-java.gemspec lib/ruby/1.8/mcollective/vendor/json/lib/ lib/ruby/1.8/mcollective/vendor/json/lib/json/ lib/ruby/1.8/mcollective/vendor/json/lib/json.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/Array.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/FalseClass.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/Hash.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/Key.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/NilClass.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/Numeric.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/String.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/TrueClass.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/add/ lib/ruby/1.8/mcollective/vendor/json/lib/json/add/core.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/add/rails.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/common.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/editor.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/ext/ lib/ruby/1.8/mcollective/vendor/json/lib/json/ext.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/ext/.keep lib/ruby/1.8/mcollective/vendor/json/lib/json/json.xpm lib/ruby/1.8/mcollective/vendor/json/lib/json/pure/ lib/ruby/1.8/mcollective/vendor/json/lib/json/pure.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/pure/generator.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/pure/parser.rb lib/ruby/1.8/mcollective/vendor/json/lib/json/version.rb lib/ruby/1.8/mcollective/vendor/json/tests/ lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/ lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail1.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail10.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail11.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail12.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail13.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail14.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail18.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail19.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail2.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail20.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail21.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail22.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail23.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail24.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail25.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail27.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail28.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail3.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail4.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail5.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail6.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail7.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail8.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/fail9.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/pass1.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/pass15.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/pass16.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/pass17.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/pass2.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/pass26.json lib/ruby/1.8/mcollective/vendor/json/tests/fixtures/pass3.json lib/ruby/1.8/mcollective/vendor/json/tests/setup_variant.rb lib/ruby/1.8/mcollective/vendor/json/tests/test_json.rb lib/ruby/1.8/mcollective/vendor/json/tests/test_json_addition.rb lib/ruby/1.8/mcollective/vendor/json/tests/test_json_encoding.rb lib/ruby/1.8/mcollective/vendor/json/tests/test_json_fixtures.rb lib/ruby/1.8/mcollective/vendor/json/tests/test_json_generate.rb lib/ruby/1.8/mcollective/vendor/json/tests/test_json_string_matching.rb lib/ruby/1.8/mcollective/vendor/json/tests/test_json_unicode.rb lib/ruby/1.8/mcollective/vendor/json/tools/ lib/ruby/1.8/mcollective/vendor/json/tools/fuzz.rb lib/ruby/1.8/mcollective/vendor/json/tools/server.rb lib/ruby/1.8/mcollective/vendor/load_json.rb lib/ruby/1.8/mcollective/vendor/load_systemu.rb lib/ruby/1.8/mcollective/vendor/require_vendored.rb lib/ruby/1.8/mcollective/vendor/systemu/ lib/ruby/1.8/mcollective/vendor/systemu/LICENSE lib/ruby/1.8/mcollective/vendor/systemu/README lib/ruby/1.8/mcollective/vendor/systemu/README.erb lib/ruby/1.8/mcollective/vendor/systemu/Rakefile lib/ruby/1.8/mcollective/vendor/systemu/lib/ lib/ruby/1.8/mcollective/vendor/systemu/lib/systemu.rb lib/ruby/1.8/mcollective/vendor/systemu/samples/ lib/ruby/1.8/mcollective/vendor/systemu/samples/a.rb lib/ruby/1.8/mcollective/vendor/systemu/samples/b.rb lib/ruby/1.8/mcollective/vendor/systemu/samples/c.rb lib/ruby/1.8/mcollective/vendor/systemu/samples/d.rb lib/ruby/1.8/mcollective/vendor/systemu/samples/e.rb lib/ruby/1.8/mcollective/vendor/systemu/samples/f.rb lib/ruby/1.8/mcollective/vendor/systemu/systemu.gemspec lib/ruby/1.8/mcollective/windows_daemon.rb sbin/mcollectived share/doc/mcollective/ share/doc/mcollective/Array.html share/doc/mcollective/COPYING.html share/doc/mcollective/Dir.html share/doc/mcollective/MCollective/ share/doc/mcollective/MCollective.html share/doc/mcollective/MCollective/Agent.html share/doc/mcollective/MCollective/Agents.html share/doc/mcollective/MCollective/Aggregate/ share/doc/mcollective/MCollective/Aggregate.html share/doc/mcollective/MCollective/Aggregate/Base.html share/doc/mcollective/MCollective/Aggregate/Result/ share/doc/mcollective/MCollective/Aggregate/Result.html share/doc/mcollective/MCollective/Aggregate/Result/Base.html share/doc/mcollective/MCollective/Aggregate/Result/CollectionResult.html share/doc/mcollective/MCollective/Aggregate/Result/NumericResult.html share/doc/mcollective/MCollective/Application.html share/doc/mcollective/MCollective/Applications.html share/doc/mcollective/MCollective/Cache.html share/doc/mcollective/MCollective/Client.html share/doc/mcollective/MCollective/Config.html share/doc/mcollective/MCollective/Connector/ share/doc/mcollective/MCollective/Connector.html share/doc/mcollective/MCollective/Connector/Base.html share/doc/mcollective/MCollective/DDL/ share/doc/mcollective/MCollective/DDL.html share/doc/mcollective/MCollective/DDL/AgentDDL.html share/doc/mcollective/MCollective/DDL/Base.html share/doc/mcollective/MCollective/DDL/DataDDL.html share/doc/mcollective/MCollective/DDL/DiscoveryDDL.html share/doc/mcollective/MCollective/DDL/ValidatorDDL.html share/doc/mcollective/MCollective/DDLValidationError.html share/doc/mcollective/MCollective/Data/ share/doc/mcollective/MCollective/Data.html share/doc/mcollective/MCollective/Data/Base.html share/doc/mcollective/MCollective/Data/Result.html share/doc/mcollective/MCollective/Discovery.html share/doc/mcollective/MCollective/Facts/ share/doc/mcollective/MCollective/Facts.html share/doc/mcollective/MCollective/Facts/Base.html share/doc/mcollective/MCollective/Generators/ share/doc/mcollective/MCollective/Generators.html share/doc/mcollective/MCollective/Generators/AgentGenerator.html share/doc/mcollective/MCollective/Generators/Base.html share/doc/mcollective/MCollective/Generators/DataGenerator.html share/doc/mcollective/MCollective/InvalidRPCData.html share/doc/mcollective/MCollective/Log.html share/doc/mcollective/MCollective/Logger/ share/doc/mcollective/MCollective/Logger.html share/doc/mcollective/MCollective/Logger/Base.html share/doc/mcollective/MCollective/Logger/Console_logger.html share/doc/mcollective/MCollective/Logger/File_logger.html share/doc/mcollective/MCollective/Logger/Syslog_logger.html share/doc/mcollective/MCollective/Matcher/ share/doc/mcollective/MCollective/Matcher.html share/doc/mcollective/MCollective/Matcher/Parser.html share/doc/mcollective/MCollective/Matcher/Scanner.html share/doc/mcollective/MCollective/Message.html share/doc/mcollective/MCollective/MissingRPCData.html share/doc/mcollective/MCollective/MsgDoesNotMatchRequestID.html share/doc/mcollective/MCollective/MsgTTLExpired.html share/doc/mcollective/MCollective/NotTargettedAtUs.html share/doc/mcollective/MCollective/Optionparser.html share/doc/mcollective/MCollective/PluginManager.html share/doc/mcollective/MCollective/PluginPackager/ share/doc/mcollective/MCollective/PluginPackager.html share/doc/mcollective/MCollective/PluginPackager/AgentDefinition.html share/doc/mcollective/MCollective/PluginPackager/StandardDefinition.html share/doc/mcollective/MCollective/RPC/ share/doc/mcollective/MCollective/RPC.html share/doc/mcollective/MCollective/RPC/ActionRunner.html share/doc/mcollective/MCollective/RPC/Agent.html share/doc/mcollective/MCollective/RPC/Audit.html share/doc/mcollective/MCollective/RPC/Client.html share/doc/mcollective/MCollective/RPC/Helpers.html share/doc/mcollective/MCollective/RPC/Progress.html share/doc/mcollective/MCollective/RPC/Reply.html share/doc/mcollective/MCollective/RPC/Request.html share/doc/mcollective/MCollective/RPC/Result.html share/doc/mcollective/MCollective/RPC/Stats.html share/doc/mcollective/MCollective/RPCAborted.html share/doc/mcollective/MCollective/RPCError.html share/doc/mcollective/MCollective/Registration/ share/doc/mcollective/MCollective/Registration.html share/doc/mcollective/MCollective/Registration/Base.html share/doc/mcollective/MCollective/Runner.html share/doc/mcollective/MCollective/RunnerStats.html share/doc/mcollective/MCollective/SSL.html share/doc/mcollective/MCollective/Security/ share/doc/mcollective/MCollective/Security.html share/doc/mcollective/MCollective/Security/Base.html share/doc/mcollective/MCollective/SecurityValidationFailed.html share/doc/mcollective/MCollective/Shell.html share/doc/mcollective/MCollective/UnixDaemon.html share/doc/mcollective/MCollective/UnknownRPCAction.html share/doc/mcollective/MCollective/UnknownRPCError.html share/doc/mcollective/MCollective/Util.html share/doc/mcollective/MCollective/Validator.html share/doc/mcollective/MCollective/ValidatorError.html share/doc/mcollective/MCollective/WindowsDaemon.html share/doc/mcollective/Object.html share/doc/mcollective/README.html share/doc/mcollective/Rakefile.html share/doc/mcollective/String.html share/doc/mcollective/Symbol.html share/doc/mcollective/bin/ share/doc/mcollective/bin/mco.html share/doc/mcollective/bin/mcollectived.html share/doc/mcollective/created.rid share/doc/mcollective/etc/ share/doc/mcollective/etc/ssl/ share/doc/mcollective/etc/ssl/PLACEHOLDER.html share/doc/mcollective/etc/ssl/clients/ share/doc/mcollective/etc/ssl/clients/PLACEHOLDER.html share/doc/mcollective/images/ share/doc/mcollective/images/brick.png share/doc/mcollective/images/brick_link.png share/doc/mcollective/images/bug.png share/doc/mcollective/images/bullet_black.png share/doc/mcollective/images/bullet_toggle_minus.png share/doc/mcollective/images/bullet_toggle_plus.png share/doc/mcollective/images/date.png share/doc/mcollective/images/find.png share/doc/mcollective/images/loadingAnimation.gif share/doc/mcollective/images/macFFBgHack.png share/doc/mcollective/images/package.png share/doc/mcollective/images/page_green.png share/doc/mcollective/images/page_white_text.png share/doc/mcollective/images/page_white_width.png share/doc/mcollective/images/plugin.png share/doc/mcollective/images/ruby.png share/doc/mcollective/images/tag_green.png share/doc/mcollective/images/wrench.png share/doc/mcollective/images/wrench_orange.png share/doc/mcollective/images/zoom.png share/doc/mcollective/index.html share/doc/mcollective/js/ share/doc/mcollective/js/darkfish.js share/doc/mcollective/js/jquery.js share/doc/mcollective/js/quicksearch.js share/doc/mcollective/js/thickbox-compressed.js share/doc/mcollective/lib/ share/doc/mcollective/lib/mcollective/ share/doc/mcollective/lib/mcollective/agent_rb.html share/doc/mcollective/lib/mcollective/agents_rb.html share/doc/mcollective/lib/mcollective/aggregate/ share/doc/mcollective/lib/mcollective/aggregate/base_rb.html share/doc/mcollective/lib/mcollective/aggregate/result/ share/doc/mcollective/lib/mcollective/aggregate/result/base_rb.html share/doc/mcollective/lib/mcollective/aggregate/result/collection_result_rb.html share/doc/mcollective/lib/mcollective/aggregate/result/numeric_result_rb.html share/doc/mcollective/lib/mcollective/aggregate/result_rb.html share/doc/mcollective/lib/mcollective/aggregate_rb.html share/doc/mcollective/lib/mcollective/application_rb.html share/doc/mcollective/lib/mcollective/applications_rb.html share/doc/mcollective/lib/mcollective/cache_rb.html share/doc/mcollective/lib/mcollective/client_rb.html share/doc/mcollective/lib/mcollective/config_rb.html share/doc/mcollective/lib/mcollective/connector/ share/doc/mcollective/lib/mcollective/connector/base_rb.html share/doc/mcollective/lib/mcollective/connector_rb.html share/doc/mcollective/lib/mcollective/data/ share/doc/mcollective/lib/mcollective/data/base_rb.html share/doc/mcollective/lib/mcollective/data/result_rb.html share/doc/mcollective/lib/mcollective/data_rb.html share/doc/mcollective/lib/mcollective/ddl/ share/doc/mcollective/lib/mcollective/ddl/agentddl_rb.html share/doc/mcollective/lib/mcollective/ddl/base_rb.html share/doc/mcollective/lib/mcollective/ddl/dataddl_rb.html share/doc/mcollective/lib/mcollective/ddl/discoveryddl_rb.html share/doc/mcollective/lib/mcollective/ddl/validatorddl_rb.html share/doc/mcollective/lib/mcollective/ddl_rb.html share/doc/mcollective/lib/mcollective/discovery_rb.html share/doc/mcollective/lib/mcollective/facts/ share/doc/mcollective/lib/mcollective/facts/base_rb.html share/doc/mcollective/lib/mcollective/facts_rb.html share/doc/mcollective/lib/mcollective/generators/ share/doc/mcollective/lib/mcollective/generators/agent_generator_rb.html share/doc/mcollective/lib/mcollective/generators/base_rb.html share/doc/mcollective/lib/mcollective/generators/data_generator_rb.html share/doc/mcollective/lib/mcollective/generators_rb.html share/doc/mcollective/lib/mcollective/log_rb.html share/doc/mcollective/lib/mcollective/logger/ share/doc/mcollective/lib/mcollective/logger/base_rb.html share/doc/mcollective/lib/mcollective/logger/console_logger_rb.html share/doc/mcollective/lib/mcollective/logger/file_logger_rb.html share/doc/mcollective/lib/mcollective/logger/syslog_logger_rb.html share/doc/mcollective/lib/mcollective/logger_rb.html share/doc/mcollective/lib/mcollective/matcher/ share/doc/mcollective/lib/mcollective/matcher/parser_rb.html share/doc/mcollective/lib/mcollective/matcher/scanner_rb.html share/doc/mcollective/lib/mcollective/matcher_rb.html share/doc/mcollective/lib/mcollective/message_rb.html share/doc/mcollective/lib/mcollective/monkey_patches_rb.html share/doc/mcollective/lib/mcollective/optionparser_rb.html share/doc/mcollective/lib/mcollective/pluginmanager_rb.html share/doc/mcollective/lib/mcollective/pluginpackager/ share/doc/mcollective/lib/mcollective/pluginpackager/agent_definition_rb.html share/doc/mcollective/lib/mcollective/pluginpackager/standard_definition_rb.html share/doc/mcollective/lib/mcollective/pluginpackager_rb.html share/doc/mcollective/lib/mcollective/registration/ share/doc/mcollective/lib/mcollective/registration/base_rb.html share/doc/mcollective/lib/mcollective/registration_rb.html share/doc/mcollective/lib/mcollective/rpc/ share/doc/mcollective/lib/mcollective/rpc/actionrunner_rb.html share/doc/mcollective/lib/mcollective/rpc/agent_rb.html share/doc/mcollective/lib/mcollective/rpc/audit_rb.html share/doc/mcollective/lib/mcollective/rpc/client_rb.html share/doc/mcollective/lib/mcollective/rpc/helpers_rb.html share/doc/mcollective/lib/mcollective/rpc/progress_rb.html share/doc/mcollective/lib/mcollective/rpc/reply_rb.html share/doc/mcollective/lib/mcollective/rpc/request_rb.html share/doc/mcollective/lib/mcollective/rpc/result_rb.html share/doc/mcollective/lib/mcollective/rpc/stats_rb.html share/doc/mcollective/lib/mcollective/rpc_rb.html share/doc/mcollective/lib/mcollective/runner_rb.html share/doc/mcollective/lib/mcollective/runnerstats_rb.html share/doc/mcollective/lib/mcollective/security/ share/doc/mcollective/lib/mcollective/security/base_rb.html share/doc/mcollective/lib/mcollective/security_rb.html share/doc/mcollective/lib/mcollective/shell_rb.html share/doc/mcollective/lib/mcollective/ssl_rb.html share/doc/mcollective/lib/mcollective/unix_daemon_rb.html share/doc/mcollective/lib/mcollective/util_rb.html share/doc/mcollective/lib/mcollective/validator_rb.html share/doc/mcollective/lib/mcollective/windows_daemon_rb.html share/doc/mcollective/lib/mcollective_rb.html share/doc/mcollective/rdoc.css share/examples/mcollective/ @sample ${SYSCONFDIR}/mcollective/ share/examples/mcollective/client.cfg.dist @sample ${SYSCONFDIR}/mcollective/client.cfg share/examples/mcollective/data-help.erb share/examples/mcollective/discovery-help.erb share/examples/mcollective/facts.yaml.dist @sample ${SYSCONFDIR}/mcollective/facts.yaml share/examples/mcollective/metadata-help.erb share/examples/mcollective/rpc-help.erb share/examples/mcollective/server.cfg.dist @sample ${SYSCONFDIR}/mcollective/server.cfg share/examples/mcollective/ssl/ @sample ${SYSCONFDIR}/mcollective/ssl/ share/examples/mcollective/ssl/clients/ @sample ${SYSCONFDIR}/mcollective/ssl/clients/ share/mcollective/ share/mcollective/plugins/ share/mcollective/plugins/mcollective/ share/mcollective/plugins/mcollective/agent/ share/mcollective/plugins/mcollective/agent/discovery.rb share/mcollective/plugins/mcollective/agent/rpcutil.ddl share/mcollective/plugins/mcollective/agent/rpcutil.rb share/mcollective/plugins/mcollective/aggregate/ share/mcollective/plugins/mcollective/aggregate/average.rb share/mcollective/plugins/mcollective/aggregate/sum.rb share/mcollective/plugins/mcollective/aggregate/summary.rb share/mcollective/plugins/mcollective/application/ share/mcollective/plugins/mcollective/application/completion.rb share/mcollective/plugins/mcollective/application/facts.rb share/mcollective/plugins/mcollective/application/find.rb share/mcollective/plugins/mcollective/application/help.rb share/mcollective/plugins/mcollective/application/inventory.rb share/mcollective/plugins/mcollective/application/ping.rb share/mcollective/plugins/mcollective/application/plugin.rb share/mcollective/plugins/mcollective/application/rpc.rb share/mcollective/plugins/mcollective/audit/ share/mcollective/plugins/mcollective/audit/logfile.rb share/mcollective/plugins/mcollective/connector/ share/mcollective/plugins/mcollective/connector/activemq.rb share/mcollective/plugins/mcollective/connector/rabbitmq.rb share/mcollective/plugins/mcollective/connector/stomp.rb share/mcollective/plugins/mcollective/data/ share/mcollective/plugins/mcollective/data/agent_data.ddl share/mcollective/plugins/mcollective/data/agent_data.rb share/mcollective/plugins/mcollective/data/fstat_data.ddl share/mcollective/plugins/mcollective/data/fstat_data.rb share/mcollective/plugins/mcollective/discovery/ share/mcollective/plugins/mcollective/discovery/flatfile.ddl share/mcollective/plugins/mcollective/discovery/flatfile.rb share/mcollective/plugins/mcollective/discovery/mc.ddl share/mcollective/plugins/mcollective/discovery/mc.rb share/mcollective/plugins/mcollective/facts/ share/mcollective/plugins/mcollective/facts/yaml_facts.rb share/mcollective/plugins/mcollective/pluginpackager/ share/mcollective/plugins/mcollective/pluginpackager/debpackage_packager.rb share/mcollective/plugins/mcollective/pluginpackager/ospackage_packager.rb share/mcollective/plugins/mcollective/pluginpackager/rpmpackage_packager.rb share/mcollective/plugins/mcollective/pluginpackager/templates/ share/mcollective/plugins/mcollective/pluginpackager/templates/debian/ share/mcollective/plugins/mcollective/pluginpackager/templates/debian/Makefile.erb share/mcollective/plugins/mcollective/pluginpackager/templates/debian/changelog.erb share/mcollective/plugins/mcollective/pluginpackager/templates/debian/compat.erb share/mcollective/plugins/mcollective/pluginpackager/templates/debian/control.erb share/mcollective/plugins/mcollective/pluginpackager/templates/debian/copyright.erb share/mcollective/plugins/mcollective/pluginpackager/templates/debian/rules.erb share/mcollective/plugins/mcollective/pluginpackager/templates/redhat/ share/mcollective/plugins/mcollective/pluginpackager/templates/redhat/rpm_spec.erb share/mcollective/plugins/mcollective/registration/ share/mcollective/plugins/mcollective/registration/agentlist.rb share/mcollective/plugins/mcollective/security/ share/mcollective/plugins/mcollective/security/aes_security.rb share/mcollective/plugins/mcollective/security/psk.rb share/mcollective/plugins/mcollective/security/ssl.rb share/mcollective/plugins/mcollective/validator/ share/mcollective/plugins/mcollective/validator/array_validator.ddl share/mcollective/plugins/mcollective/validator/array_validator.rb share/mcollective/plugins/mcollective/validator/ipv4address_validator.ddl share/mcollective/plugins/mcollective/validator/ipv4address_validator.rb share/mcollective/plugins/mcollective/validator/ipv6address_validator.ddl share/mcollective/plugins/mcollective/validator/ipv6address_validator.rb share/mcollective/plugins/mcollective/validator/length_validator.ddl share/mcollective/plugins/mcollective/validator/length_validator.rb share/mcollective/plugins/mcollective/validator/regex_validator.ddl share/mcollective/plugins/mcollective/validator/regex_validator.rb share/mcollective/plugins/mcollective/validator/shellsafe_validator.ddl share/mcollective/plugins/mcollective/validator/shellsafe_validator.rb share/mcollective/plugins/mcollective/validator/typecheck_validator.ddl share/mcollective/plugins/mcollective/validator/typecheck_validator.rb @rcscript ${RCDIR}/mcollectived mcollective-2.6.0/ext/openbsd/port-files/mcollective/pkg/DESCR0000644000175000017500000000017512375446407023242 0ustar jonasjonasThe Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems. mcollective-2.6.0/ext/openbsd/port-files/mcollective/pkg/MESSAGE0000644000175000017500000000023312375446407023461 0ustar jonasjonasIf you wish to have mcollective started automatically at boot time, update your pkg_scripts variable, e.g. in /etc/rc.conf.local. See rc.d(8) for details. mcollective-2.6.0/ext/openbsd/README0000644000175000017500000000021512375446407016157 0ustar jonasjonasThese files are meant to be places in /usr/ports/mystuff/sysutils/ and then follow the OpenBSD guidelines for "custom" ports. Happy hacking mcollective-2.6.0/plugins/0000755000175000017500000000000012375446416014530 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/0000755000175000017500000000000012375446416017036 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/discovery/0000755000175000017500000000000012375446416021045 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/discovery/mc.rb0000644000175000017500000000147112375446407021774 0ustar jonasjonasmodule MCollective class Discovery class Mc def self.discover(filter, timeout, limit, client) begin hosts = [] Timeout.timeout(timeout) do reqid = client.sendreq("ping", "discovery", filter) Log.debug("Waiting #{timeout} seconds for discovery replies to request #{reqid}") loop do reply = client.receive(reqid) Log.debug("Got discovery reply from #{reply.payload[:senderid]}") hosts << reply.payload[:senderid] return hosts if limit > 0 && hosts.size == limit end end rescue Timeout::Error => e rescue Exception => e raise ensure client.unsubscribe("discovery", :reply) end hosts end end end end mcollective-2.6.0/plugins/mcollective/discovery/stdin.rb0000644000175000017500000000362212375446407022516 0ustar jonasjonas# discovers against stdin instead of the traditional network discovery # the input must be a flat file with a node name per line which should match identities as configured, # or it should be a json string as output by the -j option of mco rpc require 'mcollective/rpc/helpers' module MCollective class Discovery class Stdin def self.discover(filter, timeout, limit=0, client=nil) unless client.options[:discovery_options].empty? type = client.options[:discovery_options].first.downcase else type = 'auto' end discovered = [] file = STDIN.read if file =~ /^\s*$/ raise("data piped on STDIN contained only whitespace - could not discover hosts from it.") end if type == 'auto' if file =~ /^\s*\[/ type = 'json' else type = 'text' end end if type == 'json' hosts = MCollective::RPC::Helpers.extract_hosts_from_json(file) elsif type == 'text' hosts = file.split("\n") else raise("stdin discovery plugin only knows the types auto/text/json, not \"#{type}\"") end hosts.map do |host| raise 'Identities can only match /\w\.\-/' unless host.match(/^[\w\.\-]+$/) host end # this plugin only supports identity filters, do regex matches etc against # the list found in the flatfile unless filter["identity"].empty? filter["identity"].each do |identity| identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/") if identity.is_a?(Regexp) discovered = hosts.grep(identity) elsif hosts.include?(identity) discovered << identity end end else discovered = hosts end discovered end end end end mcollective-2.6.0/plugins/mcollective/discovery/stdin.ddl0000644000175000017500000000061312375446407022653 0ustar jonasjonasmetadata :name => "stdin", :description => "STDIN based discovery for node identities", :author => "Tomas Doran ", :license => "ASL 2.0", :version => "0.1", :url => "http://marionette-collective.org/", :timeout => 0 discovery do capabilities :identity end mcollective-2.6.0/plugins/mcollective/discovery/mc.ddl0000644000175000017500000000064012375446407022131 0ustar jonasjonasmetadata :name => "mc", :description => "MCollective Broadcast based discovery", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "0.1", :url => "http://marionette-collective.org/", :timeout => 2 discovery do capabilities [:classes, :facts, :identity, :agents, :compound] end mcollective-2.6.0/plugins/mcollective/discovery/flatfile.rb0000644000175000017500000000277212375446407023170 0ustar jonasjonas# discovers against a flatfile instead of the traditional network discovery # the flat file must have a node name per line which should match identities # as configured module MCollective class Discovery class Flatfile def self.discover(filter, timeout, limit=0, client=nil) unless client.options[:discovery_options].empty? file = client.options[:discovery_options].first else raise "The flatfile discovery method needs a path to a text file" end raise "Cannot read the file %s specified as discovery source" % file unless File.readable?(file) discovered = [] hosts = [] File.readlines(file).each do |host| host = host.chomp.strip if host.empty? || host.match(/^#/) next end raise 'Identities can only match /^[\w\.\-]+$/' unless host.match(/^[\w\.\-]+$/) hosts << host end # this plugin only supports identity filters, do regex matches etc against # the list found in the flatfile if !(filter["identity"].empty?) filter["identity"].each do |identity| identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/") if identity.is_a?(Regexp) discovered = hosts.grep(identity) elsif hosts.include?(identity) discovered << identity end end else discovered = hosts end discovered end end end end mcollective-2.6.0/plugins/mcollective/discovery/flatfile.ddl0000644000175000017500000000060512375446407023321 0ustar jonasjonasmetadata :name => "flatfile", :description => "Flatfile based discovery for node identities", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "0.1", :url => "http://marionette-collective.org/", :timeout => 0 discovery do capabilities :identity end mcollective-2.6.0/plugins/mcollective/audit/0000755000175000017500000000000012375446416020144 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/audit/logfile.rb0000644000175000017500000000161512375446407022115 0ustar jonasjonasmodule MCollective module RPC # An audit plugin that just logs to a file # # You can configure which file it logs to with the setting # # plugin.rpcaudit.logfile class Logfile e puts 'Build process has failed' raise e end end # Tar up source # Expects directory $mcollective-$agent-$version # Creates file : $tmpbuildroot/$mcollective-$agent-$version def create_tar tarfile = File.join(@tmpdir, "#{@package_name_and_version}.tgz") begin PluginPackager.execute_verbosely(@verbose) do Dir.chdir(@tmpdir) do PluginPackager.safe_system("tar -cvzf #{tarfile} #{@package_name_and_version}") end end rescue => e puts "Could not create tarball - '#{tarfile}'" raise e end tarfile end # Move rpm's and srpm's to cwd def move_packages begin files_to_copy = [] files_to_copy += Dir.glob(File.join(@rpmdir, 'noarch', "#{@package_name}-*-#{@plugin.metadata[:version]}-#{@plugin.revision}*.noarch.rpm")) files_to_copy += Dir.glob(File.join(@srpmdir, "#{@package_name}-#{@plugin.metadata[:version]}-#{@plugin.revision}*.src.rpm")) FileUtils.cp(files_to_copy, '.') rescue => e puts 'Could not copy packages to working directory' raise e end end # Create the specfile and place as $tmpbuildroot/$mcollective-$agent-$version/$mcollective-$agent-$version.spec def make_spec_file spec_file = File.join(@tmpdir, @package_name_and_version, "#{@package_name_and_version}.spec") begin spec_template = ERB.new(File.read(File.join(File.dirname(__FILE__), 'templates', 'redhat', 'rpm_spec.erb')), nil, '-') File.open(spec_file, 'w') do |f| f.puts spec_template.result(binding) end rescue => e puts "Could not create specfile - '#{spec_file}'" raise e end end # Move files contained in the plugin to the correct directory # relative to the build root. def prepare_tmpdirs plugin_files.each do |file| begin targetdir = File.join(@tmpdir, @package_name_and_version, @libdir, File.dirname(File.expand_path(file)).gsub(@plugin.target_path, "")) FileUtils.mkdir_p(targetdir) unless File.directory?(targetdir) FileUtils.cp_r(file, targetdir) rescue Errno::EACCES => e puts "Could not create directory '#{targetdir}'. Permission denied" raise e rescue Errno::ENOENT => e puts "Could not copy file '#{file}' to '#{targetdir}'. File does not exist" raise e rescue => e puts 'Could not prepare temporary build directory' raise e end end end # Extract all the package files from the plugin's package data hash def plugin_files files = [] @plugin.packagedata.each do |name, data| files += data[:files].reject{ |f| File.directory?(f) } end files end # Extract the package specific files from the file list and omits directories def package_files(files) package_files = [] files.each do |f| if !File.directory?(f) package_files << File.join(@libdir, File.expand_path(f).gsub(/#{@plugin.target_path}|\.\//, '')) end end package_files end def cleanup_tmpdirs begin FileUtils.rm_r(@tmpdir) if File.directory?(@tmpdir) rescue => e puts "Could not remove temporary build directory - '#{@tmpdir}'" raise e end end end end end mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/0000755000175000017500000000000012375446416024030 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/pluginpackager/templates/redhat/0000755000175000017500000000000012375446416025277 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/pluginpackager/templates/redhat/rpm_spec.erb0000644000175000017500000000375612375446407027614 0ustar jonasjonasName: <%= @package_name %> Summary: <%= @plugin.metadata[:description] %> Version: <%= @plugin.metadata[:version] %> Release: <%= @plugin.revision %>%{?dist} License: <%= @plugin.metadata[:license]%> URL: <%= @plugin.metadata[:url]%> Vendor: <%= @plugin.vendor%> Packager: <%= @plugin.metadata[:author]%> BuildArch: noarch Group: System Tools BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Source0: <%= @package_name_and_version%>.tgz %description <%= @plugin.metadata[:description] %> %prep %setup %build <% package_files = plugin_files.map{ |f| File.join(@libdir, File.expand_path(f).gsub(/#{File.expand_path(@plugin.path)}|\.\//, '')) } -%> <% dirs = package_files.map{ |f| File.dirname(f) }.uniq -%> %install rm -rf %{buildroot} <% dirs.each do |dir| -%> %{__install} -d -m0755 %{buildroot}<%= dir%> <% end -%> <% package_files.each do |file| -%> %{__install} -m0644 -v <%= (file[0].chr == '/') ? file[1..file.size-1]: file%> %{buildroot}<%=file %> <% end -%> <% @plugin.packagedata.each do |type, data| %> %package <%= type %> Summary: <%= @plugin.metadata[:description] %> <% if data[:plugindependency] %> Requires: <%= data[:plugindependency][:name] -%> = <%= data[:plugindependency][:version]%>-<%= data[:plugindependency][:revision]%>%{?dist} <% end -%> <% PluginPackager.filter_dependencies('redhat', data[:dependencies]).each do |dep|-%> Requires: <%= dep[:name] -%> <%= ">= #{dep[:version]}" if dep[:version]%><%="-#{dep[:revision]}" if dep[:revision]%> <% end -%> %description <%= type %> <%= data[:description] %> %files <%= type %> %defattr(-, root, root, -) <% package_files(data[:files]).each do |file| -%> <%= file %> <% end -%> <% end -%> <% if @plugin.preinstall -%> %pre <%= @plugin.preinstall %> <% end -%> <% if @plugin.postinstall -%> %post <%= @plugin.postinstall%> <% end -%> %changelog * <%= Time.now.strftime("%a %b %d %Y") -%> <%= @plugin.metadata[:author]%> - <%= @plugin.metadata[:version]%>-<%= @plugin.revision %> - Built Package <%= @plugin.metadata[:name] -%> mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/module/0000755000175000017500000000000012375446416025315 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/pluginpackager/templates/module/_manifest.pp.erb0000644000175000017500000000045212375446407030373 0ustar jonasjonas# class <%= @package_name %>::<%= @klass %> { <% if @plugin.packagedata[:common] && @klass != 'common' %> include ::<%= @package_name %>::common <% end %> mcollective::plugin { '<%= @package_name %>/<%= @klass %>': source => 'puppet:///modules/<%= @package_name %>/<% @klass %>', } } mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/module/README.md.erb0000644000175000017500000000222112375446407027340 0ustar jonasjonas# <%= @package_name %> #### Table of Contents 1. [Overview](#overview) 2. [Module Description - What the module does and why it is useful](#module-description) 3. [Setup - The basics of getting started with <%= @package_name %>](#setup) * [What the <%= @package_name %> module affects](#what-the-<%= @package_name %>-module-affects) * [Setup requirements](#setup-requirements) 4. [Usage - Configuration options and additional functionality](#usage) 5. [Reference - An under-the-hood peek at what the module is doing and how](#reference) ## Overview The <%= @package_name %> module is a module that wraps a source release of the <%= @plugin.metadata[:name] %> mcollective plugin for use with the [puppetlabs mcollective](http://forge.puppetlabs.com/puppetlabs/mcollective) module. ## Module description ## Usage <% @plugin.packagedata.keys.map { |x| x.to_s }.sort.each do |klass| -%> <%# Don't document common class -%> <% if klass != "common" -%> ### class <%= @package_name %>::<%= klass %> Installs the <%= klass %> component of the <%= @plugin.metadata[:name] %> plugin. ```puppet include <%= @package_name%>::<%= klass %> ``` <% end %> <% end %> mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/module/Modulefile.erb0000644000175000017500000000035112375446407030073 0ustar jonasjonasname '<%= @plugin.vendor %>-<%= @package_name %>' version '<%= @plugin.metadata[:version] %>' description '<%= @plugin.metadata[:description] %>' project_page '<%= @plugin.metadata[:url]%>' dependency 'puppetlabs/mcollective', '1.x' mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/debian/0000755000175000017500000000000012375446416025252 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/pluginpackager/templates/debian/copyright.erb0000644000175000017500000000032212375446407027751 0ustar jonasjonasThis package was generated by the MCollective plugin packager on <%= Time.now%> Upstream Author: <%= @plugin.metadata[:author] %> <%= @plugin.metadata[:url] %> License: <%= @plugin.metadata[:license] -%> mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/debian/compat.erb0000644000175000017500000000000212375446407027217 0ustar jonasjonas7 mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/debian/control.erb0000644000175000017500000000071412375446407027426 0ustar jonasjonasSource: <%= @package_name %> Homepage: <%= @plugin.metadata[:url] %> Section: utils Priority: extra Build-Depends: debhelper (>= 7), cdbs, dpatch Maintainer: <%= @plugin.metadata[:author] %> Standards-Version: 3.9.1 <% @plugin.packagedata.each do |type, data| %> Package: <%= "#{@package_name}-#{type}" %> Architecture: all Depends: <%= build_dependency_string(data) %> Description: <%= @plugin.metadata[:description]%> <%= data[:description]%> . <% end -%> mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/debian/rules.erb0000644000175000017500000000035012375446407027074 0ustar jonasjonas#!/usr/bin/make -f include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/rules/dpatch.mk include /usr/share/cdbs/1/class/makefile.mk DEB_MAKE_INVOKE = $(DEB_MAKE_ENVVARS) make -f debian/Makefile -C $(DEB_BUILDDIR) mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/debian/Makefile.erb0000644000175000017500000000004312375446407027456 0ustar jonasjonasDESTDIR= build: clean: install: mcollective-2.6.0/plugins/mcollective/pluginpackager/templates/debian/changelog.erb0000644000175000017500000000045112375446407027673 0ustar jonasjonas<%= @package_name -%> (<%= @plugin.metadata[:version]-%>-<%= @plugin.revision-%>) unstable; urgency=low * Automated release for <%= @package_name -%> by mco plugin packager. -- The Marionette Collective <%= Time.new.strftime('%a, %d %b %Y %H:%M:%S %z') %> mcollective-2.6.0/plugins/mcollective/pluginpackager/modulepackage_packager.rb0000644000175000017500000001000312375446407027007 0ustar jonasjonasmodule MCollective module PluginPackager class ModulepackagePackager require 'erb' def initialize(plugin, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = nil, module_template = nil) assert_new_enough_puppet @plugin = plugin @package_name = "#{@plugin.mcname}_#{@plugin.metadata[:name]}".gsub(/-/, '_') @verbose = verbose @keep_artifacts = keep_artifacts @module_template = module_template || File.join(File.dirname(__FILE__), 'templates', 'module') end # Build Process : # - create module directory # - run 'puppet module build' # - move generated package back to cwd def create_packages begin puts "Building module for #{@package_name} plugin." @tmpdir = Dir.mktmpdir('mcollective_packager') make_module run_build move_package puts "Completed building module for #{@package_name} plugin." ensure if @keep_artifacts puts 'Keeping build artifacts' puts "Build artifacts saved - #{@tmpdir}" else cleanup_tmpdirs end end end private def assert_new_enough_puppet unless PluginPackager.command_available?('puppet') raise("Cannot build package. 'puppet' is not present on the system.") end s = Shell.new('puppet --version') s.runcommand actual_version = s.stdout.chomp required_version = '3.3.0' if Util.versioncmp(actual_version, required_version) < 0 raise("Cannot build package. puppet #{required_version} or greater required. We have #{actual_version}.") end end def make_module targetdir = File.join(@tmpdir, 'manifests') FileUtils.mkdir_p(targetdir) unless File.directory?(targetdir) # for each subpackage make a subclass @plugin.packagedata.each do |klass,data| data[:files].each do |file| relative_path = file.sub(/^\.\//, '') targetdir = File.join(@tmpdir, 'files', klass.to_s, 'mcollective', File.dirname(relative_path)) FileUtils.mkdir_p(targetdir) unless File.directory?(targetdir) FileUtils.cp_r(file, targetdir) end @klass = klass.to_s render_template('_manifest.pp.erb', File.join(@tmpdir, 'manifests', "#{klass}.pp")) end # render all the templates we have Dir.glob(File.join(@module_template, '*.erb')).each do |template| filename = File.basename(template, '.erb') next if filename =~ /^_/ # starting with underscore makes it private render_template("#{filename}.erb", File.join(@tmpdir, filename)) end end def render_template(template, path) begin erb = ERB.new(File.read(File.join(@module_template, template)), nil, '-') File.open(path, 'w') do |f| f.puts erb.result(binding) end rescue => e puts "Could not render template to path - '#{path}'" raise e end end def run_build begin PluginPackager.execute_verbosely(@verbose) do Dir.chdir(@tmpdir) do PluginPackager.safe_system('puppet module build') end end rescue => e puts 'Build process has failed' raise e end end # Move built package to cwd def move_package begin package_file = File.join(@tmpdir, 'pkg', "#{@plugin.vendor}-#{@package_name}-#{@plugin.metadata[:version]}.tar.gz") FileUtils.cp(package_file, '.') rescue => e puts 'Could not copy package to working directory' raise e end end def cleanup_tmpdirs begin FileUtils.rm_r(@tmpdir) if File.directory?(@tmpdir) rescue => e puts "Could not remove temporary build directory - '#{@tmpdir}'" raise e end end end end end mcollective-2.6.0/plugins/mcollective/pluginpackager/ospackage_packager.rb0000644000175000017500000000451112375446407026152 0ustar jonasjonasmodule MCollective module PluginPackager # MCollective plugin packager general OS implementation. class OspackagePackager attr_accessor :package, :verbose, :packager, :package_type # Create packager object with package parameter containing list of files, # dependencies and package metadata. def initialize(package, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = false, module_template = nil) if File.exists?("/etc/redhat-release") @packager = PluginPackager["RpmpackagePackager"].new(package, pluginpath, signature, verbose, keep_artifacts, module_template) @package_type = "RPM" elsif File.exists?("/etc/debian_version") @packager = PluginPackager["DebpackagePackager"].new(package, pluginpath, signature, verbose, keep_artifacts, module_template) @package_type = "Deb" else raise "cannot identify operating system." end @package = package @verbose = verbose end # Hands over package creation to the detected packager implementation # based on operating system. def create_packages @packager.create_packages end # Displays the package metadata and detected files def package_information puts puts "%30s%s" % ["Plugin information : ", @package.metadata[:name]] puts "%30s%s" % ["-" * 22, "-" * 22] puts "%30s%s" % ["Plugin Type : ", @package.plugintype.capitalize] puts "%30s%s" % ["Package Output Format : ", @package_type] puts "%30s%s" % ["Version : ", @package.metadata[:version]] puts "%30s%s" % ["Revision : ", @package.revision] puts "%30s%s" % ["Vendor : ", @package.vendor] puts "%30s%s" % ["Post Install Script : ", @package.postinstall] if @package.postinstall puts "%30s%s" % ["Author : ", @package.metadata[:author]] puts "%30s%s" % ["License : ", @package.metadata[:license]] puts "%30s%s" % ["URL : ", @package.metadata[:url]] if @package.packagedata.size > 0 @package.packagedata.each_with_index do |values, i| if i == 0 puts "%30s%s" % ["Identified Packages : ", values[0]] else puts "%30s%s" % [" ", values[0]] end end end end end end end mcollective-2.6.0/plugins/mcollective/pluginpackager/debpackage_packager.rb0000644000175000017500000002004312375446407026261 0ustar jonasjonasmodule MCollective module PluginPackager class DebpackagePackager require 'erb' def initialize(plugin, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = nil, module_template = nil) if PluginPackager.command_available?('debuild') @plugin = plugin @verbose = verbose @libdir = pluginpath || '/usr/share/mcollective/plugins/mcollective/' @signature = signature @package_name = "#{@plugin.mcname}-#{@plugin.metadata[:name]}" @keep_artifacts = keep_artifacts else raise("Cannot build package. 'debuild' is not present on the system.") end end # Build process : # - create buildroot # - craete buildroot/debian # - create the relative directories with package contents # - create install files for each of the plugins that are going to be built # - create debian build files # - create tarball # - create pre and post install files # - run the build script # - move packages to cwd # - clean up def create_packages begin puts "Building packages for #{@package_name} plugin." @tmpdir = Dir.mktmpdir('mcollective_packager') @build_dir = File.join(@tmpdir, "#{@package_name}_#{@plugin.metadata[:version]}") Dir.mkdir(@build_dir) create_debian_dir @plugin.packagedata.each do |type, data| prepare_tmpdirs(data) create_install_file(type, data) create_pre_and_post_install(type) end create_debian_files create_tar run_build move_packages puts "Completed building all packages for #{@package_name} plugin." ensure if @keep_artifacts puts 'Keeping build artifacts.' puts "Build artifacts saved - #{@tmpdir}" else puts 'Removing build artifacts.' cleanup_tmpdirs end end end private def create_debian_files ['control', 'Makefile', 'compat', 'rules', 'copyright', 'changelog'].each do |f| create_file(f) end end def run_build FileUtils.cd(@build_dir) do PluginPackager.execute_verbosely(@verbose) do if @signature if @signature.is_a?(String) PluginPackager.safe_system("debuild --no-lintian -i -k#{@signature}") else PluginPackager.safe_system("debuild --no-lintian -i") end else PluginPackager.safe_system("debuild --no-lintian -i -us -uc") end end end end # Creates a string used by the control file to specify dependencies # Dependencies can be formatted as : # foo (>= x.x-x) # foo (>= x.x) # foo def build_dependency_string(data) dependencies = [] PluginPackager.filter_dependencies('debian', data[:dependencies]).each do |dep| if dep[:version] && dep[:revision] dependencies << "#{dep[:name]} (>=#{dep[:version]}-#{dep[:revision]})" elsif dep[:version] dependencies << "#{dep[:name]} (>=#{dep[:version]})" else dependencies << dep[:name] end end if data[:plugindependency] dependencies << "#{data[:plugindependency][:name]} (= ${binary:Version})" end dependencies.join(', ') end # Creates an install file for each of the packages that are going to be created # for the plugin def create_install_file(type, data) install_file = "#{@package_name}-#{type}" begin install_file = File.join(@build_dir, 'debian', "#{install_file}.install") File.open(install_file, 'w') do |file| data[:files].each do |f| extended_filename = File.join(@libdir, File.expand_path(f).gsub(/^#{@plugin.target_path}/, '')) file.puts "#{extended_filename} #{File.dirname(extended_filename)}" end end rescue Errno::EACCES => e puts "Could not create install file '#{install_file}'. Permission denied" raise e rescue => e puts "Could not create install file '#{install_file}'." raise e end end # Move source package and debs to cwd def move_packages begin files_to_copy = Dir.glob(File.join(@tmpdir, '*.{deb,dsc,diff.gz,orig.tar.gz,changes}')) FileUtils.cp(files_to_copy, '.') rescue => e puts 'Could not copy packages to working directory.' raise e end end # Create pre and post install files in $buildroot/debian # from supplied scripts. # Note that all packages built for the plugin will invoke # the same pre and post install scripts. def create_pre_and_post_install(type) if @plugin.preinstall if !File.exists?(@plugin.preinstall) puts "pre-install script '#{@plugin.preinstall}' not found." raise(Errno::ENOENT, @plugin.preinstall) else FileUtils.cp(@plugin.preinstall, File.join(@build_dir, 'debian', "#{@package_name}-#{type}.preinst")) end end if @plugin.postinstall if !File.exists?(@plugin.postinstall) puts "post-install script '#{@plugin.postinstall}' not found." raise(Errno::ENOENT, @plugin.postinstall) else FileUtils.cp(@plugin.postinstall, File.join(@build_dir, 'debian', "#{@package_name}-#{type}.postinst")) end end end # Tar up source # Expects directory : $mcollective-$agent_$version # Creates file : $buildroot/$mcollective-$agent_$version.orig.tar.gz def create_tar name_and_version = "#{@package_name}_#{@plugin.metadata[:version]}" tarfile = "#{name_and_version}.orig.tar.gz" begin PluginPackager.execute_verbosely(@verbose) do Dir.chdir(@tmpdir) do PluginPackager.safe_system("tar -Pcvzf #{File.join(@tmpdir, tarfile)} #{name_and_version}") end end rescue Exception => e puts "Could not create tarball - #{tarfile}" raise e end end def create_file(filename) begin file = ERB.new(File.read(File.join(File.dirname(__FILE__), 'templates', 'debian', "#{filename}.erb")), nil, '-') File.open(File.join(@build_dir, 'debian', filename), 'w') do |f| f.puts file.result(binding) end rescue => e puts "Could not create file - '#{filename}'" raise e end end # Move files contained in the plugin to the correct directory # relative to the build root. def prepare_tmpdirs(data) data[:files].each do |file| begin targetdir = File.join(@build_dir, @libdir, File.dirname(File.expand_path(file)).gsub(/^#{@plugin.target_path}/, "")) FileUtils.mkdir_p(targetdir) unless File.directory?(targetdir) FileUtils.cp_r(file, targetdir) rescue Errno::EACCES => e puts "Could not create directory '#{targetdir}'. Permission denied" raise e rescue Errno::ENOENT => e puts "Could not copy file '#{file}' to '#{targetdir}'. File does not exist" raise e rescue => e puts 'Could not prepare build directory' raise e end end end # Create the $buildroot/debian directory def create_debian_dir deb_dir = File.join(@build_dir, 'debian') begin FileUtils.mkdir_p(deb_dir) rescue => e puts "Could not create directory '#{deb_dir}'" raise e end end def cleanup_tmpdirs begin FileUtils.rm_r(@tmpdir) if File.directory?(@tmpdir) rescue => e puts "Could not remove temporary build directory - '#{@tmpdir}'" raise e end end end end end mcollective-2.6.0/plugins/mcollective/agent/0000755000175000017500000000000012375446416020134 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/agent/rpcutil.ddl0000644000175000017500000001457112375446407022313 0ustar jonasjonasmetadata :name => "rpcutil", :description => "General helpful actions that expose stats and internals to SimpleRPC clients", :author => "R.I.Pienaar ", :license => "Apache License, Version 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 10 action "collective_info", :description => "Info about the main and sub collectives" do display :always output :main_collective, :description => "The main Collective", :display_as => "Main Collective" output :collectives, :description => "All Collectives", :display_as => "All Collectives" summarize do aggregate summary(:collectives) end end action "inventory", :description => "System Inventory" do display :always output :agents, :description => "List of agent names", :display_as => "Agents" output :facts, :description => "List of facts and values", :display_as => "Facts" output :classes, :description => "List of classes on the system", :display_as => "Classes" output :version, :description => "MCollective Version", :display_as => "Version" output :main_collective, :description => "The main Collective", :display_as => "Main Collective" output :collectives, :description => "All Collectives", :display_as => "All Collectives" output :data_plugins, :description => "List of data plugin names", :display_as => "Data Plugins" end action "get_fact", :description => "Retrieve a single fact from the fact store" do display :always input :fact, :prompt => "The name of the fact", :description => "The fact to retrieve", :type => :string, :validation => '^[\w\-\.]+$', :optional => false, :maxlength => 40 output :fact, :description => "The name of the fact being returned", :display_as => "Fact" output :value, :description => "The value of the fact", :display_as => "Value" summarize do aggregate summary(:value) end end action "get_facts", :description => "Retrieve multiple facts from the fact store" do display :always input :facts, :prompt => "Comma-separated list of facts", :description => "Facts to retrieve", :type => :string, :validation => '^\s*[\w\.\-]+(\s*,\s*[\w\.\-]+)*$', :optional => false, :maxlength => 200 output :values, :description => "List of values of the facts", :display_as => "Values" end action "daemon_stats", :description => "Get statistics from the running daemon" do display :always output :threads, :description => "List of threads active in the daemon", :display_as => "Threads" output :agents, :description => "List of agents loaded", :display_as => "Agents" output :pid, :description => "Process ID of the daemon", :display_as => "PID" output :times, :description => "Processor time consumed by the daemon", :display_as => "Times" output :validated, :description => "Messages that passed security validation", :display_as => "Security Validated" output :unvalidated, :description => "Messages that failed security validation", :display_as => "Failed Security" output :passed, :description => "Passed filter checks", :display_as => "Passed Filter" output :filtered, :description => "Didn't pass filter checks", :display_as => "Failed Filter" output :starttime, :description => "Time the server started", :display_as => "Start Time" output :total, :description => "Total messages received", :display_as => "Total Messages" output :replies, :description => "Replies sent back to clients", :display_as => "Replies" output :configfile, :description => "Config file used to start the daemon", :display_as => "Config File" output :version, :description => "MCollective Version", :display_as => "Version" output :ttlexpired, :description => "Messages that did pass TTL checks", :display_as => "TTL Expired" summarize do aggregate summary(:version) aggregate summary(:agents) end end action "agent_inventory", :description => "Inventory of all agents on the server" do display :always output :agents, :description => "List of agents on the server", :display_as => "Agents" end action "get_config_item", :description => "Get the active value of a specific config property" do display :always input :item, :prompt => "Configuration Item", :description => "The item to retrieve from the server", :type => :string, :validation => '^.+$', :optional => false, :maxlength => 50 output :item, :description => "The config property being retrieved", :display_as => "Property" output :value, :description => "The value that is in use", :display_as => "Value" summarize do aggregate summary(:value) end end action "get_data", :description => "Get data from a data plugin" do display :always input :source, :prompt => "Data Source", :description => "The data plugin to retrieve information from", :type => :string, :validation => '^\w+$', :optional => false, :maxlength => 50 input :query, :prompt => "Query", :description => "The query argument to supply to the data plugin", :type => :string, :validation => '^.+$', :optional => true, :maxlength => 200 end action "ping", :description => "Responds to requests for PING with PONG" do display :always output :pong, :description => "The local timestamp", :display_as => "Timestamp" end mcollective-2.6.0/plugins/mcollective/agent/discovery.rb0000644000175000017500000000165412375446407022476 0ustar jonasjonasmodule MCollective module Agent # Discovery agent for The Marionette Collective # # Released under the Apache License, Version 2 class Discovery attr_reader :timeout, :meta def initialize config = Config.instance.pluginconf @timeout = 5 @meta = {:license => "Apache License, Version 2", :author => "R.I.Pienaar ", :timeout => @timeout, :name => "Discovery Agent", :version => MCollective.version, :url => "http://www.marionette-collective.org", :description => "MCollective Discovery Agent"} end def handlemsg(msg, stomp) reply = "unknown request" case msg[:body] when "ping" reply = "pong" else reply = "Unknown Request: #{msg[:body]}" end reply end end end end mcollective-2.6.0/plugins/mcollective/agent/rpcutil.rb0000644000175000017500000000644112375446407022150 0ustar jonasjonasmodule MCollective module Agent class Rpcutil target_agent, :license => "unknown", :timeout => agent.timeout, :description => "unknown", :name => target_agent, :url => "unknown", :version => "unknown", :author => "unknown"} agent_data.merge!(agent.meta) reply[:agents] << agent_data end end # Retrieves a single config property that is in effect action "get_config_item" do reply.fail! "Unknown config property #{request[:item]}" unless config.respond_to?(request[:item]) reply[:item] = request[:item] reply[:value] = config.send(request[:item]) end # Responds to PING requests with the local timestamp action "ping" do reply[:pong] = Time.now.to_i end # Returns all configured collectives action "collective_info" do config = Config.instance reply[:main_collective] = config.main_collective reply[:collectives] = config.collectives end action "get_data" do if request[:query] query = Data.ddl_transform_input(Data.ddl(request[:source]), request[:query].to_s) else query = nil end data = Data[ request[:source] ].lookup(query) data.keys.each do |key| reply[key] = data[key] end end end end end mcollective-2.6.0/plugins/mcollective/registration/0000755000175000017500000000000012375446416021550 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/registration/agentlist.rb0000644000175000017500000000032212375446407024064 0ustar jonasjonasmodule MCollective module Registration # A registration plugin that simply sends in the list of agents we have class Agentlist "IPv6 Address", :description => "Validates that a value is an ipv6 address", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 mcollective-2.6.0/plugins/mcollective/validator/typecheck_validator.rb0000644000175000017500000000143212375446407025374 0ustar jonasjonasmodule MCollective module Validator class TypecheckValidator def self.validate(validator, validation_type) raise ValidatorError, "value should be a #{validation_type.to_s}" unless check_type(validator, validation_type) end def self.check_type(validator, validation_type) case validation_type when Class validator.is_a?(validation_type) when :integer validator.is_a?(Fixnum) when :float validator.is_a?(Float) when :number validator.is_a?(Numeric) when :string validator.is_a?(String) when :boolean [TrueClass, FalseClass].include?(validator.class) else false end end end end end mcollective-2.6.0/plugins/mcollective/validator/regex_validator.ddl0000644000175000017500000000056512375446407024675 0ustar jonasjonasmetadata :name => "Regex", :description => "Validates that a string matches a supplied regular expression", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 mcollective-2.6.0/plugins/mcollective/validator/ipv6address_validator.rb0000644000175000017500000000057712375446407025660 0ustar jonasjonasmodule MCollective module Validator class Ipv6addressValidator require 'ipaddr' def self.validate(validator) begin ip = IPAddr.new(validator) raise ValidatorError, "value should be an ipv6 adddress" unless ip.ipv6? rescue raise ValidatorError, "value should be an ipv6 address" end end end end end mcollective-2.6.0/plugins/mcollective/validator/typecheck_validator.ddl0000644000175000017500000000054712375446407025542 0ustar jonasjonasmetadata :name => "Typecheck", :description => "Validates that a value is of a certain type", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 mcollective-2.6.0/plugins/mcollective/validator/ipv4address_validator.rb0000644000175000017500000000057712375446407025656 0ustar jonasjonasmodule MCollective module Validator class Ipv4addressValidator require 'ipaddr' def self.validate(validator) begin ip = IPAddr.new(validator) raise ValidatorError, "value should be an ipv4 adddress" unless ip.ipv4? rescue raise ValidatorError, "value should be an ipv4 address" end end end end end mcollective-2.6.0/plugins/mcollective/validator/shellsafe_validator.rb0000644000175000017500000000061612375446407025366 0ustar jonasjonasmodule MCollective module Validator class ShellsafeValidator def self.validate(validator) raise ValidatorError, "value should be a String" unless validator.is_a?(String) ['`', '$', ';', '|', '&&', '>', '<'].each do |chr| raise ValidatorError, "value should not have #{chr} in it" if validator.match(Regexp.escape(chr)) end end end end end mcollective-2.6.0/plugins/mcollective/validator/shellsafe_validator.ddl0000644000175000017500000000054012375446407025522 0ustar jonasjonasmetadata :name => "Shellsafe", :description => "Validates that a string is shellsafe", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 mcollective-2.6.0/plugins/mcollective/validator/length_validator.rb0000644000175000017500000000043512375446407024700 0ustar jonasjonasmodule MCollective module Validator class LengthValidator def self.validate(validator, length) if (validator.size > length) && (length > 0) raise ValidatorError, "Input string is longer than #{length} character(s)" end end end end end mcollective-2.6.0/plugins/mcollective/validator/ipv4address_validator.ddl0000644000175000017500000000055012375446407026005 0ustar jonasjonasmetadata :name => "IPv4 Address", :description => "Validates that a value is an ipv4 address", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 mcollective-2.6.0/plugins/mcollective/validator/length_validator.ddl0000644000175000017500000000060412375446407025036 0ustar jonasjonasmetadata :name => "Length", :description => "Validates that the length of a string is less or equal to a specified value", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 mcollective-2.6.0/plugins/mcollective/validator/regex_validator.rb0000644000175000017500000000033712375446407024532 0ustar jonasjonasmodule MCollective module Validator class RegexValidator def self.validate(validator, regex) raise ValidatorError, "value should match #{regex}" unless validator.match(regex) end end end end mcollective-2.6.0/plugins/mcollective/validator/array_validator.ddl0000644000175000017500000000054412375446407024676 0ustar jonasjonasmetadata :name => "Array", :description => "Validates that a value is included in a list", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 mcollective-2.6.0/plugins/mcollective/validator/array_validator.rb0000644000175000017500000000036712375446407024541 0ustar jonasjonasmodule MCollective module Validator class ArrayValidator def self.validate(validator, array) raise ValidatorError, "value should be one of %s" % [ array.join(", ") ] unless array.include?(validator) end end end end mcollective-2.6.0/plugins/mcollective/security/0000755000175000017500000000000012375446416020705 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/security/aes_security.rb0000644000175000017500000003717412375446407023745 0ustar jonasjonasmodule MCollective module Security # Impliments a security system that encrypts payloads using AES and secures # the AES encrypted data using RSA public/private key encryption. # # The design goals of this plugin are: # # - Each actor - clients and servers - can have their own set of public and # private keys # - All actors are uniquely and cryptographically identified # - Requests are encrypted using the clients private key and anyone that has # the public key can see the request. Thus an atacker may see the requests # given access to network or machine due to the broadcast nature of mcollective # - The message time and TTL of messages are cryptographically secured making the # ensuring messages can not be replayed with fake TTLs or times # - Replies are encrypted using the calling clients public key. Thus no-one but # the caller can view the contents of replies. # - Servers can all have their own RSA keys, or share one, or reuse keys created # by other PKI using software like Puppet # - Requests from servers - like registration data - can be secured even to external # eaves droppers depending on the level of configuration you are prepared to do # - Given a network where you can ensure third parties are not able to access the # middleware public key distribution can happen automatically # # Configuration Options: # ====================== # # Common Options: # # # Enable this plugin # securityprovider = aes_security # # # Use YAML as serializer # plugin.aes.serializer = yaml # # # Send our public key with every request so servers can learn it # plugin.aes.send_pubkey = 1 # # Clients: # # # The clients public and private keys # plugin.aes.client_private = /home/user/.mcollective.d/user-private.pem # plugin.aes.client_public = /home/user/.mcollective.d/user.pem # # Servers: # # # Where to cache client keys or find manually distributed ones # plugin.aes.client_cert_dir = /etc/mcollective/ssl/clients # # # Cache public keys promiscuously from the network (this requires either a ca_cert to be set # or insecure_learning to be enabled) # plugin.aes.learn_pubkeys = 1 # # # Do not check if client certificate can be verified by a CA # plugin.aes.insecure_learning = 1 # # # CA cert used to verify public keys when in learning mode # plugin.aes.ca_cert = /etc/mcollective/ssl/ca.cert # # # Log but accept messages that may have been tampered with # plugin.aes.enforce_ttl = 0 # # # The servers public and private keys # plugin.aes.server_private = /etc/mcollective/ssl/server-private.pem # plugin.aes.server_public = /etc/mcollective/ssl/server-public.pem # class Aes_security body[:sslkey], :data => body[:body]} if @initiated_by == :client body[:body] = deserialize(decrypt(cryptdata, nil)) else certname = certname_from_callerid(body[:callerid]) certfile = "#{client_cert_dir}/#{certname}.pem" # if aes.ca_cert is set every certificate is validated before we try and use it if @config.pluginconf.fetch("aes.ca_cert", nil) && !validate_certificate(File.read(certfile), certname) raise "Unable to validate certificate '#{certname}' against CA" end body[:body] = deserialize(decrypt(cryptdata, body[:callerid])) # If we got a hash it's possible that this is a message with secure # TTL and message time, attempt to decode that and transform into a # traditional message. # # If it's not a hash it might be a old style message like old discovery # ones that would just be a string so we allow that unaudited but only # if enforce_ttl is disabled. This is primarly to allow a mixed old and # new plugin infrastructure to work if body[:body].is_a?(Hash) update_secure_property(body, :aes_ttl, :ttl, "TTL") update_secure_property(body, :aes_msgtime, :msgtime, "Message Time") body[:body] = body[:body][:aes_msg] if body[:body].include?(:aes_msg) else unless @config.pluginconf["aes.enforce_ttl"] == "0" raise "Message %s is in an unknown or older security protocol, ignoring" % [request_description(body)] end end end return body rescue MsgDoesNotMatchRequestID raise rescue OpenSSL::PKey::RSAError raise MsgDoesNotMatchRequestID, "Could not decrypt message using our key, possibly directed at another client" rescue Exception => e Log.warn("Could not decrypt message from client: #{e.class}: #{e}") raise SecurityValidationFailed, "Could not decrypt message" end # To avoid tampering we turn the origin body into a hash and copy some of the protocol keys # like :ttl and :msg_time into the hash before encrypting it. # # This function compares and updates the unencrypted ones based on the encrypted ones. By # default it enforces matching and presense by raising exceptions, if aes.enforce_ttl is set # to 0 it will only log warnings about violations def update_secure_property(msg, secure_property, property, description) req = request_description(msg) unless @config.pluginconf["aes.enforce_ttl"] == "0" raise "Request #{req} does not have a secure #{description}" unless msg[:body].include?(secure_property) raise "Request #{req} #{description} does not match encrypted #{description} - possible tampering" unless msg[:body][secure_property] == msg[property] else if msg[:body].include?(secure_property) Log.warn("Request #{req} #{description} does not match encrypted #{description} - possible tampering") unless msg[:body][secure_property] == msg[property] else Log.warn("Request #{req} does not have a secure #{description}") unless msg[:body].include?(secure_property) end end msg[property] = msg[:body][secure_property] if msg[:body].include?(secure_property) msg[:body].delete(secure_property) end # Encodes a reply def encodereply(sender, msg, requestid, requestcallerid) crypted = encrypt(serialize(msg), requestcallerid) req = create_reply(requestid, sender, crypted[:data]) req[:sslkey] = crypted[:key] serialize(req) end # Encodes a request msg def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) req = create_request(requestid, filter, nil, @initiated_by, target_agent, target_collective, ttl) # embed the ttl and msgtime in the crypted data later we will use these in # the decoding of a message to set the message ones from secure sources. this # is to ensure messages are not tampered with to facility replay attacks etc aes_msg = {:aes_msg => msg, :aes_ttl => ttl, :aes_msgtime => req[:msgtime]} crypted = encrypt(serialize(aes_msg), callerid) req[:body] = crypted[:data] req[:sslkey] = crypted[:key] if @config.pluginconf.include?("aes.send_pubkey") && @config.pluginconf["aes.send_pubkey"] == "1" if @initiated_by == :client req[:sslpubkey] = File.read(client_public_key) else req[:sslpubkey] = File.read(server_public_key) end end serialize(req) end # Serializes a message using the configured encoder def serialize(msg) serializer = @config.pluginconf["aes.serializer"] || "marshal" Log.debug("Serializing using #{serializer}") case serializer when "yaml" return YAML.dump(msg) else return Marshal.dump(msg) end end # De-Serializes a message using the configured encoder def deserialize(msg) serializer = @config.pluginconf["aes.serializer"] || "marshal" Log.debug("De-Serializing using #{serializer}") case serializer when "yaml" return YAML.load(msg) else return Marshal.load(msg) end end # sets the caller id to the md5 of the public key def callerid if @initiated_by == :client key = client_public_key else key = server_public_key end # First try and create a X509 certificate object. If that is possible, # we lift the callerid from the cert begin ssl_cert = OpenSSL::X509::Certificate.new(File.read(key)) id = "cert=#{certname_from_certificate(ssl_cert)}" rescue # If the public key is not a certificate, use the file name as callerid id = "cert=#{File.basename(key).gsub(/\.pem$/, '')}" end return id end def encrypt(string, certid) if @initiated_by == :client @ssl ||= SSL.new(client_public_key, client_private_key) Log.debug("Encrypting message using private key") return @ssl.encrypt_with_private(string) else # when the server is initating requests like for registration # then the certid will be our callerid if certid == callerid Log.debug("Encrypting message using private key #{server_private_key}") ssl = SSL.new(server_public_key, server_private_key) return ssl.encrypt_with_private(string) else Log.debug("Encrypting message using public key for #{certid}") ssl = SSL.new(public_key_path_for_client(certid)) return ssl.encrypt_with_public(string) end end end def decrypt(string, certid) if @initiated_by == :client @ssl ||= SSL.new(client_public_key, client_private_key) Log.debug("Decrypting message using private key") return @ssl.decrypt_with_private(string) else Log.debug("Decrypting message using public key for #{certid}") ssl = SSL.new(public_key_path_for_client(certid)) return ssl.decrypt_with_public(string) end end def validate_certificate(client_cert, certid) cert_file = @config.pluginconf.fetch("aes.ca_cert", nil) begin ssl_cert = OpenSSL::X509::Certificate.new(client_cert) rescue OpenSSL::X509::CertificateError Log.warn("Received public key that is not a X509 certficate") return false end ssl_certname = certname_from_certificate(ssl_cert) if certid != ssl_certname Log.warn("certname '#{certid}' doesn't match certificate '#{ssl_certname}'") return false end Log.debug("Loading CA Cert for verification") ca_cert = OpenSSL::X509::Store.new ca_cert.add_file cert_file if ca_cert.verify(ssl_cert) Log.debug("Verified certificate '#{ssl_certname}' against CA") else # TODO add cert id Log.warn("Unable to validate certificate '#{ssl_certname}'' against CA") return false end return true end # On servers this will look in the aes.client_cert_dir for public # keys matching the clientid, clientid is expected to be in the format # set by callerid def public_key_path_for_client(clientid) raise "Unknown callerid format in '#{clientid}'" unless clientid.match(/^cert=(.+)$/) clientid = $1 client_cert_dir + "/#{clientid}.pem" end # Figures out the client private key either from MCOLLECTIVE_AES_PRIVATE or the # plugin.aes.client_private config option def client_private_key return ENV["MCOLLECTIVE_AES_PRIVATE"] if ENV.include?("MCOLLECTIVE_AES_PRIVATE") raise("No plugin.aes.client_private configuration option specified") unless @config.pluginconf.include?("aes.client_private") return @config.pluginconf["aes.client_private"] end # Figures out the client public key either from MCOLLECTIVE_AES_PUBLIC or the # plugin.aes.client_public config option def client_public_key return ENV["MCOLLECTIVE_AES_PUBLIC"] if ENV.include?("MCOLLECTIVE_AES_PUBLIC") raise("No plugin.aes.client_public configuration option specified") unless @config.pluginconf.include?("aes.client_public") return @config.pluginconf["aes.client_public"] end # Figures out the server public key from the plugin.aes.server_public config option def server_public_key raise("No aes.server_public configuration option specified") unless @config.pluginconf.include?("aes.server_public") return @config.pluginconf["aes.server_public"] end # Figures out the server private key from the plugin.aes.server_private config option def server_private_key raise("No plugin.aes.server_private configuration option specified") unless @config.pluginconf.include?("aes.server_private") @config.pluginconf["aes.server_private"] end # Figures out where to get client public certs from the plugin.aes.client_cert_dir config option def client_cert_dir raise("No plugin.aes.client_cert_dir configuration option specified") unless @config.pluginconf.include?("aes.client_cert_dir") @config.pluginconf["aes.client_cert_dir"] end def request_description(msg) "%s from %s@%s" % [msg[:requestid], msg[:callerid], msg[:senderid]] end # Takes our cert=foo callerids and return the foo bit else nil def certname_from_callerid(id) if id =~ /^cert=([\w\.\-]+)/ return $1 else raise("Received a callerid in an unexpected format: '#{id}', ignoring") end end def certname_from_certificate(cert) id = cert.subject if id.to_s =~ /^\/CN=([\w\.\-]+)/ return $1 else raise("Received a callerid in an unexpected format in an SSL certificate: '#{id}', ignoring") end end end end end mcollective-2.6.0/plugins/mcollective/security/ssl.rb0000644000175000017500000003033012375446407022032 0ustar jonasjonasrequire 'base64' require 'openssl' module MCollective module Security # Impliments a public/private key based message validation system using SSL # public and private keys. # # The design goal of the plugin is two fold: # # - give different security credentials to clients and servers to avoid # a compromised server from sending new client requests. # - create a token that uniquely identify the client - based on the filename # of the public key # # To setup you need to create a SSL key pair that is shared by all nodes. # # openssl genrsa -out mcserver-private.pem 1024 # openssl rsa -in mcserver-private.pem -out mcserver-public.pem -outform PEM -pubout # # Distribute the private and public file to /etc/mcollective/ssl on all the nodes. # Distribute the public file to /etc/mcollective/ssl everywhere the client code runs. # # Now you should create a key pair for every one of your clients, here we create one # for user john - you could also if you are less concerned with client id create one # pair and share it with all clients: # # openssl genrsa -out john-private.pem 1024 # openssl rsa -in john-private.pem -out john-public.pem -outform PEM -pubout # # Each user has a unique userid, this is based on the name of the public key. # In this example case the userid would be 'john-public'. # # Store these somewhere like: # # /home/john/.mc/john-private.pem # /home/john/.mc/john-public.pem # # Every users public key needs to be distributed to all the nodes, save the john one # in a file called: # # /etc/mcollective/ssl/clients/john-public.pem # # If you wish to use registration or auditing that sends connections over MC to a # central host you will need also put the server-public.pem in the clients directory. # # You should be aware if you do add the node public key to the clients dir you will in # effect be weakening your overall security. You should consider doing this only if # you also set up an Authorization method that limits the requests the nodes can make. # # client.cfg: # # securityprovider = ssl # plugin.ssl_server_public = /etc/mcollective/ssl/server-public.pem # plugin.ssl_client_private = /home/john/.mc/john-private.pem # plugin.ssl_client_public = /home/john/.mc/john-public.pem # # If you have many clients per machine and dont want to configure the main config file # with the public/private keys you can set the following environment variables: # # export MCOLLECTIVE_SSL_PRIVATE=/home/john/.mc/john-private.pem # export MCOLLECTIVE_SSL_PUBLIC=/home/john/.mc/john-public.pem # # server.cfg: # # securityprovider = ssl # plugin.ssl_server_private = /etc/mcollective/ssl/server-private.pem # plugin.ssl_server_public = /etc/mcollective/ssl/server-public.pem # plugin.ssl_client_cert_dir = /etc/mcollective/etc/ssl/clients/ # # # Log but accept messages that may have been tampered with # plugin.ssl.enforce_ttl = 0 # # Serialization can be configured to use either Marshal or YAML, data types # in and out of mcollective will be preserved from client to server and reverse # # You can configure YAML serialization: # # plugins.ssl_serializer = yaml # # else the default is Marshal. Use YAML if you wish to write a client using # a language other than Ruby that doesn't support Marshal. # # Validation is as default and is provided by MCollective::Security::Base # # Initial code was contributed by Vladimir Vuksan and modified by R.I.Pienaar class Ssl < Base # Decodes a message by unserializing all the bits etc, it also validates # it as valid using the psk etc def decodemsg(msg) body = deserialize(msg.payload) should_process_msg?(msg, body[:requestid]) if validrequest?(body) body[:body] = deserialize(body[:body]) unless @initiated_by == :client if body[:body].is_a?(Hash) update_secure_property(body, :ssl_ttl, :ttl, "TTL") update_secure_property(body, :ssl_msgtime, :msgtime, "Message Time") body[:body] = body[:body][:ssl_msg] if body[:body].include?(:ssl_msg) else unless @config.pluginconf["ssl.enforce_ttl"] == nil raise "Message %s is in an unknown or older security protocol, ignoring" % [request_description(body)] end end end return body else nil end end # To avoid tampering we turn the origin body into a hash and copy some of the protocol keys # like :ttl and :msg_time into the hash before hashing it. # # This function compares and updates the unhashed ones based on the hashed ones. By # default it enforces matching and presense by raising exceptions, if ssl.enforce_ttl is set # to 0 it will only log warnings about violations def update_secure_property(msg, secure_property, property, description) req = request_description(msg) unless @config.pluginconf["ssl.enforce_ttl"] == "0" raise "Request #{req} does not have a secure #{description}" unless msg[:body].include?(secure_property) raise "Request #{req} #{description} does not match encrypted #{description} - possible tampering" unless msg[:body][secure_property] == msg[property] else if msg[:body].include?(secure_property) Log.warn("Request #{req} #{description} does not match encrypted #{description} - possible tampering") unless msg[:body][secure_property] == msg[property] else Log.warn("Request #{req} does not have a secure #{description}") unless msg[:body].include?(secure_property) end end msg[property] = msg[:body][secure_property] if msg[:body].include?(secure_property) msg[:body].delete(secure_property) end # Encodes a reply def encodereply(sender, msg, requestid, requestcallerid=nil) serialized = serialize(msg) digest = makehash(serialized) req = create_reply(requestid, sender, serialized) req[:hash] = digest serialize(req) end # Encodes a request msg def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) req = create_request(requestid, filter, "", @initiated_by, target_agent, target_collective, ttl) ssl_msg = {:ssl_msg => msg, :ssl_ttl => ttl, :ssl_msgtime => req[:msgtime]} serialized = serialize(ssl_msg) digest = makehash(serialized) req[:hash] = digest req[:body] = serialized serialize(req) end # Checks the SSL signature in the request body def validrequest?(req) message = req[:body] signature = req[:hash] Log.debug("Validating request from #{req[:callerid]}") if verify(public_key_file(req[:callerid]), signature, message.to_s) @stats.validated return true else @stats.unvalidated raise(SecurityValidationFailed, "Received an invalid signature in message") end end # sets the caller id to the md5 of the public key def callerid if @initiated_by == :client id = "cert=#{File.basename(client_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from client public key" unless valid_callerid?(id) else # servers need to set callerid as well, not usually needed but # would be if you're doing registration or auditing or generating # requests for some or other reason id = "cert=#{File.basename(server_public_key).gsub(/\.pem$/, '')}" raise "Invalid callerid generated from server public key" unless valid_callerid?(id) end return id end private # Serializes a message using the configured encoder def serialize(msg) serializer = @config.pluginconf["ssl_serializer"] || "marshal" Log.debug("Serializing using #{serializer}") case serializer when "yaml" return YAML.dump(msg) else return Marshal.dump(msg) end end # De-Serializes a message using the configured encoder def deserialize(msg) serializer = @config.pluginconf["ssl_serializer"] || "marshal" Log.debug("De-Serializing using #{serializer}") case serializer when "yaml" return YAML.load(msg) else return Marshal.load(msg) end end # Figures out where to get our private key def private_key_file if ENV.include?("MCOLLECTIVE_SSL_PRIVATE") return ENV["MCOLLECTIVE_SSL_PRIVATE"] else if @initiated_by == :node return server_private_key else return client_private_key end end end # Figures out the public key to use # # If the node is asking do it based on caller id # If the client is asking just get the node public key def public_key_file(callerid = nil) if @initiated_by == :client return server_public_key else if callerid =~ /cert=([\w\.\-]+)/ cid = $1 if File.exist?("#{client_cert_dir}/#{cid}.pem") return "#{client_cert_dir}/#{cid}.pem" else raise("Could not find a public key for #{cid} in #{client_cert_dir}/#{cid}.pem") end else raise("Caller id is not in the expected format") end end end # Figures out the client private key either from MCOLLECTIVE_SSL_PRIVATE or the # plugin.ssl_client_private config option def client_private_key return ENV["MCOLLECTIVE_SSL_PRIVATE"] if ENV.include?("MCOLLECTIVE_SSL_PRIVATE") raise("No plugin.ssl_client_private configuration option specified") unless @config.pluginconf.include?("ssl_client_private") return @config.pluginconf["ssl_client_private"] end # Figures out the client public key either from MCOLLECTIVE_SSL_PUBLIC or the # plugin.ssl_client_public config option def client_public_key return ENV["MCOLLECTIVE_SSL_PUBLIC"] if ENV.include?("MCOLLECTIVE_SSL_PUBLIC") raise("No plugin.ssl_client_public configuration option specified") unless @config.pluginconf.include?("ssl_client_public") return @config.pluginconf["ssl_client_public"] end # Figures out the server private key from the plugin.ssl_server_private config option def server_private_key raise("No plugin.ssl_server_private configuration option specified") unless @config.pluginconf.include?("ssl_server_private") @config.pluginconf["ssl_server_private"] end # Figures out the server public key from the plugin.ssl_server_public config option def server_public_key raise("No ssl_server_public configuration option specified") unless @config.pluginconf.include?("ssl_server_public") return @config.pluginconf["ssl_server_public"] end # Figures out where to get client public certs from the plugin.ssl_client_cert_dir config option def client_cert_dir raise("No plugin.ssl_client_cert_dir configuration option specified") unless @config.pluginconf.include?("ssl_client_cert_dir") @config.pluginconf["ssl_client_cert_dir"] end # Retrieves the value of plugin.psk and builds a hash with it and the passed body def makehash(body) Log.debug("Creating message hash using #{private_key_file}") sign(private_key_file, body.to_s) end # Code adapted from http://github.com/adamcooke/basicssl # signs a message def sign(key, string) SSL.new(nil, key).sign(string, true) end # verifies a signature def verify(key, signature, string) SSL.new(key).verify_signature(signature, string, true) end def request_description(msg) "%s from %s@%s" % [msg[:requestid], msg[:callerid], msg[:senderid]] end end end end mcollective-2.6.0/plugins/mcollective/security/psk.rb0000644000175000017500000000665112375446407022037 0ustar jonasjonasmodule MCollective module Security # Impliments message authentication using digests and shared keys # # You should configure a psk in the configuration file and all requests # will be validated for authenticity with this. # # Serialization uses Marshal, this is the default security module that is # supported out of the box. # # Validation is as default and is provided by MCollective::Security::Base # # You can configure the caller id being created, this can adjust how you # create authorization plugins. For example you can use a unix group instead # of uid to do authorization. class Psk < Base require 'etc' # Decodes a message by unserializing all the bits etc, it also validates # it as valid using the psk etc def decodemsg(msg) body = Marshal.load(msg.payload) should_process_msg?(msg, body[:requestid]) if validrequest?(body) body[:body] = Marshal.load(body[:body]) return body else nil end end # Encodes a reply def encodereply(sender, msg, requestid, requestcallerid=nil) serialized = Marshal.dump(msg) digest = makehash(serialized) req = create_reply(requestid, sender, serialized) req[:hash] = digest Marshal.dump(req) end # Encodes a request msg def encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) serialized = Marshal.dump(msg) digest = makehash(serialized) req = create_request(requestid, filter, serialized, @initiated_by, target_agent, target_collective, ttl) req[:hash] = digest Marshal.dump(req) end # Checks the md5 hash in the request body against our psk, the request sent for validation # should not have been deserialized already def validrequest?(req) digest = makehash(req[:body]) if digest == req[:hash] @stats.validated return true else @stats.unvalidated raise(SecurityValidationFailed, "Received an invalid signature in message") end end def callerid if @config.pluginconf.include?("psk.callertype") callertype = @config.pluginconf["psk.callertype"].to_sym if @config.pluginconf.include?("psk.callertype") else callertype = :uid end case callertype when :gid id = "gid=#{Process.gid}" when :group raise "Cannot use the 'group' callertype for the PSK security plugin on the Windows platform" if Util.windows? id = "group=#{Etc.getgrgid(Process.gid).name}" when :user id = "user=#{Etc.getlogin}" when :identity id = "identity=#{@config.identity}" else id ="uid=#{Process.uid}" end Log.debug("Setting callerid to #{id} based on callertype=#{callertype}") id end private # Retrieves the value of plugin.psk and builds a hash with it and the passed body def makehash(body) if ENV.include?("MCOLLECTIVE_PSK") psk = ENV["MCOLLECTIVE_PSK"] else raise("No plugin.psk configuration option specified") unless @config.pluginconf.include?("psk") psk = @config.pluginconf["psk"] end Digest::MD5.hexdigest(body.to_s + psk) end end end end mcollective-2.6.0/plugins/mcollective/application/0000755000175000017500000000000012375446416021341 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/application/find.rb0000644000175000017500000000107212375446407022606 0ustar jonasjonasclass MCollective::Application::Find 0 ? exit(0) : exit(1) end end mcollective-2.6.0/plugins/mcollective/application/help.rb0000644000175000017500000000127412375446407022622 0ustar jonasjonasmodule MCollective class Application::Help 0 end def main if configuration.include?(:application) puts Applications[configuration[:application]].help else puts "The Marionette Collective version #{MCollective.version}" puts Applications.list.sort.each do |app| begin puts " %-15s %s" % [app, Applications[app].application_description] rescue end end puts end end end end mcollective-2.6.0/plugins/mcollective/application/plugin.rb0000644000175000017500000003273012375446407023171 0ustar jonasjonasmodule MCollective class Application::Plugin mco plugin info mco plugin doc mco plugin doc mco plugin generate agent [actions=val,val] mco plugin generate data [outputs=val,val] info : Display plugin information including package details. package : Create all available plugin packages. doc : Display documentation for a specific plugin. END_OF_USAGE option :pluginname, :description => 'Plugin name', :arguments => ['-n', '--name NAME'], :type => String option :postinstall, :description => 'Post install script', :arguments => ['--postinstall POSTINSTALL'], :type => String option :preinstall, :description => 'Pre install script', :arguments => ['--preinstall PREINSTALL'], :type => String option :revision, :description => 'Revision number', :arguments => ['--revision REVISION'], :type => String option :iteration, :description => 'DEPRECATED - Use --revision instead', :arguments => ['--iteration ITERATION'], :type => String option :vendor, :description => 'Vendor name', :arguments => ['--vendor VENDOR'], :type => String option :pluginpath, :description => 'MCollective plugin path', :arguments => ['--pluginpath PATH'], :type => String option :mcname, :description => 'MCollective type (mcollective, pe-mcollective) that the packages depend on', :arguments => ['--mcname NAME'], :type => String option :mcversion, :description => 'Version of MCollective that the packages depend on', :arguments => ['--mcversion MCVERSION'], :type => String option :dependency, :description => 'Adds a dependency to the plugin', :arguments => ['--dependency DEPENDENCIES'], :type => :array option :format, :description => 'Package output format. Defaults to rpmpackage or debpackage', :arguments => ['--format OUTPUTFORMAT'], :type => String option :sign, :description => 'Embed a signature in the package', :arguments => ['--sign'], :type => :boolean option :rpctemplate, :description => 'Template to use.', :arguments => ['--template HELPTEMPLATE'], :type => String option :description, :description => 'Plugin description', :arguments => ['--description DESCRIPTION'], :type => String option :author, :description => 'The author of the plugin', :arguments => ['--author AUTHOR'], :type => String option :license, :description => 'The license under which the plugin is distributed', :arguments => ['--license LICENSE'], :type => String option :version, :description => 'The version of the plugin', :arguments => ['--pluginversion VERSION'], :type => String option :url, :description => 'Url at which information about the plugin can be found', :arguments => ['--url URL'], :type => String option :timeout, :description => "The plugin's timeout", :arguments => ['--timeout TIMEOUT'], :type => Integer option :actions, :description => 'Actions to be generated for an Agent Plugin', :arguments => ['--actions [ACTIONS]'], :type => Array option :outputs, :description => 'Outputs to be generated for an Data Plugin', :arguments => ['--outputs [OUTPUTS]'], :type => Array option :keep_artifacts, :description => "Don't remove artifacts after building packages", :arguments => ['--keep-artifacts'], :type => :boolean option :module_template, :description => "Path to the template used by the modulepackager", :arguments => ['--module-template PATH'], :type => String # Handle alternative format that optparser can't parse. def post_option_parser(configuration) if ARGV.length >= 1 configuration[:action] = ARGV.delete_at(0) configuration[:target] = ARGV.delete_at(0) || "." if configuration[:action] == "generate" unless ARGV[0] && ARGV[0].match(/(actions|outputs)=(.+)/i) unless configuration[:pluginname] configuration[:pluginname] = ARGV.delete_at(0) else ARGV.delete_at(0) end end ARGV.each do |argument| if argument.match(/(actions|outputs)=(.+)/i) configuration[$1.downcase.to_sym]= $2.split(",") else raise "Could not parse --arg '#{argument}'" end end end end end # Display info about plugin def info_command plugin = prepare_plugin packager = PluginPackager["#{configuration[:format].capitalize}Packager"] packager.new(plugin).package_information end # Generate a plugin skeleton def generate_command raise "undefined plugin type. cannot generate plugin. valid types are 'agent' and 'data'" if configuration["target"] == '.' unless configuration[:pluginname] puts "No plugin name specified. Using 'new_plugin'" configuration[:pluginname] = "new_plugin" end load_plugin_config_values case configuration[:target].downcase when 'agent' Generators::AgentGenerator.new(configuration[:pluginname], configuration[:actions], configuration[:pluginname], configuration[:description], configuration[:author], configuration[:license], configuration[:version], configuration[:url], configuration[:timeout]) when 'data' raise "data plugin must have at least one output" unless configuration[:outputs] Generators::DataGenerator.new(configuration[:pluginname], configuration[:outputs], configuration[:pluginname], configuration[:description], configuration[:author], configuration[:license], configuration[:version], configuration[:url], configuration[:timeout]) else raise "invalid plugin type. cannot generate plugin '#{configuration[:target]}'" end end # Package plugin def package_command if configuration[:sign] && Config.instance.pluginconf.include?("debian_packager.keyname") configuration[:sign] = Config.instance.pluginconf["debian_packager.keyname"] configuration[:sign] = "\"#{configuration[:sign]}\"" unless configuration[:sign].match(/\".*\"/) end plugin = prepare_plugin (configuration[:pluginpath] = configuration[:pluginpath] + "/") if (configuration[:pluginpath] && !configuration[:pluginpath].match(/^.*\/$/)) packager = PluginPackager["#{configuration[:format].capitalize}Packager"] packager.new(plugin, configuration[:pluginpath], configuration[:sign], options[:verbose], configuration[:keep_artifacts], configuration[:module_template]).create_packages end # Agents are just called 'agent' but newer plugin types are # called plugin_plugintype for example facter_facts etc so # this will first try the old way then the new way. def load_plugin_ddl(plugin, type) [plugin, "#{plugin}_#{type}"].each do |p| ddl = DDL.new(p, type, false) if ddl.findddlfile(p, type) ddl.loadddlfile return ddl end end return nil end # Show application list and plugin help def doc_command known_plugin_types = [["Agents", :agent], ["Aggregate", :aggregate], ["Connectors", :connector], ["Data Queries", :data], ["Discovery Methods", :discovery], ["Validator Plugins", :validator]] if configuration.include?(:target) && configuration[:target] != "." if configuration[:target] =~ /^(.+?)\/(.+)$/ ddl = load_plugin_ddl($2.to_sym, $1) else found_plugin_type = nil known_plugin_types.each do |plugin_type| PluginManager.find(plugin_type[1], "ddl").each do |ddl| pluginname = ddl.gsub(/_#{plugin_type[1]}$/, "") if pluginname == configuration[:target] abort "Duplicate plugin name found, please specify a full path like agent/rpcutil" if found_plugin_type found_plugin_type = plugin_type[1] end end end abort "Could not find a plugin named '%s' in any supported plugin type" % configuration[:target] unless found_plugin_type ddl = load_plugin_ddl(configuration[:target], found_plugin_type) end if ddl puts ddl.help(configuration[:rpctemplate]) else abort "Could not find a '%s' plugin named '%s'" % configuration[:target].split('/') end else puts "Please specify a plugin. Available plugins are:" puts load_errors = [] known_plugin_types.each do |plugin_type| puts "%s:" % plugin_type[0] PluginManager.find(plugin_type[1], "ddl").each do |ddl| begin help = DDL.new(ddl, plugin_type[1]) pluginname = ddl.gsub(/_#{plugin_type[1]}$/, "") puts " %-25s %s" % [pluginname, help.meta[:description]] rescue => e load_errors << [plugin_type[1], ddl, e] end end puts end unless load_errors.empty? puts "Plugin Load Errors:" load_errors.each do |e| puts " %-25s %s" % ["#{e[0]}/#{e[1]}", Util.colorize(:yellow, e[2])] end end end end # Creates the correct package plugin object. def prepare_plugin plugintype = set_plugin_type unless configuration[:plugintype] configuration[:format] = "ospackage" unless configuration[:format] PluginPackager.load_packagers plugin_class = PluginPackager[configuration[:plugintype]] if configuration[:dependency] && configuration[:dependency].size == 1 configuration[:dependency] = configuration[:dependency][0].split(" ") elsif configuration[:dependency] configuration[:dependency].map!{|dep| {:name => dep, :version => nil}} end mcdependency = {:mcname => configuration[:mcname], :mcversion => configuration[:mcversion]} #Deprecation warning for --iteration if configuration[:iteration] puts 'Warning. The --iteration flag has been deprecated. Please use --revision instead.' configuration[:revision] = configuration[:iteration] unless configuration[:revision] end plugin_class.new(configuration, mcdependency, plugintype) end def directory_for_type(type) File.directory?(File.join(configuration[:target], type)) end # Identify plugin type if not provided. def set_plugin_type if directory_for_type("agent") || directory_for_type("application") configuration[:plugintype] = "AgentDefinition" return "Agent" elsif directory_for_type(plugintype = identify_plugin) configuration[:plugintype] = "StandardDefinition" return plugintype else raise RuntimeError, "target directory is not a valid mcollective plugin" end end # If plugintype is StandardDefinition, identify which of the special # plugin types we are dealing with based on directory structure. # To keep it simple we limit it to one type per target directory. def identify_plugin plugintype = Dir.glob(File.join(configuration[:target], "*")).select do |file| File.directory?(file) && file.match(/(connector|facts|registration|security|audit|pluginpackager|data|discovery|validator)/) end raise RuntimeError, "more than one plugin type detected in directory" if plugintype.size > 1 raise RuntimeError, "no plugins detected in directory" if plugintype.size < 1 File.basename(plugintype[0]) end # Load preset metadata values from config if they are present # This makes it possible to override metadata values in a local # client config file. # # Example : plugin.metadata.license = Apache 2 def load_plugin_config_values config = Config.instance [:pluginname, :description, :author, :license, :version, :url, :timeout].each do |confoption| configuration[confoption] = config.pluginconf["metadata.#{confoption}"] unless configuration[confoption] end end def main abort "No action specified, please run 'mco help plugin' for help" unless configuration.include?(:action) cmd = "#{configuration[:action]}_command" if respond_to? cmd send cmd else abort "Invalid action #{configuration[:action]}, please run 'mco help plugin' for help." end end end end mcollective-2.6.0/plugins/mcollective/application/inventory.rb0000644000175000017500000002274512375446407023735 0ustar jonasjonasclass MCollective::Application::Inventory "Script to run", :arguments => ["--script SCRIPT"] option :collectives, :description => "List all known collectives", :arguments => ["--list-collectives", "--lc"], :default => false, :type => :bool option :collectivemap, :description => "Create a DOT graph of all collectives", :arguments => ["--collective-graph MAP", "--cg MAP", "--map MAP"] def post_option_parser(configuration) configuration[:node] = ARGV.shift if ARGV.size > 0 end def validate_configuration(configuration) unless configuration[:node] || configuration[:script] || configuration[:collectives] || configuration[:collectivemap] raise "Need to specify either a node name, script to run or other options" end end # Get all the known collectives and nodes that belong to them def get_collectives util = rpcclient("rpcutil") util.progress = false collectives = {} nodes = 0 total = 0 util.collective_info do |r, cinfo| begin if cinfo[:data] && cinfo[:data][:collectives] cinfo[:data][:collectives].each do |collective| collectives[collective] ||= [] collectives[collective] << cinfo[:sender] end nodes += 1 total += 1 end end end {:collectives => collectives, :nodes => nodes, :total_nodes => total} end # Writes a crude DOT graph to a file def collectives_map(file) File.open(file, "w") do |graph| puts "Retrieving collective info...." collectives = get_collectives graph.puts 'graph {' collectives[:collectives].keys.sort.each do |collective| graph.puts ' subgraph "%s" {' % [ collective ] collectives[:collectives][collective].each do |member| graph.puts ' "%s" -- "%s"' % [ member, collective ] end graph.puts ' }' end graph.puts '}' puts "Graph of #{collectives[:total_nodes]} nodes has been written to #{file}" end end # Prints a report of all known sub collectives def collectives_report collectives = get_collectives puts " %-30s %s" % [ "Collective", "Nodes" ] puts " %-30s %s" % [ "==========", "=====" ] collectives[:collectives].sort_by {|key,count| count.size}.each do |collective| puts " %-30s %d" % [ collective[0], collective[1].size ] end puts puts " %30s %d" % [ "Total nodes:", collectives[:nodes] ] puts end def node_inventory node = configuration[:node] util = rpcclient("rpcutil") util.identity_filter node util.progress = false nodestats = util.custom_request("daemon_stats", {}, node, {"identity" => node}).first unless nodestats STDERR.puts "Did not receive any results from node #{node}" exit 1 end unless nodestats[:statuscode] == 0 STDERR.puts "Failed to retrieve daemon_stats from #{node}: #{nodestats[:statusmsg]}" else util.custom_request("inventory", {}, node, {"identity" => node}).each do |resp| unless resp[:statuscode] == 0 STDERR.puts "Failed to retrieve inventory for #{node}: #{resp[:statusmsg]}" next end data = resp[:data] begin puts "Inventory for #{resp[:sender]}:" puts nodestats = nodestats[:data] puts " Server Statistics:" puts " Version: #{nodestats[:version]}" puts " Start Time: #{Time.at(nodestats[:starttime])}" puts " Config File: #{nodestats[:configfile]}" puts " Collectives: #{data[:collectives].join(', ')}" if data.include?(:collectives) puts " Main Collective: #{data[:main_collective]}" if data.include?(:main_collective) puts " Process ID: #{nodestats[:pid]}" puts " Total Messages: #{nodestats[:total]}" puts " Messages Passed Filters: #{nodestats[:passed]}" puts " Messages Filtered: #{nodestats[:filtered]}" puts " Expired Messages: #{nodestats[:ttlexpired]}" puts " Replies Sent: #{nodestats[:replies]}" puts " Total Processor Time: #{nodestats[:times][:utime]} seconds" puts " System Time: #{nodestats[:times][:stime]} seconds" puts puts " Agents:" if data[:agents].size > 0 data[:agents].sort.in_groups_of(3, "") do |agents| puts " %-15s %-15s %-15s" % agents end else puts " No agents installed" end puts puts " Data Plugins:" if data[:data_plugins].size > 0 data[:data_plugins].sort.in_groups_of(3, "") do |plugins| puts " %-15s %-15s %-15s" % plugins.map{|p| p.gsub("_data", "")} end else puts " No data plugins installed" end puts puts " Configuration Management Classes:" if data[:classes].size > 0 field_size = MCollective::Util.field_size(data[:classes], 30) fields_num = MCollective::Util.field_number(field_size) format = " " + (" %-#{field_size}s" * fields_num) data[:classes].sort.in_groups_of(fields_num, "") do |klasses| puts format % klasses end else puts " No classes applied" end puts puts " Facts:" if data[:facts].size > 0 data[:facts].sort_by{|f| f[0]}.each do |f| puts " #{f[0]} => #{f[1]}" end else puts " No facts known" end break rescue Exception => e STDERR.puts "Failed to display node inventory: #{e.class}: #{e}" end end end halt util.stats end # Helpers to create a simple DSL for scriptlets def format(fmt) @fmt = fmt end def fields(&blk) @flds = blk end def identity @node[:identity] end def facts @node[:facts] end def classes @node[:classes] end def agents @node[:agents] end def page_length(len) @page_length = len end def page_heading(fmt) @page_heading = fmt end def page_body(fmt) @page_body = fmt end # Expects a simple printf style format and apply it to # each node: # # inventory do # format "%s:\t\t%s\t\t%s" # # fields { [ identity, facts["serialnumber"], facts["productname"] ] } # end def inventory(&blk) raise "Need to give a block to inventory" unless block_given? blk.call if block_given? raise "Need to define a format" if @fmt.nil? raise "Need to define inventory fields" if @flds.nil? util = rpcclient("rpcutil") util.progress = false util.inventory do |t, resp| @node = {:identity => resp[:sender], :facts => resp[:data][:facts], :classes => resp[:data][:classes], :agents => resp[:data][:agents]} puts @fmt % @flds.call end end # Use the ruby formatr gem to build reports using Perls formats # # It is kind of ugly but brings a lot of flexibility in report # writing without building an entire reporting language. # # You need to have formatr installed to enable reports like: # # formatted_inventory do # page_length 20 # # page_heading < resp[:sender], :facts => resp[:data][:facts], :classes => resp[:data][:classes], :agents => resp[:data][:agents]} body_fmt.printFormat(binding) end rescue Exception => e STDERR.puts "Could not create report: #{e.class}: #{e}" exit 1 end @fmt = nil @flds = nil @page_heading = nil @page_body = nil @page_length = 40 def main if configuration[:script] if File.exist?(configuration[:script]) eval(File.read(configuration[:script])) else raise "Could not find script to run: #{configuration[:script]}" end elsif configuration[:collectivemap] collectives_map(configuration[:collectivemap]) elsif configuration[:collectives] collectives_report else node_inventory end end end mcollective-2.6.0/plugins/mcollective/application/completion.rb0000644000175000017500000000616512375446407024047 0ustar jonasjonasmodule MCollective class Application::Completion "List all known agents", :arguments => "--list-agents", :required => false, :type => :boolean option :list_actions, :description => "List all actions for an agent", :arguments => "--list-actions", :required => false, :type => :boolean option :list_inputs, :description => "List all inputs for an action", :arguments => "--list-inputs", :required => false, :type => :boolean option :list_applications, :description => "List all known applications", :arguments => "--list-applications", :required => false, :type => :boolean option :agent, :description => "The agent to operate on", :arguments => "--agent AGENT", :required => false option :action, :description => "The action to operate on", :arguments => "--action ACTION", :required => false def list_agents if options[:verbose] PluginManager.find(:agent, "ddl").each do |agent| begin ddl = DDL.new(agent) puts "%s:%s" % [ agent, ddl.meta[:description] ] rescue end end else PluginManager.find(:agent, "ddl").each {|p| puts p} end end def list_actions abort "Please specify an agent to list actions for" unless configuration[:agent] if options[:verbose] ddl = DDL.new(configuration[:agent], :agent) ddl.actions.sort.each do |action| puts "%s:%s" % [action, ddl.action_interface(action)[:description]] end else DDL.new(configuration[:agent], :agent).actions.sort.each {|a| puts a} end rescue end def list_inputs abort "Please specify an action and agent to list inputs for" unless configuration[:agent] && configuration[:action] if options[:verbose] ddl = DDL.new(configuration[:agent], :agent) action = ddl.action_interface(configuration[:action]) action[:input].keys.sort.each do |input| puts "%s:%s" % [input, action[:input][input][:description]] end else DDL.new(configuration[:agent], :agent).action_interface(configuration[:action])[:input].keys.sort.each {|i| puts i} end rescue end def list_applications if options[:verbose] Applications.list.each do |app| puts "%s:%s" % [app, Applications[app].application_description] end else Applications.list.each {|a| puts a} end end def main actions = configuration.keys.map{|k| k.to_s}.grep(/^list_/) abort "Please choose either --list-[agents|actions|inputs|applications]" if actions.empty? abort "Please choose only one of --list-[agents|actions|inputs|applications]" if actions.size > 1 send actions.first end end end mcollective-2.6.0/plugins/mcollective/application/ping.rb0000644000175000017500000000412312375446407022623 0ustar jonasjonas# encoding: utf-8 module MCollective class Application::Ping "Shows a graph of ping distribution", :arguments => ["--graph", "-g"], :default => false, :type => :bool # Convert the times structure into a array representing # buckets of responses in 50 ms intervals. Return a small # sparkline graph using UTF8 characters def spark(resp_times) return "" unless configuration[:graph] || Config.instance.pluginconf["rpc.graph"] ticks=%w[â– â–‚ â–ƒ â–„ â–… â–† â–‡] histo = {} # round each time to its nearest 50ms # and keep a count for each 50ms resp_times.each do |time| time = Integer(time + 50 - (time % 50)) histo[time] ||= 0 histo[time] += 1 end # set the 50ms intervals that saw no traffic to 0 ((histo.keys.max - histo.keys.min) / 50).times do |i| time = (i * 50) + histo.keys.min histo[time] = 0 unless histo[time] end # get a numerically sorted list of times histo = histo.keys.sort.map{|k| histo[k]} range = histo.max - histo.min scale = ticks.size - 1 distance = histo.max.to_f / scale histo.map do |val| tick = (val / distance).round tick = 0 if tick < 0 ticks[tick] end.join end def main client = MCollective::Client.new(options[:config]) client.options = options start = Time.now.to_f times = [] client.req("ping", "discovery") do |resp| times << (Time.now.to_f - start) * 1000 puts "%-40s time=%.2f ms" % [resp[:senderid], times.last] end puts("\n\n---- ping statistics ----") if times.size > 0 sum = times.inject(0){|acc,i|acc +i} avg = sum / times.length.to_f puts "%d replies max: %.2f min: %.2f avg: %.2f %s" % [times.size, times.max, times.min, avg, spark(times)] else puts("No responses received") end halt client.stats end end end mcollective-2.6.0/plugins/mcollective/application/facts.rb0000644000175000017500000000253712375446407022775 0ustar jonasjonasclass MCollective::Application::Facts 0 end def validate_configuration(configuration) raise "Please specify a fact to report for" unless configuration.include?(:fact) end def show_single_fact_report(fact, facts, verbose=false) puts("Report for fact: #{fact}\n\n") field_size = MCollective::Util.field_size(facts.keys) facts.keys.sort.each do |k| printf(" %-#{field_size}s found %d times\n", k, facts[k].size) if verbose puts facts[k].sort.each do |f| puts(" #{f}") end puts end end end def main rpcutil = rpcclient("rpcutil") rpcutil.progress = false facts = {} rpcutil.get_fact(:fact => configuration[:fact]) do |resp| begin value = resp[:body][:data][:value] if value facts.include?(value) ? facts[value] << resp[:senderid] : facts[value] = [ resp[:senderid] ] end rescue Exception => e STDERR.puts "Could not parse facts for #{resp[:senderid]}: #{e.class}: #{e}" end end show_single_fact_report(configuration[:fact], facts, options[:verbose]) printrpcstats halt rpcutil.stats end end mcollective-2.6.0/plugins/mcollective/application/rpc.rb0000644000175000017500000001014712375446407022455 0ustar jonasjonasclass MCollective::Application::Rpc --action [--argument --argument ...]" usage "mco rpc [options] [filters] [ ...]" option :show_results, :description => "Do not process results, just send request", :arguments => ["--no-results", "--nr"], :default => true, :type => :bool option :agent, :description => "Agent to call", :arguments => ["-a", "--agent AGENT"] option :action, :description => "Action to call", :arguments => ["--action ACTION"] option :arguments, :description => "Arguments to pass to agent", :arguments => ["--arg", "--argument ARGUMENT"], :type => :array, :default => [], :validate => Proc.new {|val| val.match(/^(.+?)=(.+)$/) ? true : "Could not parse --arg #{val} should be of the form key=val" } def post_option_parser(configuration) # handle the alternative format that optparse cant parse unless (configuration.include?(:agent) && configuration.include?(:action)) if ARGV.length >= 2 configuration[:agent] = ARGV[0] ARGV.delete_at(0) configuration[:action] = ARGV[0] ARGV.delete_at(0) ARGV.each do |v| if v =~ /^(.+?)=(.+)$/ configuration[:arguments] = [] unless configuration.include?(:arguments) configuration[:arguments] << v else STDERR.puts("Could not parse --arg #{v}") exit(1) end end else STDERR.puts("No agent, action and arguments specified") exit(1) end end # convert arguments to symbols for keys to comply with simplerpc conventions args = configuration[:arguments].clone configuration[:arguments] = {} args.each do |v| if v =~ /^(.+?)=(.+)$/ configuration[:arguments][$1.to_sym] = $2 end end end def string_to_ddl_type(arguments, ddl) return if ddl.empty? arguments.keys.each do |key| if ddl[:input].keys.include?(key) case ddl[:input][key][:type] when :boolean arguments[key] = MCollective::DDL.string_to_boolean(arguments[key]) when :number, :integer, :float arguments[key] = MCollective::DDL.string_to_number(arguments[key]) end end end end def main mc = rpcclient(configuration[:agent]) mc.agent_filter(configuration[:agent]) string_to_ddl_type(configuration[:arguments], mc.ddl.action_interface(configuration[:action])) if mc.ddl mc.validate_request(configuration[:action], configuration[:arguments]) if mc.reply_to configuration[:arguments][:process_results] = true puts "Request sent with id: " + mc.send(configuration[:action], configuration[:arguments]) + " replies to #{mc.reply_to}" elsif !configuration[:show_results] configuration[:arguments][:process_results] = false puts "Request sent with id: " + mc.send(configuration[:action], configuration[:arguments]) else discover_args = {:verbose => true} # IF the discovery method hasn't been explicitly overridden # and we're not being run interactively, # and someone has piped us some data # Then we assume it's a discovery list - this can be either: # - list of hosts in plaintext # - JSON that came from another rpc or printrpc if mc.default_discovery_method && !STDIN.tty? && !STDIN.eof? # Then we override discovery to try to grok the data on STDIN mc.discovery_method = 'stdin' mc.discovery_options = 'auto' discover_args = {:verbose => false} end mc.discover discover_args printrpc mc.send(configuration[:action], configuration[:arguments]) printrpcstats :summarize => true, :caption => "#{configuration[:agent]}##{configuration[:action]} call stats" if mc.discover.size > 0 halt mc.stats end end end mcollective-2.6.0/plugins/mcollective/facts/0000755000175000017500000000000012375446416020136 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/facts/yaml_facts.rb0000644000175000017500000000300012375446407022576 0ustar jonasjonasmodule MCollective module Facts require 'yaml' # A factsource that reads a hash of facts from a YAML file # # Multiple files can be specified seperated with a : in the # config file, they will be merged with later files overriding # earlier ones in the list. class Yaml_facts e Log.error("Failed to load yaml facts from #{file}: #{e.class}: #{e}") end end facts end # force fact reloads when the mtime on the yaml file change def force_reload? config = Config.instance fact_files = config.pluginconf["yaml"].split(File::PATH_SEPARATOR) fact_files.each do |file| @yaml_file_mtimes[file] ||= File.stat(file).mtime mtime = File.stat(file).mtime if mtime > @yaml_file_mtimes[file] @yaml_file_mtimes[file] = mtime Log.debug("Forcing fact reload due to age of #{file}") return true end end false end end end end mcollective-2.6.0/plugins/mcollective/data/0000755000175000017500000000000012375446416017747 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/data/fact_data.rb0000644000175000017500000000271512375446407022207 0ustar jonasjonasmodule MCollective module Data class Fact_data= facts.size # array index out would be out of bounds, so we don't have the value return end when Hash if !facts.include?(level) # we don't have the key for the next level, so give up return end else # this isn't a container data type, so we can't walk into it return end facts = facts[level] end result[:exists] = true case facts when Array, Hash # Currently data plugins cannot return structured data, so until # this is fixed flatten the data with json and flag that we have # munged the data result[:value] = facts.to_json result[:value_encoding] = 'application/json' else result[:value] = facts result[:value_encoding] = 'text/plain' end end end end end mcollective-2.6.0/plugins/mcollective/data/agent_data.ddl0000644000175000017500000000142212375446407022522 0ustar jonasjonasmetadata :name => "Agent", :description => "Meta data about installed MColletive Agents", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 dataquery :description => "Agent Meta Data" do input :query, :prompt => "Agent Name", :description => "Valid agent name", :type => :string, :validation => /^[\w\_]+$/, :maxlength => 20 [:license, :timeout, :description, :url, :version, :author].each do |item| output item, :description => "Agent #{item}", :display_as => item.to_s.capitalize end end mcollective-2.6.0/plugins/mcollective/data/fstat_data.ddl0000644000175000017500000000503212375446407022546 0ustar jonasjonasmetadata :name => "File Stat", :description => "Retrieve file stat data for a given file", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 dataquery :description => "File stat information" do input :query, :prompt => "File Name", :description => "Valid File Name", :type => :string, :validation => /.+/, :maxlength => 120 output :name, :description => "File name", :display_as => "Name" output :output, :description => "Human readable information about the file", :display_as => "Status" output :present, :description => "Indicates if the file exist using 0 or 1", :display_as => "Present" output :size, :description => "File size", :display_as => "Size" output :mode, :description => "File mode", :display_as => "Mode" output :md5, :description => "File MD5 digest", :display_as => "MD5" output :mtime, :description => "File modification time", :display_as => "Modification time" output :ctime, :description => "File change time", :display_as => "Change time" output :atime, :description => "File access time", :display_as => "Access time" output :mtime_seconds, :description => "File modification time in seconds", :display_as => "Modification time" output :ctime_seconds, :description => "File change time in seconds", :display_as => "Change time" output :atime_seconds, :description => "File access time in seconds", :display_as => "Access time" output :mtime_age, :description => "File modification age in seconds", :display_as => "Modification age" output :ctime_age, :description => "File change age in seconds", :display_as => "Change age" output :atime_age, :description => "File access age in seconds", :display_as => "Access age" output :uid, :description => "File owner", :display_as => "Owner" output :gid, :description => "File group", :display_as => "Group" output :type, :description => "File type", :display_as => "Type" end mcollective-2.6.0/plugins/mcollective/data/fstat_data.rb0000644000175000017500000000313112375446407022404 0ustar jonasjonasmodule MCollective module Data class Fstat_data "Fact", :description => "Structured fact query", :author => "Puppet Labs", :license => "ASL 2.0", :version => "1.0", :url => "http://marionette-collective.org/", :timeout => 1 dataquery :description => "Fact" do input :query, :prompt => 'Fact Path', :description => 'Path to a fact, eg network.eth0.address', :type => :string, :validation => /./, :maxlength => 256 output :exists, :description => 'Fact is present', :display_as => 'exists' output :value, :description => 'Fact value', :display_as => 'value' output :value_encoding, :description => 'Encoding of the fact value (text/plain or application/json)', :display_as => 'value_encoding' end mcollective-2.6.0/plugins/mcollective/data/agent_data.rb0000644000175000017500000000065512375446407022371 0ustar jonasjonasmodule MCollective module Data class Agent_data..agent # - Replies use temp-topics so they are private and transient. # - Point to Point messages using topics are supported by subscribing to # /queue/.nodes with a selector "mc_identity = 'identity' # # The use of temp-topics for the replies is a huge improvement over the old style. # In the old way all clients got replies for all clients that were active at that # time, this would mean that they would need to decrypt, validate etc in order to # determine if they need to ignore the message, this was computationally expensive # and on large busy networks the messages were being sent all over the show cross # broker boundaries. # # The new way means the messages go point2point back to only whoever requested the # message, they only get their own replies and this is ap private channel that # casual observers cannot just snoop into. # # This plugin supports 1.1.6 and newer of the Stomp rubygem. # # connector = activemq # plugin.activemq.pool.size = 2 # # plugin.activemq.pool.1.host = stomp1.your.net # plugin.activemq.pool.1.port = 61613 # plugin.activemq.pool.1.user = you # plugin.activemq.pool.1.password = secret # plugin.activemq.pool.1.ssl = true # plugin.activemq.pool.1.ssl.cert = /path/to/your.cert # plugin.activemq.pool.1.ssl.key = /path/to/your.key # plugin.activemq.pool.1.ssl.ca = /path/to/your.ca # plugin.activemq.pool.1.ssl.fallback = true # # plugin.activemq.pool.2.host = stomp2.your.net # plugin.activemq.pool.2.port = 61613 # plugin.activemq.pool.2.user = you # plugin.activemq.pool.2.password = secret # plugin.activemq.pool.2.ssl = false # # Using this method you can supply just STOMP_USER and STOMP_PASSWORD. The port will # default to 61613 if not specified. # # The ssl options are only usable in version of the Stomp gem newer than 1.2.2 where these # will imply full SSL validation will be done and you'll only be able to connect to a # ActiveMQ server that has a cert signed by the same CA. If you only set ssl = true # and do not supply the cert, key and ca properties or if you have an older gem it # will fall back to unverified mode only if ssl.fallback is true # # In addition you can set the following options for the rubygem: # # plugin.activemq.initial_reconnect_delay = 0.01 # plugin.activemq.max_reconnect_delay = 30.0 # plugin.activemq.use_exponential_back_off = true # plugin.activemq.back_off_multiplier = 2 # plugin.activemq.max_reconnect_attempts = 0 # plugin.activemq.randomize = false # plugin.activemq.timeout = -1 # # You can set the initial connetion timeout - this is when your stomp server is simply # unreachable - after which it would failover to the next in the pool: # # plugin.activemq.connect_timeout = 30 # # ActiveMQ JMS message priorities can be set: # # plugin.activemq.priority = 4 # # This plugin supports Stomp protocol 1.1 when combined with the stomp gem version # 1.2.10 or newer. To enable network heartbeats which will help keep the connection # alive over NAT connections and aggresive session tracking firewalls you can set: # # plugin.activemq.heartbeat_interval = 30 # # which will cause a heartbeat to be sent on 30 second intervals and one to be expected # from the broker every 30 seconds. The shortest supported period is 30 seconds, if # you set it lower it will get forced to 30 seconds. # # After 2 failures to receive a heartbeat the connection will be reset via the normal # failover mechanism. # # By default if heartbeat_interval is set it will request Stomp 1.1 but support fallback # to 1.0, but you can enable strict Stomp 1.1 only operation # # plugin.activemq.stomp_1_0_fallback = 0 class Activemq e end # Stomp 1.1+ - heart beat send (transmit) failed. def on_hbwrite_fail(params, ticker_data) Log.error("Heartbeat write failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) rescue Exception => e end # Log heart beat fires def on_hbfire(params, srind, curt) case srind when "receive_fire" Log.debug("Received heartbeat from %s: %s, %s" % [stomp_url(params), srind, curt]) when "send_fire" Log.debug("Publishing heartbeat to %s: %s, %s" % [stomp_url(params), srind, curt]) end rescue Exception => e end def stomp_url(params) "%s://%s@%s:%d" % [ params[:cur_ssl] ? "stomp+ssl" : "stomp", params[:cur_login], params[:cur_host], params[:cur_port]] end end def initialize @config = Config.instance @subscriptions = [] @msgpriority = 0 @base64 = false @use_exponential_back_off = get_bool_option("activemq.use_exponential_back_off", "true") @initial_reconnect_delay = Float(get_option("activemq.initial_reconnect_delay", 0.01)) @back_off_multiplier = Integer(get_option("activemq.back_off_multiplier", 2)) @max_reconnect_delay = Float(get_option("activemq.max_reconnect_delay", 30.0)) @reconnect_delay = @initial_reconnect_delay end # Connects to the ActiveMQ middleware def connect(connector = ::Stomp::Connection) if @connection Log.debug("Already connection, not re-initializing connection") return end begin @base64 = get_bool_option("activemq.base64", "false") @msgpriority = get_option("activemq.priority", 0).to_i pools = Integer(get_option("activemq.pool.size")) hosts = [] 1.upto(pools) do |poolnum| host = {} host[:host] = get_option("activemq.pool.#{poolnum}.host") host[:port] = get_option("activemq.pool.#{poolnum}.port", 61613).to_i host[:login] = get_env_or_option("STOMP_USER", "activemq.pool.#{poolnum}.user", '') host[:passcode] = get_env_or_option("STOMP_PASSWORD", "activemq.pool.#{poolnum}.password", '') host[:ssl] = get_bool_option("activemq.pool.#{poolnum}.ssl", "false") # if ssl is enabled set :ssl to the hash of parameters if host[:ssl] host[:ssl] = ssl_parameters(poolnum, get_bool_option("activemq.pool.#{poolnum}.ssl.fallback", "false")) end Log.debug("Adding #{host[:host]}:#{host[:port]} to the connection pool") hosts << host end raise "No hosts found for the ActiveMQ connection pool" if hosts.size == 0 connection = {:hosts => hosts} # Various STOMP gem options, defaults here matches defaults for 1.1.6 the meaning of # these can be guessed, the documentation isn't clear connection[:use_exponential_back_off] = @use_exponential_back_off connection[:initial_reconnect_delay] = @initial_reconnect_delay connection[:back_off_multiplier] = @back_off_multiplier connection[:max_reconnect_delay] = @max_reconnect_delay connection[:max_reconnect_attempts] = Integer(get_option("activemq.max_reconnect_attempts", 0)) connection[:randomize] = get_bool_option("activemq.randomize", "false") connection[:backup] = get_bool_option("activemq.backup", "false") connection[:timeout] = Integer(get_option("activemq.timeout", -1)) connection[:connect_timeout] = Integer(get_option("activemq.connect_timeout", 30)) connection[:reliable] = true connection[:connect_headers] = connection_headers connection[:max_hbrlck_fails] = Integer(get_option("activemq.max_hbrlck_fails", 2)) connection[:max_hbread_fails] = Integer(get_option("activemq.max_hbread_fails", 2)) connection[:logger] = EventLogger.new @connection = connector.new(connection) rescue Exception => e raise("Could not connect to ActiveMQ Server: #{e}") end end def stomp_version ::Stomp::Version::STRING end def stomp_version_supports_heartbeat? return Util.versioncmp(stomp_version, "1.2.10") >= 0 end def connection_headers headers = {:"accept-version" => "1.0"} heartbeat_interval = Integer(get_option("activemq.heartbeat_interval", 0)) stomp_1_0_fallback = get_bool_option("activemq.stomp_1_0_fallback", true) headers[:host] = get_option("activemq.vhost", "mcollective") if heartbeat_interval > 0 unless stomp_version_supports_heartbeat? raise("Setting STOMP 1.1 properties like heartbeat intervals require at least version 1.2.10 of the STOMP gem") end if heartbeat_interval < 30 Log.warn("Connection heartbeat is set to %d, forcing to minimum value of 30s") heartbeat_interval = 30 end heartbeat_interval = heartbeat_interval * 1000 headers[:"heart-beat"] = "%d,%d" % [heartbeat_interval + 500, heartbeat_interval - 500] if stomp_1_0_fallback headers[:"accept-version"] = "1.1,1.0" else headers[:"accept-version"] = "1.1" end else if stomp_version_supports_heartbeat? Log.info("Connecting without STOMP 1.1 heartbeats, if you are using ActiveMQ 5.8 or newer consider setting plugin.activemq.heartbeat_interval") end end headers end # Sets the SSL paramaters for a specific connection def ssl_parameters(poolnum, fallback) params = {:cert_file => get_cert_file(poolnum), :key_file => get_key_file(poolnum), :ts_files => get_option("activemq.pool.#{poolnum}.ssl.ca", false)} raise "cert, key and ca has to be supplied for verified SSL mode" unless params[:cert_file] && params[:key_file] && params[:ts_files] raise "Cannot find certificate file #{params[:cert_file]}" unless File.exist?(params[:cert_file]) raise "Cannot find key file #{params[:key_file]}" unless File.exist?(params[:key_file]) params[:ts_files].split(",").each do |ca| raise "Cannot find CA file #{ca}" unless File.exist?(ca) end begin Stomp::SSLParams.new(params) rescue NameError raise "Stomp gem >= 1.2.2 is needed" end rescue Exception => e if fallback Log.warn("Failed to set full SSL verified mode, falling back to unverified: #{e.class}: #{e}") return true else Log.error("Failed to set full SSL verified mode: #{e.class}: #{e}") raise(e) end end # Returns the name of the private key file used by ActiveMQ # Will first check if an environment variable MCOLLECTIVE_ACTIVEMQ_POOLX_SSL_KEY exists, # where X is the ActiveMQ pool number. # If the environment variable doesn't exist, it will try and load the value from the config. def get_key_file(poolnum) ENV["MCOLLECTIVE_ACTIVEMQ_POOL%s_SSL_KEY" % poolnum] || get_option("activemq.pool.#{poolnum}.ssl.key", false) end # Returns the name of the certficate file used by ActiveMQ # Will first check if an environment variable MCOLLECTIVE_ACTIVEMQ_POOLX_SSL_CERT exists, # where X is the ActiveMQ pool number. # If the environment variable doesn't exist, it will try and load the value from the config. def get_cert_file(poolnum) ENV["MCOLLECTIVE_ACTIVEMQ_POOL%s_SSL_CERT" % poolnum] || get_option("activemq.pool.#{poolnum}.ssl.cert", false) end # Calculate the exponential backoff needed def exponential_back_off if !@use_exponential_back_off return nil end backoff = @reconnect_delay # calculate next delay @reconnect_delay = @reconnect_delay * @back_off_multiplier # cap at max reconnect delay if @reconnect_delay > @max_reconnect_delay @reconnect_delay = @max_reconnect_delay end return backoff end # Receives a message from the ActiveMQ connection def receive Log.debug("Waiting for a message from ActiveMQ") # When the Stomp library > 1.2.0 is mid reconnecting due to its reliable connection # handling it sets the connection to closed. If we happen to be receiving at just # that time we will get an exception warning about the closed connection so handling # that here with a sleep and a retry. begin msg = @connection.receive rescue ::Stomp::Error::NoCurrentConnection sleep 1 retry end # In older stomp gems an attempt to receive after failed authentication can return nil if msg.nil? raise MessageNotReceived.new(exponential_back_off), "No message received from ActiveMQ." end # We expect all messages we get to be of STOMP frame type MESSAGE, raise on unexpected types if msg.command != 'MESSAGE' Log.debug("Unexpected '#{msg.command}' frame. Headers: #{msg.headers.inspect} Body: #{msg.body.inspect}") raise UnexpectedMessageType.new(exponential_back_off), "Received frame of type '#{msg.command}' expected 'MESSAGE'" end Message.new(msg.body, msg, :base64 => @base64, :headers => msg.headers) end # Sends a message to the ActiveMQ connection def publish(msg) msg.base64_encode! if @base64 target = target_for(msg) if msg.type == :direct_request msg.discovered_hosts.each do |node| target[:headers] = headers_for(msg, node) Log.debug("Sending a direct message to ActiveMQ target '#{target[:name]}' with headers '#{target[:headers].inspect}'") @connection.publish(target[:name], msg.payload, target[:headers]) end else target[:headers].merge!(headers_for(msg)) Log.debug("Sending a broadcast message to ActiveMQ target '#{target[:name]}' with headers '#{target[:headers].inspect}'") @connection.publish(target[:name], msg.payload, target[:headers]) end end # Subscribe to a topic or queue def subscribe(agent, type, collective) source = make_target(agent, type, collective) unless @subscriptions.include?(source[:id]) Log.debug("Subscribing to #{source[:name]} with headers #{source[:headers].inspect.chomp}") @connection.subscribe(source[:name], source[:headers], source[:id]) @subscriptions << source[:id] end rescue ::Stomp::Error::DuplicateSubscription Log.error("Received subscription request for #{source.inspect.chomp} but already had a matching subscription, ignoring") end # UnSubscribe to a topic or queue def unsubscribe(agent, type, collective) source = make_target(agent, type, collective) Log.debug("Unsubscribing from #{source[:name]}") @connection.unsubscribe(source[:name], source[:headers], source[:id]) @subscriptions.delete(source[:id]) end def target_for(msg) if msg.type == :reply target = {:name => msg.request.headers["reply-to"], :headers => {}} elsif [:request, :direct_request].include?(msg.type) target = make_target(msg.agent, msg.type, msg.collective) else raise "Don't now how to create a target for message type #{msg.type}" end return target end # Disconnects from the ActiveMQ connection def disconnect Log.debug("Disconnecting from ActiveMQ") @connection.disconnect @connection = nil end def headers_for(msg, identity=nil) headers = {} headers = {"priority" => @msgpriority} if @msgpriority > 0 headers["timestamp"] = (Time.now.utc.to_i * 1000).to_s # set the expires header based on the TTL, we build a small additional # timeout of 10 seconds in here to allow for network latency etc headers["expires"] = ((Time.now.utc.to_i + msg.ttl + 10) * 1000).to_s if [:request, :direct_request].include?(msg.type) target = make_target(msg.agent, :reply, msg.collective) if msg.reply_to headers["reply-to"] = msg.reply_to else headers["reply-to"] = target[:name] end headers["mc_identity"] = identity if msg.type == :direct_request end return headers end def make_target(agent, type, collective) raise("Unknown target type #{type}") unless [:directed, :broadcast, :reply, :request, :direct_request].include?(type) raise("Unknown collective '#{collective}' known collectives are '#{@config.collectives.join ', '}'") unless @config.collectives.include?(collective) target = {:name => nil, :headers => {}} case type when :reply target[:name] = ["/queue/" + collective, :reply, "#{Config.instance.identity}_#{$$}", Client.request_sequence].join(".") when :broadcast target[:name] = ["/topic/" + collective, agent, :agent].join(".") when :request target[:name] = ["/topic/" + collective, agent, :agent].join(".") when :direct_request target[:name] = ["/queue/" + collective, :nodes].join(".") when :directed target[:name] = ["/queue/" + collective, :nodes].join(".") target[:headers]["selector"] = "mc_identity = '#{@config.identity}'" target[:id] = "%s_directed_to_identity" % collective end target[:id] = target[:name] unless target[:id] target end # looks in the environment first then in the config file # for a specific option, accepts an optional default. # # raises an exception when it cant find a value anywhere def get_env_or_option(env, opt, default=nil) return ENV[env] if ENV.include?(env) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default if default raise("No #{env} environment or plugin.#{opt} configuration option given") end # looks for a config option, accepts an optional default # # raises an exception when it cant find a value anywhere def get_option(opt, default=nil) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default unless default.nil? raise("No plugin.#{opt} configuration option given") end # looks up a boolean value in the config def get_bool_option(val, default) Util.str_to_bool(@config.pluginconf.fetch(val, default)) end end end end # vi:tabstop=4:expandtab:ai mcollective-2.6.0/plugins/mcollective/connector/rabbitmq.ddl0000644000175000017500000000062212375446407023316 0ustar jonasjonasmetadata :name => "RabbitMQ Connector", :description => "Connector plugin for RabbitMQ middleware", :author => "Puppet Labs", :license => "ASL 2.0", :version => "1.0.0", :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", :timeout => 60 requires :mcollective => "2.6.0" mcollective-2.6.0/plugins/mcollective/connector/rabbitmq.rb0000644000175000017500000004256412375446407023171 0ustar jonasjonasrequire 'stomp' module MCollective module Connector class Rabbitmq e end # Stomp 1.1+ - heart beat send (transmit) failed. def on_hbwrite_fail(params, ticker_data) Log.error("Heartbeat write failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) rescue Exception => e end # Log heart beat fires def on_hbfire(params, srind, curt) case srind when "receive_fire" Log.debug("Received heartbeat from %s: %s, %s" % [stomp_url(params), srind, curt]) when "send_fire" Log.debug("Publishing heartbeat to %s: %s, %s" % [stomp_url(params), srind, curt]) end rescue Exception => e end def stomp_url(params) "%s://%s@%s:%d" % [ params[:cur_ssl] ? "stomp+ssl" : "stomp", params[:cur_login], params[:cur_host], params[:cur_port]] end end def initialize @config = Config.instance @subscriptions = [] @base64 = false @use_exponential_back_off = get_bool_option("rabbitmq.use_exponential_back_off", "true") @initial_reconnect_delay = Float(get_option("rabbitmq.initial_reconnect_delay", 0.01)) @back_off_multiplier = Integer(get_option("rabbitmq.back_off_multiplier", 2)) @max_reconnect_delay = Float(get_option("rabbitmq.max_reconnect_delay", 30.0)) @reconnect_delay = @initial_reconnect_delay end # Connects to the RabbitMQ middleware def connect(connector = ::Stomp::Connection) if @connection Log.debug("Already connection, not re-initializing connection") return end begin @base64 = get_bool_option("rabbitmq.base64", "false") pools = Integer(get_option("rabbitmq.pool.size")) hosts = [] 1.upto(pools) do |poolnum| host = {} host[:host] = get_option("rabbitmq.pool.#{poolnum}.host") host[:port] = get_option("rabbitmq.pool.#{poolnum}.port", 61613).to_i host[:login] = get_env_or_option("STOMP_USER", "rabbitmq.pool.#{poolnum}.user", '') host[:passcode] = get_env_or_option("STOMP_PASSWORD", "rabbitmq.pool.#{poolnum}.password", '') host[:ssl] = get_bool_option("rabbitmq.pool.#{poolnum}.ssl", "false") # if ssl is enabled set :ssl to the hash of parameters if host[:ssl] host[:ssl] = ssl_parameters(poolnum, get_bool_option("rabbitmq.pool.#{poolnum}.ssl.fallback", "false")) end Log.debug("Adding #{host[:host]}:#{host[:port]} to the connection pool") hosts << host end raise "No hosts found for the RabbitMQ connection pool" if hosts.size == 0 connection = {:hosts => hosts} # Various STOMP gem options, defaults here matches defaults for 1.1.6 the meaning of # these can be guessed, the documentation isn't clear connection[:use_exponential_back_off] = @use_exponential_back_off connection[:initial_reconnect_delay] = @initial_reconnect_delay connection[:back_off_multiplier] = @back_off_multiplier connection[:max_reconnect_delay] = @max_reconnect_delay connection[:max_reconnect_attempts] = Integer(get_option("rabbitmq.max_reconnect_attempts", 0)) connection[:randomize] = get_bool_option("rabbitmq.randomize", "false") connection[:backup] = get_bool_option("rabbitmq.backup", "false") connection[:timeout] = Integer(get_option("rabbitmq.timeout", -1)) connection[:connect_timeout] = Integer(get_option("rabbitmq.connect_timeout", 30)) connection[:reliable] = true connection[:max_hbrlck_fails] = Integer(get_option("rabbitmq.max_hbrlck_fails", 2)) connection[:max_hbread_fails] = Integer(get_option("rabbitmq.max_hbread_fails", 2)) connection[:connect_headers] = connection_headers connection[:logger] = EventLogger.new @connection = connector.new(connection) rescue Exception => e raise("Could not connect to RabbitMQ Server: #{e}") end end def connection_headers headers = {:"accept-version" => "1.0"} heartbeat_interval = Integer(get_option("rabbitmq.heartbeat_interval", 0)) stomp_1_0_fallback = get_bool_option("rabbitmq.stomp_1_0_fallback", true) headers[:host] = get_option("rabbitmq.vhost", "/") if heartbeat_interval > 0 unless stomp_version_supports_heartbeat? raise("Setting STOMP 1.1 properties like heartbeat intervals require at least version 1.2.10 of the STOMP gem") end if heartbeat_interval < 30 Log.warn("Connection heartbeat is set to %d, forcing to minimum value of 30s") heartbeat_interval = 30 end heartbeat_interval = heartbeat_interval * 1000 headers[:"heart-beat"] = "%d,%d" % [heartbeat_interval + 500, heartbeat_interval - 500] if stomp_1_0_fallback headers[:"accept-version"] = "1.1,1.0" else headers[:"accept-version"] = "1.1" end else if stomp_version_supports_heartbeat? Log.info("Connecting without STOMP 1.1 heartbeats, consider setting plugin.rabbitmq.heartbeat_interval") end end headers end def stomp_version ::Stomp::Version::STRING end def stomp_version_supports_heartbeat? return Util.versioncmp(stomp_version, "1.2.10") >= 0 end # Sets the SSL paramaters for a specific connection def ssl_parameters(poolnum, fallback) params = {:cert_file => get_cert_file(poolnum), :key_file => get_key_file(poolnum), :ts_files => get_option("rabbitmq.pool.#{poolnum}.ssl.ca", false)} raise "cert, key and ca has to be supplied for verified SSL mode" unless params[:cert_file] && params[:key_file] && params[:ts_files] raise "Cannot find certificate file #{params[:cert_file]}" unless File.exist?(params[:cert_file]) raise "Cannot find key file #{params[:key_file]}" unless File.exist?(params[:key_file]) params[:ts_files].split(",").each do |ca| raise "Cannot find CA file #{ca}" unless File.exist?(ca) end begin Stomp::SSLParams.new(params) rescue NameError raise "Stomp gem >= 1.2.2 is needed" end rescue Exception => e if fallback Log.warn("Failed to set full SSL verified mode, falling back to unverified: #{e.class}: #{e}") return true else Log.error("Failed to set full SSL verified mode: #{e.class}: #{e}") raise(e) end end # Returns the name of the private key file used by RabbitMQ # Will first check if an environment variable MCOLLECTIVE_RABBITMQ_POOLX_SSL_KEY exists, # where X is the RabbitMQ pool number. # If the environment variable doesn't exist, it will try and load the value from the config. def get_key_file(poolnum) ENV["MCOLLECTIVE_RABBITMQ_POOL%s_SSL_KEY" % poolnum] || get_option("rabbitmq.pool.#{poolnum}.ssl.key", false) end # Returns the name of the certificate file used by RabbitMQ # Will first check if an environment variable MCOLLECTIVE_RABBITMQ_POOLX_SSL_CERT exists, # where X is the RabbitMQ pool number. # If the environment variable doesn't exist, it will try and load the value from the config. def get_cert_file(poolnum) ENV["MCOLLECTIVE_RABBITMQ_POOL%s_SSL_CERT" % poolnum] || get_option("rabbitmq.pool.#{poolnum}.ssl.cert", false) end # Calculate the exponential backoff needed def exponential_back_off if !@use_exponential_back_off return nil end backoff = @reconnect_delay # calculate next delay @reconnect_delay = @reconnect_delay * @back_off_multiplier # cap at max reconnect delay if @reconnect_delay > @max_reconnect_delay @reconnect_delay = @max_reconnect_delay end return backoff end # Receives a message from the RabbitMQ connection def receive Log.debug("Waiting for a message from RabbitMQ") # When the Stomp library > 1.2.0 is mid reconnecting due to its reliable connection # handling it sets the connection to closed. If we happen to be receiving at just # that time we will get an exception warning about the closed connection so handling # that here with a sleep and a retry. begin msg = @connection.receive rescue ::Stomp::Error::NoCurrentConnection sleep 1 retry end # In older stomp gems an attempt to receive after failed authentication can return nil if msg.nil? raise MessageNotReceived.new(exponential_back_off), "No message received from RabbitMQ." end raise "Received a processing error from RabbitMQ: '%s'" % msg.body.chomp if msg.body =~ /Processing error/ # We expect all messages we get to be of STOMP frame type MESSAGE, raise on unexpected types if msg.command != 'MESSAGE' Log.debug("Unexpected '#{msg.command}' frame. Headers: #{msg.headers.inspect} Body: #{msg.body.inspect}") raise UnexpectedMessageType.new(exponential_back_off), "Received frame of type '#{msg.command}' expected 'MESSAGE'" end Message.new(msg.body, msg, :base64 => @base64, :headers => msg.headers) end # Sends a message to the RabbitMQ connection def publish(msg) msg.base64_encode! if @base64 if msg.type == :direct_request msg.discovered_hosts.each do |node| target = target_for(msg, node) Log.debug("Sending a direct message to RabbitMQ target '#{target[:name]}' with headers '#{target[:headers].inspect}'") @connection.publish(target[:name], msg.payload, target[:headers]) end else target = target_for(msg) Log.debug("Sending a broadcast message to RabbitMQ target '#{target[:name]}' with headers '#{target[:headers].inspect}'") @connection.publish(target[:name], msg.payload, target[:headers]) end end def target_for(msg, node=nil) if msg.type == :reply target = {:name => msg.request.headers["reply-to"], :headers => {}, :id => ""} elsif [:request, :direct_request].include?(msg.type) target = make_target(msg.agent, msg.type, msg.collective, msg.reply_to, node) else raise "Don't now how to create a target for message type #{msg.type}" end # marks messages as valid for ttl + 10 seconds, we do this here # rather than in make_target as this should only be set on publish target[:headers]["expiration"] = ((msg.ttl + 10) * 1000).to_s return target end def make_target(agent, type, collective, reply_to=nil, node=nil) raise("Unknown target type #{type}") unless [:directed, :broadcast, :reply, :request, :direct_request].include?(type) raise("Unknown collective '#{collective}' known collectives are '#{@config.collectives.join ', '}'") unless @config.collectives.include?(collective) target = {:name => "", :headers => {}, :id => nil} if reply_to reply_path = reply_to elsif get_bool_option("rabbitmq.use_reply_exchange", false) reply_path = "/exchange/mcollective_reply/%s_%s_%s" % [ @config.identity, $$, Client.request_sequence ] else reply_path = "/temp-queue/mcollective_reply_%s" % agent end case type when :reply # receiving replies on a temp queue target[:name] = reply_path target[:id] = "mcollective_%s_replies" % agent when :broadcast, :request # publishing a request to all nodes with an agent target[:name] = "/exchange/%s_broadcast/%s" % [collective, agent] if reply_to target[:headers]["reply-to"] = reply_to else target[:headers]["reply-to"] = reply_path end target[:id] = "%s_broadcast_%s" % [collective, agent] when :direct_request # a request to a specific node raise "Directed requests need to have a node identity" unless node target[:name] = "/exchange/%s_directed/%s" % [ collective, node] target[:headers]["reply-to"] = reply_path when :directed # subscribing to directed messages target[:name] = "/exchange/%s_directed/%s" % [ collective, @config.identity ] target[:id] = "%s_%s_directed_to_identity" % [ collective, @config.identity ] end target end # Subscribe to a topic or queue def subscribe(agent, type, collective) if type == :reply # On rabbitmq if you send a message with a reply-to: header set to # '/temp-queue/*' it automatically creates a private queue, munges # the reply-to: header to point to this private queue, and # subscribes you to it. As such you should never attempt to # SUBSCRIBE or UNSUBSCRIBE to '/temp-queue/*' directly as that'll # cause great pain and suffering. # https://www.rabbitmq.com/stomp.html#d.tqd # The exception to this is in 'use_reply_exchange' mode, when the # reply-to will be set to a queue in an explicit exchange. if !get_bool_option("rabbitmq.use_reply_exchange", false) # We aren't in 'use_reply_exchange' mode, don't subscribe. return end end source = make_target(agent, type, collective) unless @subscriptions.include?(source[:id]) Log.debug("Subscribing to #{source[:name]} with headers #{source[:headers].inspect.chomp}") @connection.subscribe(source[:name], source[:headers], source[:id]) @subscriptions << source[:id] end rescue ::Stomp::Error::DuplicateSubscription Log.error("Received subscription request for #{source.inspect.chomp} but already had a matching subscription, ignoring") end # Subscribe to a topic or queue def unsubscribe(agent, type, collective) if type == :reply # For a more detailed discussion of this logic, please see #subscribe if !get_bool_option("rabbitmq.use_reply_exchange", false) # We shouldn't try to unsubscribe from a '/temp-queue/*' queue. return end end source = make_target(agent, type, collective) Log.debug("Unsubscribing from #{source[:name]}") @connection.unsubscribe(source[:name], source[:headers], source[:id]) @subscriptions.delete(source[:id]) end # Disconnects from the RabbitMQ connection def disconnect Log.debug("Disconnecting from RabbitMQ") @connection.disconnect @connection = nil end # looks in the environment first then in the config file # for a specific option, accepts an optional default. # # raises an exception when it cant find a value anywhere def get_env_or_option(env, opt, default=nil) return ENV[env] if ENV.include?(env) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default if default raise("No #{env} environment or plugin.#{opt} configuration option given") end # looks for a config option, accepts an optional default # # raises an exception when it cant find a value anywhere def get_option(opt, default=nil) return @config.pluginconf[opt] if @config.pluginconf.include?(opt) return default unless default.nil? raise("No plugin.#{opt} configuration option given") end # looks up a boolean value in the config def get_bool_option(val, default) Util.str_to_bool(@config.pluginconf.fetch(val, default)) end end end end # vi:tabstop=4:expandtab:ai mcollective-2.6.0/plugins/mcollective/connector/activemq.ddl0000644000175000017500000000062212375446407023326 0ustar jonasjonasmetadata :name => "ActiveMQ Connector", :description => "Connector plugin for ActiveMQ middleware", :author => "Puppet Labs", :license => "ASL 2.0", :version => "1.0.0", :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", :timeout => 60 requires :mcollective => "2.6.0" mcollective-2.6.0/plugins/mcollective/aggregate/0000755000175000017500000000000012375446416020764 5ustar jonasjonasmcollective-2.6.0/plugins/mcollective/aggregate/sum.ddl0000644000175000017500000000127412375446407022261 0ustar jonasjonasmetadata :name => "Sum", :description => "Determine the total added value of a set of values", :author => "Pieter Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", :timeout => 5 usage <<-USAGE This aggregate plugin will determine the total added value of a set of values. DDL Example: summarize do aggregate sum(:value) end Sample Output: host1.example.com Value: 10 host2.example.com Value: 20 Summary of Value: Sum of Value: 30 USAGE mcollective-2.6.0/plugins/mcollective/aggregate/sum.rb0000644000175000017500000000071312375446407022116 0ustar jonasjonasmodule MCollective class Aggregate class Sum "summary", :description => "Displays the summary of a set of results", :author => "Pieter Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", :timeout => 5 usage <<-USAGE This aggregate plugin will display the summary of a set of results. DDL Example: summarize do aggregate summary(:value) end Sample Output: host1.example.com Property: collectives Value: ["mcollective", "uk_collective"] Summary of Value: mcollective = 25 uk_collective = 15 fr_collective = 9 us_collective = 1 USAGE mcollective-2.6.0/plugins/mcollective/aggregate/summary.rb0000644000175000017500000000256312375446407023014 0ustar jonasjonasmodule MCollective class Aggregate class Summary "average", :description => "Displays the average of a set of numeric values", :author => "Pieter Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "http://projects.puppetlabs.com/projects/mcollective-plugins/wiki", :timeout => 5 usage <<-USAGE This aggregate plugin will determine te average of a set of numeric values. DDL Example: summarize do aggregate average(:value) end Sample Output: host1.example.com Value: 10 host2.example.com Value: 20 Summary of Value: Average of Value: 15.000000 USAGE mcollective-2.6.0/COPYING0000644000175000017500000002612412375446407014107 0ustar jonasjonas Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2010, 2011 Puppet Labs Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. mcollective-2.6.0/install.rb0000755000175000017500000002231212375446407015045 0ustar jonasjonas#! /usr/bin/env ruby #-- # Copyright 2004 Austin Ziegler # Install utility. Based on the original installation script for rdoc by the # Pragmatic Programmers. # # This program is free software. It may be redistributed and/or modified under # the terms of the GPL version 2 (or later) or the Ruby licence. # # Usage # ----- # In most cases, if you have a typical project layout, you will need to do # absolutely nothing to make this work for you. This layout is: # # bin/ # executable files -- "commands" # lib/ # the source of the library # # The default behaviour: # 1) Build Rdoc documentation from all files in bin/ (excluding .bat and .cmd), # all .rb files in lib/, ./README, ./ChangeLog, and ./Install. # and all .rb files in lib/. # 2) Install configuration files in etc/. # 3) Install commands from bin/ into the Ruby bin directory. # 4) Install system commands from bin/ into the Ruby sbin directory. # 5) Install all library files from lib/ into Ruby's site_lib/version # directory. # 6) Install all plugins from plugins/ into the plugins directory # (usually $libexecdir/mcollective). # #++ require 'rbconfig' require 'find' require 'fileutils' require 'tempfile' require 'optparse' require 'ostruct' include FileUtils begin require 'rdoc/rdoc' $haverdoc = true rescue LoadError puts "Missing rdoc; skipping documentation" $haverdoc = false end if (defined?(RbConfig) ? RbConfig : Config)::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/i $stderr.puts "install.rb does not support Microsoft Windows. See ext/windows/README.md for information on installing on Microsoft Windows." exit(-1) end PREREQS = %w{} InstallOptions = OpenStruct.new def glob(list) g = list.map { |i| Dir.glob(i) } g.flatten! g.compact! g.uniq! g end def check_prereqs PREREQS.each do |pre| begin require pre rescue LoadError puts "Could not load #{pre} Ruby library; cannot install" exit(-1) end end end def do_configs(configs, target, strip = 'etc/') Dir.mkdir(target) unless File.directory? target configs.each do |cf| ocf = File.join(target, cf.gsub(Regexp.new(strip), '')) oc = File.dirname(ocf) makedirs(oc, {:mode => 0755, :verbose => true}) install(cf, ocf, {:mode => 0644, :preserve => true, :verbose => true}) end end def do_bins(bins, target, strip = 's?bin/') Dir.mkdir(target) unless File.directory? target bins.each do |bf| obf = bf.gsub(/#{strip}/, '') install_binfile(bf, obf, target) end end def do_libs(libs, target, strip = 'lib/') libs.each do |lf| olf = File.join(target, lf.sub(/^#{strip}/, '')) op = File.dirname(olf) if File.directory?(lf) makedirs(olf, {:mode => 0755, :verbose => true}) else makedirs(op, {:mode => 0755, :verbose => true}) install(lf, olf, {:mode => 0644, :preserve => true, :verbose => true}) end end end ## # Prepare the file installation. # def prepare_installation InstallOptions.configs = true # Only try to do docs if we're sure they have rdoc if $haverdoc InstallOptions.rdoc = true else InstallOptions.rdoc = false end ARGV.options do |opts| opts.banner = "Usage: #{File.basename($0)} [options]" opts.separator "" opts.on('--[no-]rdoc', 'Creation of RDoc output.', 'Default is create rdoc.') do |onrdoc| InstallOptions.rdoc = onrdoc end opts.on('--[no-]configs', 'Installation of config files', 'Default is install configs.') do |onconfigs| InstallOptions.configs = onconfigs end opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir| InstallOptions.destdir = destdir end opts.on('--configdir[=OPTIONAL]', 'Installation directory for config files', 'Default /etc/mcollective') do |configdir| InstallOptions.configdir = configdir end opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides RbConfig::CONFIG["bindir"]') do |bindir| InstallOptions.bindir = bindir end opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides RbConfig::CONFIG["sbindir"]') do |sbindir| InstallOptions.sbindir = sbindir end opts.on('--ruby[=OPTIONAL]', 'Ruby interpreter to use with installation', 'overrides ruby used to call install.rb') do |ruby| InstallOptions.ruby = ruby end opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides RbConfig::CONFIG["sitelibdir"]') do |sitelibdir| InstallOptions.sitelibdir = sitelibdir end opts.on('--plugindir[=OPTIONAL]', 'Installation directory for plugins', 'Default /usr/libexec/mcollective') do |plugindir| InstallOptions.plugindir = plugindir end opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick| InstallOptions.rdoc = false InstallOptions.ri = false InstallOptions.configs = true end opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full| InstallOptions.rdoc = true InstallOptions.ri = true InstallOptions.configs = true end opts.separator("") opts.on_tail('--help', "Shows this help text.") do $stderr.puts opts exit end opts.parse! end version = [RbConfig::CONFIG["MAJOR"], RbConfig::CONFIG["MINOR"]].join(".") libdir = File.join(RbConfig::CONFIG["libdir"], "ruby", version) # Mac OS X 10.5 and higher declare bindir # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin # which is not generally where people expect executables to be installed # These settings are appropriate defaults for all OS X versions. if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/ RbConfig::CONFIG['bindir'] = "/usr/bin" RbConfig::CONFIG['sbindir'] = "/usr/sbin" end if InstallOptions.configdir configdir = InstallOptions.configdir else configdir = "/etc/mcollective" end if InstallOptions.bindir bindir = InstallOptions.bindir else bindir = RbConfig::CONFIG['bindir'] end if InstallOptions.sbindir sbindir = InstallOptions.sbindir else sbindir = RbConfig::CONFIG['sbindir'] end if InstallOptions.sitelibdir sitelibdir = InstallOptions.sitelibdir else sitelibdir = RbConfig::CONFIG["sitelibdir"] if sitelibdir.nil? sitelibdir = $LOAD_PATH.find { |x| x =~ /site_ruby/ } if sitelibdir.nil? sitelibdir = File.join(libdir, "site_ruby") elsif sitelibdir !~ Regexp.quote(version) sitelibdir = File.join(sitelibdir, version) end end end if InstallOptions.plugindir plugindir = InstallOptions.plugindir else plugindir = "/usr/libexec/mcollective" end if InstallOptions.destdir destdir = InstallOptions.destdir else destdir = '' end configdir = File.join(destdir, configdir) bindir = File.join(destdir, bindir) sbindir = File.join(destdir, sbindir) sitelibdir = File.join(destdir, sitelibdir) plugindir = File.join(destdir, plugindir) makedirs(configdir) if InstallOptions.configs makedirs(bindir) makedirs(sbindir) makedirs(sitelibdir) makedirs(plugindir) InstallOptions.sitelibdir = sitelibdir InstallOptions.configdir = configdir InstallOptions.bindir = bindir InstallOptions.sbindir = sbindir InstallOptions.plugindir = plugindir end ## # Build the rdoc documentation. # def build_rdoc(files) return unless $haverdoc begin r = RDoc::RDoc.new r.document(["--main", "MCollective", "--line-numbers"] + files) rescue RDoc::RDocError => e $stderr.puts e.message rescue Exception => e $stderr.puts "Couldn't build RDoc documentation\n#{e.message}" end end ## # Install file(s) from ./bin to RbConfig::CONFIG['bindir']. Patch it on the way # to insert a #! line; on a Unix install, the command is named as expected def install_binfile(from, op_file, target) tmp_file = Tempfile.new('mcollective-binfile') if InstallOptions.ruby ruby = InstallOptions.ruby else ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name']) end File.open(from) do |ip| File.open(tmp_file.path, "w") do |op| op.puts "#!#{ruby}" contents = ip.readlines contents.shift if contents[0] =~ /^#!/ op.write contents.join end end install(tmp_file.path, File.join(target, op_file), :mode => 0755, :preserve => true, :verbose => true) tmp_file.unlink end # Change directory into the mcollective root so we don't get the wrong files for install. cd File.dirname(__FILE__) do # Set these values to what you want installed. configs = glob(%w{etc/*.dist}) erbs = glob(%w{etc/*.erb}) bins = glob(%w{bin/mco}) sbins = glob(%w{bin/mcollectived}) rdoc = glob(%w{bin/* lib/**/*.rb README* }) libs = glob(%w{lib/**/*}) plugins = glob(%w{plugins/**/*}) check_prereqs prepare_installation build_rdoc(rdoc) if InstallOptions.rdoc do_configs(configs, InstallOptions.configdir, 'etc/|\.dist') if InstallOptions.configs do_configs(erbs, InstallOptions.configdir) if InstallOptions.configs do_bins(bins, InstallOptions.bindir) do_bins(sbins, InstallOptions.sbindir) do_libs(libs, InstallOptions.sitelibdir) do_libs(plugins, InstallOptions.plugindir, 'plugins/') end mcollective-2.6.0/mcollective.init0000755000175000017500000000555712375446407016261 0ustar jonasjonas#!/bin/sh # # mcollective Application Server for STOMP based agents # # chkconfig: 345 24 76 # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # ### BEGIN INIT INFO # Provides: mcollective # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO mcollectived="/usr/sbin/mcollectived" # Lockfile if [ -d /var/lock/subsys ]; then # RedHat/CentOS/etc who use subsys lock="/var/lock/subsys/mcollective" else # The rest of them lock="/var/lock/mcollective" fi # PID directory pidfile="/var/run/mcollectived.pid" # Source function library. . /lib/lsb/init-functions # Check that binary exists if ! [ -f $mcollectived ] then echo "mcollectived binary not found" exit 0 fi # See how we were called. case "$1" in start) echo -n "Starting mcollective: " if [ -f ${lock} ]; then # we were not shut down correctly if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} rm -f ${lock} sleep 2 fi rm -f ${pidfile} ${mcollectived} --pid=${pidfile} --config="/etc/mcollective/server.cfg" --daemonize if [ $? = 0 ]; then log_success_msg touch $lock exit 0 else log_failure_msg exit 1 fi ;; stop) echo -n "Shutting down mcollective: " if [ -s ${pidfile} ]; then kill `cat ${pidfile}` >/dev/null 2>&1 fi rm -f ${pidfile} log_success_msg rm -f $lock ;; restart) $0 stop sleep 2 $0 start ;; condrestart) if [ -f $lock ]; then $0 stop # avoid race sleep 2 $0 start fi ;; status) if [ -f ${lock} ]; then if [ -s ${pidfile} ]; then if [ -e /proc/`cat ${pidfile}` ]; then echo "mcollectived (`cat ${pidfile}`) is running" exit 0 else echo "mcollectived (`cat ${pidfile}`) is NOT running" exit 1 fi fi else echo "mcollectived: service not started" exit 1 fi ;; force-reload) echo "not implemented" ;; *) echo "Usage: mcollectived {start|stop|restart|condrestart|status}" exit 1 ;; esac exit 0 mcollective-2.6.0/bin/0000755000175000017500000000000012375446416013617 5ustar jonasjonasmcollective-2.6.0/bin/mcollectived0000755000175000017500000000350212375446407016217 0ustar jonasjonas#!/usr/bin/env ruby # For security reasons, ensure that '.' is not on the load path # This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path $LOAD_PATH.delete '.' require 'mcollective' require 'getoptlong' opts = GetoptLong.new( [ '--daemonize', GetoptLong::NO_ARGUMENT], [ '--no-daemonize', GetoptLong::NO_ARGUMENT], [ '--help', '-h', GetoptLong::NO_ARGUMENT ], [ '--config', '-c', GetoptLong::REQUIRED_ARGUMENT], [ '--pidfile', '-p', GetoptLong::REQUIRED_ARGUMENT] ) daemonize = nil if MCollective::Util.windows? configfile = File.join(MCollective::Util.windows_prefix, "etc", "server.cfg") else configfile = "/etc/mcollective/server.cfg" end pid = "" opts.each do |opt, arg| case opt when '--help' puts "Usage: mcollectived.rb [--config /path/to/config] [--pidfile /path/to/pid] [--daemonize | --no-daemonize]" exit when '--config' configfile = arg when '--pidfile' pid = arg when '--daemonize' daemonize = true when '--no-daemonize' daemonize = false end end config = MCollective::Config.instance config.loadconfig(configfile) unless config.configured # If daemonize has not been set on the cli, reach into the config file to decide if daemonize == nil daemonize = config.daemonize end MCollective::Log.info("The Marionette Collective #{MCollective::VERSION} started logging at #{config.loglevel} level") if daemonize MCollective::Log.debug("Starting in the background (#{config.daemonize})") if MCollective::Util.windows? require 'mcollective/windows_daemon' MCollective::WindowsDaemon.daemonize_runner else require 'mcollective/unix_daemon' MCollective::UnixDaemon.daemonize_runner(pid) end else MCollective::Log.debug("Starting in the foreground") runner = MCollective::Runner.new(configfile) runner.main_loop end mcollective-2.6.0/bin/mco0000755000175000017500000000202712375446407014324 0ustar jonasjonas#!/usr/bin/env ruby # For security reasons, ensure that '.' is not on the load path # This is primarily for 1.8.7 since 1.9.2+ doesn't put '.' on the load path $LOAD_PATH.delete '.' require 'mcollective' Version = MCollective.version known_applications = MCollective::Applications.list # links from mc-ping to mc will result in ping being run if $0 =~ /mc\-([a-zA-Z\-_\.]+)$/ app_name = $1 else app_name = ARGV.first ARGV.delete_at(0) end if known_applications.include?(app_name) # make sure the various options classes shows the right help etc $0 = app_name MCollective::Applications.run(app_name) else puts "The Marionette Collective version #{MCollective.version}" puts puts "usage: #{$0} command " puts puts "Known commands:" puts known_applications.sort.uniq.in_groups_of(3) do |apps| puts " %-20s %-20s %-20s" % [apps[0], apps[1], apps[2]] end puts puts "Type '#{$0} help' for a detailed list of commands and '#{$0} help command'" puts "to get detailed help for a command" puts end mcollective-2.6.0/etc/0000755000175000017500000000000012375446416013622 5ustar jonasjonasmcollective-2.6.0/etc/data-help.erb0000644000175000017500000000221012375446407016146 0ustar jonasjonas<%= metastring %> QUERY FUNCTION INPUT: % if entities[:data][:input][:query] Description: <%= entities[:data][:input][:query][:description] %> Prompt: <%= entities[:data][:input][:query][:prompt] %> Type: <%= entities[:data][:input][:query][:type] %> % if entities[:data][:input][:query][:type] == :string Validation: <%= entities[:data][:input][:query][:validation] %> Length: <%= entities[:data][:input][:query][:maxlength] %> % elsif entities[:data][:input][:query][:type] == :list Valid Values: <%= entities[:data][:input][:query][:list].join(", ") %> % end % if entities[:data][:input][:query][:default] Default Value: <%= entities[:data][:input][:query][:default] %> % end % else This plugin does not take any input % end QUERY FUNCTION OUTPUT: % entities[:data][:output].keys.sort.each do |output| <%= output %>: Description: <%= entities[:data][:output][output][:description] %> Display As: <%= entities[:data][:output][output][:display_as] %> % end mcollective-2.6.0/etc/client.cfg.dist0000644000175000017500000000071612375446407016527 0ustar jonasjonasmain_collective = mcollective collectives = mcollective libdir = /usr/libexec/mcollective logger_type = console loglevel = warn # Plugins securityprovider = psk plugin.psk = unset connector = activemq plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = stomp1 plugin.activemq.pool.1.port = 6163 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = marionette # Facts factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml mcollective-2.6.0/etc/ssl/0000755000175000017500000000000012375446416014423 5ustar jonasjonasmcollective-2.6.0/etc/ssl/clients/0000755000175000017500000000000012375446416016064 5ustar jonasjonasmcollective-2.6.0/etc/ssl/clients/PLACEHOLDER0000644000175000017500000000000012375446407017517 0ustar jonasjonasmcollective-2.6.0/etc/ssl/PLACEHOLDER0000644000175000017500000000000012375446407016056 0ustar jonasjonasmcollective-2.6.0/etc/facts.yaml.dist0000644000175000017500000000002312375446407016543 0ustar jonasjonas--- mcollective: 1 mcollective-2.6.0/etc/rpc-help.erb0000644000175000017500000000323612375446407016032 0ustar jonasjonas<%= metastring %> ACTIONS: ======== <%= entities.keys.sort.join(", ") %> % entities.keys.sort.each do |action| <%= action %> action: <% (action.size + 8).times do %>-<% end %> <%= entities[action][:description] %> INPUT: % if entities[action][:input].size > 0 % entities[action][:input].keys.sort.each do |input| <%= input %>: Description: <%= entities[action][:input][input][:description] %> Prompt: <%= entities[action][:input][input][:prompt] %> Type: <%= entities[action][:input][input][:type] %> Optional: <%= !!entities[action][:input][input][:optional] %> % if entities[action][:input][input][:type] == :string Validation: <%= entities[action][:input][input][:validation] %> Length: <%= entities[action][:input][input][:maxlength] %> % elsif entities[action][:input][input][:type] == :list Valid Values: <%= entities[action][:input][input][:list].join(", ") %> % end % if entities[action][:input][input][:default] Default Value: <%= entities[action][:input][input][:default] %> % end % end % else This action does not have any inputs % end OUTPUT: % entities[action][:output].keys.sort.each do |output| <%= output %>: Description: <%= entities[action][:output][output][:description] %> Display As: <%= entities[action][:output][output][:display_as] %> % if entities[action][:output][output][:default] Default Value: <%= entities[action][:output][output][:default] %> % end % end % end mcollective-2.6.0/etc/discovery-help.erb0000644000175000017500000000067712375446407017263 0ustar jonasjonas<%= metastring %> DISCOVERY METHOD CAPABILITIES: % [["Filter based on configuration management classes", :classes], % ["Filter based on system facts", :facts], % ["Filter based on mcollective identity", :identity], % ["Filter based on mcollective agents", :agents], % ["Compound filters combining classes and facts", :compound]].each do |cap| % if entities[:discovery][:capabilities].include?(cap.last) <%= cap.first %> % end % end mcollective-2.6.0/etc/metadata-help.erb0000644000175000017500000000067212375446407017027 0ustar jonasjonas<%= meta[:name] %> <%= "=" * meta[:name].size %> <%= meta[:description] %> Author: <%= meta[:author] %> Version: <%= meta[:version] %> License: <%= meta[:license] %> Timeout: <%= meta[:timeout] %> Home Page: <%= meta[:url] %> % if requirements[:mcollective] Requires MCollective <%= requirements[:mcollective] %> or newer % end % unless @usage == "" <%= Util.align_text(@usage, nil, 3) if @usage != "" %> % end mcollective-2.6.0/etc/server.cfg.dist0000644000175000017500000000075112375446407016556 0ustar jonasjonasmain_collective = mcollective collectives = mcollective libdir = /usr/libexec/mcollective logfile = /var/log/mcollective.log loglevel = info daemonize = 1 # Plugins securityprovider = psk plugin.psk = unset connector = activemq plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = stomp1 plugin.activemq.pool.1.port = 6163 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = marionette # Facts factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml mcollective-2.6.0/doc/0000755000175000017500000000000012417211236013577 5ustar jonasjonasmcollective-2.6.0/doc/rdoc.css0000644000175000017500000002417512375446414015264 0ustar jonasjonas/* * "Darkfish" Rdoc CSS * $Id: rdoc.css 54 2009-01-27 01:09:48Z deveiant $ * * Author: Michael Granger * */ /* vim: ft=css et sw=2 ts=2 sts=2 */ /* Base Green is: #6C8C22 */ * { padding: 0; margin: 0; } body { background: #efefef; font: 14px "Helvetica Neue", Helvetica, Tahoma, sans-serif; margin-left: 40px; } body.file-popup { font-size: 90%; margin-left: 0; } h1 { font-size: 300%; text-shadow: rgba(135,145,135,0.65) 2px 2px 3px; color: #6C8C22; } h2,h3,h4 { margin-top: 1.5em; } h1 span, h2 span, h3 span, h4 span, h5 span, h6 span { display: none; padding-left: 1em; font-size: 50%; vertical-align: super; } h1:hover span, h2:hover span, h3:hover span, h4:hover span, h5:hover span, h6:hover span { display: inline; } :link, :visited { color: #6C8C22; text-decoration: none; } :link:hover, :visited:hover { border-bottom: 1px dotted #6C8C22; } pre { background: #ddd; padding: 0.5em 0; } blockquote { background: #ddd; margin: 1em; padding: 0.25em; } blockquote > :first-child { margin-top: 0 !important; } /* @group Generic Classes */ .initially-hidden { display: none; } #search-field { width: 98%; background: #eee; border: none; height: 1.5em; -webkit-border-radius: 4px; } #search-field:focus { background: #f1edba; } #search-field:-moz-placeholder, #search-field::-webkit-input-placeholder { font-weight: bold; color: #666; } .missing-docs { font-size: 120%; background: white url(images/wrench_orange.png) no-repeat 4px center; color: #ccc; line-height: 2em; border: 1px solid #d00; opacity: 1; padding-left: 20px; text-indent: 24px; letter-spacing: 3px; font-weight: bold; -webkit-border-radius: 5px; -moz-border-radius: 5px; } .target-section { border: 2px solid #dcce90; border-left-width: 8px; padding: 0 1em; background: #fff3c2; } /* @end */ /* @group Index Page, Standalone file pages */ .indexpage ul { line-height: 160%; list-style: none; } .indexpage ul :link, .indexpage ul :visited { font-size: 16px; } .indexpage li { padding-left: 20px; } .indexpage ul > li { background: url(images/bullet_black.png) no-repeat left 4px; } .indexpage li.method { background: url(images/plugin.png) no-repeat left 4px; } .indexpage li.module { background: url(images/package.png) no-repeat left 4px; } .indexpage li.class { background: url(images/ruby.png) no-repeat left 4px; } .indexpage li.file { background: url(images/page_white_text.png) no-repeat left 4px; } .indexpage li li { background: url(images/tag_blue.png) no-repeat left 4px; } .indexpage li .toc-toggle { width: 16px; height: 16px; background: url(images/add.png) no-repeat; } .indexpage li .toc-toggle.open { background: url(images/delete.png) no-repeat; } /* @end */ /* @group Top-Level Structure */ #metadata { float: left; width: 260px; } #documentation { margin: 2em 1em 5em 300px; min-width: 340px; } #validator-badges { clear: both; margin: 1em 1em 2em; font-size: smaller; } /* @end */ /* @group Metadata Section */ #metadata .section { background-color: #dedede; -moz-border-radius: 5px; -webkit-border-radius: 5px; border: 1px solid #aaa; margin: 0 8px 8px; font-size: 90%; overflow: hidden; } #metadata h3.section-header { margin: 0; padding: 2px 8px; background: #ccc; color: #666; -moz-border-radius-topleft: 4px; -moz-border-radius-topright: 4px; -webkit-border-top-left-radius: 4px; -webkit-border-top-right-radius: 4px; border-bottom: 1px solid #aaa; } #metadata #home-section h3.section-header { border-bottom: 0; } #metadata ul, #metadata dl, #metadata p { padding: 8px; list-style: none; } #file-metadata { margin-top: 2em; } #file-metadata ul { padding-left: 28px; list-style-image: url(images/page_green.png); } #table-of-contents { margin-top: 2em; } #table-of-contents ul { padding-left: 28px; list-style-image: url(images/tag_blue.png); } dl.svninfo { color: #666; margin: 0; } dl.svninfo dt { font-weight: bold; } ul.link-list li { white-space: nowrap; line-height: 20px; } ul.link-list .type { font-size: 8px; text-transform: uppercase; color: white; background: #969696; padding: 2px 4px; -webkit-border-radius: 5px; } .calls-super { background: url(images/arrow_up.png) no-repeat right center; } /* @end */ /* @group Class Metadata Section */ #class-metadata { margin-top: 2em; } /* @end */ /* @group Project Metadata Section */ #project-metadata { margin-top: 2em; } #project-metadata .section { border: 1px solid #aaa; } #project-metadata h3.section-header { border-bottom: 1px solid #aaa; position: relative; } #project-metadata form { color: #777; background: #ccc; } /* @end */ /* @group Documentation Section */ .description { font-size: 100%; color: #333; } .description p { margin: 1em 0.4em; } .description li p { margin: 0; } .description ol, .description ul { margin-left: 1.5em; } .description ol li, .description ul li { line-height: 1.4em; } .note-list { margin: 8px 0; } .label-list { margin: 8px 1.5em; border: 1px solid #ccc; } .description .label-list { font-size: 14px; } .note-list dt { font-weight: bold; } .note-list dd { padding: 0 12px; } .label-list dt { padding: 2px 4px; font-weight: bold; background: #ddd; } .label-list dd { padding: 2px 12px; } .label-list dd + dt, .note-list dd + dt { margin-top: 0.7em; } #documentation .section { font-size: 90%; } #documentation h2.section-header { margin-top: 1em; padding: 0.25em 0.5em; background: #ccc; color: #333; font-size: 175%; border: 1px solid #bbb; -moz-border-radius: 3px; -webkit-border-radius: 3px; } .documentation-section-title { position: relative; } .documentation-section-title .section-click-top { position: absolute; top: 6px; right: 12px; font-size: 10px; color: #9b9877; visibility: hidden; padding-right: 0.5px; } .documentation-section-title:hover .section-click-top { visibility: visible; } #documentation h3.section-header { margin-top: 1em; padding: 0.25em 0.5em; background-color: #dedede; color: #333; font-size: 150%; border: 1px solid #bbb; -moz-border-radius: 3px; -webkit-border-radius: 3px; } #constants-list > dl, #attributes-list > dl { margin: 1em 0 2em; border: 0; } #constants-list > dl dt, #attributes-list > dl dt { padding-left: 0; font-weight: bold; font-family: Monaco, "Andale Mono"; background: inherit; } #constants-list > dl dt a, #attributes-list > dl dt a { color: inherit; } #constants-list > dl dd, #attributes-list > dl dd { margin: 0 0 1em 0; padding: 0; color: #666; } .documentation-section h2 { position: relative; } .documentation-section h2 a { position: absolute; top: 8px; right: 10px; font-size: 12px; color: #9b9877; visibility: hidden; } .documentation-section h2:hover a { visibility: visible; } /* @group Method Details */ #documentation .method-source-code { display: none; } #documentation .method-description .method-calls-super { color: #333; font-weight: bolder; } #documentation .method-detail { margin: 0.5em 0; padding: 0.5em 0; cursor: pointer; } #documentation .method-detail:hover { background-color: #f1edba; } #documentation .method-heading { position: relative; padding: 2px 4px 0 20px; font-size: 125%; font-weight: bold; color: #333; background: url(images/brick.png) no-repeat left bottom; } #documentation .method-heading :link, #documentation .method-heading :visited { color: inherit; } #documentation .method-click-advice { position: absolute; top: 2px; right: 5px; font-size: 10px; color: #9b9877; visibility: hidden; padding-right: 20px; line-height: 20px; background: url(images/zoom.png) no-repeat right top; } #documentation .method-heading:hover .method-click-advice { visibility: visible; } #documentation .method-alias .method-heading { color: #666; background: url(images/brick_link.png) no-repeat left bottom; } #documentation .method-description, #documentation .aliases { margin: 0 20px; color: #666; } #documentation .method-description p, #documentation .aliases p { line-height: 1.2em; } #documentation .aliases { padding-top: 4px; font-style: italic; cursor: default; } #documentation .method-description p { margin-bottom: 0.5em; } #documentation .method-description ul { margin-left: 1.5em; } pre { margin: 0.5em 0; } #documentation .attribute-method-heading { background: url(images/tag_green.png) no-repeat left bottom; } #documentation #attribute-method-details .method-detail:hover { background-color: transparent; cursor: default; } #documentation .attribute-access-type { font-size: 60%; text-transform: uppercase; vertical-align: super; padding: 0 2px; } /* @end */ /* @end */ /* @group Source Code */ pre { overflow: auto; background: #262626; color: white; border: 1px dashed #999; padding: 0.5em; } .description pre { margin: 0 0.4em; } .ruby-constant { color: #7fffd4; background: transparent; } .ruby-keyword { color: #00ffff; background: transparent; } .ruby-ivar { color: #eedd82; background: transparent; } .ruby-operator { color: #00ffee; background: transparent; } .ruby-identifier { color: #ffdead; background: transparent; } .ruby-node { color: #ffa07a; background: transparent; } .ruby-comment { color: #dc0000; font-weight: bold; background: transparent; } .ruby-regexp { color: #ffa07a; background: transparent; } .ruby-value { color: #7fffd4; background: transparent; } /* @end */ /* @group search results */ #search-results h1 { font-size: 1em; font-weight: normal; text-shadow: none; } #search-results .current { background: #ccc; border-bottom: 1px solid transparent; } #search-results li { list-style: none; border-bottom: 1px solid #aaa; -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; margin-bottom: 0.5em; } #search-results li:last-child { border-bottom: none; margin-bottom: 0; } #search-results li p { padding: 0; margin: 0.5em; } #search-results .search-namespace { font-weight: bold; } #search-results li em { background: yellow; font-style: normal; } #search-results pre { margin: 0.5em; } /* @end */ mcollective-2.6.0/doc/GetoptLong.html0000644000175000017500000002537012375446414016571 0ustar jonasjonas module GetoptLong - mcollective version 2.6.0

module GetoptLong

mcollective-2.6.0/doc/Symbol.html0000644000175000017500000003124512375446416015754 0ustar jonasjonas class Symbol - mcollective version 2.6.0

class Symbol

Make arrays of Symbols sortable

Public Instance Methods

<=>(other) click to toggle source
# File lib/mcollective/monkey_patches.rb, line 13
def <=>(other)
  self.to_s <=> other.to_s
end
mcollective-2.6.0/doc/Gemfile.html0000644000175000017500000002513512375446416016060 0ustar jonasjonas Gemfile - mcollective version 2.6.0

source 'rubygems.org'

gem 'json' gem 'stomp' gem 'systemu'

group :dev do

gem 'rake'

end

group :test do

gem 'rdoc'
gem 'rspec', '~> 2.11.0'
gem 'mocha', '~> 0.10.0'
gem 'mcollective-test'

end

mcollective-2.6.0/doc/MCollective.html0000644000175000017500000003733312375446414016717 0ustar jonasjonas module MCollective - mcollective version 2.6.0

module MCollective

The Marionette Collective

Framework to build and run Systems Administration agents running on a publish/subscribe middleware system. The system allows you to treat your network as the only true source of the state of your platform via discovery agents and allow you to run agents matching discovery criteria.

For an overview of the idea behind this and what it enables please see:

http://www.devco.net/archives/2009/10/18/middleware_for_systems_administration.php

Constants

VERSION

Public Class Methods

version() click to toggle source
# File lib/mcollective.rb, line 64
def self.version
  VERSION
end
mcollective-2.6.0/doc/created.rid0000644000175000017500000001361212375446416015726 0ustar jonasjonasThu, 21 Aug 2014 12:48:25 -0700 ./COPYING Thu, 21 Aug 2014 12:48:23 -0700 ./Gemfile Thu, 21 Aug 2014 12:48:23 -0700 ./README Thu, 21 Aug 2014 12:48:23 -0700 ./Rakefile Thu, 21 Aug 2014 12:48:23 -0700 ./bin/mco Thu, 21 Aug 2014 12:48:23 -0700 ./bin/mcollectived Thu, 21 Aug 2014 12:48:23 -0700 ./etc/client.cfg.dist Thu, 21 Aug 2014 12:48:23 -0700 ./etc/data-help.erb Thu, 21 Aug 2014 12:48:23 -0700 ./etc/discovery-help.erb Thu, 21 Aug 2014 12:48:23 -0700 ./etc/facts.yaml.dist Thu, 21 Aug 2014 12:48:23 -0700 ./etc/metadata-help.erb Thu, 21 Aug 2014 12:48:23 -0700 ./etc/rpc-help.erb Thu, 21 Aug 2014 12:48:23 -0700 ./etc/server.cfg.dist Thu, 21 Aug 2014 12:48:23 -0700 ./etc/ssl/PLACEHOLDER Thu, 21 Aug 2014 12:48:23 -0700 ./etc/ssl/clients/PLACEHOLDER Thu, 21 Aug 2014 12:48:23 -0700 ./install.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/agent.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/agents.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/aggregate.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/aggregate/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/aggregate/result.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/aggregate/result/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/aggregate/result/collection_result.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/aggregate/result/numeric_result.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/application.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/applications.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/cache.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/client.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/config.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/connector.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/connector/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/data.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/data/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/data/result.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/ddl.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/ddl/agentddl.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/ddl/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/ddl/dataddl.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/ddl/discoveryddl.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/ddl/validatorddl.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/discovery.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/exceptions.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/facts.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/facts/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators/agent_generator.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators/data_generator.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators/templates/action_snippet.erb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators/templates/data_input_snippet.erb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators/templates/ddl.erb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/generators/templates/plugin.erb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/log.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/logger.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/logger/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/logger/console_logger.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/logger/file_logger.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/logger/syslog_logger.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/matcher.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/matcher/parser.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/matcher/scanner.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/message.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/monkey_patches.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/optionparser.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/pluginmanager.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/pluginpackager.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/pluginpackager/agent_definition.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/pluginpackager/standard_definition.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/registration.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/registration/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/actionrunner.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/agent.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/audit.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/client.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/helpers.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/progress.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/reply.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/request.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/result.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/rpc/stats.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/runner.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/runnerstats.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/security.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/security/base.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/shell.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/ssl.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/unix_daemon.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/util.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/validator.rb Thu, 21 Aug 2014 12:48:23 -0700 ./lib/mcollective/windows_daemon.rb Thu, 21 Aug 2014 12:48:23 -0700 ./mcollective.init Thu, 21 Aug 2014 12:48:23 -0700 ./doc/created.rid Thu, 21 Aug 2014 12:48:25 -0700 mcollective-2.6.0/doc/images/0000755000175000017500000000000012375446416015061 5ustar jonasjonasmcollective-2.6.0/doc/images/date.png0000644000175000017500000000116212375446414016502 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<IDAT8Ë•RmkA~î¼Ô‹çK$‚ˆ5X¤%¢ íÇ‚!àó³¿#?PŠ…@PZ(*EúAÅ#¾Ü¹Û™w˜Ô:ð0»;3ϼì@J ‰–Íf?W«Õo™LæŠî¿í` …OµZí+ûð›§M“¿Øëæçù<†ww• ^ãIؼ½Å›ËKü¼¿ÇæúZÙÒRjºFÂNV0+Ãv»E8Çq €¸®+˜äbF"ÊvÂÜs¬¾;Ã4 tåÔíõ ˆÄ>9ckšèt»ÊÖyxð àõØDŒ²7ÎÎгm\Pöµ¦S€t¬]ßIo…@ô#Ù/ÝØ¯àÃé)Þ[äf—œƒÈ^Ñ»£#dHk„àbG"õ«¨×ëÒuÝ¿Âq¹\.Xøc8ÖÀY¯×h·ÛH§Óè÷ûª÷ù|Žr¹ì}¹/ú!FˆÐäs¹R©B¡(³è÷˜§]ð…ÑjµT5”ª£Vü˜?ZxYâl6ƒ Ésðd2Qg†çwpûdÜ­¶šC2™äåÁp8„A¿ÆÕ厊”Ø®Ð*³©X,¢¯å¬<}" úW›„Ž‚ ûаæ 8s"‘ÀjµR-ù-tiMÉ1ÊeýK¼ ˜¬Ùl¢T*=L§Ó/Fã#þCè7~°þ -:MW1¹ùIEND®B`‚mcollective-2.6.0/doc/images/wrench.png0000644000175000017500000000114212375446414017051 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ôIDAT8Ë•“ËoQÅù_ü[ŒcŒ+W.]‹Ñ…¤išá5  g  m°–ÖJ[FÔq†—Éè¤#_Ð)ÚqÕ¸ÓÓ;,“¶@÷‘{ó;ßùÎÍupL©TÊ™H$¢(Z‚ XÿßM„“ɤ“€Ýn¦i"ã\nt:ôû}äóyD£Ñó ðøØåÏ0 ¹\ ÃÚ–IUËãñ4(Šrž‘¶áÍ úHM°k̈À_¿ßá4·Ç_z—üŠ€iy€'¯"[ý êén1Æû³rMž¥Ž_ïAú`béíñõ=$Ikå_p-¶qÃS~¦À²=li~3ÀBv"q²ZâAªëԧ̸—r¸[ØÔöG–¹—]<&áeÔ!î'Ú¸67 Üyôôºq‹$OÚX!ð=_™Ý~1ÍGsÜ~øE³ƒZQ†x&qWøŽK3ÓÁÇ!Þ¤ëu®Înã¢kzØGrjùQûn»IEND®B`‚mcollective-2.6.0/doc/images/plugin.png0000644000175000017500000000111712375446414017063 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<áIDAT8Ë¥’=H•qƯf~\?‘Ä,ˆ›yoX£{KCµ´7ÑPcM´5º4EkS$¤8V*w²¸‚ŠJÝB%Ëî{žs^1㪋g9ðçœçžß9IDp8´Ûラ»%w%I¯ =¹´—@]móvœbS?ù–|bÒÐ~ÔÜ?ÿøGÕªi¤lT70³™ý’ˆàáô½p’£,Ç@Û™Äܘ^žY3“$Õ™O¯Ø};®žÎxøú3o}~ÛW½tÜ ªÎ$™ÖS³jôbjLEMa8¿»¶÷Äy÷Þ¿µÝww>†¡ ¡­J«Òª´*m*¿ÿuÓ=g6=WÞ|êvÓjz :@BŸ(%UÆZéë4¿\‹“1ãÄÓ÷m©Þfèôj"‚j¥A¦Æš6Ê“hT‚*J ‚ V šZJZ $@‡1“0PHZÃ(} )B”e°  Ã‘„ @)zcàÔŠeè° –Á0Â2FH±ššùdÆ#±ÿåËà±W?³9 Ãÿî¥WªêñÈi!ˆGcŒï{sþéûÞŽl6ÕIEND®B`‚mcollective-2.6.0/doc/images/wrench_orange.png0000644000175000017500000000111012375446414020377 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ÚIDAT8Ë•“»kSQÇó8H)N]uA,¥ƒˆ“w“ÒjÍûazcbb6ÖêÅ&­ Qcn^b4b2e l„ x¥W„ }$çÅçûûžï9ÇX¦µt:­$“Én"‘0âñ¸ñÿÞT8•J)N†Ã!ãñ˜h4ʉîF£ù|žp8|2X,f˜Õ[­ªª–͵`0ˆßïÇçóSÄòï~¿OµZ% } º¦iôz=Ün÷ñ‘Hä‚XÞët:4 ŠÅ"…Br¹L¥RÁétv‡_ÝT'™-±lÔëušÍ&f_«ÕÐuR©„Ãá˜Øl6å ¼u]e× Ù5~lÞ¢Ýn“Ëåðz½{¦e©jØíö®ÕjU†¸iÂwáÛ|}šÂøÉç§Ëåš?Ìíþ$³¬²³&°€ÀûûÐx ËürŸû|TNÿà%•ì*4ŸÃ‡ l‹Ð;'T^ëÙ—G <»¦²½"pFªà­Œwì Ç!yÖÏhÇÝ”…Ôeø²E±üæ6äî ø ܙצ½ ‘‹P—³îJÕì:”C\DWç^ÏòÑ,<<ß4ƒú –A쬜ž Þѹð]‚ËsŸš6ÛÞ‘øéî×—IEND®B`‚mcollective-2.6.0/doc/images/bullet_toggle_minus.png0000644000175000017500000000031712375446414021631 0ustar jonasjonas‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<aIDAT(ÏcøÏ€2ÐQÁ‘½ûÿïú¿åÿúÿ+ÿ/Þ‹EÁÞÿ‡àpÖ, ¶%Ö‚á¡ÿ°)X”€€CÿÛ±)XŒdB=6³ÜP†MÁ„u]ÿ[€z+ÿ—üÏ_70!I¶È“zS5¯…IEND®B`‚mcollective-2.6.0/doc/images/tag_green.png0000644000175000017500000000114512375446414017521 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<÷IDAT8Ë…“Ia†ûOÌUÿ éø{æbFcôªN4FMtÜnîñÆÒ-=ì6c li4ÈBH!Ñ(ÚdXòúÕ—ÐÂâ¡.Uõ>õÖ×ÕBµZE¥RA¹\F©TB±XD¡P€®ëÈçó„}!†år¹½^C2™Ì^ˆ@“I`š&ŸÜétlå˜ $“É,S3M£D.—Ó'“ æó9Ï0NCÓ´­¦®™øœY6§Ó)F?»ˆ2¯µÛm$ Äb± ˆ@“W–‹H<þõÏN¯ã‘tÍx­Õj‘ D"‘5ˆÀv´Æã1ošÍfþèâäô^¹÷g÷ðœVf³I.lˆÍf¯°­ÑhÄ›¾™9<–ñV½‹OÚC|8»¿i4ä>ŸC8%•J‰lGk8ò¦z·€§¾#¼Smȉr„XÉÅëµZ\@’¤{—x<.²­Á`°ùøõ^oâ¥r óÅŒ»…Bðx<—×^”í'ªªjõû}òäóU¼ðßÀ›è1þüý}Qì°W¸ÑhT ‡Ã]"·kêL|ÓsË»Ýn‡ýˆÛŽ# Š~¿ß¢K\}b³üšx'€BQ'{i‹¬^¯oïPȲìôz½‰].—cë)ÿïweSL|iWýù(«ÓSIEND®B`‚mcollective-2.6.0/doc/images/bug.png0000644000175000017500000000140612375446414016343 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<˜IDAT8Ë“ÉOSQÆù\^[ ØªtzïŠ% l$–*“**”2:  iRpˆ¦Š´¶ c0ÑèÆÄ…Kw&&`44Äðù‰(¦eáâKîɽçwÎwrO€¸XÊ\¦‘¹HiÙsÆcUQz@¹ýï›ö™šø;Ö„Id„TaË€ž¯ö)jèù·C'ÙŠKTóï8=¨Š¨ýʯæ9Þ‚^z›“í–ØÔΘ͹™1OFöZ[ô¬”WÊ-GçzÖ?‡Ð®&­%*€ñ©M¤„ÆGnæN!øaO>Nc॒[ɨX·ÿõ0NôÅqg*¤1SužÜb|î{g|îfz)̾»ž§&\ — 5\ Í0 ‰3¦i² DŸö;`|î0>A?Tx4^òËÙ`¼oûqs©ö`>ʦ²`ôÂ÷fC«v§@m†X ¦å¤[r\†ÀûAt.–¢) G™[ ÃŒ÷×°Ïð`¥’N1Ä¢ï)­B룲ˆÑW×s+ý:”Nd¡Øs÷V»a*DÕX.pBÜ&B²]°›ÔH@TÏÿ3@ÕPÚÚ  wVÚP6™3yp-¶ÃâÑÃ4š Ǽ “$ÙH®'ް9„™{m@ßUô¥¹$èZjCñ¸®…XÜ:TŽä gÎ꺢ËLÕ:¢:æ?õ[#š¹{1ˆ®P=.2 ž¹F\™i°ŽA-D 7Â7qš£XI¯Š×¤b4¸ka÷ÔAjï% ͼj&QË«HÜä–&€s.žˆ `•j¹“¬üŸ‚KLE3ü*ΫX wØ6²â_àlðÊÁ=Ÿ@ü€Ç¿“hˆßŠv÷ ·,qq‚úIEND®B`‚mcollective-2.6.0/doc/images/bullet_toggle_plus.png0000644000175000017500000000032112375446414021454 0ustar jonasjonas‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<cIDAT(ÏcøÏ€2ÐQÁ‘½ûÿïú¿åÿúÿ+ÿ/Þ‹EÁÞÿ‡àpÖ, ¶%ÖþÿćþOÀ¦` PýoǦ`1’ õØÌBrC6Öuýoê­ü_ò?ÝÀ„$Ù }J ƦgIEND®B`‚mcollective-2.6.0/doc/images/package.png0000644000175000017500000000152512375446414017163 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<çIDAT8Ë}“Kh\u…¿{gîÌ4ÃL¦’”)‰BŸ1>Òn\ÔâF²±)µ¡PAD¨  Q¤TWf!”"ØŠ¸®Š¦` ¡HC0ijC“1ͳ“ÌL›fwîëÿû¹(v!ƒg}ø6ß9–ªÒ)wyc‘/T4«"žœ˜ëÔ³þ X˜8Ñ«¢¨Èû»÷Ì›v‡Ë×›*ú¹ôÜØõû‹WGU}[E>ÉîÙß7ø&¶šåœt‚Ji’Gë³+*rQE¾>û{û `áêè¢Ò=ÃÇûŸÂí9o/rorœþgaw=MЪ²Uš¥QŸVcƾ;}ź;qâDÎ>µÌr Ïã7jìTç1a“¨U&mì8þ6‰îl7K½¼ÀVio{åǤ9sðÕËVÔ¨Ñ,O"a´WÅ«—©¯mptìkœÌ.h-£­V}ÝÅÈàÖ¯K§“*ÒR¬\ÊM“- Юޡ«'O¾'CüU0)ð7 ½†·9Msù&î3§P#­¤Š€V 'w'Å¯Ì Í N,ïo°•póú#âLRi4ŠQ’j„Ç hÉ]¸½GˆS\ÿ²ù;QƒØíƤ³ˆB¬H£Æ`«1…ª‰@<Ѐd÷^²{â9¦]â Æø!Æó‰¼‰"Ô¶ŠdlÇDÔ @|0mp‹Ä Ä ‰›QÓÇÉ a+¨ˆk«‘K7¾¢½½ é"$ºÀ„  h$?$öB©r‡N#&Cif ™N¼7ÚÿS°³²V[¼6¶…lßaìL/HbFå6ºt÷¿ŽåX¿=Çֽɕد}ŽÈGO¦|ë»—zUäC5r®øâh¾8üD>õµ)4VÊwþâÁêͦŠ|«"—޶´ÑñL\>2¤FΫÈ[û^yÇjW–¹?ÿ'q{ýgùBiîßøo¦.¨È§ˆ¤TäËcç¯têýܲÂ^&ýÈIEND®B`‚mcollective-2.6.0/doc/images/zoom.png0000644000175000017500000000126412375446414016554 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<FIDAT8ËÒKHÔAÀñï̪-–ŠK¾C=Ø ¡CmÒfuîP–‰)D—êB§"ÂK»$A„ e¤¦t¶¨¤ð ô‚JŒÍZÿyuÙmüÁ0ø}f~3#œsLÞ²|!!|g-V©9­¼Që鎺³ýc¬`²ïT«™C8*¬—èô£5Þb¬F'âÎR!DÆÐ‹› ­kbâ^sPÈÌ!«½»²¾«¨ÊLocÂgÀע≣‡.=^uéíÖ˜ˆ¬ïê[ €¨ê65ôÞ¶FG´§• !§Ô P ä%wN5•\«Òq=(¡t@ȀѶ(t2)Õ§ "«L?Bþ2¬uXcY‘äV$¦ÆÚƒÔ&ý´çÍa]còÂt²¥€Ô<â *¡æÒã©QçÄ1àð}E )h˜’Y—­§§Ò„ê°Ž’…æ6` øDXr<:=UR½Íï÷ÌžáóûO®úÎ9žÝ8Ò*}òªV&b==h”é·ÖqÙÜ+;¶ËœâJT<ÆÔÓçüúò­øø­—³ËÀÈõÃA !CÎ:”§£¾Ì¼Êú}änAHÉ~0bM”ž¸36» ü+†/ç——ö”ƒøsóIĢ̾™àÉÀ«ÏEkÿ ž …óJ {ÊvíÄ!ùùé=_ßMÏyKªb]Àö½aµ¤{6W°03ñ—ßï9ßßï{L€i'˜v‡ñ Ó%IID=zÀ/¶;îŠÂÛ"ïõˆ\k«ÈÞ¸)´k±u¹9™¹xÉ]2§$ÃëW0= óóÂÒú»·$‡‡Q††H¢>}F|`€øãn6ϜϤkN7 >òâ9LLÀè(,.ÂÜŒ¡ƒÄ}>Ö¼^‡ ·›OMM|éí%꿨:/T]F$ ò6VW)©)t‡ƒ5»õÆF¾K–ëêøÜÓò·Qq"¡©r–f óî‘y_êOºa| ˜Åp¹HJ¡*MT›X8Ì×[^Dùñ˜r¸²ò·-|íæ|[Ûäv(33°°€!Ÿ.êëÉ44°Ñ×ÇŠ«eÇbÊ¡rëטu¹Íšóúd¾+ôË@Î S[K¶¿Ÿo~TKõºrÐjýk¶®´˜ØìoraŒŒPˆDPîy5å@YË?ióÜ…½éSg£¹@'š˼Zj¿Åù_MÕ5™÷½Ì»’Úwôê.ÿ ;á'5ãÚ¿_ªãÊIEND®B`‚mcollective-2.6.0/doc/images/delete.png0000644000175000017500000000131312375446414017025 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<]IDAT8Ë¥“ûKSaÇý[¶¢‰n„„QP˜ó2wÖÚܦγL[,biŽašAÅ\øC¨¥vÕÊ_2Ml”ZFjסå±òÒNMjmž³kÊ·¹`&.#záûËËûù¼<Ï“ 岿bV¯ÎPæ÷T3¥%¹I­†{Gª™qRiv•È…ë ætzâ#E±ß6„ˆ¼EddüÝÌJñª`Ÿ«ÅDRÁ2<]Nñ ·;°4õѾ;ˆ¶Ûm>‡7›°8ÜÉ€Qe6ÿLžI¬Ìèt‚ìæ®·c‰q!zñ |v ü¶j„/Xi ¶ž@øÞ Ì%1|hŸû±l !ˆÁô|­‹®±ø! ïY#‚uºUáN’w]Á˼ H3è„àu„ t]E´³>k%¾I“f¡’o«ÇR…‡D:“0åÚ`ä~¢ |§ øÓñ(rॠáon„3oG0!˜$‹‚¡ÎV„ë ž*[W0_ª‚¿©ýâ-+‚‰ãµÖ d§ÁWÇ&2¾ZfMFô‰ÒVJpËiF&B°³ >­ ÞRɘ•gƒ- Ð~ CâmèÍÚ´ÒÄ×ERÁ ឫРp«5Þ°y•ø¨È+‹Á21ø¶ŒK—aw·h£`Õ ä#Šüôa×Zñ½ž†‡Tâ³ZoüåL¨óÑ“•ÊÇ`"é(?•ï'žÜËŽJváKµÞ†óñ|ª:†G9[—aöw8é2 Jw Äéf'±“y¿ëmæzsÓ˜žìTswæá_·ñ_óÒιIrþIEND®B`‚mcollective-2.6.0/doc/images/brick.png0000644000175000017500000000070412375446414016660 0ustar jonasjonas‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<VIDATÁ?HTàïݦ’^ÙŸÍ ju ¢±É¥±Á¢ÖpOhi/ˆ¢¡8Ô©½!‡p*Áˆ0ã‚ÐŽ¼²;ßûõ}E¬,tnîM2¾Ó|13 "by¢ÕK’$iõ^NFD±²Ð™Ý;«Ö4eZ|òU‡j|«ùjf^´’$ig)kYËbÚI’¤uõKÏêWâ¸øìPÛcJ'U>ê4ÞMKý‹®B݆€eSúºJ›‡µtO9­iÔ„ •ʆíé—n#2à·ëèÛu` *ÃÎø£òÓ_£ ¥R4Êášè+õ*‡Ž€žB¥ªåíªŽu• ñϘšMY­U×·o½ùòAaL¥žGµ­Û½›kEð|"÷2wþØ9¥m…[ûy’Gw¾SðôBæ3{¹øeÛþrÜ~À㫹ŸÁ<œ{ @ÀÓ¹% ó@ÆIEND®B`‚mcollective-2.6.0/doc/images/page_white_text.png0000644000175000017500000000052612375446414020750 0ustar jonasjonas‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<èIDATÁ1nSAÀÙÇž4A¸‚Ž‚(¡I“Sp.KPp"„ÒQÒAŠ7%Žƒßþû1Ó¢;ó°ó+„‹çýZ######Ïù´µ±DWk=æCù›?ù‘û|ÞÚX‚æjä˜9昇ì³ËÈSóekMs9¾•¯NNV«ÕG@ÿà¶‹kD)ÃÞ4¼hn­†“¡”éÐ.Q¦@ønJ)1]:À;1@ø¡ ÓT˜Þ:Àt€Ÿ¦išÞ:ˆö€I²$fM³ž-Ú+g”]îþ´•Ï^LvIEND®B`‚mcollective-2.6.0/doc/images/loadingAnimation.gif0000644000175000017500000001337612375446414021035 0ustar jonasjonasGIF89aÐ Äûûû÷÷÷óóóïïïêêêæææâââÞÞÞÚÚÚÖÖÖÒÒÒÎÎÎÊÊÊÅÅÅÁÁÁ½½½¹¹¹µµµ±±±©©©ÿÿÿ!ÿ NETSCAPE2.0!ù ,Ð ÿ Å@Rižhª®lë¾p,Ïtm¿#6NÏüŒÒ+ör‹¤rD4ÊhÓé@F“Cjz]LÖë×–j·]﹬ÌRÙØñ3-ÃÁH$wƒPy‡ |KI†ˆ‰‹ŒŽŠ\K“ˆŠŒ Q\˜‡•]PžšI–¢Ÿ  ¬¨¥°¤’~$² ~Æ ÁËÁÃÅÇÈÊÌÁ–ÐÒÓÖÇØÌÕÏ×ÓÔÛÆÝËÕJÇÉâÕÐåìÚéÆëâÚàÜìßï æÀ%:`–Á@ XÈP@AO 2|ø(¢D‡º**¼H‘E‰}láF=ÿž$¹Æ‹@\9±åL˜ J(ÈÕ)?‚ ``ÁÏC…=š4èRO@›>}5éTDU‡^=”U©Q¨H¥~¥ÖêX¬eµžåšÖ«Î‚:<…Š!€»æžb`¯^O àõ»v”àÁy ':<ø/%Æ„éòEœ˜n`ÊŽ3A¾›ÙðÂÆŠë~¾`îÛ¸Rf*Ð4µLÀ¬›ªE3èìDZœ[öí±“ÞFUÛõëÕºg*>|AoáÊGªë4QÅ]ïÀÞV;÷¤Þ™ <€íG)Ÿç®}ùñëÓSvOÖ<}´í¿ž&p ðæÄyôØ]†Xg Xà‡ ¢`€"1š A˜ fJh!æ’B2§8'‘m ”øuÑ™tbj)ò–œŠºÅH s6.7ãL;jÔ£J?Š´‘p6æÐ‚l¨@k[‚ “Ø-)–’Pº„V•X™¥”fQ9å{fýÄ¥Z^v &™é0 l¶éæ›pÆ)g 9P!ù ,Î ÿ EMb¹œTŠŽ5+…ªkûÆslãyíò‹”Î[õJ;žÐw3iH¢Ê’:—QªèXÕR$ÚÃâà511™ç@¯Ôeg;=Ž›(gúÚ™×Ùn%pf"ƒ|…‡w}‚ˆzv[xE%` +•% ‰š‰Ÿ¤1" ¡¨ª ¦ª¨¤²1›°¶™©ª¬§£½ ·¯½­Ã‰±Æ·È1ŸË+ +Õ'i 1Ö˜~ ÛÔ×~ÚÜãÓåá%Ýtæâ±îë"íßóÖéŽïìèòçäöÝ«'ð–$|dЉ'Ná7o%pèDzõ:ð)†DŠö©è€HŒ$ÅÿœD˜ò€Eétl¨±äÌ‘5żlP•4 #Žóy€”5Ÿí—SåRzC>¥ j¹©U›šœz›Ò§]ß$åjÕQM ʆķÀ˜772à1@Ü’üDH¼;Ü‹%Ð…º²ïÀq€ÞÛ›XÆÀ‰‰MÆg·±ßŒNά®gÝMWz!`¤›îdÓÏÝÑ¥]þØôuj™€a×Î}ûa ݸ÷6;»5Õ¬D•zT«h,5ä~¶z~“@y[µ:­3Mú7²g‚Gª]„ÛðÒáqÿ=@c5 4ÿu·¹ò¿†ñ탟5_ÇÜÀ_9“½—_A˜Mt B\ý—K ⵘ}" @lÔÕD›iÜ·Quç+bøaq&²Ò{#'¢L²HÇjz¢"$&˜¢ Ò$B!ù ,Î ÿ EIäˆè²@*žPãR*;ò[·®ãðºÕÍL­ŠÜl7DÒŽÍ%³G‰I0)´÷Ëm½ÒIÔ²º‡ÅØØ`ÌÐI©Ã}>ăí·}-RÍõwFu}y„|Oˆ(pŠ*Œ"Žrt{R‘“A>( Š^( •"£ª4¨®± ±³® °3²´½¶®¬ºÁ½¹À©3£¿3ÅÊÌ.Î.¤µ·ÆÐ§Éž v.*o 3àâu æßá zëçîzåò»ðì(è øöüêùDìë×.¥zÿ Ha ÀïÚÑ€§P €‰ê*6¼ñÌ=‰%¥Ñ£¥ú8ÿêA%ϤH”]J‚¹Q&–ߨÙ2äL z¡ë…†AÉ”>50j(‚ ]éT¦.„s µ*­IÑ@m:n¬Tx^7†}šv(ÚHÀy’!8º/í À«4^Ö¹ÅðGi`zùÚAˆ"àÂ?l§8]É'STÒ§X’*3Π@X¾¤MS<@µèœ®-¡.íùÀìØ/Aï¤Ùy5ìÚ|žU×vëÛ¬ÆÏ°VÏrä4<A:\à4Å×bmœüdv®i›ç{Ë¿ÐÓ›òWê'7†ïþnü…Ìé×aÜÐq{ù"8V™_úÌu_^òØâž2ö•Ø_ÔÆ[k‚¡oâ¦Tpbp :¦áM ‚sákóx_"VØ“‡™!ù ,Î ÿ %R4ž yŠÔ¬Ô¢®í»Ê\ÃK®û·Ù©ÅÃýl'#ÍÅÓQˆ<àP:’é A*É4¤ G‡…£™Z…ÇÁòéL†±ÓnqÛ,‡ÓÑI‡\ÏïEoIUzqx#)Šƒ_"" „‘“ •0„š0› +¢0¤#ž¨#¦I­"«£ž¯'±ª¬µ„2 0›  qÈ+ÃËtÍ'ÏÌ ÐkÇÖØ`ÚÎ×nÞÓàÑÉÜ€â#äÙÒêì|î2çaqôVkÊÂûøþñãoE€f NC¸ž})TÇ`¾Š€R8±Ðć=žû5ÀÚ&ëõ¥D9ìÛ¦ ZŽ{¹@æ»—1M†Û´Ž¦Í‡)ÃüTy²¦N39]Ʊé A°oôÂØV jÕ>Z0Õ V©TËyºÀÝïeÍ]U‡¶ë4´f!P c0z® kW+_½ û^´«Ñž`v£"ãÅŠa@x:@O,žÈ¬tç™K5ózÙsRÐtH5Úy3ëÊ¥ª>yz=„3[/÷Z|‹&¼°âÝx @ŽŽ7nÝÃGd…nÜ óBé^‡©|{Z¯C£Nñ°3ñZó&$’p¾{f®;^C}Cö÷ÃoŸ þù÷%!ù ,Î ÿ %Š$5£¸,%”:„¦kùÂ.]ç#>µ”Èá ƒB"ÈZRpÇ]2öS±’ÊhkÔ9ŸE­-õe<Í©Ãá¬k Gj6Ó-ŒÃtŽ·ÈÍ××wsz|mƒ…‚OQti€Œ‡x‘‹41 ) ƒU" œžƒ ›B §£ª¬Q ¤¦´°¦¢¤»B§©ª¿À¹)¶À¨¦±·Æ#«¼ ¡ B+uÖØÚ  ×)ÙÛŽÞåÚ é#æìèäïëíó"æáŽãß úpÜág¯ß¿=ïQÈÇÁšPüa QÜÁ…*Ö‘XŽb<88ÒÓÁÅl$áÿ˜œ˜’ÐQ,?î y2£Ì=0ÕµT#ràΗ5æ Õ ˜9`jLmgTÒJ2í‡ÁMŒWy6ÍjukÕ«K÷y·0©iºŽP0J¡Cg&(`ðÏÜoq]Þ혗']u}C*  ð‚¿ïà^\sx¢a·…GtKpeÈaô¤@›jòvþl›gÐVêD­z$êÍ£Q‹>m÷bìÚ1ÿ´öùÚ6í}ó4 @•lñ´`Ѫ¼zvsêGt® 2yÖÀš/V+½ùSëßÇŠ=^˜ƒ‡ï"ä+Y  09Ú›>0}ŸòÞLjËî`ŠùíabøqÃO „õ'8^ÛXŸdû’Û|5 […¬ahW€Y¸á7Ö‘W6!‚Ÿi¤ÍÇ!‰²qHXŠ&®(@‰3í&B!ù ,Î ÿ Q$é@RS’Ë"®Ô ©k;ÂÎ Sö]æ3X'¢•†+àζS ŸéõŒâtUi¬˜½k ëîé`À‡mÒ0¯ÐêcÌ]‚—×÷wÚñlŸ÷dt$v}‚„k~zq,l†ˆryu€x”?)B†† œž†¢   ¢©«; ¨0¯0±©Ÿ°£;ª¦·¼µ+ ¤¹Â­À¬²³ÁG† 0-gÑÓ+Õ ÒÔÖ.zàÛâßÚ%Üã“åêçäé$ÜÞz òôèáÝûæýñøÕ+l’(jxü9hN¡†ê²tâ<‰; 8O€Ã7óyœxH#Ç|ÿž¬ö‘bH–$ѼLipåȅѹ ¨Ö2"ØÉ-ã¢A¿í(ª”ŸP’$µ·tj»@{­Ús(W£Ó$( ðÏ=„!u’5—ö(¾jmÏn wmĸo­ô™ A»ñÂ@w/·…ý…¢ àdÔ1+:'ÂÊØl¶´1Üf™š#‡|,z2æš0HgœùÙäåÒÇ@1}ãÕ©Ù¯·[¥¸3ëS+|çþ7UÛǃï.Ù{¹Î Påµ0ʶc=+PL‚0õ3î:~ÿ‹Öpøë'¸ÕÎýúv{ÓßO’ûn<Åó׿¶~)À@ÏòuäŸP6 ¨Òk (’¨™Ã K'všK Vó`I9(Z…LÈG!!ù ,Î ÿ ÕPeRN*'µ,Ô¢äìÂv*Ïo<ë¶ÞîäÁ†¦bKøs M½œ3ˆk>o¾–òÄÔR® ÆsAE1f‡m):·Òk®­vîÜÎväM{wl ƒ…r‡tqPmŒ|ŠˆuF~?W a3ƒ& ƒŸA¢6 š ©«›œª6 §-¤ƒ ´­²3º¨¥- ¬œž¸.»,Áµ¶É6o 3/hÑÓ-Õ×oÒÔÖ ØàÛâäÚ'ÜdÐåêÖìzÙáÝzßé&ÜãÞîùçüø(èCGo߉OÒä¢Àaá¾9ÑœD= ækOOEuj(à¢Ç‘ÕLÿʱãÊþT¦IR&K‹.ÕØHŠÛÎ=­ý š“§ ŸtˆþtI’©Â£Bé0E:qjÔªP°êÔáhªYÖ܉ ʾëBíF¶dÃŽT0@¸ÜK!Aݽ_âaw°9¸ÞhP (”!Ñ¢,é”fÊÊ“mZ¦,rÆâÆ[|.ª˜1é‘£wZŽŒæh%Í.Í:›^mÙRiç¶½7VÞ¿}c\ÛÄâÃuï;oà‰ý>oý—ÖüÓõu+],Eìb›ƒd]/\îæù.¨®€àá¹âû*h3ô;¤é߯¬ß~ûþÎ’ ˜“|HoXgƒ€¼6’] ¡¡‰ !!ù ,Î ÿ E9Ž$ž#%5(µ,Ô’rûÖ(iÚ0.ê¼ÞÌáÝŠ´`lXtÁ†D%´ydJ­6ÆîÇH¡^.êpðžÀâÙl¤„[kï=.Ë¿î´(>×Søx~€gypv}‡lNtjˆx{e> ‚( ™~~ ›Ÿ¡Ÿ- šª¤ª¬-¨ª  £,-°(¨¥¢±®˜¦¿·¯µ­Ä¹¾±ž ' Ÿe -/pÐÔ(ÖØu Û'ÝÍßá"Ý ‡ÓÕ×éuëÜí‡àì îŽôñöêæãóýþ•«GN„ ƒìãÀÅ¡LÕ”1žÃ:ÅI<„àÞ9uŒ’¢G 'ÿþ9ñcÉ•-Q¾$ÓÚL–'lDÁàÎ7UÝT‘ùTª×„ štÞѦï€BUƒ@%J«ªJŪð)W?LÝS¤¾Y ¤b“ŒZvm®ÕÞ‰êÎý¸Ð‘]xõF¬0ïI·{ýV—XgÜ|â´-Bf¤E¬–#ßÌ|.@•4O†ýüP4Òu8S g Ö¥_Ÿnö;Ó¨Õž­(›Õ¢Që)µŠó³wÇ®<.Ü(Š°Ê³2?ëôÈÖeGïÚ|L•Õ…"xØ/…ƒ-¢÷Û/ìy€ƒ+,ßp=Eúï1Ž›  dùQ€øÙ·Ü~¢€2Kê”›$ªáõàr `m*YsáC zFn¶ ak}¶“nn˜ÚI#Š—“…´Q!ù ,Î ÿ %R4žŽÔœ"CA,åÀñÑ(~Úº8׼دööDAÖ•Ì2hs´ÜÝŒµ+Áp]¬ÃÂˤ€ObòÎKÛãw8^>Üu¸z´`Ï÷H~htkv"x…m†€|1Z, †‘–-™‚#œ5•1"£1’¢™™¨' ¦¯'¡[´#¶”–˜,œ Š¥,}, Â'Ä pÊ#ÄÆhÉ1 ÒwÔà ÍaÚË×ÁÕáÞÏ"ÖÝÓæÑâÛéw ëÄÀ#Èpð"bù1°âV- ¿akèc—ÉB€) °P@Ã;ÿ]Xq£ÄŒ#Nìˆ-A¿"ß²Syr›©–àL¥4eM¦Êšph²\@ÌeÎq/oªœ ô§ÏÍD2(𯘳q ÅÌs:gªÒu@ª«Fu+‹¬QåAUÄôÝÓ¯ZãÍ#xbRŒ‡‹‘4Åv™G1 ˆì M¯¬ˆüÎÉ»—ðÁš;8FâˆP`BÛ©3he£11O~™ çœËŸ‹†>z‚(éC¦3—©úA=¯Ë\ŸýZ-×Ú‡¾A“í­ì‰¼aïÆ-qiSâR“­Üö°Ym¢G„(‹1¾çî¦Í¾—¸Å½€µ=dtïqëB‹[ÞîÞ!ù ,Ð ÿ %Ždižhª®lë¾p,Ï4É0T:”Ô”‹…n瘂9â„\Ч¨©< ¥P)…JÊZ“Ýè+^«fð(û³-N …Ǥ4¥Ãan¾ç÷t#o~$z|u„#†Svx…€@އ‚“•‰"‹'›ˆŠ‘¢% £#m# ±«³S³µ·%± ¸³± À¼$¨Å%µÂ§ ¿ËÁ¸²ÑÇ#¨ÐËÖ°ÊËͽÙ$Ò& AÑŒ"µæÈÏ% ©… ï$ñó§ªø#úôH݃'/ 9óü“ðßBH EdèOâC)L„˜q#F‚o– ØògŸB’bÿDIʤC–œ¸´Ô•'C’ÂùRgÌ™Gú´ 4^M=F\¸dd‹“3 Xæå ™KlžüÙ£ˆÌŠ@«@¸†ÝTöë?}¥æIVÂöUT+¯öõ»öì’Æo§l­|gr· T·e»öluÅÓ­ßÕÞ’úvì×Gä]l<Ý‘æ»sg¥Š_D ›)4EØQÀã÷ñúÒ_i¸Š)ÀPi#¦÷!h Da %¸’€.@ÕÛqËMØÜOQ `€pnˆ„yHW‡ÂC¡"Ø$¢s* ¢›]hp$ÄèV 8æ¨ãŽ<öèc !;mcollective-2.6.0/doc/images/bullet_black.png0000644000175000017500000000032312375446414020206 0ustar jonasjonas‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<eIDAT(ÏcøÏ€2 .ŠRŠ òWe®Š7JaWÐõ¿ëÛÿäÿ\ XÈ]íúŸþ?öãæ«XH^íøŸô?ìÝì „þ7¥Ãÿ3`·‚[Š­á*60H ÚÄÑþs–õàVIEND®B`‚mcollective-2.6.0/doc/images/add.png0000644000175000017500000000133512375446414016317 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<oIDAT8Ë¥“ëK“a‡ý[¶/ƒ¤†Y(¨)%X(oÙlŠŠNÛ–skŠÎön.ºÍ-µ¡Ûh¤;8ÃfâÔÒEêëP¢"jï¿ÞMGˈø}yພ羹ï$Iÿ“ß.tÓuÌ„lu‰¨æ AXþ¢:Ü𼂒ZËHÑh1óDnZJ´OJBÏÖ{±Z„ÿóÖ?­`2`‡ÒS¾‰â=¹N$ü„Å‘é=;¾íaöƒ &jw °ï›°ôqÚJGò#ÜÁ<"N ’2h8èÞµ`ïë6†·õx¸Ön_+ ~úüZto¶À}`é¢Ðx%XЛ͈ hXÑ¿¬Æ»/›ô‹}èÝÒBóJ‰_G½&ƒ|QÅr-œû6ÜÉAÞƒ ELÐ⬡\ýU3:WUh[‘C6+Š 6.fÊ *ÆÀôK͸ÜFÅŠqþ¡÷½ ýou4Ü„?ñdó|XüÃÈÒ¥ÆMvÞáD` Ú *_ý‰Ò[ «éì#A½­œ2ú´0liÐôRñ|xÖq`4w=\ôûÔèòÞÃuòQ â±Åm+GÀÙ|%$ÞÒ5ˆœÔ¹‹áØ5ãRO*Ø÷YGMš‹UO ƒ¤G€qj4Ö°(XëŒ& s1±cÂË­(LVžfÄ Rù£¢dèj¤ÑQ '-1úÚAÎTA>U ãj4,´pÀV±"4L$eÎ@.ArBù™èY a~m€y÷Y])Q8tN¸L×ô™ÌÜžt2»ó"•¡I §µŸ Ÿo=CS±Èdå)æ_·ñ_óAFË(ÓÁIEND®B`‚mcollective-2.6.0/doc/images/arrow_up.png0000644000175000017500000000056412375446414017430 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<IDAT8Ë¥“?/QÅϲ™„hg5²Ñj%ì'‘ˆF,[ ¾’ŠLH–¨ 3•(„ˆ†v‡è¼wÿ=•fcoN{O~9çÞÜVM4Q7Ü¿Ù)v/ûWQ€½ëí»´=»œ&Þàbã¶ÊׯpS»O“ÎâüÌÌ å^Ê×'&^:\˨6Š…eND!& ˜šœ9ê’£Ó_|«?\«ÝÔ Ås·råxó,÷µ«º‘g°*,(Fï#d[çùO¾•aAAŽ*¯P p1…øO+C›$`˜)¼ãˆŽÀ*Íw`AÁž#Ê0˜$àÙÑ „*Š?ÿÂõëºb&NÛRIEND®B`‚mcollective-2.6.0/doc/images/page_white_width.png0000644000175000017500000000046512375446414021105 0ustar jonasjonas‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ÇIDAT(Ï…‘A‚0E¥âÖ•n\x½Ž§qob♼† ÙRÊw( ögÚIæu2¿UÄï5ùS‡®¥†6­VX¹àʲJ6YhlÁOe<±Æ¤j³56á“S&¢ˆÞ¹5ˆÿån2 •ì,ˆàbeã %,rÌ[;ÝzÖ³»ð X`ÑÚì<ªÆÛdzè®B”Ǹ¢7|É!Aøý/ˆn2=VìŒÚâY¾Œ ãræoÿ v¶øZdIEND®B`‚mcollective-2.6.0/doc/images/transparent.png0000644000175000017500000000014112375446414020122 0ustar jonasjonas‰PNG  IHDR%=m"PLTEÿÿÿ§ÄÈtRNS@æØf IDATc` 0„¬ñzIEND®B`‚mcollective-2.6.0/doc/images/brick_link.png0000644000175000017500000000137412375446414017701 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ŽIDAT8ËmRÍKâQýÁÀü -f=³æ?ˆIZT‹v³ÒrpmªEDR-*"+RJ,+"ZLRô.JÅ¢Œ†>DÔ¬Ÿ?§ÉR;óÎc’ÉéÁË}÷žsÞ}W ”b{{ûûÊÊJlffæ™`ÌÜ[µÿ%ÊÜn÷Js.—ëÛ/„ZAËËË8>>F¡P@>ŸÇáá¡ÌñŽ5¬}q$ {ö_µËËKx<„B!‰ùùy™+q”•‹‹‹IEÂçóauuN§S‚1st“ËåpppöLMM¹¡”;99ßïG ŠT/=/Žö÷÷±µµ… LNNæaOÍd2H$8;;Ãùù9ÄûpzzZtÄxggáp^¯W" bbbBUæææ$A:Æíí-"‘ˆ´»··WœU9D6nnnJÐíøø¸ÊJUUqssƒd2‰X,&Uù´ÝÝ]ÙHËtF'$ €ÍfSñ·÷÷÷Ð4 ñx\>C¢¿¿v»½½½XZZÂÚÚúúúdÎjµr€Ê*³³³^²G£Q¤R)ŒŒŒð×××’ŒcÃÀÀ€tÄ]±Æl6GñMïÅr˜¦§§ŽŽŽ¢££±8¨­­…ÉdÂðð0ÚÛÛe\SSÃÆß$immMWÒáp”544øzzzžIpuu½^õõu90¾_¬9ªªªžººº>’ »»ûáÕ^WTT|ì ššš ÓéP]]ºº: Æ$¨¯¯ÿA‚æææ»WŸÄ¶Ãƒƒƒwâ{5nÛØØZZZÐÖÖ&cæÄ]Š5¬}EP^^þ®²²ò³Á`¸0¿,˃ÀcccãW/"Ö˜ãkXû §ùùY®j–IEND®B`‚mcollective-2.6.0/doc/images/find.png0000644000175000017500000000122312375446414016503 0ustar jonasjonas‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<%IDAT8Ëcøÿÿ?%˜ê4÷Ïy´dÞÿ­çþñ'/Xf‡Æ¦Þ"Ê€Šæ¾GëviQTó¿oöŠÿ@ƒŸà5 )»ä×ô%ÿk;§þo›4ÿ¿‰¥Ø7ŸÿY%uÿ ªZÁbVö®Ë”Ty…E™À,X³“£¨¶Ý£©o6Ø©…Õmÿ=üÃþ»xüÏ)kø›–÷?"§éíÄeÿKZ¦€å¸¹yªØØÙÁL˜»šeÊÂõ­Ûõ?·¼ Œ«'®ú?eù®ÿ}K¶ÿjœ¾ámïüÿѹÍÿÓ Á.dfayÀÀÀÀ 6è\VÍÓoüŸ]Zÿäç”ÜòÿÑÉ9ÿÕ´ôÀ6‚\K/êbøĬ`Ò *YýBc~ZØ:ý÷ Œøolaû_[ßø¿¬‚òS1qI¹­bRÿþþ/*.ùŸ—ÿ?##ã; Lð@ô Š: ¸]‡Ïü_µåÀÿîË@¶$§ª©³¢²eÂÿµ;å@1ÂÇ/x%Ü|‚ÿäW4ƒ ºè|Fá=có« 9 ÍWA12 5¿âzaõN®?(8ºûþ‡S¤¡­ÿSQEQBZö(à`€Â‚••í?ŠÖŽnàx% P`ê™ýúŸ_IUã ÈP,Áä€a€j(˜X‚èçÿ@›ÿKË*ü¢п·9¹¸ÿã} cÚe&R1ž2hZh‘ndStCœ¶kWºÍZê6·!H›¦m\šÆ$í~°Ù‹o:Åwñ>ù Ùƒo{’ Æaø¬ˆ"Lö"³ž›4M'S¹÷»ßùî9'çä^ ùqZÓ/USOÅÂüÄäßò^C+ühM‹†J&G@Ó²yï³óÆltîoß«þcÕš• ð ¾”5Ä"áY i\ÔtàÖ‰ï15ÂÍLsX§ g8ocáŒ#–f45@š ÂÅB:K¸@8˜iàó ØÎä'&©’.‹<«ER/ådE² öðsƒò_°¨”é›­çmšNÑ|ŠÞ9}pŒæÕÁ?_½A¸pX6ã£5~BÍ$®&½çîti˜íeš—Y)%$¼bT®3liæ ‰šæÓíôP’°Ÿ4¿43YóãíP•ë1ÅõöKFôº½×Û‘“ã5>§)Ö@þ½÷õrŠåy’ðë´Õô[’:VÛÛäͦ#ÃÄwQ?HB‚Žd(à‘B ašcĪøL"J¤ÒitTy²8Ö;(“–íGxÉ_¸^õ[²¸öàûžÝ%׎¼…Å·£ØQíµéº²šua¥£ná7¹å›m« QþŠå±H^eÊO‚Q×u6æS—üu Ï2”î%vX º¬ð^ø*l O…—¿ÔÈÎÞ­Ë€q,>«žSÍÆì%ÒLÒëd¸¿ŠõBÆù1CZ¾$MœŠ9òÚP 'w‚ëæâ\/מ»Ì]áú¹­.r#ŽÂõE|!ð¾3¾>_·oˆa§Û¾Ódë£1Zë»Ó‘º¢±z”Û'ö=Žª²±¾±~V+´¢cjJ³tO%mN—ó“ï„ |ˆ®-‰«bWO+ o™ ^— I¯HÙ.°;í¶SÖ]æi_s9ó*péýÃë.7U^ÀÑs. 3uä °|^,ëÛ<ž·€‘;Ûc­=maº‹>V«Ût.[»«ÕŸÏªÕÝçä x£ü©# Ö¡_2 pHYs  šœdiTXtXML:com.adobe.xmp Adobe ImageReady åz`IDAT8}RMka~vW#¦j-įˆµV´&ZjR ‚§BšÜ"íQðÚßèè!zñR!—¤JA‹Å"¦ÑT ~zi*U×í̶ŠÍG_öÝ}gæ™ç™yEQpÝ:::º+Šâz2™üt~&Îåo*•ò˜L¦o’$½Ïd2‘eßeû ' †V @4Õ3H6›½DX–0OÞØØ@(R‹µÛm”Ëå‘F£ÙÇãodÀÉF£±Åɾ͇xU–p:<"‘ˆNŽ£—Tód¦}ÏÀëÓ\ÈÆ3àù,@§ÓAµZ‘¤ÝýýýÉår¹©r›“7ƒ!–€© Á¬tZ Ò±¦WàwÜõFÛï÷_´Z­>Ÿ¯ÃlhRâÏÙlÞ¢l­k1¡Ê<\µØ¼ ¼ù*á Éq:‡ÃZªUB:¶‹¡ªÍ{Û’ð¹Ü¡P‰@MáˆäøelÛ€^¯‡z½>á©:ÆD"1¤iX¹ãµZ {÷e<±ß)iJlèÁ EÚn)ev»Á`PO“y·¸ÃÉdb=;;C£ÑX€ô.€càåÖ kÚ1k&8Àjµ²­×¨_ ’ËådÀ{~?jècËfiŒR©Ô¡sõbÅëõ’OÀ‚ÁˆA¸Àùù9šÍ&žydØV;®8ÜÙÙÑÑ$Ôp.r€=±XL•ÓívÁ}á@!Oa»X,*‡C ;®²zºôÊçóJðøÜn÷’çY©TþÀa…Bî!ª^¦L{nó÷7ð€ãkŠø‹IEND®B`‚mcollective-2.6.0/doc/images/macFFBgHack.png0000644000175000017500000000031712375446414017602 0ustar jonasjonas‰PNG  IHDRÄé…csBIT|dˆ pHYs  ÒÝ~ütEXtSoftwareAdobe Fireworks CS3˜ÖFtEXtCreation Time7/16/07Z§ÿ (IDATH‰íÍA0!´§U[ Gϱ½JJJJJJJJJÀYÑñüŸ…IEND®B`‚mcollective-2.6.0/doc/Rakefile.html0000644000175000017500000003266712375446416016242 0ustar jonasjonas Rakefile - mcollective version 2.6.0

RAKE_ROOT = File.expand_path(File.dirname(__FILE__))

# Allow override of RELEASE using BUILD_NUMBER ENV = ENV if ENV

begin

load File.join(RAKE_ROOT, 'ext', 'packaging.rake')

rescue LoadError end

def announce(msg='')

STDERR.puts "================"
STDERR.puts msg
STDERR.puts "================"

end

def safe_system *args

raise RuntimeError, "Failed: #{args.join(' ')}" unless system(*args)

end

def load_tools

unless File.directory?(File.join(RAKE_ROOT, 'ext', 'packaging'))
  Rake::Task["package:bootstrap"].invoke
  begin
    load File.join(RAKE_ROOT, 'ext', 'packaging.rake')
  rescue LoadError
    STDERR.puts "Could not load packaging tools. exiting"
    exit 1
  end
end

end

def move_artifacts

mv("pkg", "build")

end

desc “Cleanup†task :clean do

rm_rf "build"
rm_rf "doc"

end

desc “Create the .debs†task :deb => :clean do

load_tools
announce("Building debian packages for #{@build.project}-#{@build.version}-#{@build.release}")
Rake::Task["package:deb"].invoke

if ENV['SIGNED'] == '1'
  deb_flag = "-k#{ENV['SIGNWITH']}" if ENV['SIGNWITH']
  safe_system %{/usr/bin/debsign #{deb_flag} pkg/deb  .changes}
end
move_artifacts

end

desc “Build documentation†task :doc => :clean do

load_tools
Rake::Task["package:doc"].invoke

end

desc “Build a gem†task :gem => :clean do

load_tools
Rake::Task["gem"].reenable
Rake::Task["package:gem"].invoke

end

desc “Create a tarball for this release†task :package => :clean do

load_tools
announce "Creating #{@build.project}-#{@build.version}.tar.gz"
Rake::Task["package:tar"].invoke
move_artifacts

end

desc “Creates a RPM†task :rpm => :clean do

load_tools
announce("Building RPM for #{@build.project}-#{@build.version}-#{@build.release}")
Rake::Task["package:rpm"].invoke
Rake::Task["package:srpm"].invoke
if ENV['SIGNED'] == '1'
  safe_system %{/usr/bin/rpm --sign pkg/   *.rpm}
end
move_artifacts

end

desc “Run spec tests†task :test do

sh "cd spec && rake"

end

desc “Creates the website as a tarball†task :website => :clean do

FileUtils.mkdir_p("build/marionette-collective.org/html")

Dir.chdir("website") do
  safe_system("jekyll ../build/marionette-collective.org/html")
end

unless File.exist?("build/marionette-collective.org/html/index.html")
  raise "Failed to build website"
end

Dir.chdir("build") do
  safe_system("tar -cvzf marionette-collective-org-#{Time.now.to_i}.tgz marionette-collective.org")
end

end

mcollective-2.6.0/doc/README.html0000644000175000017500000002515712375446416015451 0ustar jonasjonas README - mcollective version 2.6.0

The Marionette Collective

The Marionette Collective aka. mcollective is a framework to build server orchestration or parallel job execution systems.

For full information, wikis, ticketing and downloads please see marionette-collective.org/

mcollective-2.6.0/doc/MCollective/0000755000175000017500000000000012375446416016022 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/WindowsDaemon.html0000644000175000017500000004620712375446416021477 0ustar jonasjonas class MCollective::WindowsDaemon - mcollective version 2.6.0

class MCollective::WindowsDaemon

Public Class Methods

daemonize_runner(pid=nil) click to toggle source
# File lib/mcollective/windows_daemon.rb, line 6
def self.daemonize_runner(pid=nil)
  raise "Writing pid files are not supported on the Windows Platform" if pid
  raise "The Windows Daemonizer should only be used on the Windows Platform" unless Util.windows?

  WindowsDaemon.mainloop
end

Public Instance Methods

service_main() click to toggle source
# File lib/mcollective/windows_daemon.rb, line 13
def service_main
  Log.debug("Starting Windows Service Daemon")

  @runner = Runner.new(nil)
  @runner.main_loop

  # On shut down there may be threads outside of the runner's context that are
  # in a sleeping state, causing the stop action to wait for them to cleanly exit.
  # We get around this by iterating the list of threads and killing everything that
  # isn't the main thread, letting us shut down cleanly.
  Thread.list.each do |t|
    if t != Thread.current
      t.kill
    end
  end
end
service_pause() click to toggle source
# File lib/mcollective/windows_daemon.rb, line 35
def service_pause
  Log.info("Pausing MCollective Windows server")
  @runner.pause
end
service_resume() click to toggle source
# File lib/mcollective/windows_daemon.rb, line 40
def service_resume
  Log.info("Resuming MCollective Windows server")
  @runner.resume
end
service_stop() click to toggle source
# File lib/mcollective/windows_daemon.rb, line 30
def service_stop
  Log.info("Windows service stopping")
  @runner.stop
end
mcollective-2.6.0/doc/MCollective/Generators.html0000644000175000017500000002604612375446415021030 0ustar jonasjonas module MCollective::Generators - mcollective version 2.6.0

module MCollective::Generators

mcollective-2.6.0/doc/MCollective/DDL.html0000644000175000017500000006247512375446415017330 0ustar jonasjonas module MCollective::DDL - mcollective version 2.6.0

module MCollective::DDL

A set of classes that helps create data description language files for plugins. You can define meta data, actions, input and output describing the behavior of your agent or other plugins

DDL files are used for input validation, constructing outputs, producing online help, informing the various display routines and so forth.

A sample DDL for an agent be seen below, you'd put this in your agent dir as <agent name>.ddl

 metadata :name        => "SimpleRPC Service Agent",
          :description => "Agent to manage services using the Puppet service provider",
          :author      => "R.I.Pienaar",
          :license     => "GPLv2",
          :version     => "1.1",
          :url         => "http://mcollective-plugins.googlecode.com/",
          :timeout     => 60

 action "status", :description => "Gets the status of a service" do
    display :always

    input :service,
          :prompt      => "Service Name",
          :description => "The service to get the status for",
          :type        => :string,
          :validation  => '^[a-zA-Z\-_\d]+$',
          :optional    => true,
          :maxlength   => 30

    output :status,
           :description => "The status of service",
           :display_as  => "Service Status"
end

There are now many types of DDL and ultimately all pugins should have DDL files. The code is organized so that any plugin type will magically just work - they will be an instane of Base which has metadata and a few common cases.

For plugin types that require more specific behaviors they can just add a class here that inherits from Base and add their specific behavior.

Base defines a specific behavior for input, output and metadata which we'd like to keep standard across plugin types so do not completely override the behavior of input. The methods are written that they will gladly store extra content though so you add, do not remove. See the AgentDDL class for an example where agents want a :required argument to be always set.

Public Class Methods

load_and_cache(*args) click to toggle source
# File lib/mcollective/ddl.rb, line 69
def self.load_and_cache(*args)
  Cache.setup(:ddl, 300)

  plugin = args.first
  args.size > 1 ? type = args[1].to_s : type = "agent"
  path = "%s/%s" % [type, plugin]

  begin
    ddl = Cache.read(:ddl, path)
  rescue
    begin
      klass = DDL.const_get("%sDDL" % type.capitalize)
    rescue NameError
      klass = Base
    end

    ddl = Cache.write(:ddl, path, klass.new(*args))
  end

  return ddl
end
new(*args, &blk) click to toggle source

There used to be only one big nasty DDL class with a bunch of mashed together behaviors. It's been around for ages and we would rather not ask all the users to change their ::new calls to some other factory method that would have this exact same behavior.

So we override the behavior of new which is a hugely sucky thing to do but ultimately it's what would be least disrupting to code out there today. We did though change DDL to a module to make it possibly a little less suprising, possibly.

# File lib/mcollective/ddl.rb, line 65
def self.new(*args, &blk)
  load_and_cache(*args)
end
string_to_boolean(val) click to toggle source

As we're taking arguments on the command line we need a way to input booleans, true on the cli is a string so this method will take the ddl, find all arguments that are supposed to be boolean and if they are the strings “trueâ€/“yes†or “falseâ€/“no†turn them into the matching boolean

# File lib/mcollective/ddl.rb, line 96
def self.string_to_boolean(val)
  return true if ["true", "t", "yes", "y", "1"].include?(val.downcase)
  return false if ["false", "f", "no", "n", "0"].include?(val.downcase)

  raise "#{val} does not look like a boolean argument"
end
string_to_number(val) click to toggle source

a generic string to number function, if a number looks like a float it turns it into a float else an int. This is naive but should be sufficient for numbers typed on the cli in most cases

# File lib/mcollective/ddl.rb, line 106
def self.string_to_number(val)
  return val.to_f if val =~ /^\d+\.\d+$/
  return val.to_i if val =~ /^\d+$/

  raise "#{val} does not look like a number"
end
mcollective-2.6.0/doc/MCollective/Registration.html0000644000175000017500000002713212375446415021366 0ustar jonasjonas module MCollective::Registration - mcollective version 2.6.0

module MCollective::Registration

Registration is implimented using a module structure and installations can configure which module they want to use.

We provide a simple one that just sends back the list of current known agents in MCollective::Registration::Agentlist, you can create your own:

Create a module in plugins/mcollective/registration/<yourplugin>.rb

You can inherit from MCollective::Registration::Base in which case you just need to supply a body method, whatever this method returns will be send to the middleware connection for an agent called registration

mcollective-2.6.0/doc/MCollective/UnixDaemon.html0000644000175000017500000004162412375446416020766 0ustar jonasjonas class MCollective::UnixDaemon - mcollective version 2.6.0

class MCollective::UnixDaemon

Public Class Methods

daemonize() { || ... } click to toggle source

Daemonize the current process

# File lib/mcollective/unix_daemon.rb, line 4
def self.daemonize
  fork do
    Process.setsid
    exit if fork
    Dir.chdir('/tmp')
    STDIN.reopen('/dev/null')
    STDOUT.reopen('/dev/null', 'a')
    STDERR.reopen('/dev/null', 'a')

    yield
  end
end
daemonize_runner(pid=nil) click to toggle source
# File lib/mcollective/unix_daemon.rb, line 17
def self.daemonize_runner(pid=nil)
  raise "The Unix Daemonizer can not be used on the Windows Platform" if Util.windows?

  UnixDaemon.daemonize do
    if pid
      begin
        File.open(pid, 'w') {|f| f.write(Process.pid) }
      rescue Exception => e
      end
    end

    begin
      runner = Runner.new(nil)
      runner.main_loop
    rescue => e
      Log.warn(e.backtrace)
      Log.warn(e)
    ensure
      File.unlink(pid) if pid && File.exist?(pid)
    end
  end
end
mcollective-2.6.0/doc/MCollective/Message.html0000644000175000017500000015202612375446415020301 0ustar jonasjonas class MCollective::Message - mcollective version 2.6.0

class MCollective::Message

container for a message, its headers, agent, collective and other meta data

Constants

VALIDTYPES

Attributes

agent[RW]
collective[RW]
discovered_hosts[RW]
expected_msgid[R]
filter[RW]
headers[RW]
message[R]
msgtime[R]
options[RW]
payload[R]
reply_to[R]
request[R]
requestid[RW]
ttl[RW]
type[R]
validated[R]

Public Class Methods

new(payload, message, options = {}) click to toggle source

payload - the message body without headers etc, just the text message - the original message received from the middleware options - if the body base64 encoded? options - the agent the message is for/from options - the collective its for/from options - the message headers options - an indicator about the type of message, :message, :request, :direct_request or :reply options - if this is a reply this should old the message we are replying to options - for requests, the filter to encode into the message options - the normal client options hash options - the maximum amount of seconds this message can be valid for options - in the case of replies this is the msgid it is expecting in the replies options - specific request id to use else one will be generated

# File lib/mcollective/message.rb, line 23
def initialize(payload, message, options = {})
  options = {:base64 => false,
             :agent => nil,
             :headers => {},
             :type => :message,
             :request => nil,
             :filter => Util.empty_filter,
             :options => {},
             :ttl => 60,
             :expected_msgid => nil,
             :requestid => nil,
             :collective => nil}.merge(options)

  @payload = payload
  @message = message
  @requestid = options[:requestid]
  @discovered_hosts = nil
  @reply_to = nil

  @type = options[:type]
  @headers = options[:headers]
  @base64 = options[:base64]
  @filter = options[:filter]
  @expected_msgid = options[:expected_msgid]
  @options = options[:options]

  @ttl = @options[:ttl] || Config.instance.ttl
  @msgtime = 0

  @validated = false

  if options[:request]
    @request = options[:request]
    @agent = request.agent
    @collective = request.collective
    @type = :reply
  else
    @agent = options[:agent]
    @collective = options[:collective]
  end

  base64_decode!
end

Public Instance Methods

base64?() click to toggle source
# File lib/mcollective/message.rb, line 129
def base64?
  @base64
end
base64_decode!() click to toggle source
# File lib/mcollective/message.rb, line 115
def base64_decode!
  return unless @base64

  @payload = SSL.base64_decode(@payload)
  @base64 = false
end
base64_encode!() click to toggle source
# File lib/mcollective/message.rb, line 122
def base64_encode!
  return if @base64

  @payload = SSL.base64_encode(@payload)
  @base64 = true
end
create_reqid() click to toggle source
# File lib/mcollective/message.rb, line 226
def create_reqid
  # we gsub out the -s so that the format of the id does not
  # change from previous versions, these should just be more
  # unique than previous ones
  SSL.uuid.gsub("-", "")
end
decode!() click to toggle source
# File lib/mcollective/message.rb, line 175
def decode!
  raise "Cannot decode message type #{type}" unless [:request, :reply].include?(type)

  @payload = PluginManager["security_plugin"].decodemsg(self)

  if type == :request
    raise 'callerid in request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(payload[:callerid])
  end

  [:collective, :agent, :filter, :requestid, :ttl, :msgtime].each do |prop|
    instance_variable_set("@#{prop}", payload[prop]) if payload.include?(prop)
  end
end
encode!() click to toggle source
# File lib/mcollective/message.rb, line 133
def encode!
  case type
    when :reply
      raise "Cannot encode a reply message if no request has been associated with it" unless request
      raise 'callerid in original request is not valid, surpressing reply to potentially forged request' unless PluginManager["security_plugin"].valid_callerid?(request.payload[:callerid])

      @requestid = request.payload[:requestid]
      @payload = PluginManager["security_plugin"].encodereply(agent, payload, requestid, request.payload[:callerid])
    when :request, :direct_request
      validate_compound_filter(@filter["compound"]) unless @filter["compound"].empty?

      @requestid = create_reqid unless @requestid
      @payload = PluginManager["security_plugin"].encoderequest(Config.instance.identity, payload, requestid, filter, agent, collective, ttl)
    else
      raise "Cannot encode #{type} messages"
  end
end
expected_msgid=(msgid) click to toggle source

in the case of reply messages we are expecting replies to a previously created message. This stores a hint to that previously sent message id and can be used by other classes like the security plugins as a means of optimizing their behavior like by ignoring messages not directed at us.

# File lib/mcollective/message.rb, line 110
def expected_msgid=(msgid)
  raise "Can only store the expected msgid for reply messages" unless @type == :reply
  @expected_msgid = msgid
end
publish() click to toggle source

publish a reply message by creating a target name and sending it

# File lib/mcollective/message.rb, line 213
def publish
  # If we've been specificaly told about hosts that were discovered
  # use that information to do P2P calls if appropriate else just
  # send it as is.
  config = Config.instance
  if @discovered_hosts && config.direct_addressing && (@discovered_hosts.size <= config.direct_addressing_threshold)
    self.type = :direct_request
    Log.debug("Handling #{requestid} as a direct request")
  end

  PluginManager['connector_plugin'].publish(self)
end
reply_to=(target) click to toggle source

Sets a custom reply-to target for requests. The connector plugin should inspect this when constructing requests and set this header ensuring replies will go to the custom target otherwise the connector should just do what it usually does

# File lib/mcollective/message.rb, line 99
def reply_to=(target)
  raise "Custom reply targets can only be set on requests" unless [:request, :direct_request].include?(@type)

  @reply_to = target
end
type=(type) click to toggle source

Sets the message type to one of the known types. In the case of :direct_request the list of hosts to communicate with should have been set with discovered_hosts else an exception will be raised. This is for extra security, we never accidentally want to send a direct request without a list of hosts or something weird like that as it might result in a filterless broadcast being sent.

Additionally you simply cannot set :direct_request if direct_addressing was not enabled this is to force a workflow that doesnt not yield in a mistake when someone might assume direct_addressing is enabled when its not.

# File lib/mcollective/message.rb, line 76
def type=(type)
  raise "Unknown message type #{type}" unless VALIDTYPES.include?(type)

  if type == :direct_request
    raise "Direct requests is not enabled using the direct_addressing config option" unless Config.instance.direct_addressing

    unless @discovered_hosts && !@discovered_hosts.empty?
      raise "Can only set type to :direct_request if discovered_hosts have been set"
    end

    # clear out the filter, custom discovery sources might interpret the filters
    # different than the remote mcollectived and in directed mode really the only
    # filter that matters is the agent filter
    @filter = Util.empty_filter
    @filter["agent"] << @agent
  end

  @type = type
end
validate() click to toggle source

Perform validation against the message by checking filters and ttl

# File lib/mcollective/message.rb, line 190
def validate
  raise "Can only validate request messages" unless type == :request

  msg_age = Time.now.utc.to_i - msgtime

  if msg_age > ttl
    cid = ""
    cid += payload[:callerid] + "@" if payload.include?(:callerid)
    cid += payload[:senderid]

    if msg_age > ttl
      PluginManager["global_stats"].ttlexpired

      raise(MsgTTLExpired, "message #{requestid} from #{cid} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}.  Rejecting message.")
    end
  end

  raise(NotTargettedAtUs, "Received message is not targetted to us") unless PluginManager["security_plugin"].validate_filter?(payload[:filter])

  @validated = true
end
validate_compound_filter(compound_filter) click to toggle source
# File lib/mcollective/message.rb, line 151
def validate_compound_filter(compound_filter)
  compound_filter.each do |filter|
    filter.each do |statement|
      if statement["fstatement"]
        functionname = statement["fstatement"]["name"]
        pluginname = Data.pluginname(functionname)
        value = statement["fstatement"]["value"]

        ddl = DDL.new(pluginname, :data)

        # parses numbers and booleans entered as strings into proper
        # types of data so that DDL validation will pass
        statement["fstatement"]["params"] = Data.ddl_transform_input(ddl, statement["fstatement"]["params"])

        Data.ddl_validate(ddl, statement["fstatement"]["params"])

        unless value && Data.ddl_has_output?(ddl, value)
          DDL.validation_fail!(:PLMC41, "Data plugin '%{functionname}()' does not return a '%{value}' value", :error, {:functionname => functionname, :value => value})
        end
      end
    end
  end
end
mcollective-2.6.0/doc/MCollective/Data/0000755000175000017500000000000012375446416016673 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Data/Result.html0000644000175000017500000005011412375446415021037 0ustar jonasjonas class MCollective::Data::Result - mcollective version 2.6.0

class MCollective::Data::Result

Public Class Methods

new(outputs) click to toggle source
# File lib/mcollective/data/result.rb, line 9
def initialize(outputs)
  @data = {}

  outputs.keys.each do |output|
    @data[output] = Marshal.load(Marshal.dump(outputs[output].fetch(:default, nil)))
  end
end

Public Instance Methods

[](key) click to toggle source
# File lib/mcollective/data/result.rb, line 21
def [](key)
  @data[key.to_sym]
end
[]=(key, val) click to toggle source
# File lib/mcollective/data/result.rb, line 25
def []=(key, val)
  raise "Can only store String, Integer, Float or Boolean data but got #{val.class} for key #{key}" unless [String, Fixnum, Bignum, Float, TrueClass, FalseClass].include?(val.class)

  @data[key.to_sym] = val
end
include?(key) click to toggle source
# File lib/mcollective/data/result.rb, line 17
def include?(key)
  @data.include?(key.to_sym)
end
keys() click to toggle source
# File lib/mcollective/data/result.rb, line 31
def keys
  @data.keys
end
method_missing(method, *args) click to toggle source
# File lib/mcollective/data/result.rb, line 35
def method_missing(method, *args)
  key = method.to_sym

  raise NoMethodError, "undefined local variable or method `%s'" % key unless include?(key)

  @data[key]
end
mcollective-2.6.0/doc/MCollective/Data/Base.html0000644000175000017500000006402012375446415020434 0ustar jonasjonas class MCollective::Data::Base - mcollective version 2.6.0

class MCollective::Data::Base

Attributes

ddl[R]
name[R]
result[R]
timeout[R]

Public Class Methods

activate?() click to toggle source

Always be active unless a specific block is given with ::activate_when

# File lib/mcollective/data/base.rb, line 60
def self.activate?
  return true
end
activate_when(&block) click to toggle source

::activate_when do

file.exist?("/usr/bin/puppet")

end

# File lib/mcollective/data/base.rb, line 53
def self.activate_when(&block)
  (class << self; self; end).instance_eval do
    define_method("activate?", &block)
  end
end
inherited(klass) click to toggle source

Register plugins that inherits base

# File lib/mcollective/data/base.rb, line 7
def self.inherited(klass)
  type = klass.to_s.split("::").last.downcase

  PluginManager << {:type => type, :class => klass.to_s, :single_instance => false}
end
new() click to toggle source
# File lib/mcollective/data/base.rb, line 13
def initialize
  @name = self.class.to_s.split("::").last.downcase
  @ddl = DDL.new(@name, :data)
  @result = Result.new(@ddl.dataquery_interface[:output])
  @timeout = @ddl.meta[:timeout] || 1

  startup_hook
end
query(&block) click to toggle source
# File lib/mcollective/data/base.rb, line 42
def self.query(&block)
  self.module_eval { define_method("query_data", &block) }
end

Public Instance Methods

ddl_validate(what) click to toggle source
# File lib/mcollective/data/base.rb, line 46
def ddl_validate(what)
  Data.ddl_validate(@ddl, what)
end
lookup(what) click to toggle source
# File lib/mcollective/data/base.rb, line 22
def lookup(what)
  ddl_validate(what)

  Log.debug("Doing data query %s for '%s'" % [ @name, what ])

  Timeout::timeout(@timeout) do
    query_data(what)
  end

  @result
rescue Timeout::Error
  # Timeout::Error is a inherited from Interrupt which seems a really
  # strange choice, making it an equivelant of ^C and such.  Catch it
  # and raise something less critical that will not the runner to just
  # give up the ghost
  msg = "Data plugin %s timed out on query '%s'" % [@name, what]
  Log.error(msg)
  raise MsgTTLExpired, msg
end
startup_hook() click to toggle source
# File lib/mcollective/data/base.rb, line 64
def startup_hook;end
mcollective-2.6.0/doc/MCollective/Util.html0000644000175000017500000027147612375446416017646 0ustar jonasjonas module MCollective::Util - mcollective version 2.6.0

module MCollective::Util

Some basic utility helper methods useful to clients, agents, runner etc.

Public Class Methods

absolute_path?(path, separator=File::SEPARATOR, alt_separator=File::ALT_SEPARATOR) click to toggle source

we should really use Pathname#absolute? but it's not in all the ruby versions we support and it comes down to roughly this

# File lib/mcollective/util.rb, line 473
def self.absolute_path?(path, separator=File::SEPARATOR, alt_separator=File::ALT_SEPARATOR)
  if alt_separator
    path_matcher = /^([a-zA-Z]:){0,1}[#{Regexp.quote alt_separator}#{Regexp.quote separator}]/
  else
    path_matcher = /^#{Regexp.quote separator}/
  end

  !!path.match(path_matcher)
end
align_text(text, console_cols = nil, preamble = 5) click to toggle source

Returns an aligned_string of text relative to the size of the terminal window. If a line in the string exceeds the width of the terminal window the line will be chopped off at the whitespace chacter closest to the end of the line and prepended to the next line, keeping all indentation.

The terminal size is detected by default, but custom line widths can passed. All strings will also be left aligned with 5 whitespace characters by default.

# File lib/mcollective/util.rb, line 319
def self.align_text(text, console_cols = nil, preamble = 5)
  unless console_cols
    console_cols = terminal_dimensions[0]

    # if unknown size we default to the typical unix default
    console_cols = 80 if console_cols == 0
  end

  console_cols -= preamble

  # Return unaligned text if console window is too small
  return text if console_cols <= 0

  # If console is 0 this implies unknown so we assume the common
  # minimal unix configuration of 80 characters
  console_cols = 80 if console_cols <= 0

  text = text.split("\n")
  piece = ''
  whitespace = 0

  text.each_with_index do |line, i|
    whitespace = 0

    while whitespace < line.length && line[whitespace].chr == ' '
      whitespace += 1
    end

    # If the current line is empty, indent it so that a snippet
    # from the previous line is aligned correctly.
    if line == ""
      line = (" " * whitespace)
    end

    # If text was snipped from the previous line, prepend it to the
    # current line after any current indentation.
    if piece != ''
      # Reset whitespaces to 0 if there are more whitespaces than there are
      # console columns
      whitespace = 0 if whitespace >= console_cols

      # If the current line is empty and being prepended to, create a new
      # empty line in the text so that formatting is preserved.
      if text[i + 1] && line == (" " * whitespace)
        text.insert(i + 1, "")
      end

      # Add the snipped text to the current line
      line.insert(whitespace, "#{piece} ")
    end

    piece = ''

    # Compare the line length to the allowed line length.
    # If it exceeds it, snip the offending text from the line
    # and store it so that it can be prepended to the next line.
    if line.length > (console_cols + preamble)
      reverse = console_cols

      while line[reverse].chr != ' '
        reverse -= 1
      end

      piece = line.slice!(reverse, (line.length - 1)).lstrip
    end

    # If a snippet exists when all the columns in the text have been
    # updated, create a new line and append the snippet to it, using
    # the same left alignment as the last line in the text.
    if piece != '' && text[i+1].nil?
      text[i+1] = "#{' ' * (whitespace)}#{piece}"
      piece = ''
    end

    # Add the preamble to the line and add it to the text
    line = ((' ' * preamble) + line)
    text[i] = line
  end

  text.join("\n")
end
color(code) click to toggle source

Return color codes, if the config color= option is false just return a empty string

# File lib/mcollective/util.rb, line 279
def self.color(code)
  colorize = Config.instance.color

  colors = {:red => "",
            :green => "",
            :yellow => "",
            :cyan => "",
            :bold => "",
            :reset => ""}

  if colorize
    return colors[code] || ""
  else
    return ""
  end
end
colorize(code, msg) click to toggle source

Helper to return a string in specific color

# File lib/mcollective/util.rb, line 297
def self.colorize(code, msg)
  "%s%s%s" % [ color(code), msg, color(:reset) ]
end
command_in_path?(command) click to toggle source

Checks in PATH returns true if the command is found

# File lib/mcollective/util.rb, line 426
def self.command_in_path?(command)
  found = ENV["PATH"].split(File::PATH_SEPARATOR).map do |p|
    File.exist?(File.join(p, command))
  end

  found.include?(true)
end
config_file_for_user() click to toggle source

Picks a config file defaults to ~/.mcollective else /etc/mcollective/client.cfg

# File lib/mcollective/util.rb, line 157
def self.config_file_for_user
  # expand_path is pretty lame, it relies on HOME environment
  # which isnt't always there so just handling all exceptions
  # here as cant find reverting to default
  begin
    config = File.expand_path("~/.mcollective")

    unless File.readable?(config) && File.file?(config)
      if self.windows?
        config = File.join(self.windows_prefix, "etc", "client.cfg")
      else
        config = "/etc/mcollective/client.cfg"
      end
    end
  rescue Exception => e
    if self.windows?
      config = File.join(self.windows_prefix, "etc", "client.cfg")
    else
      config = "/etc/mcollective/client.cfg"
    end
  end

  return config
end
default_options() click to toggle source

Creates a standard options hash

# File lib/mcollective/util.rb, line 183
def self.default_options
  {:verbose           => false,
   :disctimeout       => nil,
   :timeout           => 5,
   :config            => config_file_for_user,
   :collective        => nil,
   :discovery_method  => nil,
   :discovery_options => Config.instance.default_discovery_options,
   :filter            => empty_filter}
end
empty_filter() click to toggle source

Creates an empty filter

# File lib/mcollective/util.rb, line 141
def self.empty_filter
  {"fact"     => [],
   "cf_class" => [],
   "agent"    => [],
   "identity" => [],
   "compound" => []}
end
empty_filter?(filter) click to toggle source

Checks if the passed in filter is an empty one

# File lib/mcollective/util.rb, line 136
def self.empty_filter?(filter)
  filter == empty_filter || filter == {}
end
field_number(field_size, max_size=90) click to toggle source

Calculate number of fields for printing

# File lib/mcollective/util.rb, line 519
def self.field_number(field_size, max_size=90)
  number = (max_size/field_size).to_i
  (number == 0) ? 1 : number
end
field_size(elements, min_size=40) click to toggle source

Get field size for printing

# File lib/mcollective/util.rb, line 513
def self.field_size(elements, min_size=40)
  max_length = elements.max_by { |e| e.length }.length
  max_length > min_size ? max_length : min_size
end
get_fact(fact) click to toggle source

Gets the value of a specific fact, mostly just a duplicate of MCollective::Facts.get_fact but it kind of goes with the other classes here

# File lib/mcollective/util.rb, line 61
def self.get_fact(fact)
  Facts.get_fact(fact)
end
has_agent?(agent) click to toggle source

Finds out if this MCollective has an agent by the name passed

If the passed name starts with a / it's assumed to be regex and will use regex to match

# File lib/mcollective/util.rb, line 8
def self.has_agent?(agent)
  agent = Regexp.new(agent.gsub("\/", "")) if agent.match("^/")

  if agent.is_a?(Regexp)
    if Agents.agentlist.grep(agent).size > 0
      return true
    else
      return false
    end
  else
    return Agents.agentlist.include?(agent)
  end

  false
end
has_cf_class?(klass) click to toggle source

Checks if this node has a configuration management class by parsing the a text file with just a list of classes, recipes, roles etc. This is ala the classes.txt from puppet.

If the passed name starts with a / it's assumed to be regex and will use regex to match

# File lib/mcollective/util.rb, line 38
def self.has_cf_class?(klass)
  klass = Regexp.new(klass.gsub("\/", "")) if klass.match("^/")
  cfile = Config.instance.classesfile

  Log.debug("Looking for configuration management classes in #{cfile}")

  begin
    File.readlines(cfile).each do |k|
      if klass.is_a?(Regexp)
        return true if k.chomp.match(klass)
      else
        return true if k.chomp == klass
      end
    end
  rescue Exception => e
    Log.warn("Parsing classes file '#{cfile}' failed: #{e.class}: #{e}")
  end

  false
end
has_fact?(fact, value, operator) click to toggle source

Compares fact == value,

If the passed value starts with a / it's assumed to be regex and will use regex to match

# File lib/mcollective/util.rb, line 69
def self.has_fact?(fact, value, operator)

  Log.debug("Comparing #{fact} #{operator} #{value}")
  Log.debug("where :fact = '#{fact}', :operator = '#{operator}', :value = '#{value}'")

  fact = Facts[fact]
  return false if fact.nil?

  fact = fact.clone
  case fact
  when Array
    return fact.any? { |element| test_fact_value(element, value, operator)}
  when Hash
    return fact.keys.any? { |element| test_fact_value(element, value, operator)}
  else
    return test_fact_value(fact, value, operator)
  end
end
has_identity?(identity) click to toggle source

Checks if the configured identity matches the one supplied

If the passed name starts with a / it's assumed to be regex and will use regex to match

# File lib/mcollective/util.rb, line 123
def self.has_identity?(identity)
  identity = Regexp.new(identity.gsub("\/", "")) if identity.match("^/")

  if identity.is_a?(Regexp)
    return Config.instance.identity.match(identity)
  else
    return true if Config.instance.identity == identity
  end

  false
end
loadclass(klass) click to toggle source

Wrapper around MCollective::PluginManager.loadclass

# File lib/mcollective/util.rb, line 233
def self.loadclass(klass)
  PluginManager.loadclass(klass)
end
make_subscriptions(agent, type, collective=nil) click to toggle source
# File lib/mcollective/util.rb, line 194
def self.make_subscriptions(agent, type, collective=nil)
  config = Config.instance

  raise("Unknown target type #{type}") unless [:broadcast, :directed, :reply].include?(type)

  if collective.nil?
    config.collectives.map do |c|
      {:agent => agent, :type => type, :collective => c}
    end
  else
    raise("Unknown collective '#{collective}' known collectives are '#{config.collectives.join ', '}'") unless config.collectives.include?(collective)

    [{:agent => agent, :type => type, :collective => collective}]
  end
end
mcollective_version() click to toggle source
# File lib/mcollective/util.rb, line 307
def self.mcollective_version
  MCollective::VERSION
end
parse_fact_string(fact) click to toggle source

Parse a fact filter string like foo=bar into the tuple hash thats needed

# File lib/mcollective/util.rb, line 238
def self.parse_fact_string(fact)
  if fact =~ /^([^ ]+?)[ ]*=>[ ]*(.+)/
    return {:fact => $1, :value => $2, :operator => '>=' }
  elsif fact =~ /^([^ ]+?)[ ]*=<[ ]*(.+)/
    return {:fact => $1, :value => $2, :operator => '<=' }
  elsif fact =~ /^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
    return {:fact => $1, :value => $3, :operator => $2 }
  elsif fact =~ /^(.+?)[ ]*=[ ]*\/(.+)\/$/
    return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
  elsif fact =~ /^([^= ]+?)[ ]*=[ ]*(.+)/
    return {:fact => $1, :value => $2, :operator => '==' }
  else
    raise "Could not parse fact #{fact} it does not appear to be in a valid format"
  end
end
ruby_version() click to toggle source

Returns the current ruby version as per RUBY_VERSION, mostly doing this here to aid testing

# File lib/mcollective/util.rb, line 303
def self.ruby_version
  RUBY_VERSION
end
setup_windows_sleeper() click to toggle source

On windows ^c can't interrupt the VM if its blocking on IO, so this sets up a dummy thread that sleeps and this will have the end result of being interruptable at least once a second. This is a common pattern found in Rails etc

# File lib/mcollective/util.rb, line 28
def self.setup_windows_sleeper
  Thread.new { loop { sleep 1 } } if Util.windows?
end
shellescape(str) click to toggle source

Escapes a string so it's safe to use in system() or backticks

Taken from Shellwords#shellescape since it's only in a few ruby versions

# File lib/mcollective/util.rb, line 257
def self.shellescape(str)
  return "''" if str.empty?

  str = str.dup

  # Process as a single byte sequence because not all shell
  # implementations are multibyte aware.
  str.gsub!(/([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")

  # A LF cannot be escaped with a backslash because a backslash + LF
  # combo is regarded as line continuation and simply ignored.
  str.gsub!(/\n/, "'\n'")

  return str
end
str_to_bool(val) click to toggle source

Converts a string into a boolean value Strings matching 1,y,yes,true or t will return TrueClass Any other value will return FalseClass

# File lib/mcollective/util.rb, line 486
def self.str_to_bool(val)
  clean_val = val.to_s.strip
  if clean_val =~ /^(1|yes|true|y|t)$/
    return  true
  elsif clean_val =~ /^(0|no|false|n|f)$/
    return false
  else
    raise("Cannot convert string value '#{clean_val}' into a boolean.")
  end
end
subscribe(targets) click to toggle source

Helper to subscribe to a topic on multiple collectives or just one

# File lib/mcollective/util.rb, line 211
def self.subscribe(targets)
  connection = PluginManager["connector_plugin"]

  targets = [targets].flatten

  targets.each do |target|
    connection.subscribe(target[:agent], target[:type], target[:collective])
  end
end
subscribe_to_direct_addressing_queue() click to toggle source

subscribe to the direct addressing queue

# File lib/mcollective/util.rb, line 508
def self.subscribe_to_direct_addressing_queue
  subscribe(make_subscriptions("mcollective", :directed))
end
templatepath(template_file) click to toggle source

Looks up the template directory and returns its full path

# File lib/mcollective/util.rb, line 498
def self.templatepath(template_file)
  config_dir = File.dirname(Config.instance.configfile)
  template_path = File.join(config_dir, template_file)
  return template_path if File.exists?(template_path)

  template_path = File.join("/etc/mcollective", template_file)
  return template_path
end
terminal_dimensions(stdout = STDOUT, environment = ENV) click to toggle source

Figures out the columns and lines of the current tty

Returns [0, 0] if it can't figure it out or if you're not running on a tty

# File lib/mcollective/util.rb, line 405
def self.terminal_dimensions(stdout = STDOUT, environment = ENV)
  return [0, 0] unless stdout.tty?

  return [80, 40] if Util.windows?

  if environment["COLUMNS"] && environment["LINES"]
    return [environment["COLUMNS"].to_i, environment["LINES"].to_i]

  elsif environment["TERM"] && command_in_path?("tput")
    return [%xtput cols`.to_i, %xtput lines`.to_i]

  elsif command_in_path?('stty')
    return %xstty size`.scan(/\d+/).map {|s| s.to_i }
  else
    return [0, 0]
  end
rescue
  [0, 0]
end
unsubscribe(targets) click to toggle source

Helper to unsubscribe to a topic on multiple collectives or just one

# File lib/mcollective/util.rb, line 222
def self.unsubscribe(targets)
  connection = PluginManager["connector_plugin"]

  targets = [targets].flatten

  targets.each do |target|
    connection.unsubscribe(target[:agent], target[:type], target[:collective])
  end
end
versioncmp(version_a, version_b) click to toggle source

compare two software versions as commonly found in package versions.

returns 0 if a == b returns -1 if a < b returns 1 if a > b

Code originally from Puppet

# File lib/mcollective/util.rb, line 442
def self.versioncmp(version_a, version_b)
  vre = /[-.]|\d+|[^-.\d]+/
  ax = version_a.scan(vre)
  bx = version_b.scan(vre)

  while (ax.length>0 && bx.length>0)
    a = ax.shift
    b = bx.shift

    if( a == b )                 then next
    elsif (a == '-' && b == '-') then next
    elsif (a == '-')             then return -1
    elsif (b == '-')             then return 1
    elsif (a == '.' && b == '.') then next
    elsif (a == '.' )            then return -1
    elsif (b == '.' )            then return 1
    elsif (a =~ /^\d+$/ && b =~ /^\d+$/) then
      if( a =~ /^0/ or b =~ /^0/ ) then
        return a.to_s.upcase <=> b.to_s.upcase
      end
      return a.to_i <=> b.to_i
    else
      return a.upcase <=> b.upcase
    end
  end

  version_a <=> version_b;
end
windows?() click to toggle source
# File lib/mcollective/util.rb, line 273
def self.windows?
  !!(RbConfig::CONFIG['host_os'] =~ /mswin|win32|dos|mingw|cygwin/)
end
windows_prefix() click to toggle source

Returns the PuppetLabs mcollective path for windows

# File lib/mcollective/util.rb, line 150
def self.windows_prefix
  require 'win32/dir'
  prefix = File.join(Dir::COMMON_APPDATA, "PuppetLabs", "mcollective")
end
mcollective-2.6.0/doc/MCollective/Config.html0000644000175000017500000015715712375446415020134 0ustar jonasjonas class MCollective::Config - mcollective version 2.6.0

class MCollective::Config

A pretty sucky config class, ripe for refactoring/improving

Attributes

activate_agents[R]
classesfile[R]
collectives[R]
color[R]
configdir[R]
configfile[R]
configured[R]
connector[R]
daemonize[R]
default_discovery_method[R]
default_discovery_options[R]
direct_addressing[R]
direct_addressing_threshold[R]
discovery_timeout[R]
fact_cache_time[R]
factsource[R]
identity[R]
keeplogs[R]
libdir[R]
logfacility[R]
logfile[R]
logger_type[R]
loglevel[R]
main_collective[R]
max_log_size[R]
mode[RW]
pluginconf[R]
publish_timeout[R]
registerinterval[R]
registration[R]
registration_collective[R]
registration_splay[R]
rpcaudit[R]
rpcauditprovider[R]
rpcauthorization[R]
rpcauthprovider[R]
rpclimitmethod[R]
securityprovider[R]
soft_shutdown[R]
soft_shutdown_timeout[R]
ssl_cipher[R]
threaded[R]
ttl[R]

Public Class Methods

new() click to toggle source
# File lib/mcollective/config.rb, line 21
def initialize
  @configured = false
end

Public Instance Methods

loadconfig(configfile) click to toggle source
# File lib/mcollective/config.rb, line 25
def loadconfig(configfile)
  set_config_defaults(configfile)

  if File.exists?(configfile)
    File.readlines(configfile).each do |line|

      # strip blank spaces, tabs etc off the end of all lines
      line.gsub!(/\s*$/, "")

      unless line =~ /^#|^$/
        if (line =~ /(.+?)\s*=\s*(.+)/)
          key = $1.strip
          val = $2

          begin
            case key
            when "registration"
              @registration = val.capitalize
            when "registration_collective"
              @registration_collective = val
            when "registerinterval"
              @registerinterval = Integer(val)
            when "registration_splay"
              @registration_splay = Util.str_to_bool(val)
            when "collectives"
              @collectives = val.split(",").map {|c| c.strip}
            when "main_collective"
              @main_collective = val
            when "logfile"
              @logfile = val
            when "keeplogs"
              @keeplogs = Integer(val)
            when "max_log_size"
              @max_log_size = Integer(val)
            when "loglevel"
              @loglevel = val
            when "logfacility"
              @logfacility = val
            when "libdir"
              paths = val.split(File::PATH_SEPARATOR)
              paths.each do |path|
                raise("libdir paths should be absolute paths but '%s' is relative" % path) unless Util.absolute_path?(path)

                @libdir << path
                unless $LOAD_PATH.include?(path)
                  $LOAD_PATH << path
                end
              end
            when "identity"
              @identity = val
            when "direct_addressing"
              @direct_addressing = Util.str_to_bool(val)
            when "direct_addressing_threshold"
              @direct_addressing_threshold = Integer(val)
            when "color"
              @color = Util.str_to_bool(val)
            when "daemonize"
              @daemonize = Util.str_to_bool(val)
            when "securityprovider"
              @securityprovider = val.capitalize
            when "factsource"
              @factsource = val.capitalize
            when "connector"
              @connector = val.capitalize
            when "classesfile"
              @classesfile = val
            when /^plugin.(.+)$/
              @pluginconf[$1] = val
            when "discovery_timeout"
              @discovery_timeout = Integer(val)
            when "publish_timeout"
              @publish_timeout = Integer(val)
            when "rpcaudit"
              @rpcaudit = Util.str_to_bool(val)
            when "rpcauditprovider"
              @rpcauditprovider = val.capitalize
            when "rpcauthorization"
              @rpcauthorization = Util.str_to_bool(val)
            when "rpcauthprovider"
              @rpcauthprovider = val.capitalize
            when "rpclimitmethod"
              @rpclimitmethod = val.to_sym
            when "logger_type"
              @logger_type = val
            when "fact_cache_time"
              @fact_cache_time = Integer(val)
            when "ssl_cipher"
              @ssl_cipher = val
            when "threaded"
              @threaded = Util.str_to_bool(val)
            when "ttl"
              @ttl = Integer(val)
            when "default_discovery_options"
              @default_discovery_options << val
            when "default_discovery_method"
              @default_discovery_method = val
            when "soft_shutdown"
              @soft_shutdown = Util.str_to_bool(val)
            when "soft_shutdown_timeout"
              @soft_shutdown_timeout = Integer(val)
            when "activate_agents"
              @activate_agents = Util.str_to_bool(val)
            when "topicprefix", "topicsep", "queueprefix", "rpchelptemplate", "helptemplatedir"
              Log.warn("Use of deprecated '#{key}' option.  This option is ignored and should be removed from '#{configfile}'")
            else
              raise("Unknown config parameter '#{key}'")
            end
          rescue ArgumentError => e
            raise "Could not parse value for configuration option '#{key}' with value '#{val}'"
          end
        end
      end
    end

    raise('The %s config file does not specify a libdir setting, cannot continue' % configfile) if @libdir.empty?

    read_plugin_config_dir("#{@configdir}/plugin.d")

    raise 'Identities can only match /\w\.\-/' unless @identity.match(/^[\w\.\-]+$/)

    @configured = true

    @libdir.each {|dir| Log.warn("Cannot find libdir: #{dir}") unless File.directory?(dir)}

    if @logger_type == "syslog"
      raise "The sylog logger is not usable on the Windows platform" if Util.windows?
    end

    PluginManager.loadclass("Mcollective::Facts::#{@factsource}_facts")
    PluginManager.loadclass("Mcollective::Connector::#{@connector}")
    PluginManager.loadclass("Mcollective::Security::#{@securityprovider}")
    PluginManager.loadclass("Mcollective::Registration::#{@registration}")
    PluginManager.loadclass("Mcollective::Audit::#{@rpcauditprovider}") if @rpcaudit
    PluginManager << {:type => "global_stats", :class => RunnerStats.new}

    Log.info("The Marionette Collective version #{MCollective::VERSION} started by #{$0} using config file #{configfile}")
  else
    raise("Cannot find config file '#{configfile}'")
  end
end
read_plugin_config_dir(dir) click to toggle source
# File lib/mcollective/config.rb, line 210
def read_plugin_config_dir(dir)
  return unless File.directory?(dir)

  Dir.new(dir).each do |pluginconfigfile|
    next unless pluginconfigfile =~ /^([\w]+).cfg$/

    plugin = $1
    File.open("#{dir}/#{pluginconfigfile}", "r").each do |line|
      # strip blank lines
      line.gsub!(/\s*$/, "")
      next if line =~ /^#|^$/
      if (line =~ /(.+?)\s*=\s*(.+)/)
        key = $1.strip
        val = $2
        @pluginconf["#{plugin}.#{key}"] = val
      end
    end
  end
end
set_config_defaults(configfile) click to toggle source
# File lib/mcollective/config.rb, line 166
def set_config_defaults(configfile)
  @stomp = Hash.new
  @subscribe = Array.new
  @pluginconf = Hash.new
  @connector = "activemq"
  @securityprovider = "Psk"
  @factsource = "Yaml"
  @identity = Socket.gethostname
  @registration = "Agentlist"
  @registerinterval = 0
  @registration_collective = nil
  @registration_splay = false
  @classesfile = "/var/lib/puppet/state/classes.txt"
  @rpcaudit = false
  @rpcauditprovider = ""
  @rpcauthorization = false
  @rpcauthprovider = ""
  @configdir = File.dirname(configfile)
  @color = !Util.windows?
  @configfile = configfile
  @logger_type = "file"
  @keeplogs = 5
  @max_log_size = 2097152
  @rpclimitmethod = :first
  @libdir = Array.new
  @fact_cache_time = 300
  @loglevel = "info"
  @logfacility = "user"
  @collectives = ["mcollective"]
  @main_collective = @collectives.first
  @ssl_cipher = "aes-256-cbc"
  @direct_addressing = true
  @direct_addressing_threshold = 10
  @default_discovery_method = "mc"
  @default_discovery_options = []
  @ttl = 60
  @mode = :client
  @publish_timeout = 2
  @threaded = false
  @soft_shutdown = false
  @soft_shutdown_timeout = nil
  @activate_agents = true
end
mcollective-2.6.0/doc/MCollective/Data.html0000644000175000017500000006677612375446415017606 0ustar jonasjonas module MCollective::Data - mcollective version 2.6.0

module MCollective::Data

Public Class Methods

[](plugin) click to toggle source
# File lib/mcollective/data.rb, line 26
def self.[](plugin)
  PluginManager[pluginname(plugin)]
end
ddl(plugin) click to toggle source
# File lib/mcollective/data.rb, line 37
def self.ddl(plugin)
  DDL.new(pluginname(plugin), :data)
end
ddl_has_output?(ddl, output) click to toggle source
# File lib/mcollective/data.rb, line 62
def self.ddl_has_output?(ddl, output)
  ddl.entities[:data][:output].include?(output.to_sym) rescue false
end
ddl_transform_input(ddl, input) click to toggle source

For an input where the DDL requests a boolean or some number this will convert the input to the right type where possible else just returns the origin input unedited

if anything here goes wrong just return the input value this is not really the end of the world or anything since all that will happen is that DDL validation will fail and the user will get an error, no need to be too defensive here

# File lib/mcollective/data.rb, line 74
def self.ddl_transform_input(ddl, input)
  begin
    type = ddl.entities[:data][:input][:query][:type]

    case type
      when :boolean
        return DDL.string_to_boolean(input)

      when :number, :integer, :float
        return DDL.string_to_number(input)
    end
  rescue
  end

  return input
end
ddl_validate(ddl, argument) click to toggle source
# File lib/mcollective/data.rb, line 41
def self.ddl_validate(ddl, argument)
  name = ddl.meta[:name]
  query = ddl.entities[:data]

  raise DDLValidationError, "No dataquery has been defined in the DDL for data plugin #{name}" unless query

  input = query.fetch(:input, {})
  output = query.fetch(:output, {})

  raise DDLValidationError, "No output has been defined in the DDL for data plugin #{name}" if output.keys.empty?

  if input[:query]
    return true if argument.nil? && input[:query][:optional]

    ddl.validate_input_argument(input, :query, argument)
  else
    raise("No data plugin argument was declared in the %s DDL but an input was supplied" % name) if argument
    return true
  end
end
load_data_sources() click to toggle source
# File lib/mcollective/data.rb, line 6
def self.load_data_sources
  PluginManager.find_and_load("data")

  PluginManager.grep(/_data$/).each do |plugin|
    begin
      unless PluginManager[plugin].class.activate?
        Log.debug("Disabling data plugin %s due to plugin activation policy" % plugin)
        PluginManager.delete(plugin)
      end
    rescue Exception => e
      Log.debug("Disabling data plugin %s due to exception #{e.class}: #{e}" % plugin)
      PluginManager.delete(plugin)
    end
  end
end
method_missing(method, *args) click to toggle source

Data.package(“httpdâ€).architecture

Calls superclass method
# File lib/mcollective/data.rb, line 31
def self.method_missing(method, *args)
  super unless PluginManager.include?(pluginname(method))

  PluginManager[pluginname(method)].lookup(args.first)
end
pluginname(plugin) click to toggle source
# File lib/mcollective/data.rb, line 22
def self.pluginname(plugin)
  plugin.to_s =~ /_data$/ ? plugin.to_s.downcase : "%s_data" % plugin.to_s.downcase
end
mcollective-2.6.0/doc/MCollective/Generators/0000755000175000017500000000000012375446416020133 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Generators/DataGenerator.html0000644000175000017500000005334512375446415023552 0ustar jonasjonas class MCollective::Generators::DataGenerator - mcollective version 2.6.0

class MCollective::Generators::DataGenerator

Attributes

content[RW]
ddl[RW]

Public Class Methods

new(plugin_name, outputs = [], name = nil, description = nil, author = nil , license = nil, version = nil, url = nil, timeout = nil) click to toggle source
Calls superclass method MCollective::Generators::Base.new
# File lib/mcollective/generators/data_generator.rb, line 7
def initialize(plugin_name, outputs = [],  name = nil, description = nil, author = nil ,
               license = nil, version = nil, url = nil, timeout = nil)

  super(name, description, author, license, version, url, timeout)
  @mod_name = "Data"
  @pclass = "Base"
  @plugin_name = plugin_name
  @outputs = outputs
  @ddl = create_ddl
  @content = create_plugin_content
  @plugin = create_plugin_string
  write_plugins
end

Public Instance Methods

create_ddl() click to toggle source
# File lib/mcollective/generators/data_generator.rb, line 21
def create_ddl
  query_text = "dataquery :description => \"Query information\" do\n"
  query_text += ERB.new(File.read(File.join(File.dirname(__FILE__), "templates", "data_input_snippet.erb"))).result

  @outputs.each_with_index do |output,i|
    query_text += "%2s%s" % [" ", "output :#{output},\n"]
    query_text += "%9s%s" % [" ", ":description => \"%DESCRIPTION%\",\n"]
    query_text += "%9s%s" % [" ", ":display_as => \"%DESCRIPTION%\"\n"]
    query_text += "\n" unless @outputs.size == (i + 1)
  end

  query_text += "end"

  # Use inherited method to create metadata part of the ddl
  create_metadata_string + query_text
end
create_plugin_content() click to toggle source
# File lib/mcollective/generators/data_generator.rb, line 38
def create_plugin_content
  content_text = "%6s%s" % [" ", "query do |what|\n"]

  @outputs.each do |output|
     content_text += "%8s%s" % [" ", "result[:#{output}] = nil\n"]
  end
  content_text += "%6s%s" % [" ", "end\n"]

  # Add actions to agent file
  content_text
end
mcollective-2.6.0/doc/MCollective/Generators/AgentGenerator.html0000644000175000017500000005467412375446415023745 0ustar jonasjonas class MCollective::Generators::AgentGenerator - mcollective version 2.6.0

class MCollective::Generators::AgentGenerator

Attributes

content[RW]
ddl[RW]

Public Class Methods

new(plugin_name, actions = [], name = nil, description = nil, author = nil , license = nil, version = nil, url = nil, timeout = nil) click to toggle source
Calls superclass method
# File lib/mcollective/generators/agent_generator.rb, line 7
def initialize(plugin_name, actions = [],  name = nil, description = nil, author = nil ,
               license = nil, version = nil, url = nil, timeout = nil)

  super(name, description, author, license, version, url, timeout)
  @plugin_name = plugin_name
  @actions = actions || []
  @ddl = create_ddl
  @mod_name = "Agent"
  @pclass = "RPC::Agent"
  @content = create_plugin_content
  @plugin = create_plugin_string
  write_plugins
end

Public Instance Methods

action_help() click to toggle source
# File lib/mcollective/generators/agent_generator.rb, line 45
def action_help
  action_snippet = File.read(File.join(File.dirname(__FILE__), "templates", "action_snippet.erb"))
  ERB.new(action_snippet).result
end
create_ddl() click to toggle source
# File lib/mcollective/generators/agent_generator.rb, line 21
def create_ddl
  action_text = ""
  @actions.each_with_index do |action, i|
    action_text += "action \"#{action}\", :description => \"%ACTIONDESCRIPTION%\" do\n"
    action_text += action_help if i == 0
    action_text += "end\n"
    action_text += "\n" unless @actions.size == (i + 1)
  end
  # Use inherited method to create metadata part of the ddl
  create_metadata_string + action_text
end
create_plugin_content() click to toggle source
# File lib/mcollective/generators/agent_generator.rb, line 33
def create_plugin_content
  content_text = ""

  # Add actions to agent file
  @actions.each_with_index do |action, i|
    content_text +=  "%6s%s" % [" ", "action \"#{action}\" do\n"]
    content_text +=  "%6s%s" % [" ", "end\n"]
    content_text += "\n" unless @actions.size == (i + 1)
  end
  content_text
end
mcollective-2.6.0/doc/MCollective/Generators/Base.html0000644000175000017500000005551712375446415021707 0ustar jonasjonas class MCollective::Generators::Base - mcollective version 2.6.0

class MCollective::Generators::Base

Attributes

meta[RW]
mod_name[RW]
plugin_name[RW]

Public Class Methods

new(name, description, author, license, version, url, timeout) click to toggle source
# File lib/mcollective/generators/base.rb, line 5
def initialize(name, description, author, license, version, url, timeout)
  @meta = {:name => name,
           :description => description,
           :author => author,
           :license => license,
           :version => version,
           :url => url,
           :timeout => timeout}
end

Public Instance Methods

create_metadata_string() click to toggle source
# File lib/mcollective/generators/base.rb, line 15
def create_metadata_string
  ddl_template = File.read(File.join(File.dirname(__FILE__), "templates", "ddl.erb"))
  ERB.new(ddl_template, nil, "-").result(binding)
end
create_plugin_string() click to toggle source
# File lib/mcollective/generators/base.rb, line 20
def create_plugin_string
  plugin_template = File.read(File.join(File.dirname(__FILE__), "templates", "plugin.erb"))
  ERB.new(plugin_template, nil, "-").result(binding)
end
write_plugins() click to toggle source
# File lib/mcollective/generators/base.rb, line 25
def write_plugins
  begin
    Dir.mkdir @plugin_name
    dirname = File.join(@plugin_name, @mod_name.downcase)
    Dir.mkdir dirname
    puts "Created plugin directory : #{@plugin_name}"

    File.open(File.join(dirname, "#{@plugin_name}.ddl"), "w"){|f| f.puts @ddl}
    puts "Created DDL file : #{File.join(dirname, "#{@plugin_name}.ddl")}"

    File.open(File.join(dirname, "#{@plugin_name}.rb"), "w"){|f| f.puts @plugin}
    puts "Created #{@mod_name} file : #{File.join(dirname, "#{@plugin_name}.rb")}"
  rescue Errno::EEXIST
    raise "cannot generate '#{@plugin_name}' : plugin directory already exists."
  rescue Exception => e
    FileUtils.rm_rf(@plugin_name) if File.directory?(@plugin_name)
    raise "cannot generate plugin - #{e}"
  end
end
mcollective-2.6.0/doc/MCollective/PluginPackager/0000755000175000017500000000000012375446416020716 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/PluginPackager/StandardDefinition.html0000644000175000017500000006771612375446415025375 0ustar jonasjonas class MCollective::PluginPackager::StandardDefinition - mcollective version 2.6.0

class MCollective::PluginPackager::StandardDefinition

Attributes

dependencies[RW]
mcname[RW]
mcversion[RW]
metadata[RW]
packagedata[RW]
path[RW]
plugintype[RW]
postinstall[RW]
preinstall[RW]
revision[RW]
target_path[RW]
vendor[RW]

Public Class Methods

new(configuration, mcdependency, plugintype) click to toggle source
# File lib/mcollective/pluginpackager/standard_definition.rb, line 7
def initialize(configuration, mcdependency, plugintype)
  @plugintype = plugintype
  @path = configuration[:target]
  @packagedata = {}
  @revision = configuration[:revision] || 1
  @preinstall = configuration[:preinstall]
  @postinstall = configuration[:postinstall]
  @vendor = configuration[:vendor] || "Puppet Labs"
  @dependencies = configuration[:dependency] || []
  @target_path = File.expand_path(@path)
  @metadata, mcversion = PluginPackager.get_metadata(@path, @plugintype)
  @mcname = mcdependency[:mcname] || "mcollective"
  @mcversion = mcdependency[:mcversion] || mcversion
  @dependencies << {:name => "#{mcname}-common", :version => @mcversion}
  @metadata[:name] = (configuration[:pluginname] || @metadata[:name]).downcase.gsub(/\s+|_/, "-")
  @metadata[:version] = (configuration[:version] || @metadata[:version])
  identify_packages
end

Public Instance Methods

common() click to toggle source

Obtain list of common files

# File lib/mcollective/pluginpackager/standard_definition.rb, line 54
def common
  common = {:files => [],
            :dependencies => @dependencies.clone,
            :description => "Common libraries for #{@name} connector plugin"}

  commondir = File.join(@path, "util")
  if PluginPackager.check_dir_present commondir
    common[:files] = Dir.glob(File.join(commondir, "*"))
    return common
  else
    return nil
  end
end
identify_packages() click to toggle source

Identify present packages and populate the packagedata hash

# File lib/mcollective/pluginpackager/standard_definition.rb, line 27
def identify_packages
  common_package = common
  @packagedata[:common] = common_package if common_package
  plugin_package = plugin
  @packagedata[@plugintype.to_sym] = plugin_package if plugin_package
end
plugin() click to toggle source

Obtain standard plugin files and dependencies

# File lib/mcollective/pluginpackager/standard_definition.rb, line 35
def plugin
  plugindata = {:files => [],
                :dependencies => @dependencies.clone,
                :description => "#{@name} #{@plugintype} plugin for the Marionette Collective."}

  plugindir = File.join(@path, @plugintype.to_s)
  if PluginPackager.check_dir_present plugindir
    plugindata[:files] = Dir.glob(File.join(plugindir, "*"))
  else
    return nil
  end

  plugindata[:plugindependency] = {:name => "#{@mcname}-#{@metadata[:name]}-common",
                                :version => @metadata[:version],
                                :revision => @revision} if @packagedata[:common]
  plugindata
end
mcollective-2.6.0/doc/MCollective/PluginPackager/AgentDefinition.html0000644000175000017500000010357412375446415024664 0ustar jonasjonas class MCollective::PluginPackager::AgentDefinition - mcollective version 2.6.0

class MCollective::PluginPackager::AgentDefinition

MCollective Agent Plugin package

Attributes

dependencies[RW]
mcname[RW]
mcversion[RW]
metadata[RW]
packagedata[RW]
path[RW]
plugintype[RW]
postinstall[RW]
preinstall[RW]
revision[RW]
target_path[RW]
vendor[RW]

Public Class Methods

new(configuration, mcdependency, plugintype) click to toggle source
# File lib/mcollective/pluginpackager/agent_definition.rb, line 8
def initialize(configuration, mcdependency, plugintype)
  @plugintype = plugintype
  @path = configuration[:target]
  @packagedata = {}
  @revision = configuration[:revision] || 1
  @preinstall = configuration[:preinstall]
  @postinstall = configuration[:postinstall]
  @vendor = configuration[:vendor] || "Puppet Labs"
  @dependencies = configuration[:dependency] || []
  @target_path = File.expand_path(@path)
  @metadata, mcversion = PluginPackager.get_metadata(@path, "agent")
  @mcname = mcdependency[:mcname] ||  "mcollective"
  @mcversion = mcdependency[:mcversion] || mcversion
  @metadata[:version] = (configuration[:version] || @metadata[:version])
  @dependencies << {:name => "#{@mcname}-common", :version => @mcversion}
  @metadata[:name] = (configuration[:pluginname] || @metadata[:name]).downcase.gsub(/\s+|_/, "-")
  identify_packages
end

Public Instance Methods

agent() click to toggle source

Obtain Agent package files and dependencies.

# File lib/mcollective/pluginpackager/agent_definition.rb, line 38
def agent
  agent = {:files => [],
           :dependencies => @dependencies.clone,
           :description => "Agent plugin for #{@metadata[:name]}"}

  agentdir = File.join(@path, "agent")

  if PluginPackager.check_dir_present agentdir
    ddls = Dir.glob(File.join(agentdir, "*.ddl"))
    agent[:files] = (Dir.glob(File.join(agentdir, "*")) - ddls)
  else
    return nil
  end
  agent[:plugindependency] = {:name => "#{@mcname}-#{@metadata[:name]}-common", :version => @metadata[:version], :revision => @revision}
  agent
end
client() click to toggle source

Obtain client package files and dependencies.

# File lib/mcollective/pluginpackager/agent_definition.rb, line 56
def client
  client = {:files => [],
            :dependencies => @dependencies.clone,
            :description => "Client plugin for #{@metadata[:name]}"}

  clientdir = File.join(@path, "application")
  aggregatedir = File.join(@path, "aggregate")

  client[:files] += Dir.glob(File.join(clientdir, "*")) if PluginPackager.check_dir_present clientdir
  client[:files] += Dir.glob(File.join(aggregatedir, "*")) if PluginPackager.check_dir_present aggregatedir
  client[:plugindependency] = {:name => "#{@mcname}-#{@metadata[:name]}-common", :version => @metadata[:version], :revision => @revision}
  client[:files].empty? ? nil : client
end
common() click to toggle source

Obtain common package files and dependencies.

# File lib/mcollective/pluginpackager/agent_definition.rb, line 71
def common
  common = {:files =>[],
            :dependencies => @dependencies.clone,
            :description => "Common libraries for #{@metadata[:name]}"}

  datadir = File.join(@path, "data", "**")
  utildir = File.join(@path, "util", "**", "**")
  ddldir = File.join(@path, "agent", "*.ddl")
  validatordir = File.join(@path, "validator", "**")

  [datadir, utildir, validatordir, ddldir].each do |directory|
    common[:files] += Dir.glob(directory)
  end

  # We fail if there is no ddl file present
  if common[:files].grep(/^.*\.ddl$/).empty?
    raise "cannot create package - No ddl file found in #{File.join(@path, "agent")}"
  end

  common[:files].empty? ? nil : common
end
identify_packages() click to toggle source

Identify present packages and populate packagedata hash.

# File lib/mcollective/pluginpackager/agent_definition.rb, line 28
def identify_packages
  common_package = common
  @packagedata[:common] = common_package if common_package
  agent_package = agent
  @packagedata[:agent] = agent_package if agent_package
  client_package = client
  @packagedata[:client] = client_package if client_package
end
mcollective-2.6.0/doc/MCollective/SecurityValidationFailed.html0000644000175000017500000002605512375446415023646 0ustar jonasjonas class MCollective::SecurityValidationFailed - mcollective version 2.6.0

class MCollective::SecurityValidationFailed

mcollective-2.6.0/doc/MCollective/Security/0000755000175000017500000000000012375446416017631 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Security/Base.html0000644000175000017500000013452412375446415021401 0ustar jonasjonas class MCollective::Security::Base - mcollective version 2.6.0

class MCollective::Security::Base

This is a base class the other security modules should inherit from it handles statistics and validation of messages that should in most cases apply to all security models.

To create your own security plugin you should provide a plugin that inherits from this and provides the following methods:

decodemsg - Decodes a message that was received from the middleware encodereply - Encodes a reply message to a previous request message encoderequest - Encodes a new request message validrequest? - Validates a request received from the middleware

Optionally if you are identifying users by some other means like certificate name you can provide your own callerid method that can provide the rest of the system with an id, and you would see this id being usable in SimpleRPC authorization methods

The @initiated_by variable will be set to either :client or :node depending on who is using this plugin. This is to help security providers that operate in an asymetric mode like public/private key based systems.

Specifics of each of these are a bit fluid and the interfaces for this is not set in stone yet, specifically the encode methods will be provided with a helper that takes care of encoding the core requirements. The best place to see how security works is by looking at the provided MCollective::Security::PSK plugin.

Attributes

initiated_by[RW]
stats[R]

Public Class Methods

inherited(klass) click to toggle source

Register plugins that inherits base

# File lib/mcollective/security/base.rb, line 32
def self.inherited(klass)
  PluginManager << {:type => "security_plugin", :class => klass.to_s}
end
new() click to toggle source

Initializes configuration and logging as well as prepare a zero'd hash of stats various security methods and filter validators should increment stats, see MCollective::Security::Psk for a sample

# File lib/mcollective/security/base.rb, line 38
def initialize
  @config = Config.instance
  @log = Log
  @stats = PluginManager["global_stats"]
end

Public Instance Methods

callerid() click to toggle source

Returns a unique id for the caller, by default we just use the unix user id, security plugins can provide their own means of doing ids.

# File lib/mcollective/security/base.rb, line 219
def callerid
  "uid=#{Process.uid}"
end
create_reply(reqid, agent, body) click to toggle source
# File lib/mcollective/security/base.rb, line 167
def create_reply(reqid, agent, body)
  Log.debug("Encoded a message for request #{reqid}")

  {:senderid => @config.identity,
   :requestid => reqid,
   :senderagent => agent,
   :msgtime => Time.now.utc.to_i,
   :body => body}
end
create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60) click to toggle source
# File lib/mcollective/security/base.rb, line 177
def create_request(reqid, filter, msg, initiated_by, target_agent, target_collective, ttl=60)
  Log.debug("Encoding a request for agent '#{target_agent}' in collective #{target_collective} with request id #{reqid}")

  {:body => msg,
   :senderid => @config.identity,
   :requestid => reqid,
   :filter => filter,
   :collective => target_collective,
   :agent => target_agent,
   :callerid => callerid,
   :ttl => ttl,
   :msgtime => Time.now.utc.to_i}
end
decodemsg(msg) click to toggle source

Security providers should provide this, see MCollective::Security::Psk

# File lib/mcollective/security/base.rb, line 239
def decodemsg(msg)
  Log.error("decodemsg is not implemented in #{self.class}")
end
encodereply(sender, msg, requestcallerid=nil) click to toggle source

Security providers should provide this, see MCollective::Security::Psk

# File lib/mcollective/security/base.rb, line 234
def encodereply(sender, msg, requestcallerid=nil)
  Log.error("encodereply is not implemented in #{self.class}")
end
encoderequest(sender, msg, filter={}) click to toggle source

Security providers should provide this, see MCollective::Security::Psk

# File lib/mcollective/security/base.rb, line 229
def encoderequest(sender, msg, filter={})
  Log.error("encoderequest is not implemented in #{self.class}")
end
should_process_msg?(msg, msgid) click to toggle source

Give a MC::Message instance and a message id this will figure out if you the incoming message id matches the one the Message object is expecting and raise if its not

Mostly used by security plugins to figure out if they should do the hard work of decrypting etc messages that would only later on be ignored

# File lib/mcollective/security/base.rb, line 196
def should_process_msg?(msg, msgid)
  if msg.expected_msgid
    unless msg.expected_msgid == msgid
      msgtext = "Got a message with id %s but was expecting %s, ignoring message" % [msgid, msg.expected_msgid]
      Log.debug msgtext
      raise MsgDoesNotMatchRequestID, msgtext
    end
  end

  true
end
valid_callerid?(id) click to toggle source

Validates a callerid. We do not want to allow things like \ and / in callerids since other plugins make assumptions that these are safe strings.

callerids are generally in the form uid=123 or cert=foo etc so we do that here but security plugins could override this for some complex uses

# File lib/mcollective/security/base.rb, line 213
def valid_callerid?(id)
  !!id.match(/^[\w]+=[\w\.\-]+$/)
end
validate_filter?(filter) click to toggle source

Takes a Hash with a filter in it and validates it against host information.

At present this supports filter matches against the following criteria:

  • puppet_class|cf_class - Presence of a configuration management class in

    the file configured with classesfile
  • agent - Presence of a MCollective agent with a supplied name

  • fact - The value of a fact avout this system

  • identity - the configured identity of the system

TODO: Support REGEX and/or multiple filter keys to be AND'd

# File lib/mcollective/security/base.rb, line 55
def validate_filter?(filter)
  failed = 0
  passed = 0

  passed = 1 if Util.empty_filter?(filter)

  filter.keys.each do |key|
    case key
    when /puppet_class|cf_class/
      filter[key].each do |f|
        Log.debug("Checking for class #{f}")
        if Util.has_cf_class?(f) then
          Log.debug("Passing based on configuration management class #{f}")
          passed += 1
        else
          Log.debug("Failing based on configuration management class #{f}")
          failed += 1
        end
      end

    when "compound"
      filter[key].each do |compound|
        result = false
        truth_values = []

        begin
          compound.each do |expression|
            case expression.keys.first
              when "statement"
                truth_values << Matcher.eval_compound_statement(expression).to_s
              when "fstatement"
                truth_values << Matcher.eval_compound_fstatement(expression.values.first)
              when "and"
                truth_values << "&&"
              when "or"
                truth_values << "||"
              when "("
                truth_values << "("
              when ")"
                truth_values << ")"
              when "not"
                truth_values << "!"
            end
          end

          result = eval(truth_values.join(" "))
        rescue DDLValidationError
          result = false
        end

        if result
          Log.debug("Passing based on class and fact composition")
          passed +=1
        else
          Log.debug("Failing based on class and fact composition")
          failed +=1
        end
      end

    when "agent"
      filter[key].each do |f|
        if Util.has_agent?(f) || f == "mcollective"
          Log.debug("Passing based on agent #{f}")
          passed += 1
        else
          Log.debug("Failing based on agent #{f}")
          failed += 1
        end
      end

    when "fact"
      filter[key].each do |f|
        if Util.has_fact?(f[:fact], f[:value], f[:operator])
          Log.debug("Passing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
          passed += 1
        else
          Log.debug("Failing based on fact #{f[:fact]} #{f[:operator]} #{f[:value]}")
          failed += 1
        end
      end

    when "identity"
      unless filter[key].empty?
        # Identity filters should not be 'and' but 'or' as each node can only have one identity
        matched = filter[key].select{|f| Util.has_identity?(f)}.size

        if matched == 1
          Log.debug("Passing based on identity")
          passed += 1
        else
          Log.debug("Failed based on identity")
          failed += 1
        end
      end
    end
  end

  if failed == 0 && passed > 0
    Log.debug("Message passed the filter checks")

    @stats.passed

    return true
  else
    Log.debug("Message failed the filter checks")

    @stats.filtered

    return false
  end
end
validrequest?(req) click to toggle source

Security providers should provide this, see MCollective::Security::Psk

# File lib/mcollective/security/base.rb, line 224
def validrequest?(req)
  Log.error("validrequest? is not implemented in #{self.class}")
end
mcollective-2.6.0/doc/MCollective/Connector/0000755000175000017500000000000012375446416017754 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Connector/Base.html0000644000175000017500000003322312375446415021516 0ustar jonasjonas class MCollective::Connector::Base - mcollective version 2.6.0

class MCollective::Connector::Base

Public Class Methods

inherited(klass) click to toggle source
# File lib/mcollective/connector/base.rb, line 19
def self.inherited(klass)
  plugin_name = klass.to_s.split("::").last.downcase
  ddl = DDL.new(plugin_name, :connector)
  PluginManager << {:type => "connector_plugin", :class => klass.to_s}
end
mcollective-2.6.0/doc/MCollective/Log.html0000644000175000017500000007366012375446415017444 0ustar jonasjonas class MCollective::Log - mcollective version 2.6.0

class MCollective::Log

A simple class that allows logging at various levels.

Public Class Methods

configure(logger=nil) click to toggle source

configures the logger class, if the config has not yet been loaded we default to the console logging class and do not set @configured so that future calls to the log method will keep attempting to configure the logger till we eventually get a logging preference from the config module

# File lib/mcollective/log.rb, line 79
def configure(logger=nil)
  unless logger
    logger_type = "console"

    config = Config.instance

    if config.configured
      logger_type = config.logger_type
      @configured = true
    end

    require "mcollective/logger/#{logger_type.downcase}_logger"

    logger_class = MCollective::Logger.const_get("#{logger_type.capitalize}_logger")

    set_logger(logger_class.new)
  else
    set_logger(logger)
    @configured = true
  end

  @logger.start
rescue Exception => e
  @configured = false
  STDERR.puts "Could not start logger: #{e.class} #{e}"
end
cycle_level() click to toggle source

increments the active log level

# File lib/mcollective/log.rb, line 43
def cycle_level
  @logger.cycle_level if @configured
end
debug(msg) click to toggle source

Logs at debug level

# File lib/mcollective/log.rb, line 23
def debug(msg)
  log(:debug, msg)
end
error(msg) click to toggle source

Logs at error level

# File lib/mcollective/log.rb, line 33
def error(msg)
  log(:error, msg)
end
execution_stack() click to toggle source

this method is here to facilitate testing and from

# File lib/mcollective/log.rb, line 113
def execution_stack
  caller
end
fatal(msg) click to toggle source

Logs at fatal level

# File lib/mcollective/log.rb, line 28
def fatal(msg)
  log(:fatal, msg)
end
from() click to toggle source

figures out the filename that called us

# File lib/mcollective/log.rb, line 107
def from
  path, line, method = execution_stack[3].split(/:(\d+)/)
  "%s:%s%s" % [File.basename(path), line, method]
end
info(msg) click to toggle source

Logs at info level

# File lib/mcollective/log.rb, line 13
def info(msg)
  log(:info, msg)
end
instance() click to toggle source

handle old code that relied on this class being a singleton

# File lib/mcollective/log.rb, line 38
def instance
  self
end
log(level, msg) click to toggle source

logs a message at a certain level

# File lib/mcollective/log.rb, line 55
def log(level, msg)
  configure unless @configured

  raise "Unknown log level" unless [:error, :fatal, :debug, :warn, :info].include?(level)

  if @logger
    @logger.log(level, from, msg)
  else
    t = Time.new.strftime("%H:%M:%S")

    STDERR.puts "#{t}: #{level}: #{from}: #{msg}"
  end
end
logger() click to toggle source

Obtain the class name of the currently configured logger

# File lib/mcollective/log.rb, line 8
def logger
  @logger.class
end
reopen() click to toggle source

reopen log files

# File lib/mcollective/log.rb, line 48
def reopen
  if @configured
    @logger.reopen
  end
end
set_logger(logger) click to toggle source

sets the logger class to use

# File lib/mcollective/log.rb, line 70
def set_logger(logger)
  @logger = logger
end
warn(msg) click to toggle source

Logs at warn level

# File lib/mcollective/log.rb, line 18
def warn(msg)
  log(:warn, msg)
end
mcollective-2.6.0/doc/MCollective/Registration/0000755000175000017500000000000012375446416020474 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Registration/Base.html0000644000175000017500000005752312375446415022247 0ustar jonasjonas class MCollective::Registration::Base - mcollective version 2.6.0

class MCollective::Registration::Base

This is a base class that other registration plugins can use to handle regular announcements to the mcollective

The configuration file determines how often registration messages gets sent using the registerinterval option, the plugin runs in the background in a thread.

Public Class Methods

inherited(klass) click to toggle source

Register plugins that inherits base

# File lib/mcollective/registration/base.rb, line 11
def self.inherited(klass)
  PluginManager << {:type => "registration_plugin", :class => klass.to_s}
end

Public Instance Methods

body() click to toggle source
# File lib/mcollective/registration/base.rb, line 67
def body
  raise "Registration Plugins must implement the #body method"
end
config() click to toggle source
# File lib/mcollective/registration/base.rb, line 27
def config
  Config.instance
end
interval() click to toggle source
# File lib/mcollective/registration/base.rb, line 50
def interval
  config.registerinterval
end
msg_filter() click to toggle source
# File lib/mcollective/registration/base.rb, line 31
def msg_filter
  filter = Util.empty_filter
  filter["agent"] << "registration"
  filter
end
publish(message) click to toggle source
# File lib/mcollective/registration/base.rb, line 54
def publish(message)
  unless message
    Log.debug("Skipping registration due to nil body")
  else
    req = Message.new(message, nil, {:type => :request, :agent => "registration", :collective => target_collective, :filter => msg_filter})
    req.encode!

    Log.debug("Sending registration #{req.requestid} to collective #{req.collective}")

    req.publish
  end
end
run(connection) click to toggle source

Creates a background thread that periodically send a registration notice.

The actual registration notices comes from the 'body' method of the registration plugins.

# File lib/mcollective/registration/base.rb, line 19
def run(connection)
  return false if interval == 0

  Thread.new do
    publish_thread(connection)
  end
end
target_collective() click to toggle source
# File lib/mcollective/registration/base.rb, line 37
def target_collective
  main_collective = config.main_collective

  collective = config.registration_collective || main_collective

  unless config.collectives.include?(collective)
    Log.warn("Sending registration to #{main_collective}: #{collective} is not a valid collective")
    collective = main_collective
  end

  return collective
end
mcollective-2.6.0/doc/MCollective/DDLValidationError.html0000644000175000017500000002613612375446415022347 0ustar jonasjonas class MCollective::DDLValidationError - mcollective version 2.6.0

class MCollective::DDLValidationError

Exceptions for the RPC system

mcollective-2.6.0/doc/MCollective/NotTargettedAtUs.html0000644000175000017500000002603512375446415022116 0ustar jonasjonas class MCollective::NotTargettedAtUs - mcollective version 2.6.0

class MCollective::NotTargettedAtUs

mcollective-2.6.0/doc/MCollective/Shell.html0000644000175000017500000007010512375446416017762 0ustar jonasjonas class MCollective::Shell - mcollective version 2.6.0

class MCollective::Shell

Wrapper around systemu that handles executing of system commands in a way that makes stdout, stderr and status available. Supports timeouts and sets a default sane environment.

s = Shell.new("date", opts)
s.runcommand
puts s.stdout
puts s.stderr
puts s.status.exitstatus

Options hash can have:

cwd         - the working directory the command will be run from
stdin       - a string that will be sent to stdin of the program
stdout      - a variable that will receive stdout, must support <<
stderr      - a variable that will receive stdin, must support <<
environment - the shell environment, defaults to include LC_ALL=C
              set to nil to clear the environment even of LC_ALL
timeout     - a timeout in seconds after which the subprocess is killed,
              the special value :on_thread_exit kills the subprocess
              when the invoking thread (typically the agent) has ended

Attributes

command[R]
cwd[R]
environment[R]
status[R]
stderr[R]
stdin[R]
stdout[R]
timeout[R]

Public Class Methods

new(command, options={}) click to toggle source
# File lib/mcollective/shell.rb, line 27
def initialize(command, options={})
  @environment = {"LC_ALL" => "C"}
  @command = command
  @status = nil
  @stdout = ""
  @stderr = ""
  @stdin = nil
  @cwd = Dir.tmpdir
  @timeout = nil

  options.each do |opt, val|
    case opt.to_s
      when "stdout"
        raise "stdout should support <<" unless val.respond_to?("<<")
        @stdout = val

      when "stderr"
        raise "stderr should support <<" unless val.respond_to?("<<")
        @stderr = val

      when "stdin"
        raise "stdin should be a String" unless val.is_a?(String)
        @stdin = val

      when "cwd"
        raise "Directory #{val} does not exist" unless File.directory?(val)
        @cwd = val

      when "environment"
        if val.nil?
          @environment = {}
        else
          @environment.merge!(val.dup)
          @environment = @environment.delete_if { |k,v| v.nil? }
        end

      when "timeout"
        raise "timeout should be a positive integer or the symbol :on_thread_exit symbol" unless val.eql?(:on_thread_exit) || ( val.is_a?(Fixnum) && val>0 )
        @timeout = val
    end
  end
end

Public Instance Methods

runcommand() click to toggle source

Actually does the systemu call passing in the correct environment, stdout and stderr

# File lib/mcollective/shell.rb, line 71
def runcommand
  opts = {"env"    => @environment,
          "stdout" => @stdout,
          "stderr" => @stderr,
          "cwd"    => @cwd}

  opts["stdin"] = @stdin if @stdin


  thread = Thread.current
  # Start a double fork and exec with systemu which implies a guard thread.
  # If a valid timeout is configured the guard thread will terminate the
  # executing process and reap the pid.
  # If no timeout is specified the process will run to completion with the
  # guard thread reaping the pid on completion.
  @status = systemu(@command, opts) do |cid|
    begin
      if timeout.is_a?(Fixnum)
        # wait for the specified timeout
        sleep timeout
      else
        # sleep while the agent thread is still alive
        while(thread.alive?)
          sleep 0.1
        end
      end

      # if the process is still running
      if (Process.kill(0, cid))
        # and a timeout was specified
        if timeout
          if Util.windows?
            Process.kill('KILL', cid)
          else
            # Kill the process
            Process.kill('TERM', cid)
            sleep 2
            Process.kill('KILL', cid) if (Process.kill(0, cid))
          end
        end
        # only wait if the parent thread is dead
        Process.waitpid(cid) unless thread.alive?
      end
    rescue SystemExit
    rescue Errno::ESRCH
    rescue Errno::ECHILD
      Log.warn("Could not reap process '#{cid}'.")
    rescue Exception => e
      Log.info("Unexpected exception received while waiting for child process: #{e.class}: #{e}")
    end
  end
  @status.thread.kill
  @status
end
mcollective-2.6.0/doc/MCollective/Discovery.html0000644000175000017500000011012612375446415020657 0ustar jonasjonas class MCollective::Discovery - mcollective version 2.6.0

class MCollective::Discovery

Public Class Methods

new(client) click to toggle source
# File lib/mcollective/discovery.rb, line 3
def initialize(client)
  @known_methods = find_known_methods
  @default_method = Config.instance.default_discovery_method
  @client = client
end

Public Instance Methods

check_capabilities(filter) click to toggle source

Agent filters are always present no matter what, so we cant raise an error if the capabilities suggest the discovery method cant do agents we just have to rely on the discovery plugin to not do stupid things in the presense of a agent filter

# File lib/mcollective/discovery.rb, line 62
def check_capabilities(filter)
  capabilities = ddl.discovery_interface[:capabilities]

  unless capabilities.include?(:classes)
    raise "Cannot use class filters while using the '%s' discovery method" % discovery_method unless filter["cf_class"].empty?
  end

  unless capabilities.include?(:facts)
    raise "Cannot use fact filters while using the '%s' discovery method" % discovery_method unless filter["fact"].empty?
  end

  unless capabilities.include?(:identity)
    raise "Cannot use identity filters while using the '%s' discovery method" % discovery_method unless filter["identity"].empty?
  end

  unless capabilities.include?(:compound)
    raise "Cannot use compound filters while using the '%s' discovery method" % discovery_method unless filter["compound"].empty?
  end
end
ddl() click to toggle source
# File lib/mcollective/discovery.rb, line 47
def ddl
  @ddl ||= DDL.new(discovery_method, :discovery)

  # if the discovery method got changed we might have an old DDL cached
  # this will detect that and reread the correct DDL from disk
  unless @ddl.meta[:name] == discovery_method
    @ddl = DDL.new(discovery_method, :discovery)
  end

  return @ddl
end
discover(filter, timeout, limit) click to toggle source
# File lib/mcollective/discovery.rb, line 127
def discover(filter, timeout, limit)
  raise "Limit has to be an integer" unless limit.is_a?(Fixnum)

  force_discovery_method_by_filter(filter)

  check_capabilities(filter)

  discovered = discovery_class.discover(filter, discovery_timeout(timeout, filter), limit, @client)

  if limit > 0
    return discovered[0,limit]
  else
    return discovered
  end
end
discovery_class() click to toggle source
# File lib/mcollective/discovery.rb, line 39
def discovery_class
  method = discovery_method.capitalize

  PluginManager.loadclass("MCollective::Discovery::#{method}") unless self.class.const_defined?(method)

  self.class.const_get(method)
end
discovery_method() click to toggle source
# File lib/mcollective/discovery.rb, line 21
def discovery_method
  method = "mc"

  if @client.options[:discovery_method]
    method = @client.options[:discovery_method]
  else
    method = @default_method
  end

  raise "Unknown discovery method %s" % method unless has_method?(method)

  unless method == "mc"
    raise "Custom discovery methods require direct addressing mode" unless Config.instance.direct_addressing
  end

  return method
end
discovery_timeout(timeout, filter) click to toggle source
# File lib/mcollective/discovery.rb, line 117
def discovery_timeout(timeout, filter)
  timeout = ddl.meta[:timeout] unless timeout

  unless (filter["compound"] && filter["compound"].empty?)
    timeout + timeout_for_compound_filter(filter["compound"])
  else
    timeout
  end
end
find_known_methods() click to toggle source
# File lib/mcollective/discovery.rb, line 9
def find_known_methods
  PluginManager.find("discovery")
end
force_direct_mode?() click to toggle source
# File lib/mcollective/discovery.rb, line 17
def force_direct_mode?
  discovery_method != "mc"
end
force_discovery_method_by_filter(filter) click to toggle source

checks if compound filters are used and then forces the 'mc' discovery plugin

# File lib/mcollective/discovery.rb, line 83
def force_discovery_method_by_filter(filter)
  unless discovery_method == "mc"
    unless filter["compound"].empty?
      Log.info "Switching to mc discovery method because compound filters are used"
      @client.options[:discovery_method] = "mc"

      return true
    end
  end

  return false
end
has_method?(method) click to toggle source
# File lib/mcollective/discovery.rb, line 13
def has_method?(method)
  @known_methods.include?(method)
end
timeout_for_compound_filter(compound_filter) click to toggle source

if a compound filter is specified and it has any function then we read the DDL for each of those plugins and sum up the timeout declared in the DDL

# File lib/mcollective/discovery.rb, line 99
def timeout_for_compound_filter(compound_filter)
  return 0 if compound_filter.nil? || compound_filter.empty?

  timeout = 0

  compound_filter.each do |filter|
    filter.each do |statement|
      if statement["fstatement"]
        pluginname = Data.pluginname(statement["fstatement"]["name"])
        ddl = DDL.new(pluginname, :data)
        timeout += ddl.meta[:timeout]
      end
    end
  end

  timeout
end
mcollective-2.6.0/doc/MCollective/MessageNotReceived.html0000644000175000017500000002613012375446415022425 0ustar jonasjonas class MCollective::MessageNotReceived - mcollective version 2.6.0

class MCollective::MessageNotReceived

mcollective-2.6.0/doc/MCollective/BackoffSuggestion.html0000644000175000017500000003173712375446414022324 0ustar jonasjonas class MCollective::BackoffSuggestion - mcollective version 2.6.0

class MCollective::BackoffSuggestion

Attributes

backoff[R]

Public Class Methods

new(backoff = nil) click to toggle source
# File lib/mcollective/exceptions.rb, line 14
def initialize(backoff = nil)
  @backoff = backoff
end
mcollective-2.6.0/doc/MCollective/Agent.html0000644000175000017500000002560112375446414017750 0ustar jonasjonas module MCollective::Agent - mcollective version 2.6.0

module MCollective::Agent

mcollective-2.6.0/doc/MCollective/Connector.html0000644000175000017500000003121112375446415020637 0ustar jonasjonas module MCollective::Connector - mcollective version 2.6.0

module MCollective::Connector

Connectors take care of transporting messages between clients and agents, the design doesn't your middleware to be very rich in features. All it really needs is the ability to send and receive messages to named queues/topics.

At present there are assumptions about the naming of topics and queues that is compatible with Stomp, ie.

/topic/foo.bar/baz /queue/foo.bar/baz

This is the only naming format that is supported, but you could replace Stomp with something else that supports the above, see MCollective::Connector::Stomp for the default connector.

Connector plugins handle the communications with the middleware, you can provide your own to speak to something other than Stomp, your plugins must inherit from MCollective::Connector::Base and should provide the following methods:

connect - Creates a connection to the middleware, no arguments should get its parameters from the config receive - Receive data from the middleware, should act like a blocking call only returning if/when data

was received.  It should get data from all subscribed channels/topics.  Individual messages
should be returned as MCollective::Request objects with the payload provided

publish - Takes a target and msg, should send the message to the supplied target topic or destination subscribe - Adds a subscription to a specific message source unsubscribe - Removes a subscription to a specific message source disconnect - Disconnects from the middleware

These methods are all that's needed for a new connector protocol and should hopefully be simple enough to not have tied us to Stomp.

mcollective-2.6.0/doc/MCollective/UnknownRPCError.html0000644000175000017500000002610012375446416021725 0ustar jonasjonas class MCollective::UnknownRPCError - mcollective version 2.6.0

class MCollective::UnknownRPCError

mcollective-2.6.0/doc/MCollective/Aggregate/0000755000175000017500000000000012375446416017710 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Aggregate/Result.html0000644000175000017500000002670612375446414022065 0ustar jonasjonas module MCollective::Aggregate::Result - mcollective version 2.6.0

module MCollective::Aggregate::Result

mcollective-2.6.0/doc/MCollective/Aggregate/Base.html0000644000175000017500000004645712375446414021466 0ustar jonasjonas class MCollective::Aggregate::Base - mcollective version 2.6.0

class MCollective::Aggregate::Base

Attributes

action[RW]
aggregate_format[RW]
arguments[RW]
name[RW]
output_name[RW]
result[RW]

Public Class Methods

new(output_name, arguments, aggregate_format, action) click to toggle source
# File lib/mcollective/aggregate/base.rb, line 6
def initialize(output_name, arguments, aggregate_format, action)
  @name = self.class.to_s
  @output_name = output_name

  # Any additional arguments passed in the ddl after the output field will
  # be stored in the arguments array which can be used in the function
  @arguments = arguments
  @aggregate_format = aggregate_format
  @action = action
  @result = {:value => nil, :type => nil, :output => output_name}

  startup_hook
end

Public Instance Methods

result_class(type) click to toggle source
# File lib/mcollective/aggregate/base.rb, line 35
def result_class(type)
  Result.const_get("#{type.to_s.capitalize}Result")
end
summarize() click to toggle source

Stops execution of the function and returns a specific ResultObject, aggregate functions will most likely override this but this is the simplest case so we might as well default to that

# File lib/mcollective/aggregate/base.rb, line 29
def summarize
  raise "Result type is not set while trying to summarize aggregate function results" unless @result[:type]

  result_class(@result[:type]).new(@result, @aggregate_format, @action)
end
mcollective-2.6.0/doc/MCollective/Aggregate/Result/0000755000175000017500000000000012375446416021166 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Aggregate/Result/NumericResult.html0000644000175000017500000003274212375446414024663 0ustar jonasjonas class MCollective::Aggregate::Result::NumericResult - mcollective version 2.6.0

class MCollective::Aggregate::Result::NumericResult

Public Instance Methods

to_s() click to toggle source
# File lib/mcollective/aggregate/result/numeric_result.rb, line 5
def to_s
  return "" if @result[:value].nil?

  return @aggregate_format % @result[:value]
end
mcollective-2.6.0/doc/MCollective/Aggregate/Result/CollectionResult.html0000644000175000017500000003527012375446414025353 0ustar jonasjonas class MCollective::Aggregate::Result::CollectionResult - mcollective version 2.6.0

class MCollective::Aggregate::Result::CollectionResult

Public Instance Methods

to_s() click to toggle source
# File lib/mcollective/aggregate/result/collection_result.rb, line 5
def to_s
  return "" if @result[:value].keys.include?(nil)

  result = StringIO.new

  @result[:value].sort{|x,y| x[1] <=> y[1]}.reverse.each do |value|
    result.puts @aggregate_format % [value[0], value[1]]
  end

  result.string.chomp
end
mcollective-2.6.0/doc/MCollective/Aggregate/Result/Base.html0000644000175000017500000004216212375446414022731 0ustar jonasjonas class MCollective::Aggregate::Result::Base - mcollective version 2.6.0

class MCollective::Aggregate::Result::Base

Attributes

action[RW]
aggregate_format[RW]
result[RW]

Public Class Methods

new(result, aggregate_format, action) click to toggle source
# File lib/mcollective/aggregate/result/base.rb, line 7
def initialize(result, aggregate_format, action)
  raise "No aggregate_format defined in ddl or aggregate function" unless aggregate_format

  @result = result
  @aggregate_format = aggregate_format
  @action = action
end

Public Instance Methods

result_type() click to toggle source
# File lib/mcollective/aggregate/result/base.rb, line 19
def result_type
  @result[:type]
end
to_s() click to toggle source
# File lib/mcollective/aggregate/result/base.rb, line 15
def to_s
  raise "'to_s' method not implemented for result class '#{self.class}'"
end
mcollective-2.6.0/doc/MCollective/RPCAborted.html0000644000175000017500000002606612375446415020646 0ustar jonasjonas class MCollective::RPCAborted - mcollective version 2.6.0

class MCollective::RPCAborted

mcollective-2.6.0/doc/MCollective/PluginPackager.html0000644000175000017500000006516212375446415021615 0ustar jonasjonas module MCollective::PluginPackager - mcollective version 2.6.0

module MCollective::PluginPackager

Public Class Methods

[](klass) click to toggle source
# File lib/mcollective/pluginpackager.rb, line 12
def self.[](klass)
  const_get("#{klass}")
end
check_dir_present(path) click to toggle source

Checks if a directory is present and not empty

# File lib/mcollective/pluginpackager.rb, line 31
def self.check_dir_present(path)
  (File.directory?(path) && !Dir.glob(File.join(path, "*")).empty?)
end
command_available?(build_tool) click to toggle source

Checks if a build tool is present on the system

# File lib/mcollective/pluginpackager.rb, line 54
def self.command_available?(build_tool)
  ENV["PATH"].split(File::PATH_SEPARATOR).each do |path|
    builder = File.join(path, build_tool)
    if File.exists?(builder)
      return true
    end
  end
  false
end
execute_verbosely(verbose, &block) click to toggle source

Quietly calls a block if verbose parameter is false

# File lib/mcollective/pluginpackager.rb, line 36
def self.execute_verbosely(verbose, &block)
  unless verbose
    old_stdout = $stdout.clone
    $stdout.reopen(File.new("/dev/null", "w"))
    begin
      block.call
    rescue Exception => e
      $stdout.reopen old_stdout
      raise e
    ensure
      $stdout.reopen old_stdout
    end
  else
    block.call
  end
end
filter_dependencies(prefix, dependencies) click to toggle source

Filter out platform specific dependencies Given a list of dependencies named - debian::foo redhat::bar ::filter_dependencies('debian', dependencies) will return foo.

# File lib/mcollective/pluginpackager.rb, line 74
def self.filter_dependencies(prefix, dependencies)
  dependencies.map do |dependency|
    if dependency[:name] =~ /^(\w+)::(\w+)/
      if prefix == $1
        dependency[:name] = $2
        dependency
      else
        nil
      end
    else
      dependency
    end
  end.reject{ |dependency| dependency == nil }
end
get_metadata(path, type) click to toggle source

Fetch and return metadata from plugin DDL

# File lib/mcollective/pluginpackager.rb, line 17
def self.get_metadata(path, type)
  ddl = DDL.new("package", type.to_sym, false)

  begin
    ddl_file = File.read(Dir.glob(File.join(path, type, "*.ddl")).first)
  rescue Exception
    raise "failed to load ddl file in plugin directory : #{File.join(path, type)}"
  end
  ddl.instance_eval ddl_file

  return ddl.meta, ddl.requirements[:mcollective]
end
load_packagers() click to toggle source

Package implementation plugins

# File lib/mcollective/pluginpackager.rb, line 8
def self.load_packagers
  PluginManager.find_and_load("pluginpackager")
end
safe_system(*args) click to toggle source
# File lib/mcollective/pluginpackager.rb, line 64
def self.safe_system(*args)
  raise(RuntimeError, "Failed: #{args.join(' ')}") unless system *args
end
mcollective-2.6.0/doc/MCollective/Facts/0000755000175000017500000000000012375446416017062 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Facts/Base.html0000644000175000017500000005627612375446415020641 0ustar jonasjonas class MCollective::Facts::Base - mcollective version 2.6.0

class MCollective::Facts::Base

A base class for fact providers, to make a new fully functional fact provider inherit from this and simply provide a self.get_facts method that returns a hash like:

{"foo" => "bar",
 "bar" => "baz"}

Public Class Methods

inherited(klass) click to toggle source

Registers new fact sources into the plugin manager

# File lib/mcollective/facts/base.rb, line 17
def self.inherited(klass)
  PluginManager << {:type => "facts_plugin", :class => klass.to_s}
end
new() click to toggle source
# File lib/mcollective/facts/base.rb, line 10
def initialize
  @facts = {}
  @last_good_facts = {}
  @last_facts_load = 0
end

Public Instance Methods

force_reload?() click to toggle source

Plugins can override this to provide forced fact invalidation

# File lib/mcollective/facts/base.rb, line 77
def force_reload?
  false
end
get_fact(fact=nil) click to toggle source

Returns the value of a single fact

# File lib/mcollective/facts/base.rb, line 22
def get_fact(fact=nil)
  config = Config.instance

  cache_time = config.fact_cache_time || 300

  Thread.exclusive do
    begin
      if (Time.now.to_i - @last_facts_load > cache_time.to_i ) || force_reload?
        Log.debug("Resetting facter cache, now: #{Time.now.to_i} last-known-good: #{@last_facts_load}")

        tfacts = load_facts_from_source

        # Force reset to last known good state on empty facts
        raise "Got empty facts" if tfacts.empty?

        @facts = normalize_facts(tfacts)

        @last_good_facts = @facts.clone
        @last_facts_load = Time.now.to_i
      else
        Log.debug("Using cached facts now: #{Time.now.to_i} last-known-good: #{@last_facts_load}")
      end
    rescue Exception => e
      Log.error("Failed to load facts: #{e.class}: #{e}")

      # Avoid loops where failing fact loads cause huge CPU
      # loops, this way it only retries once every cache_time
      # seconds
      @last_facts_load = Time.now.to_i

      # Revert to last known good state
      @facts = @last_good_facts.clone
    end
  end


  # If you do not supply a specific fact all facts will be returned
  if fact.nil?
    return @facts
  else
    @facts.include?(fact) ? @facts[fact] : nil
  end
end
get_facts() click to toggle source

Returns all facts

# File lib/mcollective/facts/base.rb, line 67
def get_facts
  get_fact(nil)
end
has_fact?(fact) click to toggle source

Returns true if we know about a specific fact, false otherwise

# File lib/mcollective/facts/base.rb, line 72
def has_fact?(fact)
  get_fact(nil).include?(fact)
end
mcollective-2.6.0/doc/MCollective/PluginManager.html0000644000175000017500000011150712375446415021445 0ustar jonasjonas module MCollective::PluginManager - mcollective version 2.6.0

module MCollective::PluginManager

A simple plugin manager, it stores one plugin each of a specific type the idea is that we can only have one security provider, one connector etc.

Public Class Methods

<<(plugin) click to toggle source

Adds a plugin to the list of plugins, we expect a hash like:

{:type => "base",
 :class => foo.new}

or like:

{:type => "base",
 :class => "Foo::Bar"}

In the event that we already have a class with the given type an exception will be raised.

If the :class passed is a String then we will delay instantiation till the first time someone asks for the plugin, this is because most likely the registration gets done by inherited() hooks, at which point the plugin class is not final.

If we were to do a .new here the Class initialize method would get called and not the plugins, we there for only initialize the classes when they get requested via []

By default all plugin instances are cached and returned later so there's always a single instance. You can pass :single_instance => false when calling this to instruct it to always return a new instance when a copy is requested. This only works with sending a String for :class.

# File lib/mcollective/pluginmanager.rb, line 30
def self.<<(plugin)
  plugin[:single_instance] = true unless plugin.include?(:single_instance)

  type = plugin[:type]
  klass = plugin[:class]
  single = plugin[:single_instance]

  raise("Plugin #{type} already loaded") if @plugins.include?(type)


  # If we get a string then store 'nil' as the instance, signalling that we'll
  # create the class later on demand.
  if klass.is_a?(String)
    @plugins[type] = {:loadtime => Time.now, :class => klass, :instance => nil, :single => single}
    Log.debug("Registering plugin #{type} with class #{klass} single_instance: #{single}")
  else
    @plugins[type] = {:loadtime => Time.now, :class => klass.class, :instance => klass, :single => true}
    Log.debug("Registering plugin #{type} with class #{klass.class} single_instance: true")
  end
end
[](plugin) click to toggle source

Gets a plugin by type

# File lib/mcollective/pluginmanager.rb, line 72
def self.[](plugin)
  raise("No plugin #{plugin} defined") unless @plugins.include?(plugin)

  klass = @plugins[plugin][:class]

  if @plugins[plugin][:single]
    # Create an instance of the class if one hasn't been done before
    if @plugins[plugin][:instance] == nil
      Log.debug("Returning new plugin #{plugin} with class #{klass}")
      @plugins[plugin][:instance] = create_instance(klass)
    else
      Log.debug("Returning cached plugin #{plugin} with class #{klass}")
    end

    @plugins[plugin][:instance]
  else
    Log.debug("Returning new plugin #{plugin} with class #{klass}")
    create_instance(klass)
  end
end
clear() click to toggle source

deletes all registered plugins

# File lib/mcollective/pluginmanager.rb, line 67
def self.clear
  @plugins.clear
end
create_instance(klass) click to toggle source

use eval to create an instance of a class

# File lib/mcollective/pluginmanager.rb, line 94
def self.create_instance(klass)
  begin
    eval("#{klass}.new")
  rescue Exception => e
    raise("Could not create instance of plugin #{klass}: #{e}")
  end
end
delete(plugin) click to toggle source

Removes a plugim the list

# File lib/mcollective/pluginmanager.rb, line 52
def self.delete(plugin)
  @plugins.delete(plugin) if @plugins.include?(plugin)
end
find(type, extension="rb") click to toggle source

Finds plugins in all configured libdirs

find("agent")

will return an array of just agent names, for example:

["puppetd", "package"]

Can also be used to find files of other extensions:

find("agent", "ddl")

Will return the same list but only of files with extension .ddl in the agent subdirectory

# File lib/mcollective/pluginmanager.rb, line 116
def self.find(type, extension="rb")
  extension = ".#{extension}" unless extension.match(/^\./)

  plugins = []

  Config.instance.libdir.each do |libdir|
    plugdir = File.join([libdir, "mcollective", type.to_s])
    next unless File.directory?(plugdir)

    Dir.new(plugdir).grep(/#{extension}$/).map do |plugin|
      plugins << File.basename(plugin, extension)
    end
  end

  plugins.sort.uniq
end
find_and_load(type, extension="rb") { |plugin| ... } click to toggle source

Finds and loads from disk all plugins from all libdirs that match certain criteria.

find_and_load("pluginpackager")

Will find all .rb files in the libdir/mcollective/pluginpackager/ directory in all libdirs and load them from disk.

You can influence what plugins get loaded using a block notation:

find_and_load("pluginpackager") do |plugin|
   plugin.match(/puppet/)
end

This will load only plugins matching /puppet/

# File lib/mcollective/pluginmanager.rb, line 148
def self.find_and_load(type, extension="rb")
  extension = ".#{extension}" unless extension.match(/^\./)

  klasses = find(type, extension).map do |plugin|
    if block_given?
      next unless yield(plugin)
    end

    "%s::%s::%s" % [ "MCollective", type.capitalize, plugin.capitalize ]
  end.compact

  klasses.sort.uniq.each {|klass| loadclass(klass, true)}
end
grep(regex) click to toggle source

Grep's over the plugin list and returns the list found

# File lib/mcollective/pluginmanager.rb, line 176
def self.grep(regex)
  @plugins.keys.grep(regex)
end
include?(plugin) click to toggle source

Finds out if we have a plugin with the given name

# File lib/mcollective/pluginmanager.rb, line 57
def self.include?(plugin)
  @plugins.include?(plugin)
end
loadclass(klass, squash_failures=false) click to toggle source

Loads a class from file by doing some simple search/replace on class names and then doing a require.

# File lib/mcollective/pluginmanager.rb, line 164
def self.loadclass(klass, squash_failures=false)
  fname = klass.gsub("::", "/").downcase + ".rb"

  Log.debug("Loading #{klass} from #{fname}")

  load fname
rescue Exception => e
  Log.error("Failed to load #{klass}: #{e}")
  raise unless squash_failures
end
pluginlist() click to toggle source

Provides a list of plugins we know about

# File lib/mcollective/pluginmanager.rb, line 62
def self.pluginlist
  @plugins.keys.sort
end
mcollective-2.6.0/doc/MCollective/DDL/0000755000175000017500000000000012375446416016425 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/DDL/AgentDDL.html0000644000175000017500000013130312375446415020675 0ustar jonasjonas class MCollective::DDL::AgentDDL - mcollective version 2.6.0

class MCollective::DDL::AgentDDL

A DDL class specific to agent plugins.

A full DDL can be seen below with all the possible bells and whistles present.

metadata :name => “Utilities and Helpers for SimpleRPC Agentsâ€,

:description => "General helpful actions that expose stats and internals to SimpleRPC clients",
:author      => "R.I.Pienaar <rip@devco.net>",
:license     => "Apache License, Version 2.0",
:version     => "1.0",
:url         => "http://marionette-collective.org/",
:timeout     => 10

action “get_factâ€, :description => “Retrieve a single fact from the fact store†do

 display :always

 input :fact,
       :prompt      => "The name of the fact",
       :description => "The fact to retrieve",
       :type        => :string,
       :validation  => '^[\w\-\.]+$',
       :optional    => false,
       :maxlength   => 40,
       :default     => "fqdn"

 output :fact,
        :description => "The name of the fact being returned",
        :display_as  => "Fact"

 output :value,
        :description => "The value of the fact",
        :display_as  => "Value",
        :default     => ""

summarize do
    aggregate summary(:value)
end

end

Public Class Methods

new(plugin, plugintype=:agent, loadddl=true) click to toggle source
Calls superclass method
# File lib/mcollective/ddl/agentddl.rb, line 41
def initialize(plugin, plugintype=:agent, loadddl=true)
  @process_aggregate_functions = nil

  super
end

Public Instance Methods

action(name, input, &block) click to toggle source

Creates the definition for an action, you can nest input definitions inside the action to attach inputs and validation to the actions

action "status", :description => "Restarts a Service" do
   display :always

   input  "service",
          :prompt      => "Service Action",
          :description => "The action to perform",
          :type        => :list,
          :optional    => true,
          :list        => ["start", "stop", "restart", "status"]

   output "status",
          :description => "The status of the service after the action"

end
# File lib/mcollective/ddl/agentddl.rb, line 116
def action(name, input, &block)
  raise "Action needs a :description property" unless input.include?(:description)

  unless @entities.include?(name)
    @entities[name] = {}
    @entities[name][:action] = name
    @entities[name][:input] = {}
    @entities[name][:output] = {}
    @entities[name][:display] = :failed
    @entities[name][:description] = input[:description]
  end

  # if a block is passed it might be creating input methods, call it
  # we set @current_entity so the input block can know what its talking
  # to, this is probably an epic hack, need to improve.
  @current_entity = name
  block.call if block_given?
  @current_entity = nil
end
action_interface(name) click to toggle source

Returns the interface for a specific action

# File lib/mcollective/ddl/agentddl.rb, line 202
def action_interface(name)
  @entities[name] || {}
end
actions() click to toggle source

Returns an array of actions this agent support

# File lib/mcollective/ddl/agentddl.rb, line 207
def actions
  @entities.keys
end
aggregate(function, format = {:format => nil}) click to toggle source

Sets the aggregate array for the given action

# File lib/mcollective/ddl/agentddl.rb, line 70
def aggregate(function, format = {:format => nil})
  raise(DDLValidationError, "Formats supplied to aggregation functions should be a hash") unless format.is_a?(Hash)
  raise(DDLValidationError, "Formats supplied to aggregation functions must have a :format key") unless format.keys.include?(:format)
  raise(DDLValidationError, "Functions supplied to aggregate should be a hash") unless function.is_a?(Hash)

  unless (function.keys.include?(:args)) && function[:args]
    raise DDLValidationError, "aggregate method for action '%s' missing a function parameter" % entities[@current_entity][:action]
  end

  entities[@current_entity][:aggregate] ||= []
  entities[@current_entity][:aggregate] << (format[:format].nil? ? function : function.merge(format))
end
display(pref) click to toggle source

Sets the display preference to either :ok, :failed, :flatten or :always operates on action level

# File lib/mcollective/ddl/agentddl.rb, line 85
def display(pref)
  if pref == :flatten
    Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release.")
  end

  # defaults to old behavior, complain if its supplied and invalid
  unless [:ok, :failed, :flatten, :always].include?(pref)
    raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always"
  end

  action = @current_entity
  @entities[action][:display] = pref
end
input(argument, properties) click to toggle source
Calls superclass method
# File lib/mcollective/ddl/agentddl.rb, line 47
def input(argument, properties)
  raise "Input needs a :optional property" unless properties.include?(:optional)

  super
end
is_function?(method_name) click to toggle source

Checks if a method name matches a aggregate plugin. This is used by method missing so that we dont greedily assume that every #method_missing call in an agent ddl has hit a aggregate function.

# File lib/mcollective/ddl/agentddl.rb, line 150
def is_function?(method_name)
  PluginManager.find("aggregate").include?(method_name.to_s)
end
method_missing(name, *args, &block) click to toggle source

If the method name matches a # aggregate function, we return the function with args as a hash. This will only be active if the @process_aggregate_functions is set to true which only happens in the summarize block

# File lib/mcollective/ddl/agentddl.rb, line 139
def method_missing(name, *args, &block)
  unless @process_aggregate_functions || is_function?(name)
    raise NoMethodError, "undefined local variable or method `#{name}'", caller
  end

  return {:function => name, :args => args}
end
set_default_input_arguments(action, arguments) click to toggle source

For a given action and arguments look up the DDL interface to that action and if any arguments in the DDL have a :default value assign that to any input that does not have an argument in the input arguments

This is intended to only be called on clients and not on servers as the clients should never be able to publish non compliant requests and the servers should really not tamper with incoming requests since doing so might raise validation errors that were not raised on the client breaking our fail-fast approach to input validation

# File lib/mcollective/ddl/agentddl.rb, line 163
def set_default_input_arguments(action, arguments)
  input = action_interface(action)[:input]

  return unless input

  input.keys.each do |key|
    if !arguments.include?(key) && !input[key][:default].nil? && !input[key][:optional]
      Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]])
      arguments[key] = input[key][:default]
    end
  end
end
summarize(&block) click to toggle source

Calls the summarize block defined in the ddl. Block will not be called if the ddl is getting processed on the server side. This means that aggregate plugins only have to be present on the client side.

The @process_aggregate_functions variable is used by the #method_missing block to determine if it should kick in, this way we very tightly control where we activate the #method_missing behavior turning it into a noop otherwise to maximise the chance of providing good user feedback

# File lib/mcollective/ddl/agentddl.rb, line 61
def summarize(&block)
  unless @config.mode == :server
    @process_aggregate_functions = true
    block.call
    @process_aggregate_functions = nil
  end
end
validate_rpc_request(action, arguments) click to toggle source

Helper to use the DDL to figure out if the remote call to an agent should be allowed based on action name and inputs.

# File lib/mcollective/ddl/agentddl.rb, line 178
def validate_rpc_request(action, arguments)
  # is the action known?
  unless actions.include?(action)
    raise DDLValidationError, "Attempted to call action #{action} for #{@pluginname} but it's not declared in the DDL"
  end

  input = action_interface(action)[:input] || {}

  input.keys.each do |key|
    unless input[key][:optional]
      unless arguments.keys.include?(key)
        raise DDLValidationError, "Action #{action} needs a #{key} argument"
      end
    end

    if arguments.keys.include?(key)
      validate_input_argument(input, key, arguments[key])
    end
  end

  true
end
mcollective-2.6.0/doc/MCollective/DDL/ValidatorDDL.html0000644000175000017500000002663212375446415021574 0ustar jonasjonas class MCollective::DDL::ValidatorDDL - mcollective version 2.6.0

class MCollective::DDL::ValidatorDDL

mcollective-2.6.0/doc/MCollective/DDL/Base.html0000644000175000017500000013352212375446415020172 0ustar jonasjonas class MCollective::DDL::Base - mcollective version 2.6.0

class MCollective::DDL::Base

The base class for all kinds of DDL files. DDL files when run gets parsed and builds up a hash of the basic primitive types, ideally restricted so it can be converted to JSON though today there are some Ruby Symbols in them which might be fixed laster on.

The Hash being built should be stored in @entities, the format is generally not prescribed but there's a definite feel to how DDL files look so study the agent and discovery ones to see how the structure applies to very different use cases.

For every plugin type you should have a single word name - that corresponds to the directory in the libdir where these plugins live. If you need anything above and beyond 'metadata' in your plugin DDL then add a PlugintypeDDL class here and add your specific behaviors to those.

Attributes

entities[R]
meta[R]
pluginname[R]
plugintype[R]
requirements[R]
usage[R]

Public Class Methods

new(plugin, plugintype=:agent, loadddl=true) click to toggle source
# File lib/mcollective/ddl/base.rb, line 22
def initialize(plugin, plugintype=:agent, loadddl=true)
  @entities = {}
  @meta = {}
  @usage = ""
  @config = Config.instance
  @pluginname = plugin
  @plugintype = plugintype.to_sym
  @requirements = {}

  loadddlfile if loadddl
end

Public Instance Methods

findddlfile(ddlname=nil, ddltype=nil) click to toggle source
# File lib/mcollective/ddl/base.rb, line 87
def findddlfile(ddlname=nil, ddltype=nil)
  ddlname = @pluginname unless ddlname
  ddltype = @plugintype unless ddltype

  @config.libdir.each do |libdir|
    ddlfile = File.join([libdir, "mcollective", ddltype.to_s, "#{ddlname}.ddl"])
    if File.exist?(ddlfile)
      Log.debug("Found #{ddlname} ddl at #{ddlfile}")
      return ddlfile
    end
  end
  return false
end
help(template=nil) click to toggle source

Generates help using the template based on the data created with metadata and input.

If no template name is provided one will be chosen based on the plugin type. If the provided template path is not absolute then the template will be loaded either from the config dir and if that does not exist, default to /etc/mcollective

# File lib/mcollective/ddl/base.rb, line 42
def help(template=nil)
  template = template_for_plugintype unless template
  template = Util.templatepath(template) unless Util.absolute_path?(template)

  template = File.read(template)
  meta = @meta
  entities = @entities

  unless template == "metadata-help.erb"
    metadata_template = Util.templatepath("metadata-help.erb")
    metadata_template = File.read(metadata_template)
    metastring = ERB.new(metadata_template, 0, '%')
    metastring = metastring.result(binding)
  end

  erb = ERB.new(template, 0, '%')
  erb.result(binding)
end
input(argument, properties) click to toggle source

Registers an input argument for a given action

See the documentation for action for how to use this

# File lib/mcollective/ddl/base.rb, line 151
def input(argument, properties)
  raise "Cannot figure out what entity input #{argument} belongs to" unless @current_entity

  entity = @current_entity

  [:prompt, :description, :type].each do |arg|
    raise "Input needs a :#{arg} property" unless properties.include?(arg)
  end

  @entities[entity][:input][argument] = {:prompt => properties[:prompt],
                                         :description => properties[:description],
                                         :type => properties[:type],
                                         :default => properties[:default],
                                         :optional => properties[:optional]}

  case properties[:type]
    when :string
      raise "Input type :string needs a :validation argument" unless properties.include?(:validation)
      raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength)

      @entities[entity][:input][argument][:validation] = properties[:validation]
      @entities[entity][:input][argument][:maxlength] = properties[:maxlength]

    when :list
      raise "Input type :list needs a :list argument" unless properties.include?(:list)

      @entities[entity][:input][argument][:list] = properties[:list]
  end
end
loadddlfile() click to toggle source
# File lib/mcollective/ddl/base.rb, line 79
def loadddlfile
  if ddlfile = findddlfile
    instance_eval(File.read(ddlfile), ddlfile, 1)
  else
    raise("Can't find DDL for #{@plugintype} plugin '#{@pluginname}'")
  end
end
metadata(meta) click to toggle source

Registers meta data for the introspection hash

# File lib/mcollective/ddl/base.rb, line 213
def metadata(meta)
  [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
    raise "Metadata needs a :#{arg} property" unless meta.include?(arg)
  end

  @meta = meta
end
output(argument, properties) click to toggle source

Registers an output argument for a given action

See the documentation for action for how to use this

# File lib/mcollective/ddl/base.rb, line 184
def output(argument, properties)
  raise "Cannot figure out what action input #{argument} belongs to" unless @current_entity
  raise "Output #{argument} needs a description argument" unless properties.include?(:description)
  raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as)

  action = @current_entity

  @entities[action][:output][argument] = {:description => properties[:description],
                                          :display_as  => properties[:display_as],
                                          :default     => properties[:default]}
end
requires(requirement) click to toggle source
# File lib/mcollective/ddl/base.rb, line 196
def requires(requirement)
  raise "Requirement should be a hash in the form :item => 'requirement'" unless requirement.is_a?(Hash)

  valid_requirements = [:mcollective]

  requirement.keys.each do |key|
    unless valid_requirements.include?(key)
      raise "Requirement %s is not a valid requirement, only %s is supported" % [key, valid_requirements.join(", ")]
    end

    @requirements[key] = requirement[key]
  end

  validate_requirements
end
template_for_plugintype() click to toggle source
# File lib/mcollective/ddl/base.rb, line 65
def template_for_plugintype
  case @plugintype
  when :agent
    return "rpc-help.erb"
  else
    if File.exists?(Util.templatepath("#{@plugintype}-help.erb"))
      return "#{@plugintype}-help.erb"
    else
      # Default help template gets loaded if plugintype-help does not exist.
      return "metadata-help.erb"
    end
  end
end
validate_input_argument(input, key, argument) click to toggle source

validate strings, lists and booleans, we'll add more types of validators when all the use cases are clear

only does validation for arguments actually given, since some might be optional. We validate the presense of the argument earlier so this is a safe assumption, just to skip them.

:string can have maxlength and regex. A maxlength of 0 will bypasss checks :list has a array of valid values

# File lib/mcollective/ddl/base.rb, line 125
def validate_input_argument(input, key, argument)
  Validator.load_validators

  case input[key][:type]
  when :string
    Validator.validate(argument, :string)

    Validator.length(argument, input[key][:maxlength].to_i)

    Validator.validate(argument, input[key][:validation])

  when :list
    Validator.validate(argument, input[key][:list])

  else
    Validator.validate(argument, input[key][:type])
  end

  return true
rescue => e
  raise DDLValidationError, "Cannot validate input %s: %s" % [key, e.to_s]
end
validate_requirements() click to toggle source
# File lib/mcollective/ddl/base.rb, line 101
def validate_requirements
  if requirement = @requirements[:mcollective]
    if Util.mcollective_version == "@DEVELOPMENT_VERSION@"
      Log.warn("DDL requirements validation being skipped in development")
      return true
    end

    if Util.versioncmp(Util.mcollective_version, requirement) < 0
      raise DDLValidationError, "%s plugin '%s' requires MCollective version %s or newer" % [@plugintype.to_s.capitalize, @pluginname, requirement]
    end
  end

  true
end
mcollective-2.6.0/doc/MCollective/DDL/DataDDL.html0000644000175000017500000004757212375446415020526 0ustar jonasjonas class MCollective::DDL::DataDDL - mcollective version 2.6.0

class MCollective::DDL::DataDDL

A DDL file for the data query plugins.

Query plugins can today take only one input by convention in the DDL that is called :query, otherwise the input is identical to the standard input.

metadata :name => “Agentâ€,

:description => "Meta data about installed MColletive Agents",
:author      => "R.I.Pienaar <rip@devco.net>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "http://marionette-collective.org/",
:timeout     => 1

dataquery :description => “Agent Meta Data†do

input :query,
      :prompt => "Agent Name",
      :description => "Valid agent name",
      :type => :string,
      :validation => /^[\w\_]+$/,
      :maxlength => 20

[:license, :timeout, :description, :url, :version, :author].each do |item|
  output item,
         :description => "Agent #{item}",
         :display_as => item.to_s.capitalize
end

end

Public Instance Methods

dataquery(input, &block) click to toggle source
# File lib/mcollective/ddl/dataddl.rb, line 31
def dataquery(input, &block)
  raise "Data queries need a :description" unless input.include?(:description)
  raise "Data queries can only have one definition" if @entities[:data]

  @entities[:data]  = {:description => input[:description],
                       :input => {},
                       :output => {}}

  @current_entity = :data
  block.call if block_given?
  @current_entity = nil
end
dataquery_interface() click to toggle source

Returns the interface for the data query

# File lib/mcollective/ddl/dataddl.rb, line 51
def dataquery_interface
  @entities[:data] || {}
end
input(argument, properties) click to toggle source
Calls superclass method MCollective::DDL::Base#input
# File lib/mcollective/ddl/dataddl.rb, line 44
def input(argument, properties)
  raise "The only valid input name for a data query is 'query'" if argument != :query

  super
end
mcollective-2.6.0/doc/MCollective/DDL/DiscoveryDDL.html0000644000175000017500000004502012375446415021606 0ustar jonasjonas class MCollective::DDL::DiscoveryDDL - mcollective version 2.6.0

class MCollective::DDL::DiscoveryDDL

DDL for discovery plugins, a full example can be seen below

metadata :name => “mcâ€,

:description => "MCollective Broadcast based discovery",
:author      => "R.I.Pienaar <rip@devco.net>",
:license     => "ASL 2.0",
:version     => "0.1",
:url         => "http://marionette-collective.org/",
:timeout     => 2

discovery do

capabilities [:classes, :facts, :identity, :agents, :compound]

end

Public Instance Methods

capabilities(*caps) click to toggle source

records valid capabilities for discovery plugins

# File lib/mcollective/ddl/discoveryddl.rb, line 22
def capabilities(*caps)
  caps = [caps].flatten

  raise "Discovery plugin capabilities can't be empty" if caps.empty?

  caps.each do |cap|
    if [:classes, :facts, :identity, :agents, :compound].include?(cap)
      @entities[:discovery][:capabilities] << cap
    else
      raise "%s is not a valid capability, valid capabilities are :classes, :facts, :identity, :agents and :compound" % cap
    end
  end
end
discovery(&block) click to toggle source

Creates the definition for new discovery plugins

discovery do
   capabilities [:classes, :facts, :identity, :agents, :compound]
end
# File lib/mcollective/ddl/discoveryddl.rb, line 41
def discovery(&block)
  raise "Discovery plugins can only have one definition" if @entities[:discovery]

  @entities[:discovery] = {:capabilities => []}

  @current_entity = :discovery
  block.call if block_given?
  @current_entity = nil
end
discovery_interface() click to toggle source
# File lib/mcollective/ddl/discoveryddl.rb, line 17
def discovery_interface
  @entities[:discovery]
end
mcollective-2.6.0/doc/MCollective/UnexpectedMessageType.html0000644000175000017500000002613612375446416023173 0ustar jonasjonas class MCollective::UnexpectedMessageType - mcollective version 2.6.0

class MCollective::UnexpectedMessageType

mcollective-2.6.0/doc/MCollective/RPCError.html0000644000175000017500000002601612375446415020352 0ustar jonasjonas class MCollective::RPCError - mcollective version 2.6.0

class MCollective::RPCError

mcollective-2.6.0/doc/MCollective/MissingRPCData.html0000644000175000017500000002607612375446415021472 0ustar jonasjonas class MCollective::MissingRPCData - mcollective version 2.6.0

class MCollective::MissingRPCData

mcollective-2.6.0/doc/MCollective/Aggregate.html0000644000175000017500000007002612375446414020601 0ustar jonasjonas class MCollective::Aggregate - mcollective version 2.6.0

class MCollective::Aggregate

Attributes

action[RW]
ddl[RW]
failed[RW]
functions[RW]

Public Class Methods

new(ddl) click to toggle source
# File lib/mcollective/aggregate.rb, line 8
def initialize(ddl)
  @functions = []
  @ddl = ddl
  @action = ddl[:action]
  @failed = []

  create_functions
end

Public Instance Methods

call_functions(reply) click to toggle source

Call all the appropriate functions with the reply data received from RPC::Client

# File lib/mcollective/aggregate.rb, line 45
def call_functions(reply)
  @functions.each do |function|
    Log.debug("Calling aggregate function #{function} for result")
    begin
      function.process_result(reply[:data][function.output_name], reply)
    rescue Exception => e
      Log.error("Could not process aggregate function for '#{function.output_name}'. #{e.to_s}")
      @failed << {:name => function.output_name, :type => :process_result}
      @functions.delete(function)
    end
  end
end
contains_output?(output) click to toggle source

Check if the function param is defined as an output for the action in the ddl

# File lib/mcollective/aggregate.rb, line 40
def contains_output?(output)
  @ddl[:output].keys.include?(output)
end
create_functions() click to toggle source

Creates instances of the Aggregate functions and stores them in the function array. All aggregate call and summarize method calls operate on these function as a batch.

# File lib/mcollective/aggregate.rb, line 19
def create_functions
  @ddl[:aggregate].each_with_index do |agg, i|
    output = agg[:args][0]

    if contains_output?(output)
      arguments = agg[:args][1]
      format = (arguments.delete(:format) if arguments) || nil
      begin
        @functions << load_function(agg[:function]).new(output, arguments, format, @action)
      rescue Exception => e
        Log.error("Cannot create aggregate function '#{output}'. #{e.to_s}")
        @failed << {:name => output, :type => :startup}
      end
    else
      Log.error("Cannot create aggregate function '#{output}'. '#{output}' has not been specified as a valid ddl output.")
      @failed << {:name => output, :type => :create}
    end
  end
end
load_function(function_name) click to toggle source

Loads function from disk for use

# File lib/mcollective/aggregate.rb, line 76
def load_function(function_name)
  function_name = function_name.to_s.capitalize

  PluginManager.loadclass("MCollective::Aggregate::#{function_name}") unless Aggregate.const_defined?(function_name)
  Aggregate.const_get(function_name)
rescue Exception
  raise "Aggregate function file '#{function_name.downcase}.rb' cannot be loaded"
end
summarize() click to toggle source

Finalizes the function returning a result object

# File lib/mcollective/aggregate.rb, line 59
def summarize
  summary = @functions.map do |function|
    begin
      function.summarize
    rescue Exception => e
      Log.error("Could not summarize aggregate result for '#{function.output_name}'. #{e.to_s}")
      @failed << {:name => function.output_name, :type => :summarize}
      nil
    end
  end

  summary.reject{|x| x.nil?}.sort do |x,y|
    x.result[:output] <=> y.result[:output]
  end
end
mcollective-2.6.0/doc/MCollective/MsgDoesNotMatchRequestID.html0000644000175000017500000002605512375446415023504 0ustar jonasjonas class MCollective::MsgDoesNotMatchRequestID - mcollective version 2.6.0

class MCollective::MsgDoesNotMatchRequestID

mcollective-2.6.0/doc/MCollective/RunnerStats.html0000644000175000017500000006327012375446415021207 0ustar jonasjonas class MCollective::RunnerStats - mcollective version 2.6.0

class MCollective::RunnerStats

Class to store stats about the mcollectived, it should live in the PluginManager so that agents etc can get hold of it and return the stats to callers

Public Class Methods

new() click to toggle source
# File lib/mcollective/runnerstats.rb, line 5
def initialize
  @starttime = Time.now.to_i
  @validated = 0
  @unvalidated = 0
  @filtered = 0
  @passed = 0
  @total = 0
  @replies = 0
  @ttlexpired = 0

  @mutex = Mutex.new
end

Public Instance Methods

filtered() click to toggle source

Records a message that didnt pass the filters

# File lib/mcollective/runnerstats.rb, line 31
def filtered
  Log.debug("Incrementing filtered stat")
  @filtered += 1
end
passed() click to toggle source

Records a message that passed the filters

# File lib/mcollective/runnerstats.rb, line 25
def passed
  Log.debug("Incrementing passed stat")
  @passed += 1
end
received() click to toggle source

Records receipt of a message

# File lib/mcollective/runnerstats.rb, line 48
def received
  Log.debug("Incrementing total stat")
  @total += 1
end
sent() click to toggle source

Records sending a message

# File lib/mcollective/runnerstats.rb, line 54
def sent
  @mutex.synchronize do
    Log.debug("Incrementing replies stat")
    @replies += 1
  end
end
to_hash() click to toggle source

Returns a hash with all stats

# File lib/mcollective/runnerstats.rb, line 62
def to_hash
  stats = {:validated => @validated,
    :unvalidated => @unvalidated,
    :passed => @passed,
    :filtered => @filtered,
    :starttime => @starttime,
    :total => @total,
    :ttlexpired => @ttlexpired,
    :replies => @replies}

  reply = {:stats => stats,
    :threads => [],
    :pid => Process.pid,
    :times => {} }

  ::Process.times.each_pair{|k,v|
    k = k.to_sym
    reply[:times][k] = v
  }

  Thread.list.each do |t|
    reply[:threads] << "#{t.inspect}"
  end

  reply[:agents] = Agents.agentlist
  reply
end
ttlexpired() click to toggle source

Records a message that failed TTL checks

# File lib/mcollective/runnerstats.rb, line 19
def ttlexpired
  Log.debug("Incrementing ttl expired stat")
  @ttlexpired += 1
end
unvalidated() click to toggle source
# File lib/mcollective/runnerstats.rb, line 42
def unvalidated
  Log.debug("Incrementing unvalidated stat")
  @unvalidated += 1
end
validated() click to toggle source

Records a message that validated ok

# File lib/mcollective/runnerstats.rb, line 37
def validated
  Log.debug("Incrementing validated stat")
  @validated += 1
end
mcollective-2.6.0/doc/MCollective/RPC.html0000644000175000017500000010206112375446415017333 0ustar jonasjonas module MCollective::RPC - mcollective version 2.6.0

module MCollective::RPC

Toolset to create a standard interface of client and agent using an RPC metaphor, standard compliant agents will make it easier to create generic clients like web interfaces etc

Public Class Methods

const_missing(const_name) click to toggle source
Calls superclass method
# File lib/mcollective/rpc.rb, line 175
def self.const_missing(const_name)
  super unless const_name == :DDL

  Log.warn("MCollective::RPC::DDL is deprecatd, please use MCollective::DDL instead")
  MCollective::DDL
end
discovered(discovered) click to toggle source

means for other classes to drop discovered hosts into this module its a bit hacky but needed so that the mixin methods like printrpcstats can easily get access to it without users having to pass it around in params.

# File lib/mcollective/rpc.rb, line 107
def self.discovered(discovered)
  @@discovered = discovered
end
stats(stats) click to toggle source

means for other classes to drop stats into this module its a bit hacky but needed so that the mixin methods like printrpcstats can easily get access to it without users having to pass it around in params.

# File lib/mcollective/rpc.rb, line 99
def self.stats(stats)
  @@stats = stats
end

Public Instance Methods

empty_filter?(options) click to toggle source

Wrapper for MCollective::Util.empty_filter? to make clients less fugly to write - ticket #18

# File lib/mcollective/rpc.rb, line 167
def empty_filter?(options)
  if options.include?(:filter)
    Util.empty_filter?(options[:filter])
  else
    Util.empty_filter?(options)
  end
end
printrpc(result, flags = {}) click to toggle source

Prints the result of an RPC call.

In the default quiet mode - no flattening or verbose - only results that produce an error will be printed

To get details of each result run with the -v command line option.

# File lib/mcollective/rpc.rb, line 146
def printrpc(result, flags = {})
  verbose = @options[:verbose] rescue verbose = false
  verbose = flags[:verbose] || verbose
  flatten = flags[:flatten] || false
  format = @options[:output_format]
  forced_mode = @options[:force_display_mode] || false

  result_text =  Helpers.rpcresults(result, {:verbose => verbose, :flatten => flatten, :format => format, :force_display_mode => forced_mode})

  if result.is_a?(Array) && format == :console
    puts "\n%s\n" % [ result_text ]
  else
    # when we get just one result to print dont pad them all with
    # blank spaces etc, just print the individual result with no
    # padding
    puts result_text unless result_text == ""
  end
end
printrpcstats(flags={}) click to toggle source

Prints stats, requires stats to be saved from elsewhere using the ::stats method.

If you've passed -v on the command line a detailed stat block will be printed, else just a one liner.

You can pass flags into it:

printrpcstats :caption => "Foo", :summarize => true

This will use “Foo†as the caption to the stats in verbose mode and print out any aggregate summary information if present

# File lib/mcollective/rpc.rb, line 123
def printrpcstats(flags={})
  return unless @options[:output_format] == :console

  flags = {:summarize => false, :caption => "rpc stats"}.merge(flags)

  verbose = @options[:verbose] rescue verbose = false

  begin
    stats = @@stats
  rescue
    puts("no stats to display")
    return
  end

  puts stats.report(flags[:caption], flags[:summarize], verbose)
end
rpcclient(agent, flags = {}) { |rpc| ... } click to toggle source

Wrapper to create clients, supposed to be used as a mixin:

include MCollective::RPC

exim = rpcclient(“eximâ€) printrpc exim.mailq

or

rpcclient(“eximâ€) do |exim|

printrpc exim.mailq

end

It will take a few flags:

:configfile => "etc/client.cfg"
:options => options
:exit_on_failure => true

Options would be a build up options hash from the Optionparser you can use the rpcoptions helper to create this

:exit_on_failure is true by default, and causes the application to exit if there is a failure constructing the RPC client. Set this flag to false to cause an Exception to be raised instead.

# File lib/mcollective/rpc.rb, line 60
def rpcclient(agent, flags = {})
  configfile = flags[:configfile] || "/etc/mcollective/client.cfg"
  options = flags[:options] || nil

  if flags.key?(:exit_on_failure)
    exit_on_failure = flags[:exit_on_failure]
  else
    # We exit on failure by default for CLI-friendliness
    exit_on_failure = true
  end

  begin
    if options
      rpc = Client.new(agent, :configfile => options[:config], :options => options)
      @options = rpc.options
    else
      rpc = Client.new(agent, :configfile => configfile)
      @options = rpc.options
    end
  rescue Exception => e
    if exit_on_failure
      puts("Could not create RPC client: #{e}")
      exit!
    else
      raise e
    end
  end

  if block_given?
    yield(rpc)
  else
    return rpc
  end
end
rpcoptions() { |parser, options| ... } click to toggle source

Creates a standard options hash, pass in a block to add extra headings etc see Optionparser

# File lib/mcollective/rpc.rb, line 21
def rpcoptions
  oparser = MCollective::Optionparser.new({:verbose => false, :progress_bar => true}, "filter")

  options = oparser.parse do |parser, options|
    if block_given?
      yield(parser, options)
    end

    Helpers.add_simplerpc_options(parser, options)
  end

  return options
end
mcollective-2.6.0/doc/MCollective/Logger/0000755000175000017500000000000012375446416017241 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Logger/Console_logger.html0000644000175000017500000005515512375446415023102 0ustar jonasjonas class MCollective::Logger::Console_logger - mcollective version 2.6.0

class MCollective::Logger::Console_logger

Implements a syslog based logger using the standard ruby syslog class

Public Instance Methods

color(level) click to toggle source

Set some colors for various logging levels, will honor the color configuration option and return nothing if its configured not to

# File lib/mcollective/logger/console_logger.rb, line 39
def color(level)
  colorize = Config.instance.color

  colors = {:error => Util.color(:red),
            :fatal => Util.color(:red),
            :warn  => Util.color(:yellow),
            :info  => Util.color(:green),
            :reset => Util.color(:reset)}

  if colorize
    return colors[level] || ""
  else
    return ""
  end
end
colorize(level, msg) click to toggle source

Helper to return a string in specific color

# File lib/mcollective/logger/console_logger.rb, line 56
def colorize(level, msg)
  "%s%s%s" % [ color(level), msg, color(:reset) ]
end
log(level, from, msg, normal_output=STDERR, last_resort_output=STDERR) click to toggle source
# File lib/mcollective/logger/console_logger.rb, line 24
def log(level, from, msg, normal_output=STDERR, last_resort_output=STDERR)
  if @known_levels.index(level) >= @known_levels.index(@active_level)
    time = Time.new.strftime("%Y/%m/%d %H:%M:%S")

    normal_output.puts("%s %s: %s %s" % [colorize(level, level), time, from, msg])
  end
rescue
  # if this fails we probably cant show the user output at all,
  # STDERR it as last resort
  last_resort_output.puts("#{level}: #{msg}")
end
set_logging_level(level) click to toggle source
# File lib/mcollective/logger/console_logger.rb, line 12
def set_logging_level(level)
  # nothing to do here, we ignore high levels when we log
end
start() click to toggle source
# File lib/mcollective/logger/console_logger.rb, line 5
def start
  set_level(:info)

  config = Config.instance
  set_level(config.loglevel.to_sym) if config.configured
end
valid_levels() click to toggle source
# File lib/mcollective/logger/console_logger.rb, line 16
def valid_levels
  {:info  => :info,
   :warn  => :warning,
   :debug => :debug,
   :fatal => :crit,
   :error => :err}
end
mcollective-2.6.0/doc/MCollective/Logger/File_logger.html0000644000175000017500000005112312375446415022346 0ustar jonasjonas class MCollective::Logger::File_logger - mcollective version 2.6.0

class MCollective::Logger::File_logger

Impliments a file based logger using the standard ruby logger class

To configure you should set:

- config.logfile
- config.keeplogs defaults to 2097152
- config.max_log_size defaults to 5

Public Instance Methods

log(level, from, msg) click to toggle source
# File lib/mcollective/logger/file_logger.rb, line 37
def log(level, from, msg)
  @logger.add(map_level(level)) { "#{from} #{msg}" }
rescue
  # if this fails we probably cant show the user output at all,
  # STDERR it as last resort
  STDERR.puts("#{level}: #{msg}")
end
reopen() click to toggle source
# File lib/mcollective/logger/file_logger.rb, line 45
def reopen
  level = @logger.level
  @logger.close
  start
  @logger.level = level
end
set_logging_level(level) click to toggle source
# File lib/mcollective/logger/file_logger.rb, line 22
def set_logging_level(level)
  @logger.level = map_level(level)
rescue Exception => e
  @logger.level = ::Logger::DEBUG
  log(:error, "", "Could not set logging to #{level} using debug instead: #{e.class} #{e}")
end
start() click to toggle source
# File lib/mcollective/logger/file_logger.rb, line 13
def start
  config = Config.instance

  @logger = ::Logger.new(config.logfile, config.keeplogs, config.max_log_size)
  @logger.formatter = ::Logger::Formatter.new

  set_level(config.loglevel.to_sym)
end
valid_levels() click to toggle source
# File lib/mcollective/logger/file_logger.rb, line 29
def valid_levels
  {:info  => ::Logger::INFO,
   :warn  => ::Logger::WARN,
   :debug => ::Logger::DEBUG,
   :fatal => ::Logger::FATAL,
   :error => ::Logger::ERROR}
end
mcollective-2.6.0/doc/MCollective/Logger/Syslog_logger.html0000644000175000017500000005037612375446415022760 0ustar jonasjonas class MCollective::Logger::Syslog_logger - mcollective version 2.6.0

class MCollective::Logger::Syslog_logger

Implements a syslog based logger using the standard ruby syslog class

Public Instance Methods

log(level, from, msg) click to toggle source
# File lib/mcollective/logger/syslog_logger.rb, line 42
def log(level, from, msg)
  if @known_levels.index(level) >= @known_levels.index(@active_level)
    Syslog.send(map_level(level), "#{from} #{msg}")
  end
rescue
  # if this fails we probably cant show the user output at all,
  # STDERR it as last resort
  STDERR.puts("#{level}: #{msg}")
end
set_logging_level(level) click to toggle source
# File lib/mcollective/logger/syslog_logger.rb, line 30
def set_logging_level(level)
  # noop
end
start() click to toggle source
# File lib/mcollective/logger/syslog_logger.rb, line 9
def start
  config = Config.instance

  facility = syslog_facility(config.logfacility)
  level = config.loglevel.to_sym

  Syslog.close if Syslog.opened?
  Syslog.open(File.basename($0), 3, facility)

  set_level(level)
end
syslog_facility(facility) click to toggle source
# File lib/mcollective/logger/syslog_logger.rb, line 21
def syslog_facility(facility)
  begin
    Syslog.const_get("LOG_#{facility.upcase}")
  rescue NameError => e
    STDERR.puts "Invalid syslog facility #{facility} supplied, reverting to USER"
    Syslog::LOG_USER
  end
end
valid_levels() click to toggle source
# File lib/mcollective/logger/syslog_logger.rb, line 34
def valid_levels
  {:info  => :info,
   :warn  => :warning,
   :debug => :debug,
   :fatal => :crit,
   :error => :err}
end
mcollective-2.6.0/doc/MCollective/Logger/Base.html0000644000175000017500000005035712375446415021012 0ustar jonasjonas class MCollective::Logger::Base - mcollective version 2.6.0

class MCollective::Logger::Base

A base class for logging providers.

Logging providers should provide the following:

* start - all you need to do to setup your logging
* set_logging_level - set your logging to :info, :warn, etc
* valid_levels - a hash of maps from :info to your internal level name
* log - what needs to be done to log a specific message

Attributes

active_level[R]

Public Class Methods

new() click to toggle source
# File lib/mcollective/logger/base.rb, line 14
def initialize
  @known_levels = [:debug, :info, :warn, :error, :fatal]

  # Sanity check the class that impliments the logging
  @known_levels.each do |lvl|
    raise "Logger class did not specify a map for #{lvl}" unless valid_levels.include?(lvl)
  end
end

Public Instance Methods

cycle_level() click to toggle source

Figures out the next level and sets it

# File lib/mcollective/logger/base.rb, line 24
def cycle_level
  lvl = get_next_level
  set_level(lvl)

  log(lvl, "", "Logging level is now #{lvl.to_s.upcase}")
end
log(level, from, msg) click to toggle source
# File lib/mcollective/logger/base.rb, line 41
def log(level, from, msg)
  raise "The logging class did not supply a log method"
end
reopen() click to toggle source
# File lib/mcollective/logger/base.rb, line 45
def reopen
  # reopen may not make sense to all Loggers, but we expect it of the API
end
set_level(level) click to toggle source

Sets a new level and record it in @active_level

# File lib/mcollective/logger/base.rb, line 32
def set_level(level)
  set_logging_level(level)
  @active_level = level.to_sym
end
start() click to toggle source
# File lib/mcollective/logger/base.rb, line 37
def start
  raise "The logging class did not supply a start method"
end
mcollective-2.6.0/doc/MCollective/Logger.html0000644000175000017500000002607212375446415020135 0ustar jonasjonas module MCollective::Logger - mcollective version 2.6.0

module MCollective::Logger

mcollective-2.6.0/doc/MCollective/Validator.html0000644000175000017500000006030712375446416020643 0ustar jonasjonas module MCollective::Validator - mcollective version 2.6.0

module MCollective::Validator

Public Class Methods

[](klass) click to toggle source

Returns and instance of the Plugin class from which objects can be created. Valid plugin names are

:valplugin
"valplugin"
"ValpluginValidator"
# File lib/mcollective/validator.rb, line 24
def self.[](klass)
  if klass.is_a?(Symbol)
    klass = validator_class(klass)
  elsif !(klass.match(/.*Validator$/))
    klass = validator_class(klass)
  end

  const_get(klass)
end
has_validator?(validator) click to toggle source
# File lib/mcollective/validator.rb, line 43
def self.has_validator?(validator)
  const_defined?(validator_class(validator))
end
load_validators() click to toggle source

Loads the validator plugins. Validators will only be loaded every 5 minutes

# File lib/mcollective/validator.rb, line 7
def self.load_validators
  begin
    @@validator_mutex.lock
    if load_validators?
      @last_load = Time.now.to_i
      PluginManager.find_and_load("validator")
    end
  ensure
    @@validator_mutex.unlock
  end
end
load_validators?() click to toggle source
# File lib/mcollective/validator.rb, line 51
def self.load_validators?
  return true if @last_load.nil?
  (@last_load - Time.now.to_i) > 300
end
method_missing(method, *args, &block) click to toggle source

Allows validation plugins to be called like module methods : ::validate

# File lib/mcollective/validator.rb, line 35
def self.method_missing(method, *args, &block)
  if has_validator?(method)
    validator = Validator[method].validate(*args)
  else
    raise ValidatorError, "Unknown validator: '#{method}'."
  end
end
validate(validator, validation) click to toggle source

Generic validate method that will call the correct validator plugin based on the type of the validation parameter

# File lib/mcollective/validator.rb, line 58
def self.validate(validator, validation)
  Validator.load_validators

  begin
    if [:integer, :boolean, :float, :number, :string].include?(validation)
      Validator.typecheck(validator, validation)

    else
      case validation
        when Regexp,String
          Validator.regex(validator, validation)

        when Symbol
          Validator.send(validation, validator)

        when Array
          Validator.array(validator, validation)

        when Class
          Validator.typecheck(validator, validation)
      end
    end
  rescue => e
    raise ValidatorError, e.to_s
  end
end
validator_class(validator) click to toggle source
# File lib/mcollective/validator.rb, line 47
def self.validator_class(validator)
  "#{validator.to_s.capitalize}Validator"
end
mcollective-2.6.0/doc/MCollective/Optionparser.html0000644000175000017500000011574012375446415021404 0ustar jonasjonas class MCollective::Optionparser - mcollective version 2.6.0

class MCollective::Optionparser

A simple helper to build cli tools that supports a uniform command line layout.

Attributes

parser[R]

Public Class Methods

new(defaults = {}, include_sections = nil, exclude_sections = nil) click to toggle source

Creates a new instance of the parser, you can supply defaults and include named groups of options.

Starts a parser that defaults to verbose and that includs the filter options:

oparser = MCollective::Optionparser.new({:verbose => true}, "filter")

Stats a parser in non verbose mode that does support discovery

oparser = MCollective::Optionparser.new()

Starts a parser in verbose mode that does not show the common options:

oparser = MCollective::Optionparser.new({:verbose => true}, "filter", "common")
# File lib/mcollective/optionparser.rb, line 20
def initialize(defaults = {}, include_sections = nil, exclude_sections = nil)
  @parser = ::OptionParser.new

  @include = [include_sections].flatten
  @exclude = [exclude_sections].flatten

  @options = Util.default_options

  @options.merge!(defaults)
end

Public Instance Methods

add_common_options() click to toggle source

These options will be added to most cli tools

# File lib/mcollective/optionparser.rb, line 128
def add_common_options
  @parser.separator ""
  @parser.separator "Common Options"

  @parser.on('-T', '--target COLLECTIVE', 'Target messages to a specific sub collective') do |f|
    @options[:collective] = f
  end

  @parser.on('--dt', '--discovery-timeout SECONDS', Integer, 'Timeout for doing discovery') do |t|
    @options[:disctimeout] = t
  end

  @parser.on('-t', '--timeout SECONDS', Integer, 'Timeout for calling remote agents') do |t|
    @options[:timeout] = t
  end

  @parser.on('-q', '--quiet', 'Do not be verbose') do |v|
    @options[:verbose] = false
  end

  @parser.on('--ttl TTL', 'Set the message validity period') do |v|
    @options[:ttl] = v.to_i
  end

  @parser.on('--reply-to TARGET', 'Set a custom target for replies') do |v|
    @options[:reply_to] = v
  end

  @parser.on('--dm', '--disc-method METHOD', 'Which discovery method to use') do |v|
    raise "Discovery method is already set by a competing option" if @options[:discovery_method] && @options[:discovery_method] != v
    @options[:discovery_method] = v
  end

  @parser.on('--do', '--disc-option OPTION', 'Options to pass to the discovery method') do |a|
    @options[:discovery_options] << a
  end

  @parser.on("--nodes FILE", "List of nodes to address") do |v|
    raise "Cannot mix --disc-method, --disc-option and --nodes" if @options[:discovery_method] || @options[:discovery_options].size > 0
    raise "Cannot read the discovery file #{v}" unless File.readable?(v)

    @options[:discovery_method] = "flatfile"
    @options[:discovery_options] << v
  end

  @parser.on("--publish_timeout TIMEOUT", Integer, "Timeout for publishing requests to remote agents.") do |pt|
    @options[:publish_timeout] = pt
  end

  @parser.on("--threaded", "Start publishing requests and receiving responses in threaded mode.") do |v|
    @options[:threaded] = true
  end

  @parser.on("--sort", "Sort the output of an RPC call before processing.") do |v|
    @options[:sort] = true
  end
end
add_filter_options() click to toggle source

These options will be added if you pass 'filter' into the include list of the constructor.

# File lib/mcollective/optionparser.rb, line 73
def add_filter_options
  @parser.separator ""
  @parser.separator "Host Filters"

  @parser.on('-W', '--with FILTER', 'Combined classes and facts filter') do |f|
    f.split(" ").each do |filter|
      begin
        fact_parsed = parse_fact(filter)
        @options[:filter]["fact"] << fact_parsed
      rescue
        @options[:filter]["cf_class"] << filter
      end
    end
  end

  @parser.on('-S', '--select FILTER', 'Compound filter combining facts and classes') do |f|
    @options[:filter]["compound"] << Matcher.create_compound_callstack(f)
  end

  @parser.on('-F', '--wf', '--with-fact fact=val', 'Match hosts with a certain fact') do |f|
    fact_parsed = parse_fact(f)

    @options[:filter]["fact"] << fact_parsed if fact_parsed
  end

  @parser.on('-C', '--wc', '--with-class CLASS', 'Match hosts with a certain config management class') do |f|
    @options[:filter]["cf_class"] << f
  end

  @parser.on('-A', '--wa', '--with-agent AGENT', 'Match hosts with a certain agent') do |a|
    @options[:filter]["agent"] << a
  end

  @parser.on('-I', '--wi', '--with-identity IDENT', 'Match hosts with a certain configured identity') do |a|
    @options[:filter]["identity"] << a
  end
end
add_required_options() click to toggle source

These options should always be present

# File lib/mcollective/optionparser.rb, line 112
def add_required_options
  @parser.on('-c', '--config FILE', 'Load configuratuion from file rather than default') do |f|
    @options[:config] = f
  end

  @parser.on('-v', '--verbose', 'Be verbose') do |v|
    @options[:verbose] = v
  end

  @parser.on('-h', '--help', 'Display this screen') do
    puts @parser
    exit! 1
  end
end
parse() { |parser, options| ... } click to toggle source

Parse the options returning the options, you can pass a block that adds additional options to the Optionparser.

The sample below starts a parser that also prompts for –arguments in addition to the defaults. It also sets the description and shows a usage message specific to this app.

options = oparser.parse{|parser, options|
     parser.define_head "Control the mcollective controller daemon"
     parser.banner = "Usage: sh-mcollective [options] command"

     parser.on('--arg', '--argument ARGUMENT', 'Argument to pass to agent') do |v|
         options[:argument] = v
     end
}

Users can set default options that get parsed in using the MCOLLECTIVE_EXTRA_OPTS environemnt variable

# File lib/mcollective/optionparser.rb, line 48
def parse(&block)
  yield(@parser, @options) if block_given?

  add_required_options

  add_common_options unless @exclude.include?("common")

  @include.each do |i|
    next if @exclude.include?(i)

    options_name = "add_#{i}_options"
    send(options_name)  if respond_to?(options_name)
  end

  @parser.environment("MCOLLECTIVE_EXTRA_OPTS")

  @parser.parse!

  @options[:collective] = Config.instance.main_collective unless @options[:collective]

  @options
end
mcollective-2.6.0/doc/MCollective/Matcher/0000755000175000017500000000000012375446416017405 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/Matcher/Parser.html0000644000175000017500000010567312375446415021542 0ustar jonasjonas class MCollective::Matcher::Parser - mcollective version 2.6.0

class MCollective::Matcher::Parser

Attributes

execution_stack[R]
scanner[R]

Public Class Methods

new(args) click to toggle source
# File lib/mcollective/matcher/parser.rb, line 6
def initialize(args)
  @scanner = Scanner.new(args)
  @execution_stack = []
  @parse_errors = []
  @token_errors = []
  @paren_errors = []
  parse
  exit_with_token_errors if @token_errors.size > 0
  exit_with_parse_errors if @parse_errors.size > 0
  exit_with_paren_errors if @paren_errors.size > 0
end

Public Instance Methods

exit_with_paren_errors() click to toggle source
# File lib/mcollective/matcher/parser.rb, line 37
def exit_with_paren_errors
  @paren_errors.each do |i|
    @scanner.arguments[i] = Util.colorize(:red, @scanner.arguments[i])
  end
  raise "Missing parenthesis found while parsing -S input #{@scanner.arguments.join}"
end
exit_with_parse_errors() click to toggle source
# File lib/mcollective/matcher/parser.rb, line 28
def exit_with_parse_errors
  @parse_errors.each do |error_range|
    (error_range[0]..error_range[1]).each do |i|
      @scanner.arguments[i] = Util.colorize(:red, @scanner.arguments[i])
    end
  end
  raise "Parse errors found while parsing -S input #{ @scanner.arguments.join}"
end
exit_with_token_errors() click to toggle source

Exit and highlight any malformed tokens

# File lib/mcollective/matcher/parser.rb, line 19
def exit_with_token_errors
  @token_errors.each do |error_range|
    (error_range[0]..error_range[1]).each do |i|
      @scanner.arguments[i] = Util.colorize(:red, @scanner.arguments[i])
    end
  end
  raise "Malformed token(s) found while parsing -S input #{@scanner.arguments.join}"
end
parse() click to toggle source

Parse the input string, one token at a time a contruct the call stack

# File lib/mcollective/matcher/parser.rb, line 45
def parse
  pre_index = @scanner.token_index
  p_token,p_token_value = nil
  c_token,c_token_value = @scanner.get_token
  parenth = 0

  while (c_token != nil)
    @scanner.token_index += 1
    n_token, n_token_value = @scanner.get_token

    unless n_token == " "
      case c_token
      when "bad_token"
        @token_errors << c_token_value

      when "and"
        unless (n_token =~ /not|fstatement|statement|\(/) || (scanner.token_index == scanner.arguments.size) && !(n_token == nil)
          @parse_errors << [pre_index, scanner.token_index]
        end

        if p_token == nil
          @parse_errors << [pre_index - c_token.size, scanner.token_index]
        elsif (p_token == "and" || p_token == "or")
          @parse_errors << [pre_index - 1 - p_token.size, pre_index - 1]
        end

      when "or"
        unless (n_token =~ /not|fstatement|statement|\(/) || (scanner.token_index == scanner.arguments.size) && !(n_token == nil)
          @parse_errors << [pre_index, scanner.token_index]
        end

        if p_token == nil
          @parse_errors << [pre_index - c_token.size, scanner.token_index]
        elsif (p_token == "and" || p_token == "or")
          @parse_errors << [pre_index - 1 - p_token.size, pre_index - 1]
        end

      when "not"
        unless n_token =~ /fstatement|statement|\(|not/ && !(n_token == nil)
          @parse_errors << [pre_index, scanner.token_index]
        end

      when "statement","fstatement"
        unless n_token =~ /and|or|\)/
          unless scanner.token_index == scanner.arguments.size
            @parse_errors << [pre_index, scanner.token_index]
          end
        end

      when ")"
        unless (n_token =~ /|and|or|not|\(/)
          unless(scanner.token_index == scanner.arguments.size)
            @parse_errors << [pre_index, scanner.token_index]
          end
        end
        unless @paren_errors.empty?
          @paren_errors.pop
        else
          @paren_errors.push((n_token.nil?) ? scanner.token_index - 1: scanner.token_index - n_token_value.size)
        end

      when "("
        unless n_token =~ /fstatement|statement|not|\(/
          @parse_errors << [pre_index, scanner.token_index]
        end
        @paren_errors.push((n_token.nil?) ? scanner.token_index - 1: scanner.token_index - n_token_value.size)

      else
        @parse_errors << [pre_index, scanner.token_index]
      end

      unless n_token == " " ||c_token == "bad_token"
        @execution_stack << {c_token => c_token_value}
      end

      p_token, p_token_value = c_token, c_token_value
      c_token, c_token_value = n_token, n_token_value
    end
    pre_index = @scanner.token_index
  end
end
mcollective-2.6.0/doc/MCollective/Matcher/Scanner.html0000644000175000017500000005230012375446415021663 0ustar jonasjonas class MCollective::Matcher::Scanner - mcollective version 2.6.0

class MCollective::Matcher::Scanner

Attributes

arguments[RW]
token_index[RW]

Public Class Methods

new(arguments) click to toggle source
# File lib/mcollective/matcher/scanner.rb, line 6
def initialize(arguments)
  @token_index = 0
  @arguments = arguments.split("")
  @seperation_counter = 0
  @white_spaces = 0
end

Public Instance Methods

get_token() click to toggle source

Scans the input string and identifies single language tokens

# File lib/mcollective/matcher/scanner.rb, line 14
def get_token
  if @token_index >= @arguments.size
    return nil
  end

  case @arguments[@token_index]
  when "("
    return "(", "("

  when ")"
    return ")", ")"

  when "n"
    if (@arguments[@token_index + 1] == "o") && (@arguments[@token_index + 2] == "t") && ((@arguments[@token_index + 3] == " ") || (@arguments[@token_index + 3] == "("))
      @token_index += 2
      return "not", "not"
    else
      gen_statement
    end

  when "!"
    return "not", "not"

  when "a"
    if (@arguments[@token_index + 1] == "n") && (@arguments[@token_index + 2] == "d") && ((@arguments[@token_index + 3] == " ") || (@arguments[@token_index + 3] == "("))
      @token_index += 2
      return "and", "and"
    else
      gen_statement
    end

  when "o"
    if (@arguments[@token_index + 1] == "r") && ((@arguments[@token_index + 2] == " ") || (@arguments[@token_index + 2] == "("))
      @token_index += 1
      return "or", "or"
    else
      gen_statement
    end

  when " "
    return " ", " "

  else
    gen_statement
  end
end
mcollective-2.6.0/doc/MCollective/Agents.html0000644000175000017500000010473712375446414020143 0ustar jonasjonas class MCollective::Agents - mcollective version 2.6.0

class MCollective::Agents

A collection of agents, loads them, reloads them and dispatches messages to them. It uses the PluginManager to store, load and manage instances of plugins.

Public Class Methods

agentlist() click to toggle source

Get a list of agents that we have

# File lib/mcollective/agents.rb, line 145
def self.agentlist
  @@agents.keys
end
new(agents = {}) click to toggle source
# File lib/mcollective/agents.rb, line 5
def initialize(agents = {})
  @config = Config.instance
  raise ("Configuration has not been loaded, can't load agents") unless @config.configured

  @@agents = agents

  loadagents
end

Public Instance Methods

activate_agent?(agent) click to toggle source

Checks if a plugin should be activated by calling activate? on it if it responds to that method else always activate it

# File lib/mcollective/agents.rb, line 85
def activate_agent?(agent)
  klass = Kernel.const_get("MCollective").const_get("Agent").const_get(agent.capitalize)

  if klass.respond_to?("activate?")
    return klass.activate?
  else
    Log.debug("#{klass} does not have an activate? method, activating as default")
    return true
  end
rescue Exception => e
  Log.warn("Agent activation check for #{agent} failed: #{e.class}: #{e}")
  return false
end
class_for_agent(agent) click to toggle source

Builds a class name string given a Agent name

# File lib/mcollective/agents.rb, line 78
def class_for_agent(agent)
  "MCollective::Agent::#{agent.capitalize}"
end
clear!() click to toggle source

Deletes all agents

# File lib/mcollective/agents.rb, line 15
def clear!
  @@agents.each_key do |agent|
    PluginManager.delete "#{agent}_agent"
    Util.unsubscribe(Util.make_subscriptions(agent, :broadcast))
  end

  @@agents = {}
end
dispatch(request, connection) { |replies| ... } click to toggle source

Dispatches a message to an agent, accepts a block that will get run if there are any replies to process from the agent

# File lib/mcollective/agents.rb, line 118
def dispatch(request, connection)
  Log.debug("Dispatching a message to agent #{request.agent}")

  Thread.new do
    begin
      agent = PluginManager["#{request.agent}_agent"]

      Timeout::timeout(agent.timeout) do
        replies = agent.handlemsg(request.payload, connection)

        # Agents can decide if they wish to reply or not,
        # returning nil will mean nothing goes back to the
        # requestor
        unless replies == nil
          yield(replies)
        end
      end
    rescue Timeout::Error => e
      Log.warn("Timeout while handling message for #{request.agent}")
    rescue Exception => e
      Log.error("Execution of #{request.agent} failed: #{e}")
      Log.error(e.backtrace.join("\n\t\t"))
    end
  end
end
findagentfile(agentname) click to toggle source

searches the libdirs for agents

# File lib/mcollective/agents.rb, line 100
def findagentfile(agentname)
  @config.libdir.each do |libdir|
    agentfile = File.join([libdir, "mcollective", "agent", "#{agentname}.rb"])
    if File.exist?(agentfile)
      Log.debug("Found #{agentname} at #{agentfile}")
      return agentfile
    end
  end
  return false
end
include?(agentname) click to toggle source

Determines if we have an agent with a certain name

# File lib/mcollective/agents.rb, line 112
def include?(agentname)
  PluginManager.include?("#{agentname}_agent")
end
loadagent(agentname) click to toggle source

Loads a specified agent from disk if available

# File lib/mcollective/agents.rb, line 42
def loadagent(agentname)
  agentfile = findagentfile(agentname)
  return false unless agentfile
  classname = class_for_agent(agentname)

  PluginManager.delete("#{agentname}_agent")

  begin
    single_instance = ["registration", "discovery"].include?(agentname)

    PluginManager.loadclass(classname)

    if activate_agent?(agentname)
      PluginManager << {:type => "#{agentname}_agent", :class => classname, :single_instance => single_instance}

      # Attempt to instantiate the agent once so any validation and hooks get run
      # this does a basic sanity check on the agent as a whole, if this fails it
      # will be removed from the plugin list
      PluginManager["#{agentname}_agent"]

      Util.subscribe(Util.make_subscriptions(agentname, :broadcast)) unless @@agents.include?(agentname)

      @@agents[agentname] = {:file => agentfile}
      return true
    else
      Log.debug("Not activating agent #{agentname} due to agent policy in activate? method")
      return false
    end
  rescue Exception => e
    Log.error("Loading agent #{agentname} failed: #{e}")
    PluginManager.delete("#{agentname}_agent")
    return false
  end
end
loadagents() click to toggle source

Loads all agents from disk

# File lib/mcollective/agents.rb, line 25
def loadagents
  Log.debug("Reloading all agents from disk")

  clear!

  @config.libdir.each do |libdir|
    agentdir = "#{libdir}/mcollective/agent"
    next unless File.directory?(agentdir)

    Dir.new(agentdir).grep(/\.rb$/).each do |agent|
      agentname = File.basename(agent, ".rb")
      loadagent(agentname) unless PluginManager.include?("#{agentname}_agent")
    end
  end
end
mcollective-2.6.0/doc/MCollective/Matcher.html0000644000175000017500000011453212375446415020300 0ustar jonasjonas module MCollective::Matcher - mcollective version 2.6.0

module MCollective::Matcher

A parser and scanner that creates a stack machine for a simple fact and class matching language used on the CLI to facilitate a rich discovery language

Language EBNF

compound = [“(â€] expression [“)â€] {[“(â€] expression [“)â€]} expression = [!|not]statement [“andâ€|“orâ€] [!|not] statement char = A-Z | a-z | < | > | => | =< | _ | - |* | / { A-Z | a-z | < | > | => | =< | _ | - | * | / | } int = 0|1|2|3|4|5|6|7|8|9{|0|1|2|3|4|5|6|7|8|9|0}

Public Class Methods

create_compound_callstack(call_string) click to toggle source

Creates a callstack to be evaluated from a compound evaluation string

# File lib/mcollective/matcher.rb, line 181
def self.create_compound_callstack(call_string)
  callstack = Matcher::Parser.new(call_string).execution_stack
  callstack.each_with_index do |statement, i|
    if statement.keys.first == "fstatement"
      callstack[i]["fstatement"] = create_function_hash(statement.values.first)
    end
  end
  callstack
end
create_function_hash(function_call) click to toggle source

Helper creates a hash from a function call string

# File lib/mcollective/matcher.rb, line 17
def self.create_function_hash(function_call)
  func_hash = {}
  f = ""
  func_parts = function_call.split(/(!=|>=|<=|<|>|=)/)
  func_hash["r_compare"] = func_parts.pop
  func_hash["operator"] = func_parts.pop
  func = func_parts.join

  # Deal with dots in function parameters and functions without dot values
  if func.match(/^.+\(.*\)$/)
    f = func
  else
    func_parts = func.split(".")
    func_hash["value"] = func_parts.pop
    f = func_parts.join(".")
  end

  # Deal with regular expression matches
  if func_hash["r_compare"] =~ /^\/.*\/$/
    func_hash["operator"] = "=~" if func_hash["operator"] == "="
    func_hash["operator"] = "!=~" if func_hash["operator"] == "!="
    func_hash["r_compare"] = Regexp.new(func_hash["r_compare"].gsub(/^\/|\/$/, ""))
  # Convert = operators to == so they can be propperly evaluated
  elsif func_hash["operator"] == "="
    func_hash["operator"] = "=="
  end

  # Grab function name and parameters from left compare string
  func_hash["name"], func_hash["params"] = f.split("(")
  if func_hash["params"] == ")"
    func_hash["params"] = nil
  else

    # Walk the function parameters from the front and from the
    # back removing the first and last instances of single of
    # double qoutes. We do this to handle the case where params
    # contain escaped qoutes.
    func_hash["params"] = func_hash["params"].gsub(")", "")
    func_quotes = func_hash["params"].split(/('|")/)

    func_quotes.each_with_index do |item, i|
      if item.match(/'|"/)
        func_quotes.delete_at(i)
        break
      end
    end

    func_quotes.reverse.each_with_index do |item,i|
      if item.match(/'|"/)
        func_quotes.delete_at(func_quotes.size - i - 1)
        break
      end
    end

    func_hash["params"] = func_quotes.join
  end

  func_hash
end
eval_compound_fstatement(function_hash) click to toggle source

Returns the result of an evaluated compound statement that includes a function

# File lib/mcollective/matcher.rb, line 135
def self.eval_compound_fstatement(function_hash)
  l_compare = execute_function(function_hash)

  # Break out early and return false if the function returns nil
  return false unless l_compare

  # Prevent unwanted discovery by limiting comparison operators
  # on Strings and Booleans
  if((l_compare.is_a?(String) || l_compare.is_a?(TrueClass) || l_compare.is_a?(FalseClass)) && function_hash["operator"].match(/<|>/))
    Log.debug "Cannot do > and < comparison on Booleans and Strings '#{l_compare} #{function_hash["operator"]} #{function_hash["r_compare"]}'"
    return false
  end

  # Prevent backticks in function parameters
  if function_hash["params"] =~ /`/
    Log.debug("Cannot use backticks in function parameters")
    return false
  end

  # Escape strings for evaluation
  function_hash["r_compare"] = "\"#{function_hash["r_compare"]}\"" if(l_compare.is_a?(String)  && !(function_hash["operator"] =~ /=~|!=~/))

  # Do a regex comparison if right compare string is a regex
  if function_hash["operator"] =~ /(=~|!=~)/
    # Fail if left compare value isn't a string
    unless l_compare.is_a?(String)
      Log.debug("Cannot do a regex check on a non string value.")
      return false
    else
      compare_result = l_compare.match(function_hash["r_compare"])
      # Flip return value for != operator
      if function_hash["operator"] == "!=~"
        !((compare_result.nil?) ? false : true)
      else
        (compare_result.nil?) ? false : true
      end
    end
    # Otherwise evaluate the logical comparison
  else
    l_compare = "\"#{l_compare}\"" if l_compare.is_a?(String)
    result = eval("#{l_compare} #{function_hash["operator"]} #{function_hash["r_compare"]}")
    (result.nil?) ? false : result
  end
end
eval_compound_statement(expression) click to toggle source

Evaluates a compound statement

# File lib/mcollective/matcher.rb, line 115
def self.eval_compound_statement(expression)
  if expression.values.first =~ /^\//
    return Util.has_cf_class?(expression.values.first)
  elsif expression.values.first =~ />=|<=|=|<|>/
    optype = expression.values.first.match(/>=|<=|=|<|>/)
    name, value = expression.values.first.split(optype[0])
    unless value.split("")[0] == "/"
      optype[0] == "=" ? optype = "==" : optype = optype[0]
    else
      optype = "=~"
    end

    return Util.has_fact?(name,value, optype).to_s
  else
    return Util.has_cf_class?(expression.values.first)
  end
end
execute_function(function_hash) click to toggle source

Returns the result of an executed function

# File lib/mcollective/matcher.rb, line 78
def self.execute_function(function_hash)
  # In the case where a data plugin isn't present there are two ways we can handle
  # the raised exception. The function result can either be false or the entire
  # expression can fail.
  #
  # In the case where we return the result as false it opens us op to unexpected
  # negation behavior.
  #
  #   !foo('bar').name = bar
  #
  # In this case the user would expect discovery to match on all machines where
  # the name value of the foo function does not equal bar. If a non existent function
  # returns false then it is posible to match machines where the name value of the
  # foo function is bar.
  #
  # Instead we raise a DDLValidationError to prevent this unexpected behavior from
  # happening.

  result = Data.send(function_hash["name"], function_hash["params"])

  if function_hash["value"]
    begin
      eval_result = result.send(function_hash["value"])
    rescue
      # If data field has not been set we set the comparison result to nil
      eval_result = nil
    end
    return eval_result
  else
    return result
  end
rescue NoMethodError
  Log.debug("cannot execute discovery function '#{function_hash["name"]}'. data plugin not found")
  raise DDLValidationError
end
mcollective-2.6.0/doc/MCollective/Security.html0000644000175000017500000002775012375446415020531 0ustar jonasjonas module MCollective::Security - mcollective version 2.6.0

module MCollective::Security

Security is implimented using a module structure and installations can configure which module they want to use.

Security modules deal with various aspects of authentication and authorization:

  • Determines if a filter excludes this host from dealing with a request

  • Serialization and Deserialization of messages

  • Validation of messages against keys, certificates or whatever the class choose to impliment

  • Encoding and Decoding of messages

To impliment a new security class using SSL for example you would inherit from the base class and only impliment:

  • decodemsg

  • encodereply

  • encoderequest

  • validrequest?

Each of these methods should increment various stats counters, see the default MCollective::Security::Psk module for examples of this

Filtering can be extended by providing a new validate_filter? method.

mcollective-2.6.0/doc/MCollective/Cache.html0000644000175000017500000007714212375446414017724 0ustar jonasjonas module MCollective::Cache - mcollective version 2.6.0

module MCollective::Cache

A class to manage a number of named caches. Each cache can have a unique timeout for keys in it and each cache has an independent Mutex protecting access to it.

This cache is setup early in the process of loading the mcollective libraries, before any threads are created etc making it suitable as a cross thread cache or just a store for Mutexes you need to use across threads like in an Agent or something.

# sets up a new cache, noop if it already exist
Cache.setup(:ddl, 600)
=> true

# writes an item to the :ddl cache, this item will
# be valid on the cache for 600 seconds
Cache.write(:ddl, :something, "value")
=> "value"

# reads from the cache, read failures due to non existing
# data or expired data will raise an exception
Cache.read(:ddl, :something)
=> "value"

# time left till expiry, raises if nothing is found
Cache.ttl(:ddl, :something)
=> 500

# forcibly evict something from the cache
Cache.invalidate!(:ddl, :something)
=> "value"

# deletes an entire named cache and its mutexes
Cache.delete!(:ddl)
=> true

# you can also just use this cache as a global mutex store
Cache.setup(:mylock)

Cache.synchronize(:mylock) do
  do_something()
end

Public Class Methods

delete!(cache_name) click to toggle source
# File lib/mcollective/cache.rb, line 74
def self.delete!(cache_name)
  @locks_mutex.synchronize do
    raise("No cache called '%s'" % cache_name) unless @cache_locks.include?(cache_name)

    @cache_locks.delete(cache_name)
    @cache.delete(cache_name)
  end

  true
end
has_cache?(cache_name) click to toggle source
# File lib/mcollective/cache.rb, line 68
def self.has_cache?(cache_name)
  @locks_mutex.synchronize do
    @cache.include?(cache_name)
  end
end
invalidate!(cache_name, key) click to toggle source
# File lib/mcollective/cache.rb, line 135
def self.invalidate!(cache_name, key)
  raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)

  @cache_locks[cache_name].synchronize do
    return false unless @cache[cache_name].include?(key)

    @cache[cache_name].delete(key)
  end
end
read(cache_name, key) click to toggle source
# File lib/mcollective/cache.rb, line 97
def self.read(cache_name, key)
  raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)

  unless ttl(cache_name, key) > 0
    Log.debug("Cache expired on '%s' key '%s'" % [cache_name, key])
    raise("Cache for item '%s' on cache '%s' has expired" % [key, cache_name])
  end

  Log.debug("Cache hit on '%s' key '%s'" % [cache_name, key])

  @cache_locks[cache_name].synchronize do
    @cache[cache_name][key][:value]
  end
end
setup(cache_name, ttl=300) click to toggle source
# File lib/mcollective/cache.rb, line 54
def self.setup(cache_name, ttl=300)
  @locks_mutex.synchronize do
    break if @cache_locks.include?(cache_name)

    @cache_locks[cache_name] = Mutex.new

    @cache_locks[cache_name].synchronize do
      @cache[cache_name] = {:max_age => Float(ttl)}
    end
  end

  true
end
synchronize(cache_name) { || ... } click to toggle source
# File lib/mcollective/cache.rb, line 125
def self.synchronize(cache_name)
  raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)

  raise ("No block supplied to synchronize") unless block_given?

  @cache_locks[cache_name].synchronize do
    yield
  end
end
ttl(cache_name, key) click to toggle source
# File lib/mcollective/cache.rb, line 112
def self.ttl(cache_name, key)
  raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)

  @cache_locks[cache_name].synchronize do
    unless @cache[cache_name].include?(key)
      Log.debug("Cache miss on '%s' key '%s'" % [cache_name, key])
      raise("No item called '%s' for cache '%s'" % [key, cache_name])
    end

    @cache[cache_name][:max_age] - (Time.now - @cache[cache_name][key][:cache_create_time])
  end
end
write(cache_name, key, value) click to toggle source
# File lib/mcollective/cache.rb, line 85
def self.write(cache_name, key, value)
  raise("No cache called '%s'" % cache_name) unless @cache.include?(cache_name)

  @cache_locks[cache_name].synchronize do
    @cache[cache_name][key] ||= {}
    @cache[cache_name][key][:cache_create_time] = Time.now
    @cache[cache_name][key][:value] = value
  end

  value
end
mcollective-2.6.0/doc/MCollective/UnknownRPCAction.html0000644000175000017500000002610212375446416022053 0ustar jonasjonas class MCollective::UnknownRPCAction - mcollective version 2.6.0

class MCollective::UnknownRPCAction

mcollective-2.6.0/doc/MCollective/Facts.html0000644000175000017500000003743212375446415017760 0ustar jonasjonas module MCollective::Facts - mcollective version 2.6.0

module MCollective::Facts

This is a class that gives access to the configured fact provider such as MCollectives::Facts::Facter that uses Reductive Labs facter

The actual provider is pluggable and configurable using the 'factsource' configuration option.

To develop a new factsource simply create a class under MCollective::Facts

and provide the following classes:

self.get_fact(fact)
self.has_fact?(fact)

You can also just inherit from MCollective::Facts::Base and provide just the

self.get_facts

method that should return a hash of facts.

Public Class Methods

[](fact) click to toggle source

Get the value of a fact

# File lib/mcollective/facts.rb, line 35
def self.[](fact)
  PluginManager["facts_plugin"].get_fact(fact)
end
get_fact(fact) click to toggle source

Get the value of a fact

# File lib/mcollective/facts.rb, line 30
def self.get_fact(fact)
  PluginManager["facts_plugin"].get_fact(fact)
end
has_fact?(fact, value) click to toggle source

True if we know of a specific fact else false

# File lib/mcollective/facts.rb, line 25
def self.has_fact?(fact, value)
  PluginManager["facts_plugin"].get_fact(fact) == value ? true : false
end
mcollective-2.6.0/doc/MCollective/Client.html0000644000175000017500000016416512375446414020141 0ustar jonasjonas class MCollective::Client - mcollective version 2.6.0

class MCollective::Client

Helpers for writing clients that can talk to agents, do discovery and so forth

Attributes

discoverer[RW]
options[RW]
stats[RW]

Public Class Methods

new(configfile) click to toggle source
# File lib/mcollective/client.rb, line 6
def initialize(configfile)
  @config = Config.instance
  @config.loadconfig(configfile) unless @config.configured

  @connection = PluginManager["connector_plugin"]
  @security = PluginManager["security_plugin"]

  @security.initiated_by = :client
  @options = nil
  @subscriptions = {}

  @discoverer = Discovery.new(self)
  @connection.connect
end
request_sequence() click to toggle source
# File lib/mcollective/client.rb, line 22
def self.request_sequence
  @@request_sequence
end

Public Instance Methods

collective() click to toggle source

Returns the configured main collective if no specific collective is specified as options

# File lib/mcollective/client.rb, line 28
def collective
  if @options[:collective].nil?
    @config.main_collective
  else
    @options[:collective]
  end
end
createreq(msg, agent, filter ={}) click to toggle source
# File lib/mcollective/client.rb, line 53
def createreq(msg, agent, filter ={})
  if msg.is_a?(Message)
    request = msg
    agent = request.agent
  else
    ttl = @options[:ttl] || @config.ttl
    request = Message.new(msg, nil, {:agent => agent, :type => :request, :collective => collective, :filter => filter, :ttl => ttl})
    request.reply_to = @options[:reply_to] if @options[:reply_to]
  end

  @@request_sequence += 1

  request.encode!
  subscribe(agent, :reply) unless request.reply_to
  request
end
disconnect() click to toggle source

Disconnects cleanly from the middleware

# File lib/mcollective/client.rb, line 37
def disconnect
  Log.debug("Disconnecting from the middleware")
  @connection.disconnect
end
discover(filter, timeout, limit=0) click to toggle source

Performs a discovery of nodes matching the filter passed returns an array of nodes

An integer limit can be supplied this will have the effect of the discovery being cancelled soon as it reached the requested limit of hosts

# File lib/mcollective/client.rb, line 124
def discover(filter, timeout, limit=0)
  discovered = @discoverer.discover(filter, timeout, limit)
end
discovered_req(body, agent, options=false) click to toggle source
# File lib/mcollective/client.rb, line 242
def discovered_req(body, agent, options=false)
  raise "Client#discovered_req has been removed, please port your agent and client to the SimpleRPC framework"
end
display_stats(stats, options=false, caption="stomp call summary") click to toggle source

Prints out the stats returns from req and #discovered_req in a nice way

# File lib/mcollective/client.rb, line 247
def display_stats(stats, options=false, caption="stomp call summary")
  options = @options unless options

  if options[:verbose]
    puts("\n---- #{caption} ----")

    if stats[:discovered]
      puts("           Nodes: #{stats[:discovered]} / #{stats[:responses]}")
    else
      puts("           Nodes: #{stats[:responses]}")
    end

    printf("      Start Time: %s\n", Time.at(stats[:starttime]))
    printf("  Discovery Time: %.2fms\n", stats[:discoverytime] * 1000)
    printf("      Agent Time: %.2fms\n", stats[:blocktime] * 1000)
    printf("      Total Time: %.2fms\n", stats[:totaltime] * 1000)

  else
    if stats[:discovered]
      printf("\nFinished processing %d / %d hosts in %.2f ms\n\n", stats[:responses], stats[:discovered], stats[:blocktime] * 1000)
    else
      printf("\nFinished processing %d hosts in %.2f ms\n\n", stats[:responses], stats[:blocktime] * 1000)
    end
  end

  if stats[:noresponsefrom].size > 0
    puts("\nNo response from:\n")

    stats[:noresponsefrom].each do |c|
      puts if c % 4 == 1
      printf("%30s", c)
    end

    puts
  end
end
receive(requestid = nil) click to toggle source

Blocking call that waits for ever for a message to arrive.

If you give it a requestid this means you've previously send a request with that ID and now you just want replies that matches that id, in that case the current connection will just ignore all messages not directed at it and keep waiting for more till it finds a matching message.

# File lib/mcollective/client.rb, line 95
def receive(requestid = nil)
  reply = nil

  begin
    reply = @connection.receive
    reply.type = :reply
    reply.expected_msgid = requestid

    reply.decode!
    unless reply.requestid == requestid
      raise(MsgDoesNotMatchRequestID, "Message reqid #{reply.requestid} does not match our reqid #{requestid}")
    end
  rescue SecurityValidationFailed => e
    Log.warn("Ignoring a message that did not pass security validations")
    retry
  rescue MsgDoesNotMatchRequestID => e
    Log.debug("Ignoring a message for some other client : #{e.message}")
    retry
  end

  reply
end
req(body, agent=nil, options=false, waitfor=0, &block) click to toggle source

Send a request, performs the passed block for each response

times = req(“statusâ€, “mcollectivedâ€, options, client) {|resp|

pp resp

}

It returns a hash of times and timeouts for discovery and total run is taken from the options hash which in turn is generally built using MCollective::Optionparser

# File lib/mcollective/client.rb, line 136
def req(body, agent=nil, options=false, waitfor=0, &block)
  if body.is_a?(Message)
    agent = body.agent
    waitfor = body.discovered_hosts.size || 0
    @options = body.options
  end

  @options = options if options
  threaded = @options[:threaded]
  timeout = @discoverer.discovery_timeout(@options[:timeout], @options[:filter])
  request = createreq(body, agent, @options[:filter])
  publish_timeout = @options[:publish_timeout]
  stat = {:starttime => Time.now.to_f, :discoverytime => 0, :blocktime => 0, :totaltime => 0}
  STDOUT.sync = true
  hosts_responded = 0


  begin
    if threaded
      hosts_responded = threaded_req(request, publish_timeout, timeout, waitfor, &block)
    else
      hosts_responded = unthreaded_req(request, publish_timeout, timeout, waitfor, &block)
    end
  rescue Interrupt => e
  ensure
    unsubscribe(agent, :reply)
  end

  return update_stat(stat, hosts_responded, request.requestid)
end
sendreq(msg, agent, filter = {}) click to toggle source

Sends a request and returns the generated request id, doesn't wait for responses and doesn't execute any passed in code blocks for responses

# File lib/mcollective/client.rb, line 44
def sendreq(msg, agent, filter = {})
  request = createreq(msg, agent, filter)

  Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}")

  request.publish
  request.requestid
end
start_publisher(request, publish_timeout) click to toggle source

Starts the request publishing routine

# File lib/mcollective/client.rb, line 198
def start_publisher(request, publish_timeout)
  Log.debug("Starting publishing with publish timeout of #{publish_timeout}")
  begin
    Timeout.timeout(publish_timeout) do
      Log.debug("Sending request #{request.requestid} to the #{request.agent} agent with ttl #{request.ttl} in collective #{request.collective}")
      request.publish
    end
  rescue Timeout::Error => e
    Log.warn("Could not publish all messages. Publishing timed out.")
  end
end
start_receiver(requestid, waitfor, timeout) { |payload| ... } click to toggle source

Starts the response receiver routine Expected to return the amount of received responses.

# File lib/mcollective/client.rb, line 212
def start_receiver(requestid, waitfor, timeout, &block)
  Log.debug("Starting response receiver with timeout of #{timeout}")
  hosts_responded = 0
  begin
    Timeout.timeout(timeout) do
      begin
        resp = receive(requestid)
        yield resp.payload
        hosts_responded += 1
      end while (waitfor == 0 || hosts_responded < waitfor)
    end
  rescue Timeout::Error => e
    if (waitfor > hosts_responded)
      Log.warn("Could not receive all responses. Expected : #{waitfor}. Received : #{hosts_responded}")
    end
  end

  hosts_responded
end
subscribe(agent, type) click to toggle source
# File lib/mcollective/client.rb, line 70
def subscribe(agent, type)
  unless @subscriptions.include?(agent)
    subscription = Util.make_subscriptions(agent, type, collective)
    Log.debug("Subscribing to #{type} target for agent #{agent}")

    Util.subscribe(subscription)
    @subscriptions[agent] = 1
  end
end
threaded_req(request, publish_timeout, timeout, waitfor, &block) click to toggle source

Starts the client receiver and publisher in threads. This is activated when the 'threader_client' configuration option is set.

# File lib/mcollective/client.rb, line 177
def threaded_req(request, publish_timeout, timeout, waitfor, &block)
  Log.debug("Starting threaded client")
  publisher = Thread.new do
    start_publisher(request, publish_timeout)
  end

  # When the client is threaded we add the publishing timeout to
  # the agent timeout so that the receiver doesn't time out before
  # publishing has finished in cases where publish_timeout >= timeout.
  total_timeout = publish_timeout + timeout
  hosts_responded = 0

  receiver = Thread.new do
    hosts_responded = start_receiver(request.requestid, waitfor, total_timeout, &block)
  end

  receiver.join
  hosts_responded
end
unsubscribe(agent, type) click to toggle source
# File lib/mcollective/client.rb, line 80
def unsubscribe(agent, type)
  if @subscriptions.include?(agent)
    subscription = Util.make_subscriptions(agent, type, collective)
    Log.debug("Unsubscribing #{type} target for #{agent}")

    Util.unsubscribe(subscription)
    @subscriptions.delete(agent)
  end
end
unthreaded_req(request, publish_timeout, timeout, waitfor, &block) click to toggle source

Starts the client receiver and publisher unthreaded. This is the default client behaviour.

# File lib/mcollective/client.rb, line 169
def unthreaded_req(request, publish_timeout, timeout, waitfor, &block)
  start_publisher(request, publish_timeout)
  start_receiver(request.requestid, waitfor, timeout, &block)
end
update_stat(stat, hosts_responded, requestid) click to toggle source
# File lib/mcollective/client.rb, line 232
def update_stat(stat, hosts_responded, requestid)
  stat[:totaltime] = Time.now.to_f - stat[:starttime]
  stat[:blocktime] = stat[:totaltime] - stat[:discoverytime]
  stat[:responses] = hosts_responded
  stat[:noresponsefrom] = []
  stat[:requestid] = requestid

  @stats = stat
end
mcollective-2.6.0/doc/MCollective/RPC/0000755000175000017500000000000012375446416016446 5ustar jonasjonasmcollective-2.6.0/doc/MCollective/RPC/Audit.html0000644000175000017500000003705612375446415020414 0ustar jonasjonas class MCollective::RPC::Audit - mcollective version 2.6.0

class MCollective::RPC::Audit

Auditing of requests is done only for SimpleRPC requests, you provide a plugin in the MCollective::Audit::* namespace which the SimpleRPC framework calls for each message

We provide a simple one that logs to a logfile in the class MCollective::Audit::Logfile you can create your own:

Create a class in plugins/mcollective/audit/<yourplugin>.rb

You must inherit from MCollective::RPC::Audit which will take care of registering you with the plugin system.

Your plugin must provide #audit_request(request, connection) the request parameter will be an instance of MCollective::RPC::Request

To enable auditing you should set:

rpcaudit = 1 rpcauditprovider = Logfile

in the config file this will enable logging using the MCollective::Audit::Logile class

The Audit class acts as a base for audit plugins and takes care of registering them with the plugin manager

Public Class Methods

inherited(klass) click to toggle source
# File lib/mcollective/rpc/audit.rb, line 29
def self.inherited(klass)
  PluginManager << {:type => "rpcaudit_plugin", :class => klass.to_s}
end

Public Instance Methods

audit_request(request, connection) click to toggle source
# File lib/mcollective/rpc/audit.rb, line 33
def audit_request(request, connection)
  @log.error("audit_request is not implimented in #{this.class}")
end
mcollective-2.6.0/doc/MCollective/RPC/Result.html0000644000175000017500000005465412375446415020627 0ustar jonasjonas class MCollective::RPC::Result - mcollective version 2.6.0

class MCollective::RPC::Result

Simple class to manage compliant results from MCollective::RPC agents

Currently it just fakes Hash behaviour to the result to remain backward compatible but it also knows which agent and action produced it so you can associate results to a DDL

Attributes

action[R]
agent[R]
results[R]

Public Class Methods

new(agent, action, result={}) click to toggle source
# File lib/mcollective/rpc/result.rb, line 13
def initialize(agent, action, result={})
  @agent = agent
  @action = action
  @results = result
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/mcollective/rpc/result.rb, line 44
def <=>(other)
  self[:sender] <=> other[:sender]
end
[](idx) click to toggle source
# File lib/mcollective/rpc/result.rb, line 19
def [](idx)
  @results[idx]
end
[]=(idx, item) click to toggle source
# File lib/mcollective/rpc/result.rb, line 23
def []=(idx, item)
  @results[idx] = item
end
each() { |k,v| ... } click to toggle source
# File lib/mcollective/rpc/result.rb, line 31
def each
  @results.each_pair {|k,v| yield(k,v) }
end
fetch(key, default) click to toggle source
# File lib/mcollective/rpc/result.rb, line 27
def fetch(key, default)
  @results.fetch(key, default)
end
to_json(*a) click to toggle source
# File lib/mcollective/rpc/result.rb, line 35
def to_json(*a)
  {:agent => @agent,
   :action => @action,
   :sender => @results[:sender],
   :statuscode => @results[:statuscode],
   :statusmsg => @results[:statusmsg],
   :data => @results[:data]}.to_json(*a)
end
mcollective-2.6.0/doc/MCollective/RPC/ActionRunner.html0000644000175000017500000011136512375446415021751 0ustar jonasjonas class MCollective::RPC::ActionRunner - mcollective version 2.6.0

class MCollective::RPC::ActionRunner

A helper used by RPC::Agent#implemented_by to delegate an action to an external script. At present only JSON based serialization is supported in future ones based on key=val pairs etc will be added

It serializes the request object into an input file and creates an empty output file. It then calls the external command reading the output file at the end.

any STDERR gets logged at error level and any STDOUT gets logged at info level.

It will interpret the exit code from the application the same way MCollective::RPC::Reply#fail! and fail handles their codes creating a consistent interface, the message part of the fail message will come from STDERR

Generally externals should just exit with code 1 on failure and print to STDERR, this is exactly what Perl die() does and translates perfectly to our model

It uses the MCollective::Shell wrapper to call the external application

Attributes

action[R]
agent[R]
command[R]
format[R]
request[R]
stderr[R]
stdout[R]

Public Class Methods

new(command, request, format=:json) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 26
def initialize(command, request, format=:json)
  @agent = request.agent
  @action = request.action
  @format = format
  @request = request
  @command = path_to_command(command)
  @stdout = ""
  @stderr = ""
end

Public Instance Methods

canrun?(command) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 117
def canrun?(command)
  File.executable?(command)
end
load_json_results(file) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 91
def load_json_results(file)
  return {} unless File.readable?(file)

  JSON.load(File.read(file)) || {}
rescue JSON::ParserError
  {}
end
load_results(file) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 73
def load_results(file)
  Log.debug("Attempting to load results in #{format} format from #{file}")

  data = {}

  if respond_to?("load_#{format}_results")
    tempdata = send("load_#{format}_results", file)

    tempdata.each_pair do |k,v|
      data[k.to_sym] = v
    end
  end

  data
rescue Exception => e
  {}
end
path_to_command(command) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 129
def path_to_command(command)
  unless command[0,1] == File::SEPARATOR
    Config.instance.libdir.each do |libdir|
      command_file = File.join(libdir, "agent", agent, command)

      return command_file if File.exist?(command_file)
    end
  end

  return command
end
run() click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 36
def run
  unless canrun?(command)
    Log.warn("Cannot run #{to_s}")
    raise RPCAborted, "Cannot execute #{to_s}"
  end

  Log.debug("Running #{to_s}")

  request_file = saverequest(request)
  reply_file = tempfile("reply")
  reply_file.close

  runner = shell(command, request_file.path, reply_file.path)

  runner.runcommand

  Log.debug("#{command} exited with #{runner.status.exitstatus}")

  stderr.each_line {|l| Log.error("#{to_s}: #{l.chomp}")} unless stderr.empty?
  stdout.each_line {|l| Log.info("#{to_s}: #{l.chomp}")} unless stdout.empty?

  {:exitstatus => runner.status.exitstatus,
   :stdout     => runner.stdout,
   :stderr     => runner.stderr,
   :data       => load_results(reply_file.path)}
ensure
  request_file.close! if request_file.respond_to?("close!")
  reply_file.close! if reply_file.respond_to?("close")
end
save_json_request(req) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 113
def save_json_request(req)
  req.to_json
end
saverequest(req) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 99
def saverequest(req)
  Log.debug("Attempting to save request in #{format} format")

  if respond_to?("save_#{format}_request")
    data = send("save_#{format}_request", req)

    request_file = tempfile("request")
    request_file.puts data
    request_file.close
  end

  request_file
end
shell(command, infile, outfile) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 66
def shell(command, infile, outfile)
  env = {"MCOLLECTIVE_REQUEST_FILE" => infile,
         "MCOLLECTIVE_REPLY_FILE"   => outfile}

  Shell.new("#{command} #{infile} #{outfile}", :cwd => Dir.tmpdir, :stdout => stdout, :stderr => stderr, :environment => env)
end
tempfile(prefix) click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 125
def tempfile(prefix)
  Tempfile.new("mcollective_#{prefix}", Dir.tmpdir)
end
to_s() click to toggle source
# File lib/mcollective/rpc/actionrunner.rb, line 121
def to_s
  "%s#%s command: %s" % [ agent, action, command ]
end
mcollective-2.6.0/doc/MCollective/RPC/Agent.html0000644000175000017500000010416212375446415020375 0ustar jonasjonas class MCollective::RPC::Agent - mcollective version 2.6.0

class MCollective::RPC::Agent

A wrapper around the traditional agent, it takes care of a lot of the tedious setup you would do for each agent allowing you to just create methods following a naming standard leaving the heavy lifting up to this clas.

See marionette-collective.org/simplerpc/agents.html

It only really makes sense to use this with a Simple RPC client on the other end, basic usage would be:

module MCollective
  module Agent
    class Helloworld<RPC::Agent
      action "hello" do
        reply[:msg] = "Hello #{request[:name]}"
      end

      action "foo" do
        implemented_by "/some/script.sh"
      end
    end
  end
end

If you wish to implement the logic for an action using an external script use the implemented_by method that will cause your script to be run with 2 arguments.

The first argument is a file containing JSON with the request and the 2nd argument is where the script should save its output as a JSON hash.

We also currently have the validation code in here, this will be moved to plugins soon.

Attributes

agent_name[RW]
config[R]
ddl[R]
logger[R]
meta[R]
reply[RW]
request[RW]
timeout[R]

Public Class Methods

actions() click to toggle source

Returns an array of actions this agent support

# File lib/mcollective/rpc/agent.rb, line 160
def self.actions
  public_instance_methods.sort.grep(/_action$/).map do |method|
    $1 if method =~ /(.+)_action$/
  end
end
activate?() click to toggle source

By default RPC Agents support a toggle in the configuration that can enable and disable them based on the agent name

Example an agent called Foo can have:

plugin.foo.activate_agent = false

and this will prevent the agent from loading on this particular machine.

Agents can use the activate_when helper to override this for example:

activate_when do

File.exist?("/usr/bin/puppet")

end

# File lib/mcollective/rpc/agent.rb, line 139
def self.activate?
  agent_name = self.to_s.split("::").last.downcase
  config = Config.instance

  Log.debug("Starting default activation checks for #{agent_name}")

  # Check global state to determine if agent should be loaded
  should_activate = config.activate_agents

  # Check agent specific state to determine if agent should be loaded
  should_activate = Util.str_to_bool(config.pluginconf.fetch("#{agent_name}.activate_agent", 
                                     should_activate))

  unless should_activate
    Log.debug("Found plugin configuration '#{agent_name}.activate_agent' with value '#{should_activate}'")
  end

  return should_activate
end
new() click to toggle source
# File lib/mcollective/rpc/agent.rb, line 37
def initialize
  @agent_name = self.class.to_s.split("::").last.downcase

  load_ddl

  @logger = Log.instance
  @config = Config.instance

  # if we have a global authorization provider enable it
  # plugins can still override it per plugin
  self.class.authorized_by(@config.rpcauthprovider) if @config.rpcauthorization

  startup_hook
end

Public Instance Methods

handlemsg(msg, connection) click to toggle source
# File lib/mcollective/rpc/agent.rb, line 62
def handlemsg(msg, connection)
  @request = RPC::Request.new(msg, @ddl)
  @reply = RPC::Reply.new(@request.action, @ddl)

  begin
    # Incoming requests need to be validated against the DDL thus reusing
    # all the work users put into creating DDLs and creating a consistent
    # quality of input validation everywhere with the a simple once off
    # investment of writing a DDL
    @request.validate!

    # Calls the authorization plugin if any is defined
    # if this raises an exception we wil just skip processing this
    # message
    authorization_hook(@request) if respond_to?("authorization_hook")

    # Audits the request, currently continues processing the message
    # we should make this a configurable so that an audit failure means
    # a message wont be processed by this node depending on config
    audit_request(@request, connection)

    before_processing_hook(msg, connection)

    if respond_to?("#{@request.action}_action")
      send("#{@request.action}_action")
    else
      raise UnknownRPCAction, "Unknown action '#{@request.action}' for agent '#{@request.agent}'"
    end
  rescue RPCAborted => e
    @reply.fail e.to_s, 1

  rescue UnknownRPCAction => e
    @reply.fail e.to_s, 2

  rescue MissingRPCData => e
    @reply.fail e.to_s, 3

  rescue InvalidRPCData, DDLValidationError => e
    @reply.fail e.to_s, 4

  rescue UnknownRPCError => e
    Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
    Log.error(e.backtrace.join("\n\t"))
    @reply.fail e.to_s, 5

  rescue Exception => e
    Log.error("%s#%s failed: %s: %s" % [@agent_name, @request.action, e.class, e.to_s])
    Log.error(e.backtrace.join("\n\t"))
    @reply.fail e.to_s, 5

  end

  after_processing_hook

  if @request.should_respond?
    return @reply.to_hash
  else
    Log.debug("Client did not request a response, surpressing reply")
    return nil
  end
end
load_ddl() click to toggle source
# File lib/mcollective/rpc/agent.rb, line 52
def load_ddl
  @ddl = DDL.new(@agent_name, :agent)
  @meta = @ddl.meta
  @timeout = @meta[:timeout] || 10

rescue Exception => e
  Log.error("Failed to load DDL for the '%s' agent, DDLs are required: %s: %s" % [@agent_name, e.class, e.to_s])
  raise DDLValidationError
end
mcollective-2.6.0/doc/MCollective/RPC/Reply.html0000644000175000017500000006431512375446415020437 0ustar jonasjonas class MCollective::RPC::Reply - mcollective version 2.6.0

class MCollective::RPC::Reply

Simple class to manage compliant replies to MCollective::RPC

Attributes

data[RW]
statuscode[RW]
statusmsg[RW]

Public Class Methods

new(action, ddl) click to toggle source
# File lib/mcollective/rpc/reply.rb, line 7
def initialize(action, ddl)
  @data = {}
  @statuscode = 0
  @statusmsg = "OK"
  @ddl = ddl
  @action = action

  begin
    initialize_data
  rescue Exception => e
    Log.warn("Could not pre-populate reply data from the DDL: %s: %s" % [e.class, e.to_s ])
  end
end

Public Instance Methods

[](key) click to toggle source

Read from the data hash

# File lib/mcollective/rpc/reply.rb, line 70
def [](key)
  @data[key]
end
[]=(key, val) click to toggle source

Write to the data hash

# File lib/mcollective/rpc/reply.rb, line 65
def []=(key, val)
  @data[key] = val
end
fail(msg, code=1) click to toggle source

Helper to fill in statusmsg and code on failure

# File lib/mcollective/rpc/reply.rb, line 36
def fail(msg, code=1)
  @statusmsg = msg
  @statuscode = code
end
fail!(msg, code=1) click to toggle source

Helper that fills in statusmsg and code but also raises an appropriate error

# File lib/mcollective/rpc/reply.rb, line 42
def fail!(msg, code=1)
  @statusmsg = msg
  @statuscode = code

  case code
    when 1
      raise RPCAborted, msg

    when 2
      raise UnknownRPCAction, msg

    when 3
      raise MissingRPCData, msg

    when 4
      raise InvalidRPCData, msg

    else
      raise UnknownRPCError, msg
  end
end
fetch(key, default) click to toggle source
# File lib/mcollective/rpc/reply.rb, line 74
def fetch(key, default)
  @data.fetch(key, default)
end
initialize_data() click to toggle source
# File lib/mcollective/rpc/reply.rb, line 21
def initialize_data
  unless @ddl.actions.include?(@action)
    raise "No action '%s' defined for agent '%s' in the DDL" % [@action, @ddl.pluginname]
  end

  interface = @ddl.action_interface(@action)

  interface[:output].keys.each do |output|
    # must deep clone this data to avoid accidental updates of the DDL in cases where the
    # default is for example a string and someone does << on it
    @data[output] = Marshal.load(Marshal.dump(interface[:output][output].fetch(:default, nil)))
  end
end
to_hash() click to toggle source

Returns a compliant Hash of the reply that should be sent over the middleware

# File lib/mcollective/rpc/reply.rb, line 80
def to_hash
  return {:statuscode => @statuscode,
          :statusmsg => @statusmsg,
          :data => @data}
end
mcollective-2.6.0/doc/MCollective/RPC/Helpers.html0000644000175000017500000015645012375446415020750 0ustar jonasjonas class MCollective::RPC::Helpers - mcollective version 2.6.0

class MCollective::RPC::Helpers

Various utilities for the RPC system

Public Class Methods

add_simplerpc_options(parser, options) click to toggle source

Add SimpleRPC common options

# File lib/mcollective/rpc/helpers.rb, line 255
def self.add_simplerpc_options(parser, options)
  parser.separator ""
  parser.separator "RPC Options"

  # add SimpleRPC specific options to all clients that use our library
  parser.on('--np', '--no-progress', 'Do not show the progress bar') do |v|
    options[:progress_bar] = false
  end

  parser.on('--one', '-1', 'Send request to only one discovered nodes') do |v|
    options[:mcollective_limit_targets] = 1
  end
    
  parser.on('--batch SIZE', 'Do requests in batches') do |v|
    # validate batch string. Is it x% where x > 0 or is it an integer
    if ((v =~ /^(\d+)%$/ && Integer($1) != 0) || v =~ /^(\d+)$/)
      options[:batch_size] = v
    else
      raise(::OptionParser::InvalidArgument.new(v))
    end
  end

  parser.on('--batch-sleep SECONDS', Float, 'Sleep time between batches') do |v|
    options[:batch_sleep_time] = v
  end

  parser.on('--limit-seed NUMBER', Integer, 'Seed value for deterministic random batching') do |v|
    options[:limit_seed] = v
  end

  parser.on('--limit-nodes COUNT', '--ln', '--limit', 'Send request to only a subset of nodes, can be a percentage') do |v|
    raise "Invalid limit specified: #{v} valid limits are /^\d+%*$/" unless v =~ /^\d+%*$/

    if v =~ /^\d+$/
      options[:mcollective_limit_targets] = v.to_i
    else
      options[:mcollective_limit_targets] = v
    end
  end

  parser.on('--json', '-j', 'Produce JSON output') do |v|
    options[:progress_bar] = false
    options[:output_format] = :json
  end

  parser.on('--display MODE', 'Influence how results are displayed. One of ok, all or failed') do |v|
    if v == "all"
      options[:force_display_mode] = :always
    else
      options[:force_display_mode] = v.intern
    end

    raise "--display has to be one of 'ok', 'all' or 'failed'" unless [:ok, :failed, :always].include?(options[:force_display_mode])
  end
end
extract_hosts_from_array(hosts) click to toggle source

Given an array of something, make sure each is a string chomp off any new lines and return just the array of hosts

# File lib/mcollective/rpc/helpers.rb, line 29
def self.extract_hosts_from_array(hosts)
  [hosts].flatten.map do |host|
    raise "#{host} should be a string" unless host.is_a?(String)
    host.chomp
  end
end
extract_hosts_from_json(json) click to toggle source

Parse JSON output as produced by printrpc and extract the “sender†of each rpc response

The simplist valid JSON based data would be:

[

{"sender" => "example.com"},
{"sender" => "another.com"}

]

# File lib/mcollective/rpc/helpers.rb, line 14
def self.extract_hosts_from_json(json)
  hosts = JSON.parse(json)

  raise "JSON hosts list is not an array" unless hosts.is_a?(Array)

  hosts.map do |host|
    raise "JSON host list is not an array of Hashes" unless host.is_a?(Hash)
    raise "JSON host list does not have senders in it" unless host.include?("sender")

    host["sender"]
  end.uniq
end
old_rpcresults(result, flags = {}) click to toggle source

Backward compatible display block for results without a DDL

# File lib/mcollective/rpc/helpers.rb, line 203
def self.old_rpcresults(result, flags = {})
  result_text = ""

  if flags[:flatten]
    result.each do |r|
      if r[:statuscode] <= 1
        data = r[:data]

        unless data.is_a?(String)
          result_text << data.pretty_inspect
        else
          result_text << data
        end
      else
        result_text << r.pretty_inspect
      end
    end

    result_text << ""
  else
    [result].flatten.each do |r|

      if flags[:verbose]
        result_text << "%-40s: %s\n" % [r[:sender], r[:statusmsg]]

        if r[:statuscode] <= 1
          r[:data].pretty_inspect.split("\n").each {|m| result_text += "    #{m}"}
          result_text << "\n\n"
        elsif r[:statuscode] == 2
          # dont print anything, no useful data to display
          # past what was already shown
        elsif r[:statuscode] == 3
          # dont print anything, no useful data to display
          # past what was already shown
        elsif r[:statuscode] == 4
          # dont print anything, no useful data to display
          # past what was already shown
        else
          result_text << "    #{r[:statusmsg]}"
        end
      else
        unless r[:statuscode] == 0
          result_text << "%-40s %s\n" % [r[:sender], Util.colorize(:red, r[:statusmsg])]
        end
      end
    end
  end

  result_text << ""
end
rpcresults(result, flags = {}) click to toggle source

Returns a blob of text representing the results in a standard way

It tries hard to do sane things so you often should not need to write your own display functions

If the agent you are getting results for has a DDL it will use the hints in there to do the right thing specifically it will look at the values of display in the DDL to choose when to show results

If you do not have a DDL you can pass these flags:

printrpc exim.mailq, :flatten => true
printrpc exim.mailq, :verbose => true

If you've asked it to flatten the result it will not print sender hostnames, it will just print the result as if it's one huge result, handy for things like showing a combined mailq.

# File lib/mcollective/rpc/helpers.rb, line 54
def self.rpcresults(result, flags = {})
  flags = {:verbose => false, :flatten => false, :format => :console, :force_display_mode => false}.merge(flags)

  result_text = ""
  ddl = nil

  # if running in verbose mode, just use the old style print
  # no need for all the DDL helpers obfuscating the result
  if flags[:format] == :json
    if STDOUT.tty?
      result_text = JSON.pretty_generate(result)
    else
      result_text = result.to_json
    end
  else
    if flags[:verbose]
      result_text = old_rpcresults(result, flags)
    else
      [result].flatten.each do |r|
        begin
          ddl ||= DDL.new(r.agent).action_interface(r.action.to_s)

          sender = r[:sender]
          status = r[:statuscode]
          message = r[:statusmsg]
          result = r[:data]

          if flags[:force_display_mode]
            display = flags[:force_display_mode]
          else
            display = ddl[:display]
          end

          # appand the results only according to what the DDL says
          case display
            when :ok
              if status == 0
                result_text << text_for_result(sender, status, message, result, ddl)
              end

            when :failed
              if status > 0
                result_text << text_for_result(sender, status, message, result, ddl)
              end

            when :always
              result_text << text_for_result(sender, status, message, result, ddl)

            when :flatten
              Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release")
              result_text << text_for_flattened_result(status, result)

          end
        rescue Exception => e
          # no DDL so just do the old style print unchanged for
          # backward compat
          result_text = old_rpcresults(result, flags)
        end
      end
    end
  end

  result_text
end
text_for_flattened_result(status, result) click to toggle source

Returns text representing a flattened result of only good data

# File lib/mcollective/rpc/helpers.rb, line 190
def self.text_for_flattened_result(status, result)
  result_text = ""

  if status <= 1
    unless result.is_a?(String)
      result_text << result.pretty_inspect
    else
      result_text << result
    end
  end
end
text_for_result(sender, status, msg, result, ddl) click to toggle source

Return text representing a result

# File lib/mcollective/rpc/helpers.rb, line 120
def self.text_for_result(sender, status, msg, result, ddl)
  statusses = ["",
               Util.colorize(:red, "Request Aborted"),
               Util.colorize(:yellow, "Unknown Action"),
               Util.colorize(:yellow, "Missing Request Data"),
               Util.colorize(:yellow, "Invalid Request Data"),
               Util.colorize(:red, "Unknown Request Status")]

  result_text = "%-40s %s\n" % [sender, statusses[status]]
  result_text << "   %s\n" % [Util.colorize(:yellow, msg)] unless msg == "OK"

  # only print good data, ignore data that results from failure
  if status == 0
    if result.is_a?(Hash)
      # figure out the lengths of the display as strings, we'll use
      # it later to correctly justify the output
      lengths = result.keys.map do |k|
        begin
          ddl[:output][k][:display_as].size
        rescue
          k.to_s.size
        end
      end

      result.keys.sort_by{|k| k}.each do |k|
        # get all the output fields nicely lined up with a
        # 3 space front padding
        begin
          display_as = ddl[:output][k][:display_as]
        rescue
          display_as = k.to_s
        end

        display_length = display_as.size
        padding = lengths.max - display_length + 3
        result_text << " " * padding

        result_text << "#{display_as}:"

        if [String, Numeric].include?(result[k].class)
          lines = result[k].to_s.split("\n")

          if lines.empty?
            result_text << "\n"
          else
            lines.each_with_index do |line, i|
              i == 0 ? padtxt = " " : padtxt = " " * (padding + display_length + 2)

              result_text << "#{padtxt}#{line}\n"
            end
          end
        else
          padding = " " * (lengths.max + 5)
          result_text << " " << result[k].pretty_inspect.split("\n").join("\n" << padding) << "\n"
        end
      end
    elsif status == 1
      # for status 1 we dont want to show half baked
      # data by default since the DDL will supply all the defaults
      # it just doesnt look right
    else
      result_text << "\n\t" + result.pretty_inspect.split("\n").join("\n\t")
    end
  end

  result_text << "\n"
  result_text
end
mcollective-2.6.0/doc/MCollective/RPC/Stats.html0000644000175000017500000016143612375446415020444 0ustar jonasjonas class MCollective::RPC::Stats - mcollective version 2.6.0

class MCollective::RPC::Stats

Class to wrap all the stats and to keep track of some timings

Attributes

aggregate_failures[RW]
aggregate_summary[RW]
blocktime[RW]
ddl[RW]
discovered[RW]
discovered_nodes[RW]
discoverytime[RW]
failcount[RW]
noresponsefrom[RW]
okcount[RW]
requestid[RW]
responses[RW]
responsesfrom[RW]
starttime[RW]
totaltime[RW]

Public Class Methods

new() click to toggle source
# File lib/mcollective/rpc/stats.rb, line 9
def initialize
  reset
end

Public Instance Methods

[](key) click to toggle source

Fake hash access to keep things backward compatible

# File lib/mcollective/rpc/stats.rb, line 51
def [](key)
  to_hash[key]
rescue
  nil
end
client_stats=(stats) click to toggle source

Re-initializes the object with stats from the basic client

# File lib/mcollective/rpc/stats.rb, line 72
def client_stats=(stats)
  @noresponsefrom = stats[:noresponsefrom]
  @responses = stats[:responses]
  @starttime = stats[:starttime]
  @blocktime = stats[:blocktime]
  @totaltime = stats[:totaltime]
  @requestid = stats[:requestid]
  @discoverytime = stats[:discoverytime] if @discoverytime == 0
end
discovered_agents(agents) click to toggle source

Update discovered and #discovered_nodes based on discovery results

# File lib/mcollective/rpc/stats.rb, line 110
def discovered_agents(agents)
  @discovered_nodes = agents
  @discovered = agents.size
end
fail() click to toggle source

increment the count of failed hosts

# File lib/mcollective/rpc/stats.rb, line 65
def fail
  @failcount += 1
rescue
  @failcount = 1
end
finish_request() click to toggle source

Helper to calculate total time etc

# File lib/mcollective/rpc/stats.rb, line 116
def finish_request
  @totaltime = @blocktime + @discoverytime

  # figures out who we had no responses from
  dhosts = @discovered_nodes.clone
  @responsesfrom.each {|r| dhosts.delete(r)}
  @noresponsefrom = dhosts
rescue
  @totaltime = 0
  @noresponsefrom = []
end
no_response_report() click to toggle source

Returns a blob of text indicating what nodes did not respond

# File lib/mcollective/rpc/stats.rb, line 237
def no_response_report
  result_text = StringIO.new

  if @noresponsefrom.size > 0
    result_text.puts
    result_text.puts Util.colorize(:red, "No response from:")
    result_text.puts

    field_size = Util.field_size(@noresponsefrom, 30)
    fields_num = Util.field_number(field_size)
    format = "   " + ( " %-#{field_size}s" * fields_num )

    @noresponsefrom.sort.in_groups_of(fields_num) do |c|
      result_text.puts format % c
    end

    result_text.puts
  end

  result_text.string
end
node_responded(node) click to toggle source

Helper to keep track of who we received responses from

# File lib/mcollective/rpc/stats.rb, line 129
def node_responded(node)
  @responsesfrom << node
rescue
  @responsesfrom = [node]
end
ok() click to toggle source

increment the count of ok hosts

# File lib/mcollective/rpc/stats.rb, line 58
def ok
  @okcount += 1
rescue
  @okcount = 1
end
report(caption = "rpc stats", summarize = true, verbose = false) click to toggle source

Returns a blob of text representing the request status based on the stats contained in this class

# File lib/mcollective/rpc/stats.rb, line 187
def report(caption = "rpc stats", summarize = true, verbose = false)
  result_text = []

  if verbose
      if @aggregate_summary.size > 0 && summarize
        result_text << text_for_aggregates
      else
        result_text << ""
      end

    result_text << Util.colorize(:yellow, "---- #{caption} ----")

    if @discovered
      @responses < @discovered ? color = :red : color = :reset
      result_text << "           Nodes: %s / %s" % [ Util.colorize(color, @discovered), Util.colorize(color, @responses) ]
    else
      result_text << "           Nodes: #{@responses}"
    end

    @failcount < 0 ? color = :red : color = :reset

    result_text << "     Pass / Fail: %s / %s" % [Util.colorize(color, @okcount), Util.colorize(color, @failcount) ]
    result_text << "      Start Time: %s"      % [Time.at(@starttime)]
    result_text << "  Discovery Time: %.2fms"  % [@discoverytime * 1000]
    result_text << "      Agent Time: %.2fms"  % [@blocktime * 1000]
    result_text << "      Total Time: %.2fms"  % [@totaltime * 1000]
  else
    if @discovered
      @responses < @discovered ? color = :red : color = :green

      if @aggregate_summary.size + @aggregate_failures.size > 0 && summarize
        result_text << text_for_aggregates
      else
        result_text << ""
      end

      result_text << "Finished processing %s / %s hosts in %.2f ms" % [Util.colorize(color, @responses), Util.colorize(color, @discovered), @blocktime * 1000]
    else
      result_text << "Finished processing %s hosts in %.2f ms" % [Util.colorize(:bold, @responses), @blocktime * 1000]
    end
  end

  if no_response_report != ""
    result_text << "" << no_response_report
  end

  result_text.join("\n")
end
reset() click to toggle source

Resets stats, if discovery time is set we keep it as it was

# File lib/mcollective/rpc/stats.rb, line 14
def reset
  @noresponsefrom = []
  @responsesfrom = []
  @responses = 0
  @starttime = Time.now.to_f
  @discoverytime = 0 unless @discoverytime
  @blocktime = 0
  @totaltime = 0
  @discovered = 0
  @discovered_nodes = []
  @okcount = 0
  @failcount = 0
  @noresponsefrom = []
  @requestid = nil
  @aggregate_summary = []
  @aggregate_failures = []
end
text_for_aggregates() click to toggle source
# File lib/mcollective/rpc/stats.rb, line 135
def text_for_aggregates
  result = StringIO.new

  @aggregate_summary.each do |aggregate|
    output_item = aggregate.result[:output]

    begin
      action_interface = @ddl.action_interface(aggregate.action)
      display_as = action_interface[:output][output_item][:display_as]
    rescue
      display_as = output_item
    end

    if aggregate.is_a?(Aggregate::Result::Base)
      aggregate_report = aggregate.to_s
    else
      next
    end

    result.puts Util.colorize(:bold, "Summary of %s:" % display_as)
    result.puts
    unless aggregate_report == ""
      result.puts aggregate.to_s.split("\n").map{|x| "   " + x}.join("\n")
    else
      result.puts Util.colorize(:yellow, "     No aggregate summary could be computed")
    end
    result.puts
  end

  @aggregate_failures.each do |failed|
    case(failed[:type])
    when :startup
      message = "exception raised while processing startup hook"
    when :create
      message = "unspecified output '#{failed[:name]}' for the action"
    when :process_result
      message = "exception raised while processing result data"
    when :summarize
      message = "exception raised while summarizing"
    end

    result.puts Util.colorize(:bold, "Summary of %s:" % failed[:name])
    result.puts
    result.puts Util.colorize(:yellow, "     Could not compute summary - %s" % message)
    result.puts
  end

  result.string
end
time_block_execution(action) click to toggle source

helper to time block execution time

# File lib/mcollective/rpc/stats.rb, line 96
def time_block_execution(action)
  if action == :start
    @block_start = Time.now.to_f
  elsif action == :end
    @blocktime += Time.now.to_f - @block_start
  else
    raise("Uknown block action #{action}")
  end
rescue
  @blocktime = 0
end
time_discovery(action) click to toggle source

Utility to time discovery from :start to :end

# File lib/mcollective/rpc/stats.rb, line 83
def time_discovery(action)
  if action == :start
    @discovery_start = Time.now.to_f
  elsif action == :end
    @discoverytime = Time.now.to_f - @discovery_start
  else
    raise("Uknown discovery action #{action}")
  end
rescue
  @discoverytime = 0
end
to_hash() click to toggle source

returns a hash of our stats

# File lib/mcollective/rpc/stats.rb, line 33
def to_hash
  {:noresponsefrom    => @noresponsefrom,
   :starttime         => @starttime,
   :discoverytime     => @discoverytime,
   :blocktime         => @blocktime,
   :responses         => @responses,
   :totaltime         => @totaltime,
   :discovered        => @discovered,
   :discovered_nodes  => @discovered_nodes,
   :noresponsefrom    => @noresponsefrom,
   :okcount           => @okcount,
   :requestid         => @requestid,
   :failcount         => @failcount,
   :aggregate_summary => @aggregate_summary,
   :aggregate_failures => @aggregate_failures}
end
mcollective-2.6.0/doc/MCollective/RPC/Client.html0000644000175000017500000045610712375446415020566 0ustar jonasjonas class MCollective::RPC::Client - mcollective version 2.6.0

class MCollective::RPC::Client

The main component of the Simple RPC client system, this wraps around MCollective::Client and just brings in a lot of convention and standard approached.

Attributes

agent[R]
batch_mode[R]
batch_size[R]
batch_sleep_time[R]
client[R]
config[RW]
ddl[R]
default_discovery_method[R]
discovery_method[R]
discovery_options[R]
filter[RW]
limit_method[R]
limit_seed[R]
limit_targets[R]
output_format[R]
progress[RW]
reply_to[RW]
stats[R]
timeout[RW]
ttl[RW]
verbose[RW]

Public Class Methods

new(agent, flags = {}) { |parser, opts| ... } click to toggle source

Creates a stub for a remote agent, you can pass in an options array in the flags which will then be used else it will just create a default options array with filtering enabled based on the standard command line use.

rpc = RPC::Client.new("rpctest", :configfile => "client.cfg", :options => options)

You typically would not call this directly you'd use MCollective::RPC#rpcclient instead which is a wrapper around this that can be used as a Mixin

# File lib/mcollective/rpc/client.rb, line 20
def initialize(agent, flags = {})
  if flags.include?(:options)
    initial_options = flags[:options]

  elsif @@initial_options
    initial_options = Marshal.load(@@initial_options)

  else
    oparser = MCollective::Optionparser.new({ :verbose => false, 
                                              :progress_bar => true, 
                                              :mcollective_limit_targets => false, 
                                              :batch_size => nil, 
                                              :batch_sleep_time => 1 }, 
                                            "filter")

    initial_options = oparser.parse do |parser, opts|
      if block_given?
        yield(parser, opts)
      end

      Helpers.add_simplerpc_options(parser, opts)
    end

    @@initial_options = Marshal.dump(initial_options)
  end

  @initial_options = initial_options

  @config = initial_options[:config]
  @client = MCollective::Client.new(@config)
  @client.options = initial_options

  @stats = Stats.new
  @agent = agent
  @timeout = initial_options[:timeout] || 5
  @verbose = initial_options[:verbose]
  @filter = initial_options[:filter] || Util.empty_filter
  @discovered_agents = nil
  @progress = initial_options[:progress_bar]
  @limit_targets = initial_options[:mcollective_limit_targets]
  @limit_method = Config.instance.rpclimitmethod
  @limit_seed = initial_options[:limit_seed] || nil
  @output_format = initial_options[:output_format] || :console
  @force_direct_request = false
  @reply_to = initial_options[:reply_to]
  @discovery_method = initial_options[:discovery_method]
  if !@discovery_method
    @discovery_method = Config.instance.default_discovery_method
    @default_discovery_method = true
  else
    @default_discovery_method = false
  end
  @discovery_options = initial_options[:discovery_options] || []
  @force_display_mode = initial_options[:force_display_mode] || false

  @batch_size = initial_options[:batch_size] || 0
  @batch_sleep_time = Float(initial_options[:batch_sleep_time] || 1)
  @batch_mode = determine_batch_mode(@batch_size)

  agent_filter agent

  @discovery_timeout = @initial_options.fetch(:disctimeout, nil) || Config.instance.discovery_timeout

  @collective = @client.collective
  @ttl = initial_options[:ttl] || Config.instance.ttl
  @publish_timeout = initial_options[:publish_timeout] || Config.instance.publish_timeout
  @threaded = initial_options[:threaded] || Config.instance.threaded

  # if we can find a DDL for the service override
  # the timeout of the client so we always magically
  # wait appropriate amounts of time.
  #
  # We add the discovery timeout to the ddl supplied
  # timeout as the discovery timeout tends to be tuned
  # for local network conditions and fact source speed
  # which would other wise not be accounted for and
  # some results might get missed.
  #
  # We do this only if the timeout is the default 5
  # seconds, so that users cli overrides will still
  # get applied
  #
  # DDLs are required, failure to find a DDL is fatal
  @ddl = DDL.new(agent)
  @stats.ddl = @ddl
  @timeout = @ddl.meta[:timeout] + discovery_timeout if @timeout == 5

  # allows stderr and stdout to be overridden for testing
  # but also for web apps that might not want a bunch of stuff
  # generated to actual file handles
  if initial_options[:stderr]
    @stderr = initial_options[:stderr]
  else
    @stderr = STDERR
    @stderr.sync = true
  end

  if initial_options[:stdout]
    @stdout = initial_options[:stdout]
  else
    @stdout = STDOUT
    @stdout.sync = true
  end
end

Public Instance Methods

agent_filter(agent) click to toggle source

Sets the agent filter

# File lib/mcollective/rpc/client.rb, line 432
def agent_filter(agent)
  @filter["agent"] = @filter["agent"] | [agent]
  @filter["agent"].compact!
  reset
end
aggregate_reply(reply, aggregate) click to toggle source
# File lib/mcollective/rpc/client.rb, line 707
def aggregate_reply(reply, aggregate)
  return nil unless aggregate

  aggregate.call_functions(reply)
  return aggregate
rescue Exception => e
  Log.error("Failed to calculate aggregate summaries for reply from %s, calculating summaries disabled: %s: %s (%s)" % [reply[:senderid], e.backtrace.first, e.to_s, e.class])
  return nil
end
batch_size=(limit) click to toggle source

Sets the batch size, if the size is set to 0 that will disable batch mode

# File lib/mcollective/rpc/client.rb, line 624
def batch_size=(limit)
  unless Config.instance.direct_addressing
    raise "Can only set batch size if direct addressing is supported"
  end
  
  validate_batch_size(limit)

  @batch_size = limit
  @batch_mode = determine_batch_mode(@batch_size)
end
batch_sleep_time=(time) click to toggle source
# File lib/mcollective/rpc/client.rb, line 635
def batch_sleep_time=(time)
  raise "Can only set batch sleep time if direct addressing is supported" unless Config.instance.direct_addressing

  @batch_sleep_time = Float(time)
end
call_agent(action, args, opts, disc=:auto, &block) click to toggle source

Handles traditional calls to the remote agents with full stats blocks, non blocks and everything else supported.

Other methods of calling the nodes can reuse this code by for example specifying custom options and discovery data

# File lib/mcollective/rpc/client.rb, line 883
def call_agent(action, args, opts, disc=:auto, &block)
  # Handle fire and forget requests and make sure
  # the :process_results value is set appropriately
  #
  # specific reply-to requests should be treated like
  # fire and forget since the client will never get
  # the responses
  if args[:process_results] == false || @reply_to
    return fire_and_forget_request(action, args)
  else
    args[:process_results] = true
  end

  # Do discovery when no specific discovery array is given
  #
  # If an array is given set the force_direct_request hint that
  # will tell the message object to be a direct request one
  if disc == :auto
    discovered = discover
  else
    @force_direct_request = true if Config.instance.direct_addressing
    discovered = disc
  end

  req = new_request(action.to_s, args)

  message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => opts[:filter], :options => opts})
  message.discovered_hosts = discovered.clone

  results = []
  respcount = 0

  if discovered.size > 0
    message.type = :direct_request if @force_direct_request

    if @progress && !block_given?
      twirl = Progress.new
      @stdout.puts
      @stdout.print twirl.twirl(respcount, discovered.size)
    end

    aggregate = load_aggregate_functions(action, @ddl)

    @client.req(message) do |resp|
      respcount += 1

      if block_given?
        aggregate = process_results_with_block(action, resp, block, aggregate)
      else
        @stdout.print twirl.twirl(respcount, discovered.size) if @progress

        result, aggregate = process_results_without_block(resp, action, aggregate)

        results << result
      end
    end

    if @initial_options[:sort]
      results.sort!
    end

    @stats.aggregate_summary = aggregate.summarize if aggregate
    @stats.aggregate_failures = aggregate.failed if aggregate
    @stats.client_stats = @client.stats
  else
    @stderr.print("\nNo request sent, we did not discover any nodes.")
  end

  @stats.finish_request

  RPC.stats(@stats)

  @stdout.print("\n\n") if @progress

  if block_given?
    return stats
  else
    return [results].flatten
  end
end
call_agent_batched(action, args, opts, batch_size, sleep_time, &block) click to toggle source

Calls an agent in a way very similar to #call_agent but it supports batching the queries to the network.

The result sets, stats, block handling etc is all exactly like you would expect from normal call_agent.

This is used by #method_missing and works only with direct addressing mode

# File lib/mcollective/rpc/client.rb, line 779
def call_agent_batched(action, args, opts, batch_size, sleep_time, &block)
  raise "Batched requests requires direct addressing" unless Config.instance.direct_addressing
  raise "Cannot bypass result processing for batched requests" if args[:process_results] == false
  validate_batch_size(batch_size)

  sleep_time = Float(sleep_time)

  Log.debug("Calling #{agent}##{action} in batches of #{batch_size} with sleep time of #{sleep_time}")

  @force_direct_request = true

  discovered = discover
  results = []
  respcount = 0

  if discovered.size > 0
    req = new_request(action.to_s, args)

    aggregate = load_aggregate_functions(action, @ddl)

    if @progress && !block_given?
      twirl = Progress.new
      @stdout.puts
      @stdout.print twirl.twirl(respcount, discovered.size)
    end

    if (batch_size =~ /^(\d+)%$/)
      # determine batch_size as a percentage of the discovered array's size
      batch_size = (discovered.size / 100.0 * Integer($1)).ceil
    else
      batch_size = Integer(batch_size)
    end

    @stats.requestid = nil
    processed_nodes = 0

    discovered.in_groups_of(batch_size) do |hosts|
      message = Message.new(req, nil, {:agent => @agent, 
                                       :type => :direct_request, 
                                       :collective => @collective, 
                                       :filter => opts[:filter], 
                                       :options => opts})

      # first time round we let the Message object create a request id
      # we then re-use it for future requests to keep auditing sane etc
      @stats.requestid = message.create_reqid unless @stats.requestid
      message.requestid = @stats.requestid

      message.discovered_hosts = hosts.clone.compact

      @client.req(message) do |resp|
        respcount += 1

        if block_given?
          aggregate = process_results_with_block(action, resp, block, aggregate)
        else
          @stdout.print twirl.twirl(respcount, discovered.size) if @progress

          result, aggregate = process_results_without_block(resp, action, aggregate)

          results << result
        end
      end

      if @initial_options[:sort]
        results.sort!
      end

      @stats.noresponsefrom.concat @client.stats[:noresponsefrom]
      @stats.responses += @client.stats[:responses]
      @stats.blocktime += @client.stats[:blocktime] + sleep_time
      @stats.totaltime += @client.stats[:totaltime]
      @stats.discoverytime += @client.stats[:discoverytime]

      processed_nodes += hosts.length
      if (discovered.length > processed_nodes)
        sleep sleep_time
      end
    end

    @stats.aggregate_summary = aggregate.summarize if aggregate
    @stats.aggregate_failures = aggregate.failed if aggregate
  else
    @stderr.print("\nNo request sent, we did not discover any nodes.")
  end

  @stats.finish_request

  RPC.stats(@stats)

  @stdout.print("\n") if @progress

  if block_given?
    return stats
  else
    return [results].flatten
  end
end
class_filter(klass) click to toggle source

Sets the class filter

# File lib/mcollective/rpc/client.rb, line 408
def class_filter(klass)
  @filter["cf_class"] = @filter["cf_class"] | [klass]
  @filter["cf_class"].compact!
  reset
end
collective=(c) click to toggle source

Sets the collective we are communicating with

# File lib/mcollective/rpc/client.rb, line 583
def collective=(c)
  raise "Unknown collective #{c}" unless Config.instance.collectives.include?(c)

  @collective = c
  @client.options = options
  reset
end
compound_filter(filter) click to toggle source

Set a compound filter

# File lib/mcollective/rpc/client.rb, line 446
def compound_filter(filter)
  @filter["compound"] = @filter["compound"] |  [Matcher.create_compound_callstack(filter)]
  reset
end
custom_request(action, args, expected_agents, filter = {}, &block) click to toggle source

Constructs custom requests with custom filters and discovery data the idea is that this would be used in web applications where you might be using a cached copy of data provided by a registration agent to figure out on your own what nodes will be responding and what your filter would be.

This will help you essentially short circuit the traditional cycle of:

mc discover / call / wait for discovered nodes

by doing discovery however you like, contructing a filter and a list of nodes you expect responses from.

Other than that it will work exactly like a normal call, blocks will behave the same way, stats will be handled the same way etcetc

If you just wanted to contact one machine for example with a client that already has other filter options setup you can do:

puppet.custom_request(“runonceâ€, {}, [“your.box.comâ€], {:identity => “your.box.comâ€})

This will do runonce action on just 'your.box.com', no discovery will be done and after receiving just one response it will stop waiting for responses

If direct_addressing is enabled in the config file you can provide an empty hash as a filter, this will force that request to be a directly addressed request which technically does not need filters. If you try to use this mode with direct addressing disabled an exception will be raise

# File lib/mcollective/rpc/client.rb, line 306
def custom_request(action, args, expected_agents, filter = {}, &block)
  validate_request(action, args)

  if filter == {} && !Config.instance.direct_addressing
    raise "Attempted to do a filterless custom_request without direct_addressing enabled, preventing unexpected call to all nodes"
  end

  @stats.reset

  custom_filter = Util.empty_filter
  custom_options = options.clone

  # merge the supplied filter with the standard empty one
  # we could just use the merge method but I want to be sure
  # we dont merge in stuff that isnt actually valid
  ["identity", "fact", "agent", "cf_class", "compound"].each do |ftype|
    if filter.include?(ftype)
      custom_filter[ftype] = [filter[ftype], custom_filter[ftype]].flatten
    end
  end

  # ensure that all filters at least restrict the call to the agent we're a proxy for
  custom_filter["agent"] << @agent unless custom_filter["agent"].include?(@agent)
  custom_options[:filter] = custom_filter

  # Fake out the stats discovery would have put there
  @stats.discovered_agents([expected_agents].flatten)

  # Handle fire and forget requests
  #
  # If a specific reply-to was set then from the client perspective this should
  # be a fire and forget request too since no response will ever reach us - it
  # will go to the reply-to destination
  if args[:process_results] == false || @reply_to
    return fire_and_forget_request(action, args, custom_filter)
  end

  # Now do a call pretty much exactly like in method_missing except with our own
  # options and discovery magic
  if block_given?
    call_agent(action, args, custom_options, [expected_agents].flatten) do |r|
      block.call(r)
    end
  else
    call_agent(action, args, custom_options, [expected_agents].flatten)
  end
end
disconnect() click to toggle source

Disconnects cleanly from the middleware

# File lib/mcollective/rpc/client.rb, line 126
def disconnect
  @client.disconnect
end
discover(flags={}) click to toggle source

Does discovery based on the filters set, if a discovery was previously done return that else do a new discovery.

Alternatively if identity filters are given and none of them are regular expressions then just use the provided data as discovered data, avoiding discovery

Discovery can be forced if direct_addressing is enabled by passing in an array of nodes with :nodes or JSON data like those produced by mcollective RPC JSON output using :json

Will show a message indicating its doing discovery if running verbose or if the :verbose flag is passed in.

Use reset to force a new discovery

# File lib/mcollective/rpc/client.rb, line 478
def discover(flags={})
  flags.keys.each do |key|
    raise "Unknown option #{key} passed to discover" unless [:verbose, :hosts, :nodes, :json].include?(key)
  end

  flags.include?(:verbose) ? verbose = flags[:verbose] : verbose = @verbose

  verbose = false unless @output_format == :console

  # flags[:nodes] and flags[:hosts] are the same thing, we should never have
  # allowed :hosts as that was inconsistent with the established terminology
  flags[:nodes] = flags.delete(:hosts) if flags.include?(:hosts)

  reset if flags[:nodes] || flags[:json]

  unless @discovered_agents
    # if either hosts or JSON is supplied try to figure out discovery data from there
    # if direct_addressing is not enabled this is a critical error as the user might
    # not have supplied filters so raise an exception
    if flags[:nodes] || flags[:json]
      raise "Can only supply discovery data if direct_addressing is enabled" unless Config.instance.direct_addressing

      hosts = []

      if flags[:nodes]
        hosts = Helpers.extract_hosts_from_array(flags[:nodes])
      elsif flags[:json]
        hosts = Helpers.extract_hosts_from_json(flags[:json])
      end

      raise "Could not find any hosts in discovery data provided" if hosts.empty?

      @discovered_agents = hosts
      @force_direct_request = true

    else
      identity_filter_discovery_optimization
    end
  end

  # All else fails we do it the hard way using a traditional broadcast
  unless @discovered_agents
    @stats.time_discovery :start

    @client.options = options

    # if compound filters are used the only real option is to use the mc
    # discovery plugin since its the only capable of using data queries etc
    # and we do not want to degrade that experience just to allow compounds
    # on other discovery plugins the UX would be too bad raising complex sets
    # of errors etc.
    @client.discoverer.force_discovery_method_by_filter(options[:filter])

    if verbose
      actual_timeout = @client.discoverer.discovery_timeout(discovery_timeout, options[:filter])

      if actual_timeout > 0
        @stderr.print("Discovering hosts using the %s method for %d second(s) .... " % [@client.discoverer.discovery_method, actual_timeout])
      else
        @stderr.print("Discovering hosts using the %s method .... " % [@client.discoverer.discovery_method])
      end
    end

    # if the requested limit is a pure number and not a percent
    # and if we're configured to use the first found hosts as the
    # limit method then pass in the limit thus minimizing the amount
    # of work we do in the discover phase and speeding it up significantly
    if @limit_method == :first and @limit_targets.is_a?(Fixnum)
      @discovered_agents = @client.discover(@filter, discovery_timeout, @limit_targets)
    else
      @discovered_agents = @client.discover(@filter, discovery_timeout)
    end

    @stderr.puts(@discovered_agents.size) if verbose

    @force_direct_request = @client.discoverer.force_direct_mode?

    @stats.time_discovery :end
  end

  @stats.discovered_agents(@discovered_agents)
  RPC.discovered(@discovered_agents)

  @discovered_agents
end
discovery_method=(method) click to toggle source

Sets the discovery method. If we change the method there are a number of steps to take:

- set the new method
- if discovery options were provided, re-set those to initially
  provided ones else clear them as they might now apply to a
  different provider
- update the client options so it knows there is a new discovery
  method in force
- reset discovery data forcing a discover on the next request

The remaining item is the discovery timeout, we leave that as is since that is the user supplied timeout either via initial options or via specifically setting it on the client.

# File lib/mcollective/rpc/client.rb, line 387
def discovery_method=(method)
  @default_discovery_method = false
  @discovery_method = method

  if @initial_options[:discovery_options]
    @discovery_options = @initial_options[:discovery_options]
  else
    @discovery_options.clear
  end

  @client.options = options

  reset
end
discovery_options=(options) click to toggle source
# File lib/mcollective/rpc/client.rb, line 402
def discovery_options=(options)
  @discovery_options = [options].flatten
  reset
end
discovery_timeout() click to toggle source
# File lib/mcollective/rpc/client.rb, line 354
def discovery_timeout
  return @discovery_timeout if @discovery_timeout
  return @client.discoverer.ddl.meta[:timeout]
end
discovery_timeout=(timeout) click to toggle source
# File lib/mcollective/rpc/client.rb, line 359
def discovery_timeout=(timeout)
  @discovery_timeout = Float(timeout)

  # we calculate the overall timeout from the DDL of the agent and
  # the supplied discovery timeout unless someone specifically
  # specifies a timeout to the constructor
  #
  # But if we also then specifically set a discovery_timeout on the
  # agent that has to override the supplied timeout so we then
  # calculate a correct timeout based on DDL timeout and the
  # supplied discovery timeout
  @timeout = @ddl.meta[:timeout] + discovery_timeout
end
fact_filter(fact, value=nil, operator="=") click to toggle source

Sets the fact filter

# File lib/mcollective/rpc/client.rb, line 415
def fact_filter(fact, value=nil, operator="=")
  return if fact.nil?
  return if fact == false

  if value.nil?
    parsed = Util.parse_fact_string(fact)
    @filter["fact"] = @filter["fact"] | [parsed] unless parsed == false
  else
    parsed = Util.parse_fact_string("#{fact}#{operator}#{value}")
    @filter["fact"] = @filter["fact"] | [parsed] unless parsed == false
  end

  @filter["fact"].compact!
  reset
end
fire_and_forget_request(action, args, filter=nil) click to toggle source

for requests that do not care for results just return the request id and don't do any of the response processing.

We send the :process_results flag with to the nodes so they can make decisions based on that.

Should only be called via #method_missing

# File lib/mcollective/rpc/client.rb, line 730
def fire_and_forget_request(action, args, filter=nil)
  validate_request(action, args)

  identity_filter_discovery_optimization

  req = new_request(action.to_s, args)

  filter = options[:filter] unless filter

  message = Message.new(req, nil, {:agent => @agent, :type => :request, :collective => @collective, :filter => filter, :options => options})
  message.reply_to = @reply_to if @reply_to

  if @force_direct_request || @client.discoverer.force_direct_mode?
    message.discovered_hosts = discover.clone
    message.type = :direct_request
  end

  client.sendreq(message, nil)
end
help(template) click to toggle source

Returns help for an agent if a DDL was found

# File lib/mcollective/rpc/client.rb, line 131
def help(template)
  @ddl.help(template)
end
identity_filter(identity) click to toggle source

Sets the identity filter

# File lib/mcollective/rpc/client.rb, line 439
def identity_filter(identity)
  @filter["identity"] = @filter["identity"] | [identity]
  @filter["identity"].compact!
  reset
end
identity_filter_discovery_optimization() click to toggle source

if an identity filter is supplied and it is all strings no regex we can use that as discovery data, technically the identity filter is then redundant if we are in direct addressing mode and we could empty it out but this use case should only really be for a few -I's on the CLI

For safety we leave the filter in place for now, that way we can support this enhancement also in broadcast mode.

This is only needed for the 'mc' discovery method, other methods might change the concept of identity to mean something else so we should pass the full identity filter to them

# File lib/mcollective/rpc/client.rb, line 761
def identity_filter_discovery_optimization
  if options[:filter]["identity"].size > 0 && @discovery_method == "mc"
    regex_filters = options[:filter]["identity"].select{|i| i.match("^\/")}.size

    if regex_filters == 0
      @discovered_agents = options[:filter]["identity"].clone
      @force_direct_request = true if Config.instance.direct_addressing
    end
  end
end
limit_method=(method) click to toggle source

Sets and sanity check the #limit_method variable used to determine how to limit targets if #limit_targets is set

# File lib/mcollective/rpc/client.rb, line 615
def limit_method=(method)
  method = method.to_sym unless method.is_a?(Symbol)

  raise "Unknown limit method #{method} must be :random or :first" unless [:random, :first].include?(method)

  @limit_method = method
end
limit_targets=(limit) click to toggle source

Sets and sanity checks the #limit_targets variable used to restrict how many nodes we'll target Limit targets can be reset by passing nil or false

# File lib/mcollective/rpc/client.rb, line 594
def limit_targets=(limit)
  if !limit
    @limit_targets = nil
    return
  end

  if limit.is_a?(String)
    raise "Invalid limit specified: #{limit} valid limits are /^\d+%*$/" unless limit =~ /^\d+%*$/

    begin
      @limit_targets = Integer(limit)
    rescue
      @limit_targets = limit
    end
  else
    @limit_targets = Integer(limit)
  end
end
load_aggregate_functions(action, ddl) click to toggle source
# File lib/mcollective/rpc/client.rb, line 696
def load_aggregate_functions(action, ddl)
  return nil unless ddl
  return nil unless ddl.action_interface(action).keys.include?(:aggregate)

  return Aggregate.new(ddl.action_interface(action))

rescue => e
  Log.error("Failed to load aggregate functions, calculating summaries disabled: %s: %s (%s)" % [e.backtrace.first, e.to_s, e.class])
  return nil
end
method_missing(method_name, *args, &block) click to toggle source

Magic handler to invoke remote methods

Once the stub is created using the constructor or the MCollective::RPC#rpcclient helper you can call remote actions easily:

ret = rpc.echo(:msg => "hello world")

This will call the 'echo' action of the 'rpctest' agent and return the result as an array, the array will be a simplified result set from the usual full MCollective::Client#req with additional error codes and error text:

{

:sender => "remote.box.com",
:statuscode => 0,
:statusmsg => "OK",
:data => "hello world"

}

If :statuscode is 0 then everything went find, if it's 1 then you supplied the correct arguments etc but the request could not be completed, you'll find a human parsable reason in :statusmsg then.

Codes 2 to 5 maps directly to UnknownRPCAction, MissingRPCData, InvalidRPCData and UnknownRPCError see below for a description of those, in each case :statusmsg would be the reason for failure.

To get access to the full result of the MCollective::Client#req calls you can pass in a block:

rpc.echo(:msg => "hello world") do |resp|
   pp resp
end

In this case resp will the result from MCollective::Client#req. Instead of returning simple text and codes as above you'll also need to handle the following exceptions:

UnknownRPCAction - There is no matching action on the agent MissingRPCData - You did not supply all the needed parameters for the action InvalidRPCData - The data you did supply did not pass validation UnknownRPCError - Some other error prevented the agent from running

During calls a progress indicator will be shown of how many results we've received against how many nodes were discovered, you can disable this by setting progress to false:

rpc.progress = false

This supports a 2nd mode where it will send the SimpleRPC request and never handle the responses. It's a bit like UDP, it sends the request with the filter attached and you only get back the requestid, you have no indication about results.

You can invoke this using:

puts rpc.echo(:process_results => false)

This will output just the request id.

Batched processing is supported:

printrpc rpc.ping(:batch_size => 5)

This will do everything exactly as normal but communicate to only 5 agents at a time

# File lib/mcollective/rpc/client.rb, line 236
def method_missing(method_name, *args, &block)
  # set args to an empty hash if nothings given
  args = args[0]
  args = {} if args.nil?

  action = method_name.to_s

  @stats.reset

  validate_request(action, args)

  # TODO(ploubser): The logic here seems poor. It implies that it is valid to
  # pass arguments where batch_mode is set to false and batch_mode > 0.
  # If this is the case we completely ignore the supplied value of batch_mode
  # and do our own thing. 

  # if a global batch size is set just use that else set it
  # in the case that it was passed as an argument
  batch_mode = args.include?(:batch_size) || @batch_mode
  batch_size = args.delete(:batch_size) || @batch_size
  batch_sleep_time = args.delete(:batch_sleep_time) || @batch_sleep_time

  # if we were given a batch_size argument thats 0 and batch_mode was
  # determined to be on via global options etc this will allow a batch_size
  # of 0 to disable or batch_mode for this call only
  batch_mode = determine_batch_mode(batch_size)

  # Handle single target requests by doing discovery and picking
  # a random node.  Then do a custom request specifying a filter
  # that will only match the one node.
  if @limit_targets
    target_nodes = pick_nodes_from_discovered(@limit_targets)
    Log.debug("Picked #{target_nodes.join(',')} as limited target(s)")

    custom_request(action, args, target_nodes, {"identity" => /^(#{target_nodes.join('|')})$/}, &block)
  elsif batch_mode
    call_agent_batched(action, args, options, batch_size, batch_sleep_time, &block)
  else
    call_agent(action, args, options, :auto, &block)
  end
end
new_request(action, data) click to toggle source

Creates a suitable request hash for the SimpleRPC agent.

You'd use this if you ever wanted to take care of sending requests on your own - perhaps via Client#sendreq if you didn't care for responses.

In that case you can just do:

msg = your_rpc.new_request("some_action", :foo => :bar)
filter = your_rpc.filter

your_rpc.client.sendreq(msg, msg[:agent], filter)

This will send a SimpleRPC request to the action some_action with arguments :foo = :bar, it will return immediately and you will have no indication at all if the request was receieved or not

Clearly the use of this technique should be limited and done only if your code requires such a thing

# File lib/mcollective/rpc/client.rb, line 154
def new_request(action, data)
  callerid = PluginManager["security_plugin"].callerid

  raise 'callerid received from security plugin is not valid' unless PluginManager["security_plugin"].valid_callerid?(callerid)

  {:agent  => @agent,
   :action => action,
   :caller => callerid,
   :data   => data}
end
options() click to toggle source

Provides a normal options hash like you would get from Optionparser

# File lib/mcollective/rpc/client.rb, line 566
def options
  {:disctimeout => discovery_timeout,
   :timeout => @timeout,
   :verbose => @verbose,
   :filter => @filter,
   :collective => @collective,
   :output_format => @output_format,
   :ttl => @ttl,
   :discovery_method => @discovery_method,
   :discovery_options => @discovery_options,
   :force_display_mode => @force_display_mode,
   :config => @config,
   :publish_timeout => @publish_timeout,
   :threaded => @threaded}
end
pick_nodes_from_discovered(count) click to toggle source

Pick a number of nodes from the discovered nodes

The count should be a string that can be either just a number or a percentage like 10%

It will select nodes from the discovered list based on the rpclimitmethod configuration option which can be either :first or anything else

- :first would be a simple way to do a distance based
  selection
- anything else will just pick one at random
- if random chosen, and batch-seed set, then set srand
  for the generator, and reset afterwards
# File lib/mcollective/rpc/client.rb, line 655
def pick_nodes_from_discovered(count)
  if count =~ /%$/
    pct = Integer((discover.size * (count.to_f / 100)))
    pct == 0 ? count = 1 : count = pct
  else
    count = Integer(count)
  end

  return discover if discover.size <= count

  result = []

  if @limit_method == :first
    return discover[0, count]
  else
    # we delete from the discovered list because we want
    # to be sure there is no chance that the same node will
    # be randomly picked twice.  So we have to clone the
    # discovered list else this method will only ever work
    # once per discovery cycle and not actually return the
    # right nodes.
    haystack = discover.clone

    if @limit_seed
      haystack.sort!
      srand(@limit_seed)
    end

    count.times do
      rnd = rand(haystack.size)
      result << haystack.delete_at(rnd)
    end

    # Reset random number generator to fresh seed
    # As our seed from options is most likely short
    srand if @limit_seed
  end

  [result].flatten
end
process_results_with_block(action, resp, block, aggregate) click to toggle source

process client requests by calling a block on each result in this mode we do not do anything fancy with the result objects and we raise exceptions if there are problems with the data

# File lib/mcollective/rpc/client.rb, line 987
def process_results_with_block(action, resp, block, aggregate)
  @stats.node_responded(resp[:senderid])

  result = rpc_result_from_reply(@agent, action, resp)
  aggregate = aggregate_reply(result, aggregate) if aggregate

  @stats.ok if resp[:body][:statuscode] == 0
  @stats.fail if resp[:body][:statuscode] != 0
  @stats.time_block_execution :start

  case block.arity
    when 1
      block.call(resp)
    when 2
      block.call(resp, result)
  end

  @stats.time_block_execution :end

  return aggregate
end
process_results_without_block(resp, action, aggregate) click to toggle source

Handles result sets that has no block associated, sets fails and ok in the stats object and return a hash of the response to send to the caller

# File lib/mcollective/rpc/client.rb, line 967
def process_results_without_block(resp, action, aggregate)
  @stats.node_responded(resp[:senderid])

  result = rpc_result_from_reply(@agent, action, resp)
  aggregate = aggregate_reply(result, aggregate) if aggregate

  if resp[:body][:statuscode] == 0 || resp[:body][:statuscode] == 1
    @stats.ok if resp[:body][:statuscode] == 0
    @stats.fail if resp[:body][:statuscode] == 1
  else
    @stats.fail
  end

  [result, aggregate]
end
reset() click to toggle source

Resets various internal parts of the class, most importantly it clears out the cached discovery

# File lib/mcollective/rpc/client.rb, line 453
def reset
  @discovered_agents = nil
end
reset_filter() click to toggle source

Reet the filter to an empty one

# File lib/mcollective/rpc/client.rb, line 458
def reset_filter
  @filter = Util.empty_filter
  agent_filter @agent
end
rpc_result_from_reply(agent, action, reply) click to toggle source
# File lib/mcollective/rpc/client.rb, line 717
def rpc_result_from_reply(agent, action, reply)
  Result.new(agent, action, {:sender => reply[:senderid], :statuscode => reply[:body][:statuscode],
                             :statusmsg => reply[:body][:statusmsg], :data => reply[:body][:data]})
end
validate_request(action, args) click to toggle source

For the provided arguments and action the input arguments get modified by supplying any defaults provided in the DDL for arguments that were not supplied in the request

We then pass the modified arguments to the DDL for validation

# File lib/mcollective/rpc/client.rb, line 170
def validate_request(action, args)
  raise "No DDL found for agent %s cannot validate inputs" % @agent unless @ddl

  @ddl.set_default_input_arguments(action, args)
  @ddl.validate_rpc_request(action, args)
end
mcollective-2.6.0/doc/MCollective/RPC/Progress.html0000644000175000017500000004742412375446415021152 0ustar jonasjonas class MCollective::RPC::Progress - mcollective version 2.6.0

class MCollective::RPC::Progress

Class that shows a progress bar, currently only supports a twirling progress bar.

You can specify a size for the progress bar if you want if you dont it will use the helper functions to figure out terminal dimensions and draw an appropriately sized bar

p = ::new 100.times {|i| print p.twirl(i+1, 100) + “râ€};puts

* [ ==================================================> ] 100 / 100

Public Class Methods

new(size=nil) click to toggle source
# File lib/mcollective/rpc/progress.rb, line 15
def initialize(size=nil)
  @twirl = ['|', '/', '-', "\\", '|', '/', '-', "\\"]
  @twirldex = 0

  if size
    @size = size
  else
    cols = Util.terminal_dimensions[0] - 22

    # Defaults back to old behavior if it
    # couldn't figure out the size or if
    # its more than 60 wide
    if cols <= 0
      @size = 0
    elsif cols > 60
      @size = 60
    else
      @size = cols
    end
  end
end

Public Instance Methods

twirl(current, total) click to toggle source
# File lib/mcollective/rpc/progress.rb, line 37
def twirl(current, total)
  # if the size is negative there is just not enough
  # space on the terminal, return a simpler version
  return "\r#{current} / #{total}" if @size == 0

  if current == total
    txt = "\r %s [ " % Util.colorize(:green, "*")
  else
    txt = "\r %s [ " % Util.colorize(:red, @twirl[@twirldex])
  end

  dashes = ((current.to_f / total) * @size).round

  dashes.times { txt << "=" }
  txt << ">"

  (@size - dashes).times { txt << " " }

  txt << " ] #{current} / #{total}"

  @twirldex == 7 ? @twirldex = 0 : @twirldex += 1

  return txt
end
mcollective-2.6.0/doc/MCollective/RPC/Request.html0000644000175000017500000006464112375446415020776 0ustar jonasjonas class MCollective::RPC::Request - mcollective version 2.6.0

class MCollective::RPC::Request

Simple class to manage compliant requests for MCollective::RPC agents

Attributes

action[RW]
agent[RW]
caller[RW]
data[RW]
ddl[RW]
sender[RW]
time[RW]
uniqid[RW]

Public Class Methods

new(msg, ddl) click to toggle source
# File lib/mcollective/rpc/request.rb, line 7
def initialize(msg, ddl)
  @time = msg[:msgtime]
  @action = msg[:body][:action]
  @data = msg[:body][:data]
  @sender = msg[:senderid]
  @agent = msg[:body][:agent]
  @uniqid = msg[:requestid]
  @caller = msg[:callerid] || "unknown"
  @ddl = ddl
end

Public Instance Methods

[](key) click to toggle source

If data is a hash, gives easy access to its members, else returns nil

# File lib/mcollective/rpc/request.rb, line 34
def [](key)
  return nil unless @data.is_a?(Hash)
  return @data[key]
end
fetch(key, default) click to toggle source
# File lib/mcollective/rpc/request.rb, line 39
def fetch(key, default)
  return nil unless @data.is_a?(Hash)
  return @data.fetch(key, default)
end
include?(key) click to toggle source

If data is a hash, quick helper to get access to it's include? method else returns false

# File lib/mcollective/rpc/request.rb, line 20
def include?(key)
  return false unless @data.is_a?(Hash)
  return @data.include?(key)
end
should_respond?() click to toggle source

If no :process_results is specified always respond else respond based on the supplied property

# File lib/mcollective/rpc/request.rb, line 27
def should_respond?
  return @data[:process_results] if @data.include?(:process_results)

  return true
end
to_hash() click to toggle source
# File lib/mcollective/rpc/request.rb, line 44
def to_hash
  return {:agent => @agent,
          :action => @action,
          :data => @data}
end
to_json() click to toggle source
# File lib/mcollective/rpc/request.rb, line 55
def to_json
  to_hash.merge!({:sender   => @sender,
                  :callerid => @callerid,
                  :uniqid   => @uniqid}).to_json
end
validate!() click to toggle source

Validate the request against the DDL

# File lib/mcollective/rpc/request.rb, line 51
def validate!
  @ddl.validate_rpc_request(@action, @data)
end
mcollective-2.6.0/doc/MCollective/ValidatorError.html0000644000175000017500000002603112375446416021651 0ustar jonasjonas class MCollective::ValidatorError - mcollective version 2.6.0

class MCollective::ValidatorError

mcollective-2.6.0/doc/MCollective/MsgTTLExpired.html0000644000175000017500000002602712375446415021351 0ustar jonasjonas class MCollective::MsgTTLExpired - mcollective version 2.6.0

class MCollective::MsgTTLExpired

mcollective-2.6.0/doc/MCollective/Applications.html0000644000175000017500000007114012375446414021337 0ustar jonasjonas class MCollective::Applications - mcollective version 2.6.0

class MCollective::Applications

Public Class Methods

[](appname) click to toggle source
# File lib/mcollective/applications.rb, line 3
def self.[](appname)
  load_application(appname)
  PluginManager["#{appname}_application"]
end
filter_extra_options(opts) click to toggle source

Filters a string of opts out using Shellwords keeping only things related to –config and -c

# File lib/mcollective/applications.rb, line 49
def self.filter_extra_options(opts)
  res = ""
  words = Shellwords.shellwords(opts)
  words.each_with_index do |word,idx|
    if word == "-c"
      return "--config=#{words[idx + 1]}"
    elsif word == "--config"
      return "--config=#{words[idx + 1]}"
    elsif word =~ /\-c=/
      return word
    elsif word =~ /\-\-config=/
      return word
    end
  end

  return ""
end
list() click to toggle source

Returns an array of applications found in the lib dirs

# File lib/mcollective/applications.rb, line 36
def self.list
  load_config

  PluginManager.find("application")
rescue SystemExit
  exit 1
rescue Exception => e
  STDERR.puts "Failed to generate application list: #{e.class}: #{e}"
  exit 1
end
load_application(appname) click to toggle source
# File lib/mcollective/applications.rb, line 26
def self.load_application(appname)
  return if PluginManager.include?("#{appname}_application")

  load_config

  PluginManager.loadclass "MCollective::Application::#{appname.capitalize}"
  PluginManager << {:type => "#{appname}_application", :class => "MCollective::Application::#{appname.capitalize}"}
end
load_config() click to toggle source

We need to know the config file in order to know the libdir so that we can find applications.

The problem is the CLI might be stuffed with options only the app in the libdir might understand so we have a chicken and egg situation.

We're parsing and filtering MCOLLECTIVE_EXTRA_OPTS removing all but config related options and parsing the options looking just for the config file.

We're handling failures gracefully and finally restoring the ARG and MCOLLECTIVE_EXTRA_OPTS to the state they were before we started parsing.

This is mostly a hack, when we're redoing how config works this stuff should be made less sucky

# File lib/mcollective/applications.rb, line 84
def self.load_config
  return if Config.instance.configured

  original_argv = ARGV.clone
  original_extra_opts = ENV["MCOLLECTIVE_EXTRA_OPTS"].clone rescue nil
  configfile = nil

  parser = OptionParser.new
  parser.on("--config CONFIG", "-c", "Config file") do |f|
    configfile = f
  end

  parser.program_name = $0

  parser.on("--help")

  # avoid option parsers own internal version handling that sux
  parser.on("-v", "--verbose")

  if original_extra_opts
    begin
      # optparse will parse the whole ENV in one go and refuse
      # to play along with the retry trick I do below so in
      # order to handle unknown options properly I parse out
      # only -c and --config deleting everything else and
      # then restore the environment variable later when I
      # am done with it
      ENV["MCOLLECTIVE_EXTRA_OPTS"] = filter_extra_options(ENV["MCOLLECTIVE_EXTRA_OPTS"].clone)
      parser.environment("MCOLLECTIVE_EXTRA_OPTS")
    rescue Exception => e
      Log.error("Failed to parse MCOLLECTIVE_EXTRA_OPTS: #{e}")
    end

    ENV["MCOLLECTIVE_EXTRA_OPTS"] = original_extra_opts.clone
  end

  begin
    parser.parse!
  rescue OptionParser::InvalidOption => e
    retry
  end

  ARGV.clear
  original_argv.each {|a| ARGV << a}

  configfile = Util.config_file_for_user unless configfile

  Config.instance.loadconfig(configfile)
end
run(appname) click to toggle source
# File lib/mcollective/applications.rb, line 8
def self.run(appname)
  load_config

  begin
    load_application(appname)
  rescue Exception => e
    e.backtrace.first << Util.colorize(:red, "  <----")
    STDERR.puts "Application '#{appname}' failed to load:"
    STDERR.puts
    STDERR.puts Util.colorize(:red, "   #{e} (#{e.class})")
    STDERR.puts
    STDERR.puts "       %s" % [e.backtrace.join("\n       ")]
    exit 1
  end

  PluginManager["#{appname}_application"].run
end
mcollective-2.6.0/doc/MCollective/InvalidRPCData.html0000644000175000017500000002607612375446415021447 0ustar jonasjonas class MCollective::InvalidRPCData - mcollective version 2.6.0

class MCollective::InvalidRPCData

mcollective-2.6.0/doc/MCollective/Runner.html0000644000175000017500000007005312375446415020165 0ustar jonasjonas class MCollective::Runner - mcollective version 2.6.0

class MCollective::Runner

Attributes

state[R]

Public Class Methods

new(configfile) click to toggle source
# File lib/mcollective/runner.rb, line 5
def initialize(configfile)
  begin
    @config = Config.instance
    @config.loadconfig(configfile) unless @config.configured
    @config.mode = :server
    @stats = PluginManager["global_stats"]
    @connection = PluginManager["connector_plugin"]

    # @state describes the current contextual state of the MCollective runner.
    # Valid states are:
    #   :running   - MCollective is alive and receiving messages from the middleware
    #   :stopping  - MCollective is shutting down and in the process of terminating
    #   :stopped   - MCollective is not running
    #   :pausing   - MCollective is going into it's paused state
    #   :unpausing - MCollective is waking up from it's paused state
    #   :paused    - MCollective is paused and not receiving messages but can be woken up
    @state = :stopped
    @exit_receiver_thread = false
    @registration_thread = nil
    @agent_threads = []

    @security = PluginManager["security_plugin"]
    @security.initiated_by = :node

    unless Util.windows?
      Signal.trap("USR1") do
        Thread.new do
          Log.info("Reloading all agents after receiving USR1 signal")
          @agents.loadagents
        end
      end

      Signal.trap("USR2") do
        Thread.new do
          Log.info("Cycling logging level due to USR2 signal")
          Log.cycle_level
        end
      end

      Signal.trap("WINCH") do
        Thread.new do
          Log.info("Reopening logfiles due to WINCH signal")
          Log.reopen
          Log.info("Reopened logfiles due to WINCH signal")
        end
      end
    else
      Util.setup_windows_sleeper
    end
  rescue => e
    Log.error("Failed to initialize MCollective runner.")
    Log.error(e)
    Log.error(e.backtrace.join("\n\t"))
    raise e
  end
end

Public Instance Methods

main_loop() click to toggle source

The main runner loop

# File lib/mcollective/runner.rb, line 69
def main_loop
  # Enter the main context
  @receiver_thread = start_receiver_thread
  loop do
    begin
      case @state
      when :stopping
        Log.debug("Stopping MCollective server")

        # If soft_shutdown has been enabled we wait for all running agents to
        # finish, one way or the other.
        if @config.soft_shutdown
          soft_shutdown
        end

        stop_threads
        @state = :stopped
        return

      # When pausing we stop the receiver thread but keep everything else alive
      # This means that running agents also run to completion.
      when :pausing
        Log.debug("Pausing MCollective server")
        stop_threads
        @state = :paused

      when :unpausing
        Log.debug("Unpausing MCollective server")
        start_receiver_thread
      end

      # prune dead threads from the agent_threads array
      @agent_threads.reject! { |t| !t.alive? }
      sleep 0.1
    rescue SignalException => e
      Log.info("Exiting after signal: #{e}")
      stop
    rescue => e
      Log.error("A failure occurred in the MCollective runner.")
      Log.error(e)
      Log.error(e.backtrace.join("\n\t"))
      stop
    end
  end
end
pause() click to toggle source
# File lib/mcollective/runner.rb, line 119
def pause
  if @state == :running
    @state = :pausing
  else
    Log.error("Cannot pause MCollective while not in a running state")
  end
end
resume() click to toggle source
# File lib/mcollective/runner.rb, line 127
def resume
  if @state == :paused
    @state = :unpausing
  else
    Log.error("Cannot unpause MCollective when it is not paused")
  end
end
run() click to toggle source

Deprecated

# File lib/mcollective/runner.rb, line 63
def run
  Log.warn("The #run method has been deprecated. Use #main_loop instead.")
  main_loop
end
stop() click to toggle source
# File lib/mcollective/runner.rb, line 115
def stop
  @state = :stopping
end
mcollective-2.6.0/doc/MCollective/SSL.html0000644000175000017500000016455312375446415017366 0ustar jonasjonas class MCollective::SSL - mcollective version 2.6.0

class MCollective::SSL

A class that assists in encrypting and decrypting data using a combination of RSA and AES

Data will be AES encrypted for speed, the Key used in # the AES stage will be encrypted using RSA

ssl = SSL.new(public_key, private_key, passphrase)

data = File.read("largefile.dat")

crypted_data = ssl.encrypt_with_private(data)

pp crypted_data

This will result in a hash of data like:

crypted = {:key  => "crd4NHvG....=",
           :data => "XWXlqN+i...=="}

The key and data will all be base 64 encoded already by default you can pass a 2nd parameter as false to #encrypt_with_private and counterparts that will prevent the base 64 encoding

You can pass the data hash into ssl.decrypt_with_public which should return your original data

There are matching methods for using a public key to encrypt data to be decrypted using a private key

Attributes

private_key_file[R]
public_key_file[R]
ssl_cipher[R]

Public Class Methods

base64_decode(string) click to toggle source
# File lib/mcollective/ssl.rb, line 195
def self.base64_decode(string)
  # The Base 64 character set is A-Z a-z 0-9 + / =
  # Also allow for whitespace, but raise if we get anything else
  if string !~ /^[A-Za-z0-9+\/=\s]+$/
    raise ArgumentError, 'invalid base64'
  end
  Base64.decode64(string)
end
base64_encode(string) click to toggle source
# File lib/mcollective/ssl.rb, line 186
def self.base64_encode(string)
  Base64.encode64(string)
end
md5(string) click to toggle source
# File lib/mcollective/ssl.rb, line 208
def self.md5(string)
  Digest::MD5.hexdigest(string)
end
new(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil) click to toggle source
# File lib/mcollective/ssl.rb, line 37
def initialize(pubkey=nil, privkey=nil, passphrase=nil, cipher=nil)
  @public_key_file = pubkey
  @private_key_file = privkey

  @public_key  = read_key(:public, pubkey)
  @private_key = read_key(:private, privkey, passphrase)

  @ssl_cipher = "aes-256-cbc"
  @ssl_cipher = Config.instance.ssl_cipher if Config.instance.ssl_cipher
  @ssl_cipher = cipher if cipher

  raise "The supplied cipher '#{@ssl_cipher}' is not supported" unless OpenSSL::Cipher.ciphers.include?(@ssl_cipher)
end
uuid(string=nil) click to toggle source

Creates a RFC 4122 version 5 UUID. If string is supplied it will produce repeatable UUIDs for that string else a random 128bit string will be used from OpenSSL::BN

Code used with permission from:

https://github.com/kwilczynski/puppet-functions/blob/master/lib/puppet/parser/functions/uuid.rb
# File lib/mcollective/ssl.rb, line 218
def self.uuid(string=nil)
  string ||= OpenSSL::Random.random_bytes(16).unpack('H*').shift

  uuid_name_space_dns = "\x6b\xa7\xb8\x10\x9d\xad\x11\xd1\x80\xb4\x00\xc0\x4f\xd4\x30\xc8"

  sha1 = Digest::SHA1.new
  sha1.update(uuid_name_space_dns)
  sha1.update(string)

  # first 16 bytes..
  bytes = sha1.digest[0, 16].bytes.to_a

  # version 5 adjustments
  bytes[6] &= 0x0f
  bytes[6] |= 0x50

  # variant is DCE 1.1
  bytes[8] &= 0x3f
  bytes[8] |= 0x80

  bytes = [4, 2, 2, 2, 6].collect do |i|
    bytes.slice!(0, i).pack('C*').unpack('H*')
  end

  bytes.join('-')
end

Public Instance Methods

aes_decrypt(key, crypt_string) click to toggle source

decrypts a string given key, iv and data

# File lib/mcollective/ssl.rb, line 158
def aes_decrypt(key, crypt_string)
  cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)

  cipher.decrypt
  cipher.key = key
  cipher.pkcs5_keyivgen(key)
  decrypted_data = cipher.update(crypt_string) + cipher.final
end
aes_encrypt(plain_string) click to toggle source

encrypts a string, returns a hash of key, iv and data

# File lib/mcollective/ssl.rb, line 144
def aes_encrypt(plain_string)
  cipher = OpenSSL::Cipher::Cipher.new(ssl_cipher)
  cipher.encrypt

  key = cipher.random_key

  cipher.key = key
  cipher.pkcs5_keyivgen(key)
  encrypted_data = cipher.update(plain_string) + cipher.final

  {:key => key, :data => encrypted_data}
end
base64_decode(string) click to toggle source

base 64 decode a string

# File lib/mcollective/ssl.rb, line 191
def base64_decode(string)
  SSL.base64_decode(string)
end
base64_encode(string) click to toggle source

base 64 encode a string

# File lib/mcollective/ssl.rb, line 182
def base64_encode(string)
  SSL.base64_encode(string)
end
decrypt_with_private(crypted, base64=true) click to toggle source

Decrypts data, expects a hash as create with crypt_with_public

# File lib/mcollective/ssl.rb, line 88
def decrypt_with_private(crypted, base64=true)
  raise "Crypted data should include a key" unless crypted.include?(:key)
  raise "Crypted data should include data" unless crypted.include?(:data)

  if base64
    key = rsa_decrypt_with_private(base64_decode(crypted[:key]))
    aes_decrypt(key, base64_decode(crypted[:data]))
  else
    key = rsa_decrypt_with_private(crypted[:key])
    aes_decrypt(key, crypted[:data])
  end
end
decrypt_with_public(crypted, base64=true) click to toggle source

Decrypts data, expects a hash as create with crypt_with_private

# File lib/mcollective/ssl.rb, line 102
def decrypt_with_public(crypted, base64=true)
  raise "Crypted data should include a key" unless crypted.include?(:key)
  raise "Crypted data should include data" unless crypted.include?(:data)

  if base64
    key = rsa_decrypt_with_public(base64_decode(crypted[:key]))
    aes_decrypt(key, base64_decode(crypted[:data]))
  else
    key = rsa_decrypt_with_public(crypted[:key])
    aes_decrypt(key, crypted[:data])
  end
end
encrypt_with_private(plain_text, base64=true) click to toggle source

Encrypts supplied data using AES and then encrypts using RSA the key and IV

Return a hash with everything optionally base 64 encoded

# File lib/mcollective/ssl.rb, line 73
def encrypt_with_private(plain_text, base64=true)
  crypted = aes_encrypt(plain_text)

  if base64
    key = base64_encode(rsa_encrypt_with_private(crypted[:key]))
    data = base64_encode(crypted[:data])
  else
    key = rsa_encrypt_with_private(crypted[:key])
    data = crypted[:data]
  end

  {:key => key, :data => data}
end
encrypt_with_public(plain_text, base64=true) click to toggle source

Encrypts supplied data using AES and then encrypts using RSA the key and IV

Return a hash with everything optionally base 64 encoded

# File lib/mcollective/ssl.rb, line 55
def encrypt_with_public(plain_text, base64=true)
  crypted = aes_encrypt(plain_text)

  if base64
    key = base64_encode(rsa_encrypt_with_public(crypted[:key]))
    data = base64_encode(crypted[:data])
  else
    key = rsa_encrypt_with_public(crypted[:key])
    data = crypted[:data]
  end

  {:key => key, :data => data}
end
md5(string) click to toggle source
# File lib/mcollective/ssl.rb, line 204
def md5(string)
  SSL.md5(string)
end
read_key(type, key=nil, passphrase=nil) click to toggle source

Reads either a :public or :private key from disk, uses an optional passphrase to read the private key

# File lib/mcollective/ssl.rb, line 247
def read_key(type, key=nil, passphrase=nil)
  return key if key.nil?

  raise "Could not find key #{key}" unless File.exist?(key)
  raise "#{type} key file '#{key}' is empty" if File.zero?(key)

  if type == :public
    begin
      key = OpenSSL::PKey::RSA.new(File.read(key))
    rescue OpenSSL::PKey::RSAError
      key = OpenSSL::X509::Certificate.new(File.read(key)).public_key
    end

    # Ruby < 1.9.3 had a bug where it does not correctly clear the
    # queue of errors while reading a key.  It tries various ways
    # to read the key and each failing attempt pushes an error onto
    # the queue.  With pubkeys only the 3rd attempt pass leaving 2
    # stale errors on the error queue.
    #
    # In 1.9.3 they fixed this by simply discarding the errors after
    # every attempt.  So we simulate this fix here for older rubies
    # as without it we get SSL_read errors from the Stomp+TLS sessions
    #
    # We do this only on 1.8 relying on 1.9.3 to do the right thing
    # and we do not support 1.9 less than 1.9.3
    #
    # See  http://bugs.ruby-lang.org/issues/4550
    OpenSSL.errors if Util.ruby_version =~ /^1.8/

    return key
  elsif type == :private
    return OpenSSL::PKey::RSA.new(File.read(key), passphrase)
  else
    raise "Can only load :public or :private keys"
  end
end
rsa_decrypt_with_private(crypt_string) click to toggle source

Use the private key to RSA decrypt data

# File lib/mcollective/ssl.rb, line 123
def rsa_decrypt_with_private(crypt_string)
  raise "No private key set" unless @private_key

  @private_key.private_decrypt(crypt_string)
end
rsa_decrypt_with_public(crypt_string) click to toggle source

Use the public key to RSA decrypt data

# File lib/mcollective/ssl.rb, line 137
def rsa_decrypt_with_public(crypt_string)
  raise "No public key set" unless @public_key

  @public_key.public_decrypt(crypt_string)
end
rsa_encrypt_with_private(plain_string) click to toggle source

Use the private key to RSA encrypt data

# File lib/mcollective/ssl.rb, line 130
def rsa_encrypt_with_private(plain_string)
  raise "No private key set" unless @private_key

  @private_key.private_encrypt(plain_string)
end
rsa_encrypt_with_public(plain_string) click to toggle source

Use the public key to RSA encrypt data

# File lib/mcollective/ssl.rb, line 116
def rsa_encrypt_with_public(plain_string)
  raise "No public key set" unless @public_key

  @public_key.public_encrypt(plain_string)
end
sign(string, base64=false) click to toggle source

Signs a string using the private key

# File lib/mcollective/ssl.rb, line 168
def sign(string, base64=false)
  sig = @private_key.sign(OpenSSL::Digest::SHA1.new, string)

  base64 ? base64_encode(sig) : sig
end
verify_signature(signature, string, base64=false) click to toggle source

Using the public key verifies that a string was signed using the private key

# File lib/mcollective/ssl.rb, line 175
def verify_signature(signature, string, base64=false)
  signature = base64_decode(signature) if base64

  @public_key.verify(OpenSSL::Digest::SHA1.new, signature, string)
end
mcollective-2.6.0/doc/MCollective/Application.html0000644000175000017500000021525412375446414021162 0ustar jonasjonas class MCollective::Application - mcollective version 2.6.0

class MCollective::Application

Public Class Methods

[](option) click to toggle source

retrieves a specific option

# File lib/mcollective/application.rb, line 20
def [](option)
  intialize_application_options unless @application_options
  @application_options[option]
end
[]=(option, value) click to toggle source

set an option in the options hash

# File lib/mcollective/application.rb, line 14
def []=(option, value)
  intialize_application_options unless @application_options
  @application_options[option] = value
end
application_options() click to toggle source

Intialize a blank set of options if its the first time used else returns active options

# File lib/mcollective/application.rb, line 8
def application_options
  intialize_application_options unless @application_options
  @application_options
end
description(descr) click to toggle source

Sets the application description, there can be only one description per application so multiple calls will just change the description

# File lib/mcollective/application.rb, line 28
def description(descr)
  self[:description] = descr
end
exclude_argument_sections(*sections) click to toggle source
# File lib/mcollective/application.rb, line 38
def exclude_argument_sections(*sections)
  sections = [sections].flatten

  sections.each do |s|
    raise "Unknown CLI argument section #{s}" unless ["rpc", "common", "filter"].include?(s)
  end

  intialize_application_options unless @application_options
  self[:exclude_arg_sections] = sections
end
intialize_application_options() click to toggle source

Creates an empty set of options

# File lib/mcollective/application.rb, line 79
def intialize_application_options
  @application_options = {:description          => nil,
                          :usage                => [],
                          :cli_arguments        => [],
                          :exclude_arg_sections => []}
end
option(name, arguments) click to toggle source

Wrapper to create command line options

- name: varaible name that will be used to access the option value
- description: textual info shown in --help
- arguments: a list of possible arguments that can be used
  to activate this option
- type: a data type that ObjectParser understand of :bool or :array
- required: true or false if this option has to be supplied
- validate: a proc that will be called with the value used to validate
  the supplied value

 option :foo,
        :description => "The foo option"
        :arguments   => ["--foo ARG"]

after this the value supplied will be in configuration

# File lib/mcollective/application.rb, line 65
def option(name, arguments)
  opt = {:name => name,
         :description => nil,
         :arguments => [],
         :type => String,
         :required => false,
         :validate => Proc.new { true }}

  arguments.each_pair{|k,v| opt[k] = v}

  self[:cli_arguments] << opt
end
usage(usage) click to toggle source

Supplies usage information, calling multiple times will create multiple usage lines in –help output

# File lib/mcollective/application.rb, line 34
def usage(usage)
  self[:usage] << usage
end

Public Instance Methods

application_cli_arguments() click to toggle source

Returns an array of all the arguments built using calls to optin

# File lib/mcollective/application.rb, line 247
def application_cli_arguments
  application_options[:cli_arguments]
end
application_description() click to toggle source

Retrieve the current application description

# File lib/mcollective/application.rb, line 234
def application_description
  application_options[:description]
end
application_failure(e, err_dest=STDERR) click to toggle source

Handles failure, if we're far enough in the initialization phase it will log backtraces if its in verbose mode only

# File lib/mcollective/application.rb, line 253
def application_failure(e, err_dest=STDERR)
  # peole can use exit() anywhere and not get nasty backtraces as a result
  if e.is_a?(SystemExit)
    disconnect
    raise(e)
  end

  if options[:verbose]
    err_dest.puts "\nThe %s application failed to run: %s\n" % [ Util.colorize(:bold, $0), Util.colorize(:red, e.to_s)]
  else
    err_dest.puts "\nThe %s application failed to run, use -v for full error backtrace details: %s\n" % [ Util.colorize(:bold, $0), Util.colorize(:red, e.to_s)]
  end

  if options.nil? || options[:verbose]
    e.backtrace.first << Util.colorize(:red, "  <----")
    err_dest.puts "\n%s %s" % [ Util.colorize(:red, e.to_s), Util.colorize(:bold, "(#{e.class.to_s})")]
    e.backtrace.each{|l| err_dest.puts "\tfrom #{l}"}
  end

  disconnect

  exit 1
end
application_options() click to toggle source

Retrieves the full hash of application options

# File lib/mcollective/application.rb, line 229
def application_options
  self.class.application_options
end
application_parse_options(help=false) click to toggle source

Builds an ObjectParser config, parse the CLI options and validates based on the option config

# File lib/mcollective/application.rb, line 135
def application_parse_options(help=false)
  @options ||= {:verbose => false}

  @options = clioptions(help) do |parser, options|
    parser.define_head application_description if application_description
    parser.banner = ""

    if application_usage
      parser.separator ""

      application_usage.each do |u|
        parser.separator "Usage: #{u}"
      end

      parser.separator ""
    end

    parser.separator "Application Options" unless application_cli_arguments.empty?

    parser.define_tail ""
    parser.define_tail "The Marionette Collective #{MCollective.version}"


    application_cli_arguments.each do |carg|
      opts_array = []

      opts_array << :on

      # if a default is set from the application set it up front
      if carg.include?(:default)
        configuration[carg[:name]] = carg[:default]
      end

      # :arguments are multiple possible ones
      if carg[:arguments].is_a?(Array)
        carg[:arguments].each {|a| opts_array << a}
      else
        opts_array << carg[:arguments]
      end

      # type was given and its not one of our special types, just pass it onto optparse
      opts_array << carg[:type] if carg[:type] && ![:boolean, :bool, :array].include?(carg[:type])

      opts_array << carg[:description]

      # Handle our special types else just rely on the optparser to handle the types
      if [:bool, :boolean].include?(carg[:type])
        parser.send(*opts_array) do |v|
          validate_option(carg[:validate], carg[:name], v)

          configuration[carg[:name]] = v
        end

      elsif carg[:type] == :array
        parser.send(*opts_array) do |v|
          validate_option(carg[:validate], carg[:name], v)

          configuration[carg[:name]] = [] unless configuration.include?(carg[:name])
          configuration[carg[:name]] << v
        end

      else
        parser.send(*opts_array) do |v|
          validate_option(carg[:validate], carg[:name], v)

          configuration[carg[:name]] = v
        end
      end
    end
  end
end
application_usage() click to toggle source

Return the current usage text false if nothing is set

# File lib/mcollective/application.rb, line 239
def application_usage
  usage = application_options[:usage]

  usage.empty? ? false : usage
end
clioptions(help) { |parser, options| ... } click to toggle source

Creates a standard options hash, pass in a block to add extra headings etc see Optionparser

# File lib/mcollective/application.rb, line 111
def clioptions(help)
  oparser = Optionparser.new({:verbose => false, :progress_bar => true}, "filter", application_options[:exclude_arg_sections])

  options = oparser.parse do |parser, options|
    if block_given?
      yield(parser, options)
    end

    RPC::Helpers.add_simplerpc_options(parser, options) unless application_options[:exclude_arg_sections].include?("rpc")
  end

  return oparser.parser.help if help

  validate_cli_options

  post_option_parser(configuration) if respond_to?(:post_option_parser)

  return options
rescue Exception => e
  application_failure(e)
end
configuration() click to toggle source

The application configuration built from CLI arguments

# File lib/mcollective/application.rb, line 88
def configuration
  @application_configuration ||= {}
  @application_configuration
end
disconnect() click to toggle source
# File lib/mcollective/application.rb, line 299
def disconnect
  MCollective::PluginManager["connector_plugin"].disconnect
rescue
end
halt(stats) click to toggle source

A helper that creates a consistent exit code for applications by looking at an instance of MCollective::RPC::Stats

Exit with 0 if nodes were discovered and all passed Exit with 0 if no discovery were done and > 0 responses were received, all ok Exit with 1 if no nodes were discovered Exit with 2 if nodes were discovered but some RPC requests failed Exit with 3 if nodes were discovered, but no responses received Exit with 4 if no discovery were done and no responses were received

# File lib/mcollective/application.rb, line 349
def halt(stats)
  exit(halt_code(stats))
end
halt_code(stats) click to toggle source
# File lib/mcollective/application.rb, line 311
def halt_code(stats)
  request_stats = {:discoverytime => 0,
                   :discovered => 0,
                   :okcount => 0,
                   :failcount => 0}.merge(stats.to_hash)

  if (request_stats[:discoverytime] == 0 && request_stats[:responses] == 0)
    return 4
  end

  if (request_stats[:discovered] > 0)
    if (request_stats[:responses] == 0)
      return 3
    elsif (request_stats[:failcount] > 0)
      return 2
    end
  end

  if (request_stats[:discovered] == 0)
    if (request_stats[:responses] && request_stats[:responses] > 0)
      return 0
    else
      return 1
    end
  end

  return 0
end
help() click to toggle source
# File lib/mcollective/application.rb, line 277
def help
  application_parse_options(true)
end
main() click to toggle source

Fake abstract class that logs if the user tries to use an application without supplying a main override method.

# File lib/mcollective/application.rb, line 306
def main
  STDERR.puts "Applications need to supply a 'main' method"
  exit 1
end
options() click to toggle source

The active options hash used for MC::Client and other configuration

# File lib/mcollective/application.rb, line 94
def options
  @options
end
rpcclient(agent, flags = {}) click to toggle source

Wrapper around MC::RPC#rpcclient that forcably supplies our options hash if someone forgets to pass in options in an application the filters and other cli options wouldnt take effect which could have a disasterous outcome

Calls superclass method MCollective::RPC#rpcclient
# File lib/mcollective/application.rb, line 356
def rpcclient(agent, flags = {})
  flags[:options] = options unless flags.include?(:options)
  flags[:exit_on_failure] = false

  super
end
run() click to toggle source

The main logic loop, builds up the options, validate configuration and calls the main as supplied by the user. Disconnects when done and pass any exception onto the #application_failure helper

# File lib/mcollective/application.rb, line 284
def run
  application_parse_options

  validate_configuration(configuration) if respond_to?(:validate_configuration)

  Util.setup_windows_sleeper if Util.windows?

  main

  disconnect

rescue Exception => e
  application_failure(e)
end
validate_cli_options() click to toggle source
# File lib/mcollective/application.rb, line 207
def validate_cli_options
  # Check all required parameters were set
  validation_passed = true
  application_cli_arguments.each do |carg|
    # Check for required arguments
    if carg[:required]
      unless configuration[ carg[:name] ]
        validation_passed = false
        STDERR.puts "The #{carg[:name]} option is mandatory"
      end
    end
  end

  unless validation_passed
    STDERR.puts "\nPlease run with --help for detailed help"
    exit 1
  end


end
validate_option(blk, name, value) click to toggle source

Calls the supplied block in an option for validation, an error raised will log to STDERR and exit the application

# File lib/mcollective/application.rb, line 100
def validate_option(blk, name, value)
  validation_result = blk.call(value)

  unless validation_result == true
    STDERR.puts "Validation of #{name} failed: #{validation_result}"
    exit 1
  end
end
mcollective-2.6.0/doc/etc/0000755000175000017500000000000012375446416014367 5ustar jonasjonasmcollective-2.6.0/doc/etc/ssl/0000755000175000017500000000000012375446416015170 5ustar jonasjonasmcollective-2.6.0/doc/etc/ssl/PLACEHOLDER.html0000644000175000017500000002523512375446416017607 0ustar jonasjonas PLACEHOLDER - mcollective version 2.6.0
mcollective-2.6.0/doc/etc/ssl/clients/0000755000175000017500000000000012375446416016631 5ustar jonasjonasmcollective-2.6.0/doc/etc/ssl/clients/PLACEHOLDER.html0000644000175000017500000002576012375446416021253 0ustar jonasjonas PLACEHOLDER - mcollective version 2.6.0
mcollective-2.6.0/doc/etc/server_cfg_dist.html0000644000175000017500000002552312375446416020434 0ustar jonasjonas server.cfg.dist - mcollective version 2.6.0

main_collective = mcollective collectives = mcollective libdir = /usr/libexec/mcollective logfile = /var/log/mcollective.log loglevel = info daemonize = 1

# Plugins securityprovider = psk plugin.psk = unset

connector = activemq plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = stomp1 plugin.activemq.pool.1.port = 6163 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = marionette

# Facts factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml

mcollective-2.6.0/doc/etc/client_cfg_dist.html0000644000175000017500000002547012375446416020405 0ustar jonasjonas client.cfg.dist - mcollective version 2.6.0

main_collective = mcollective collectives = mcollective libdir = /usr/libexec/mcollective logger_type = console loglevel = warn

# Plugins securityprovider = psk plugin.psk = unset

connector = activemq plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = stomp1 plugin.activemq.pool.1.port = 6163 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = marionette

# Facts factsource = yaml plugin.yaml = /etc/mcollective/facts.yaml

mcollective-2.6.0/doc/etc/facts_yaml_dist.html0000644000175000017500000002457612375446416020440 0ustar jonasjonas facts.yaml.dist - mcollective version 2.6.0

mcollective: 1

mcollective-2.6.0/doc/COPYING.html0000644000175000017500000005343312375446416015622 0ustar jonasjonas COPYING - mcollective version 2.6.0
         Apache License
   Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

  1. Definitions.

    “License†shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.

    “Licensor†shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.

    “Legal Entity†shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control†means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

    “You†(or “Yourâ€) shall mean an individual or Legal Entity exercising permissions granted by this License.

    “Source†form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.

    “Object†form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.

    “Work†shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).

    “Derivative Works†shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.

    “Contribution†shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted†means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.â€

    “Contributor†shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.

  2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.

  3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

  4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:

    (a) You must give any other recipients of the Work or

    Derivative Works a copy of this License; and

    (b) You must cause any modified files to carry prominent notices

    stating that You changed the files; and

    © You must retain, in the Source form of any Derivative Works

    that You distribute, all copyright, patent, trademark, and
    attribution notices from the Source form of the Work,
    excluding those notices that do not pertain to any part of
    the Derivative Works; and

    (d) If the Work includes a “NOTICE†text file as part of its

    distribution, then any Derivative Works that You distribute must
    include a readable copy of the attribution notices contained
    within such NOTICE file, excluding those notices that do not
    pertain to any part of the Derivative Works, in at least one
    of the following places: within a NOTICE text file distributed
    as part of the Derivative Works; within the Source form or
    documentation, if provided along with the Derivative Works; or,
    within a display generated by the Derivative Works, if and
    wherever such third-party notices normally appear. The contents
    of the NOTICE file are for informational purposes only and
    do not modify the License. You may add Your own attribution
    notices within Derivative Works that You distribute, alongside
    or as an addendum to the NOTICE text from the Work, provided
    that such additional attribution notices cannot be construed
    as modifying the License.

    You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.

  5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.

  6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.

  7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS†BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.

  8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.

  9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS

APPENDIX: How to apply the Apache License to your work.

To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!)  The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.

Copyright 2010, 2011 Puppet Labs

Licensed under the Apache License, Version 2.0 (the “Licenseâ€); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS†BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

mcollective-2.6.0/doc/mcollective_init.html0000644000175000017500000003206512375446416020041 0ustar jonasjonas mcollective.init - mcollective version 2.6.0

#!/bin/sh # # mcollective Application Server for STOMP based agents # # chkconfig: 345 24 76 # # description: mcollective lets you build powerful Stomp compatible middleware clients in ruby without having to worry too # much about all the setup and management of a Stomp connection, it also provides stats, logging and so forth # as a bonus. # ### BEGIN INIT INFO # Provides: mcollective # Required-Start: $remote_fs # Required-Stop: $remote_fs # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO

mcollectived=“/usr/sbin/mcollectivedâ€

# Lockfile if [ -d /var/lock/subsys ]; then

# RedHat/CentOS/etc who use subsys
lock="/var/lock/subsys/mcollective"

else

# The rest of them
lock="/var/lock/mcollective"

fi

# PID directory pidfile=“/var/run/mcollectived.pidâ€

# Source function library. . /lib/lsb/init-functions

# Check that binary exists if ! [ -f $mcollectived ] then

echo "mcollectived binary not found"
exit 0

fi

# See how we were called. case “$1†in

start)
    echo -n "Starting mcollective: "

    if [ -f ${lock} ]; then
        # we were not shut down correctly
        if [ -s ${pidfile} ]; then
            kill `cat ${pidfile}` >/dev/null 2>&1
        fi
        rm -f ${pidfile}

        rm -f ${lock}
        sleep 2
    fi

    rm -f ${pidfile}

    ${mcollectived} --pid=${pidfile} --config="/etc/mcollective/server.cfg" --daemonize
    if [ $? = 0 ]; then
        log_success_msg
        touch $lock
        exit 0
    else
        log_failure_msg
        exit 1
    fi
    ;;
stop)
    echo -n "Shutting down mcollective: "

    if [ -s ${pidfile} ]; then
        kill `cat ${pidfile}` >/dev/null 2>&1
    fi
    rm -f ${pidfile}

    log_success_msg
    rm -f $lock
    ;;
restart)
    $0 stop
    sleep 2
    $0 start
    ;;
condrestart)
    if [ -f $lock ]; then
        $0 stop
        # avoid race
        sleep 2
        $0 start
    fi
    ;;
status)
    if [ -f ${lock} ]; then
        if [ -s ${pidfile} ]; then
            if [ -e /proc/`cat ${pidfile}` ]; then
                echo "mcollectived (`cat ${pidfile}`) is running"
                exit 0
            else
                echo "mcollectived (`cat ${pidfile}`) is NOT running"
                exit 1
            fi
        fi
    else
        echo "mcollectived: service not started"
        exit 1
    fi
    ;;
force-reload)
    echo "not implemented"
    ;;
*)
    echo "Usage: mcollectived {start|stop|restart|condrestart|status}"
    exit 1
    ;;

esac exit 0

mcollective-2.6.0/doc/doc/0000755000175000017500000000000012375446416014361 5ustar jonasjonasmcollective-2.6.0/doc/doc/created_rid.html0000644000175000017500000002451212375446416017520 0ustar jonasjonas created.rid - mcollective version 2.6.0
mcollective-2.6.0/doc/String.html0000644000175000017500000003411312375446416015752 0ustar jonasjonas class String - mcollective version 2.6.0

class String

Public Instance Methods

bytes(&block) click to toggle source
# File lib/mcollective/monkey_patches.rb, line 63
def bytes(&block)
  # This should not be necessary, really ...
  require 'enumerator'
  return to_enum(:each_byte) unless block_given?
  each_byte(&block)
end
start_with?(str) click to toggle source
# File lib/mcollective/monkey_patches.rb, line 4
def start_with?(str)
  return self[0 .. (str.length-1)] == str
end
mcollective-2.6.0/doc/index.html0000644000175000017500000002436612375446414015622 0ustar jonasjonas mcollective version 2.6.0

This is the API documentation for mcollective version 2.6.0.

mcollective-2.6.0/doc/Dir.html0000644000175000017500000004433512375446414015227 0ustar jonasjonas class Dir - mcollective version 2.6.0

class Dir

Public Class Methods

mktmpdir(prefix_suffix=nil, tmpdir=nil) { |path| ... } click to toggle source
# File lib/mcollective/monkey_patches.rb, line 72
def self.mktmpdir(prefix_suffix=nil, tmpdir=nil)
  case prefix_suffix
  when nil
    prefix = "d"
    suffix = ""
  when String
    prefix = prefix_suffix
    suffix = ""
  when Array
    prefix = prefix_suffix[0]
    suffix = prefix_suffix[1]
  else
    raise ArgumentError, "unexpected prefix_suffix: #{prefix_suffix.inspect}"
  end
  tmpdir ||= Dir.tmpdir
  t = Time.now.strftime("%Y%m%d")
  n = nil
  begin
    path = "#{tmpdir}/#{prefix}#{t}-#{$$}-#{rand(0x100000000).to_s(36)}"
    path << "-#{n}" if n
    path << suffix
    Dir.mkdir(path, 0700)
  rescue Errno::EEXIST
    n ||= 0
    n += 1
    retry
  end

  if block_given?
    begin
      yield path
    ensure
      FileUtils.remove_entry_secure path
    end
  else
    path
  end
end
tmpdir() click to toggle source
# File lib/mcollective/monkey_patches.rb, line 111
def self.tmpdir
  tmp = '.'
  for dir in [ENV['TMPDIR'], ENV['TMP'], ENV['TEMP'], '/tmp']
    if dir and stat = File.stat(dir) and stat.directory? and stat.writable?
      tmp = dir
      break
    end rescue nil
  end
  File.expand_path(tmp)
end
mcollective-2.6.0/doc/Object.html0000644000175000017500000014337212375446416015722 0ustar jonasjonas class Object - mcollective version 2.6.0

class Object

Constants

InstallOptions
PREREQS
RbConfig
Version

Public Instance Methods

build_rdoc(files) click to toggle source

Build the rdoc documentation.

# File install.rb, line 248
def build_rdoc(files)
  return unless $haverdoc
  begin
    r = RDoc::RDoc.new
    r.document(["--main", "MCollective", "--line-numbers"] + files)
  rescue RDoc::RDocError => e
    $stderr.puts e.message
  rescue Exception => e
    $stderr.puts "Couldn't build RDoc documentation\n#{e.message}"
  end
end
check_prereqs() click to toggle source
# File install.rb, line 65
def check_prereqs
  PREREQS.each do |pre|
    begin
      require pre
    rescue LoadError
      puts "Could not load #{pre} Ruby library; cannot install"
      exit(-1)
    end
  end
end
do_bins(bins, target, strip = 's?bin/') click to toggle source
# File install.rb, line 86
def do_bins(bins, target, strip = 's?bin/')
  Dir.mkdir(target) unless File.directory? target
  bins.each do |bf|
    obf = bf.gsub(/#{strip}/, '')
    install_binfile(bf, obf, target)
  end
end
do_configs(configs, target, strip = 'etc/') click to toggle source
# File install.rb, line 76
def do_configs(configs, target, strip = 'etc/')
  Dir.mkdir(target) unless File.directory? target
  configs.each do |cf|
    ocf = File.join(target, cf.gsub(Regexp.new(strip), ''))
    oc = File.dirname(ocf)
    makedirs(oc, {:mode => 0755, :verbose => true})
    install(cf, ocf, {:mode => 0644, :preserve => true, :verbose => true})
  end
end
do_libs(libs, target, strip = 'lib/') click to toggle source
# File install.rb, line 94
def do_libs(libs, target, strip = 'lib/')
  libs.each do |lf|
    olf = File.join(target, lf.sub(/^#{strip}/, ''))
    op = File.dirname(olf)
    if File.directory?(lf)
      makedirs(olf, {:mode => 0755, :verbose => true})
    else
      makedirs(op, {:mode => 0755, :verbose => true})
      install(lf, olf, {:mode => 0644, :preserve => true, :verbose => true})
    end
  end
end
glob(list) click to toggle source
# File install.rb, line 57
def glob(list)
  g = list.map { |i| Dir.glob(i) }
  g.flatten!
  g.compact!
  g.uniq!
  g
end
install_binfile(from, op_file, target) click to toggle source

Install file(s) from ./bin to RbConfig::CONFIG. Patch it on the way to insert a #! line; on a Unix install, the command is named as expected

# File install.rb, line 263
def install_binfile(from, op_file, target)
  tmp_file = Tempfile.new('mcollective-binfile')

  if InstallOptions.ruby
    ruby = InstallOptions.ruby
  else
    ruby = File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
  end

  File.open(from) do |ip|
    File.open(tmp_file.path, "w") do |op|
      op.puts "#!#{ruby}"
      contents = ip.readlines
      contents.shift if contents[0] =~ /^#!/
      op.write contents.join
    end
  end

  install(tmp_file.path, File.join(target, op_file), :mode => 0755, :preserve => true, :verbose => true)
  tmp_file.unlink
end
prepare_installation() click to toggle source

Prepare the file installation.

# File install.rb, line 110
def prepare_installation
  InstallOptions.configs = true

  # Only try to do docs if we're sure they have rdoc
  if $haverdoc
    InstallOptions.rdoc = true
  else
    InstallOptions.rdoc = false
  end


  ARGV.options do |opts|
    opts.banner = "Usage: #{File.basename($0)} [options]"
    opts.separator ""
    opts.on('--[no-]rdoc', 'Creation of RDoc output.', 'Default is create rdoc.') do |onrdoc|
      InstallOptions.rdoc = onrdoc
    end
    opts.on('--[no-]configs', 'Installation of config files', 'Default is install configs.') do |onconfigs|
      InstallOptions.configs = onconfigs
    end
    opts.on('--destdir[=OPTIONAL]', 'Installation prefix for all targets', 'Default essentially /') do |destdir|
      InstallOptions.destdir = destdir
    end
    opts.on('--configdir[=OPTIONAL]', 'Installation directory for config files', 'Default /etc/mcollective') do |configdir|
      InstallOptions.configdir = configdir
    end
    opts.on('--bindir[=OPTIONAL]', 'Installation directory for binaries', 'overrides RbConfig::CONFIG["bindir"]') do |bindir|
      InstallOptions.bindir = bindir
    end
    opts.on('--sbindir[=OPTIONAL]', 'Installation directory for system binaries', 'overrides RbConfig::CONFIG["sbindir"]') do |sbindir|
      InstallOptions.sbindir = sbindir
    end
    opts.on('--ruby[=OPTIONAL]', 'Ruby interpreter to use with installation', 'overrides ruby used to call install.rb') do |ruby|
      InstallOptions.ruby = ruby
    end
    opts.on('--sitelibdir[=OPTIONAL]', 'Installation directory for libraries', 'overrides RbConfig::CONFIG["sitelibdir"]') do |sitelibdir|
      InstallOptions.sitelibdir = sitelibdir
    end
    opts.on('--plugindir[=OPTIONAL]', 'Installation directory for plugins', 'Default /usr/libexec/mcollective') do |plugindir|
      InstallOptions.plugindir = plugindir
    end
    opts.on('--quick', 'Performs a quick installation. Only the', 'installation is done.') do |quick|
      InstallOptions.rdoc    = false
      InstallOptions.ri      = false
      InstallOptions.configs = true
    end
    opts.on('--full', 'Performs a full installation. All', 'optional installation steps are run.') do |full|
      InstallOptions.rdoc    = true
      InstallOptions.ri      = true
      InstallOptions.configs = true
    end
    opts.separator("")
    opts.on_tail('--help', "Shows this help text.") do
      $stderr.puts opts
      exit
    end

    opts.parse!
  end

  version = [RbConfig::CONFIG["MAJOR"], RbConfig::CONFIG["MINOR"]].join(".")
  libdir = File.join(RbConfig::CONFIG["libdir"], "ruby", version)

  # Mac OS X 10.5 and higher declare bindir
  # /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin
  # which is not generally where people expect executables to be installed
  # These settings are appropriate defaults for all OS X versions.
  if RUBY_PLATFORM =~ /^universal-darwin[\d\.]+$/
    RbConfig::CONFIG['bindir'] = "/usr/bin"
    RbConfig::CONFIG['sbindir'] = "/usr/sbin"
  end

  if InstallOptions.configdir
    configdir = InstallOptions.configdir
  else
    configdir = "/etc/mcollective"
  end

  if InstallOptions.bindir
    bindir = InstallOptions.bindir
  else
    bindir = RbConfig::CONFIG['bindir']
  end

  if InstallOptions.sbindir
    sbindir = InstallOptions.sbindir
  else
    sbindir = RbConfig::CONFIG['sbindir']
  end

  if InstallOptions.sitelibdir
    sitelibdir = InstallOptions.sitelibdir
  else
    sitelibdir = RbConfig::CONFIG["sitelibdir"]
    if sitelibdir.nil?
      sitelibdir = $LOAD_PATH.find { |x| x =~ /site_ruby/ }
      if sitelibdir.nil?
        sitelibdir = File.join(libdir, "site_ruby")
      elsif sitelibdir !~ Regexp.quote(version)
        sitelibdir = File.join(sitelibdir, version)
      end
    end
  end

  if InstallOptions.plugindir
    plugindir = InstallOptions.plugindir
  else
    plugindir = "/usr/libexec/mcollective"
  end

  if InstallOptions.destdir
    destdir = InstallOptions.destdir
  else
    destdir = ''
  end

  configdir   = File.join(destdir, configdir)
  bindir      = File.join(destdir, bindir)
  sbindir     = File.join(destdir, sbindir)
  sitelibdir  = File.join(destdir, sitelibdir)
  plugindir   = File.join(destdir, plugindir)

  makedirs(configdir) if InstallOptions.configs
  makedirs(bindir)
  makedirs(sbindir)
  makedirs(sitelibdir)
  makedirs(plugindir)

  InstallOptions.sitelibdir = sitelibdir
  InstallOptions.configdir = configdir
  InstallOptions.bindir  = bindir
  InstallOptions.sbindir  = sbindir
  InstallOptions.plugindir  = plugindir
end
mcollective-2.6.0/doc/Array.html0000644000175000017500000003743512375446414015572 0ustar jonasjonas class Array - mcollective version 2.6.0

class Array

a method # that walks an array in groups, pass a block to call the block on each sub array

Public Instance Methods

in_groups_of(chunk_size, padded_with=nil) { |a| ... } click to toggle source
# File lib/mcollective/monkey_patches.rb, line 29
def in_groups_of(chunk_size, padded_with=nil, &block)
  arr = self.clone

  # how many to add
  padding = chunk_size - (arr.size % chunk_size)

  # pad at the end
  arr.concat([padded_with] * padding) unless padding == chunk_size

  # how many chunks we'll make
  count = arr.size / chunk_size

  # make that many arrays
  result = []
  count.times {|s| result <<  arr[s * chunk_size, chunk_size]}

  if block_given?
    result.each_with_index do |a, i|
      case block.arity
        when 1
          yield(a)
        when 2
          yield(a, (i == result.size - 1))
        else
          raise "Expected 1 or 2 arguments, got #{block.arity}"
      end
    end
  else
    result
  end
end
mcollective-2.6.0/doc/table_of_contents.html0000644000175000017500000023127712375446416020206 0ustar jonasjonas Table of Contents - mcollective version 2.6.0

Table of Contents - mcollective version 2.6.0

Pages

Classes/Modules

Methods