mcollective-2.12.1/0000755005276200011600000000000013265671736014013 5ustar jenkinsjenkinsmcollective-2.12.1/lib/0000755005276200011600000000000013265671736014561 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/0000755005276200011600000000000013265671736017067 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/rpc.rb0000644005276200011600000001304613265671724020201 0ustar jenkinsjenkinsrequire '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 require "mcollective/rpc/actionrunner" require "mcollective/rpc/agent" require "mcollective/rpc/audit" require "mcollective/rpc/client" require "mcollective/rpc/helpers" require "mcollective/rpc/progress" require "mcollective/rpc/reply" require "mcollective/rpc/request" require "mcollective/rpc/result" require "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] || Util.config_file_for_user 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.12.1/lib/mcollective/monkey_patches.rb0000644005276200011600000000745513265671724022435 0ustar jenkinsjenkins# 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 # Reject all SSLv2 ciphers and all SSLv2 or SSLv3 handshakes by default require 'openssl' class OpenSSL::SSL::SSLContext if DEFAULT_PARAMS[:options] DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 else DEFAULT_PARAMS[:options] = OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 end # ruby 1.8.5 doesn't define this constant, but has it on by default if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS) DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS end if DEFAULT_PARAMS[:ciphers] DEFAULT_PARAMS[:ciphers] << ':!SSLv2' end alias __mcollective_original_initialize initialize private :__mcollective_original_initialize def initialize(*args) __mcollective_original_initialize(*args) params = { :options => DEFAULT_PARAMS[:options], :ciphers => DEFAULT_PARAMS[:ciphers], } set_params(params) end end mcollective-2.12.1/lib/mcollective/connector.rb0000644005276200011600000000127013265671724021403 0ustar jenkinsjenkinsmodule 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 require "mcollective/connector/base" end end mcollective-2.12.1/lib/mcollective/pluginmanager.rb0000644005276200011600000001337113265671724022247 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/client.rb0000644005276200011600000002660313265671724020676 0ustar jenkinsjenkinsmodule MCollective # Helpers for writing clients that can talk to agents, do discovery and so forth class Client attr_accessor :options, :stats, :discoverer, :connection_timeout def initialize(options) @config = Config.instance @options = nil if options.is_a?(String) # String is the path to a config file @config.loadconfig(options) unless @config.configured elsif options.is_a?(Hash) @config.loadconfig(options[:config]) unless @config.configured @options = options @connection_timeout = options[:connection_timeout] else raise "Invalid parameter passed to Client constructor. Valid types are Hash or String" end @connection_timeout ||= @config.connection_timeout @connection = PluginManager["connector_plugin"] @security = PluginManager["security_plugin"] @security.initiated_by = :client @subscriptions = {} @discoverer = Discovery.new(self) # Time box the connection if a timeout has been specified # connection_timeout defaults to nil which means it will try forever if # not specified begin Timeout::timeout(@connection_timeout, ClientTimeoutError) do @connection.connect end rescue ClientTimeoutError => e Log.error("Timeout occured while trying to connect to middleware") raise e end 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) publish(request) 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 Log.debug("Received reply to #{reply.requestid} from #{reply.payload[:senderid]}") 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) @discoverer.discover(filter.merge({'collective' => collective}), 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=[], &block) if body.is_a?(Message) agent = body.agent waitfor = body.discovered_hosts || [] @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] || @config.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 publish(request) end rescue Timeout::Error => e Log.warn("Could not publish all messages. Publishing timed out.") end end def publish(request) Log.info("Sending request #{request.requestid} for agent '#{request.agent}' with ttl #{request.ttl} in collective '#{request.collective}'") request.publish 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 if (waitfor.is_a?(Array)) unfinished = Hash.new(0) waitfor.each {|w| unfinished[w] += 1} else unfinished = [] end begin Timeout.timeout(timeout) do loop do resp = receive(requestid) if block.arity == 2 yield resp.payload, resp else yield resp.payload end hosts_responded += 1 if (waitfor.is_a?(Array)) sender = resp.payload[:senderid] if unfinished[sender] <= 1 unfinished.delete(sender) else unfinished[sender] -= 1 end break if !waitfor.empty? && unfinished.empty? else break unless waitfor == 0 || hosts_responded < waitfor end end end rescue Timeout::Error => e if waitfor.is_a?(Array) if !unfinished.empty? Log.warn("Could not receive all responses. Did not receive responses from #{unfinished.keys.join(', ')}") end elsif (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[:unexpectedresponsefrom] = [] 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 if stats[:unexpectedresponsefrom].size > 0 puts("\nUnexpected response from:\n") stats[:unexpectedresponsefrom].each do |c| puts if c % 4 == 1 printf("%30s", c) end puts end end end end mcollective-2.12.1/lib/mcollective/rpc/0000755005276200011600000000000013265671736017653 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/rpc/helpers.rb0000644005276200011600000002566313265671724021653 0ustar jenkinsjenkinsmodule MCollective module RPC # Various utilities for the RPC system class Helpers # Parse JSON output as produced by printrpc or puppet query # and extract the "sender" / "certname" of each entry # # The simplist valid JSON based data would be: # # [ # {"sender" => "example.com"}, # {"sender" => "another.com"} # ] # # or # # [ # {"certname" => "example.com"}, # {"certname" => "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) unless host.include?("sender") || host.include?("certname") raise "JSON host list does not have senders in it" end host["sender"] || host["certname"] 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.12.1/lib/mcollective/rpc/client.rb0000644005276200011600000011667513265671724021473 0ustar jenkinsjenkinsmodule 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(@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] || Config.instance.default_batch_size @batch_sleep_time = Float(initial_options[:batch_sleep_time] || Config.instance.default_batch_sleep_time) @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 if initial_options[:stdin] @stdin = initial_options[:stdin] else @stdin = STDIN 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 # Detects data on STDIN and sets the STDIN discovery method # # 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 # # Then we override discovery to try to grok the data on STDIN def detect_and_set_stdin_discovery if self.default_discovery_method && !@stdin.tty? && !@stdin.eof? self.discovery_method = 'stdin' self.discovery_options = 'auto' end 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 filter = @filter.merge({'collective' => @collective}) if @limit_method == :first and @limit_targets.is_a?(Integer) @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) senderid = reply.include?("senderid") ? reply["senderid"] : reply[:senderid] body = reply.include?("body") ? reply["body"] : reply[:body] s_code = body.include?("statuscode") ? body["statuscode"] : body[:statuscode] s_msg = body.include?("statusmsg") ? body["statusmsg"] : body[:statusmsg] data = body.include?("data") ? body["data"] : body[:data] Result.new(agent, action, {:sender => senderid, :statuscode => s_code, :statusmsg => s_msg, :data => 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.unexpectedresponsefrom.concat @client.stats[:unexpectedresponsefrom] @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 result[:statuscode] == 0 || result[:statuscode] == 1 @stats.ok if result[:statuscode] == 0 @stats.fail if result[: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 result[:statuscode] == 0 @stats.fail if result[: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.12.1/lib/mcollective/rpc/progress.rb0000644005276200011600000000326313265671724022045 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/rpc/reply.rb0000644005276200011600000000424613265671724021336 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/rpc/audit.rb0000644005276200011600000000244513265671724021310 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/rpc/result.rb0000644005276200011600000000436313265671724021521 0ustar jenkinsjenkinsmodule 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 convert_data_based_on_ddl if ddl end def ddl @_ddl ||= DDL.new(agent) rescue nil end def data @results[:data] = @results.delete("data") if @results.include?("data") self[:data] end # Converts keys on the supplied data to those listed as outputs # in the DDL. This is to facilitate JSON based transports # without forcing everyone to rewrite DDLs and clients to # convert symbols to strings, the data will be on symbol keys # if the DDL has a symbol and not a string output defined def convert_data_based_on_ddl interface = ddl.action_interface(action) return if interface.fetch(:output, {}).empty? interface[:output].each do |output, properties| next if data.include?(output) if output.is_a?(Symbol) && data.include?(output.to_s) data[output] = data.delete(output.to_s) end end end def compatible_key(key) if key.is_a?(Symbol) && @results.include?(key.to_s) key.to_s else key end end def [](key) @results[compatible_key(key)] end def []=(key, item) @results[key] = item end def fetch(key, default) @results.fetch(compatible_key(key), default) end def each @results.each_pair {|k,v| yield(k,v) } end def to_json(*a) {:agent => @agent, :action => @action, :sender => self[:sender], :statuscode => self[:statuscode], :statusmsg => self[:statusmsg], :data => data}.to_json(*a) end def <=>(other) self[:sender] <=> other[:sender] end end end end mcollective-2.12.1/lib/mcollective/rpc/agent.rb0000644005276200011600000003237313265671724021303 0ustar jenkinsjenkinsmodule 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 https://docs.puppetlabs.com/mcollective/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.12.1/lib/mcollective/rpc/request.rb0000644005276200011600000000537613265671724021700 0ustar jenkinsjenkinsmodule 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] || msg[:body]["action"] @data = msg[:body][:data] || msg[:body]["data"] @sender = msg[:senderid] @agent = msg[:body][:agent] || msg[:body]["agent"] @uniqid = msg[:requestid] @caller = msg[:callerid] || "unknown" @ddl = ddl end # In a scenario where a request came from a JSON pure medium like a REST # service or other language client DDL::AgentDDL#validate_rpc_request will # check "package" against the intput :package should the input "package" not # also be known # # Thus once the request is built it will also have "package" and not :package # data, so we need to fetch the correct key out of the hash. def compatible_key(key) return key if data.include?(key) if ddl input = ddl.action_interface(action)[:input] # if :package is requested and the DDL also declares "package" we cant tell it to fetch # "package", hence the check against the input here return key.to_s if key.is_a?(Symbol) && !input.include?(key.to_s) && data.include?(key.to_s) end key 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) @data.include?(compatible_key(key)) end # If no :process_results is specified always respond else respond # based on the supplied property def should_respond? return false unless @data.is_a?(Hash) return @data[:process_results] if @data.include?(:process_results) return @data["process_results"] if @data.include?("process_results") 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[compatible_key(key)] end def fetch(key, default) return nil unless @data.is_a?(Hash) return @data.fetch(compatible_key(key), default) end def to_hash {: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.12.1/lib/mcollective/rpc/stats.rb0000644005276200011600000002250713265671724021341 0ustar jenkinsjenkinsmodule MCollective module RPC # Class to wrap all the stats and to keep track of some timings class Stats attr_accessor :noresponsefrom, :unexpectedresponsefrom, :starttime, :discoverytime, :blocktime, :responses attr_accessor :totaltime, :discovered, :discovered_nodes, :okcount, :failcount, :noresponsefrom attr_accessor :responsesfrom, :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 = [] @unexpectedresponsefrom = [] @responsesfrom = [] @responses = 0 @starttime = Time.now.to_f @discoverytime = 0 unless @discoverytime @blocktime = 0 @totaltime = 0 @discovered = 0 @discovered_nodes = [] @okcount = 0 @failcount = 0 @requestid = nil @aggregate_summary = [] @aggregate_failures = [] end # returns a hash of our stats def to_hash {:noresponsefrom => @noresponsefrom, :unexpectedresponsefrom => @unexpectedresponsefrom, :starttime => @starttime, :discoverytime => @discoverytime, :blocktime => @blocktime, :responses => @responses, :totaltime => @totaltime, :discovered => @discovered, :discovered_nodes => @discovered_nodes, :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] @unexpectedresponsefrom = stats[:unexpectedresponsefrom] @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 # figure out who responded unexpectedly @unexpectedresponsefrom = @responsesfrom - @discovered_nodes # figure out who we had no responses from @noresponsefrom = @discovered_nodes - @responsesfrom rescue @totaltime = 0 @noresponsefrom = [] @unexpectedresponsefrom = [] 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 no_response_r = no_response_report unexpected_response_r = unexpected_response_report if no_response_r || unexpected_response_r result_text << "" end if no_response_r != "" result_text << "" << no_response_r end if unexpected_response_r != "" result_text << "" << unexpected_response_r end if no_response_r || unexpected_response_r result_text << "" 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 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 end result_text.string end # Returns a blob of text indicating what nodes responded but weren't discovered def unexpected_response_report result_text = StringIO.new if @unexpectedresponsefrom.size > 0 result_text.puts Util.colorize(:red, "Unexpected response from:") result_text.puts field_size = Util.field_size(@unexpectedresponsefrom, 30) fields_num = Util.field_number(field_size) format = " " + ( " %-#{field_size}s" * fields_num ) @unexpectedresponsefrom.sort.in_groups_of(fields_num) do |c| result_text.puts format % c end end result_text.string end end end end mcollective-2.12.1/lib/mcollective/rpc/actionrunner.rb0000644005276200011600000001165113265671724022710 0ustar jenkinsjenkinsmodule 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) if Util.absolute_path?(command) return command end Config.instance.libdir.each do |libdir| command_file_old = File.join(libdir, "agent", agent, command) command_file_new = File.join(libdir, "mcollective", "agent", agent, command) command_file_old_exists = File.exists?(command_file_old) command_file_new_exists = File.exists?(command_file_new) if command_file_new_exists && command_file_old_exists Log.debug("Found 'implemented_by' scripts found in two locations #{command_file_old} and #{command_file_new}") Log.debug("Running script: #{command_file_new}") return command_file_new elsif command_file_old_exists Log.debug("Running script: #{command_file_old}") return command_file_old elsif command_file_new_exists Log.debug("Running script: #{command_file_new}") return command_file_new end end Log.warn("No script found for: #{command}") command end end end end mcollective-2.12.1/lib/mcollective/facts/0000755005276200011600000000000013265671736020167 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/facts/yaml_facts.rb0000644005276200011600000000327513265671724022642 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/facts/base.rb0000644005276200011600000000536113265671724021430 0ustar jenkinsjenkinsmodule 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 @mutex = Mutex.new @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 @mutex.synchronize 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.12.1/lib/mcollective/security.rb0000644005276200011600000000173513265671724021266 0ustar jenkinsjenkinsmodule 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 require "mcollective/security/base" end end mcollective-2.12.1/lib/mcollective/log.rb0000644005276200011600000000542213265671724020175 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/matcher.rb0000644005276200011600000001753113265671724021043 0ustar jenkinsjenkinsmodule 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 require "mcollective/matcher/parser" require "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) r_compare = function_hash["r_compare"] operator = function_hash["operator"] # Break out early and return false if the function returns nil if l_compare.nil? return false end # 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 # Do a regex comparison if right compare string is a regex if 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 result = l_compare.match(r_compare) # Flip return value for != operator if function_hash["operator"] == "!=~" return !result else return !!result end end # Otherwise do a normal comparison while taking the type into account else if l_compare.is_a? String r_compare = r_compare.to_s elsif r_compare.is_a? String if l_compare.is_a? Numeric r_compare = r_compare.strip begin r_compare = Integer(r_compare) rescue ArgumentError begin r_compare = Float(r_compare) rescue ArgumentError raise ArgumentError, "invalid numeric value: #{r_compare}" end end elsif l_compare.is_a? TrueClass or l_compare.is_a? FalseClass r_compare = r_compare.strip if r_compare == true.to_s r_compare = true elsif r_compare == false.to_s r_compare = false else raise ArgumentError, "invalid boolean value: #{r_compare}" end end end operator = operator.strip if operator =~ /(?:(!=|<=|>=|<|>)|==?)/ operator = $1 ? $1.to_sym : :== else raise ArgumentError, "invalid operator: #{operator}" end result = l_compare.send(operator, r_compare) return 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.12.1/lib/mcollective/unix_daemon.rb0000644005276200011600000000404713265671724021724 0ustar jenkinsjenkinsmodule 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 # Clean up stale pidfile if needed if File.exist?(pid) lock_pid = File.read(pid) begin lock_pid = Integer(lock_pid) rescue ArgumentError, TypeError lock_pid = nil end # If there's no pid in the pidfile, remove it if lock_pid.nil? File.unlink(pid) else begin # This will raise an error if the process doesn't # exist, and do nothing otherwise Process.kill(0, lock_pid) # If we reach this point then the process is running. # We should raise an error rather than continuing on # trying to create the PID raise "Process is already running with PID #{lock_pid}" rescue Errno::ESRCH # Errno::ESRCH = no such process # PID in pidfile doesn't exist, remove pidfile File.unlink(pid) end end end # Use exclusive create on the PID to avoid race condition # when two mcollectived processes start at the same time opt = File::CREAT | File::EXCL | File::WRONLY File.open(pid, opt) {|f| f.print(Process.pid) } 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.12.1/lib/mcollective/application/0000755005276200011600000000000013265671736021372 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/application/rpc.rb0000644005276200011600000000707713265671724022513 0ustar jenkinsjenkinsclass 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} mc.detect_and_set_stdin_discovery 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.12.1/lib/mcollective/application/plugin.rb0000644005276200011600000003312713265671724023220 0ustar jenkinsjenkinsmodule 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 plugin_directory_exists?(plugin_type) File.directory?(File.join(PluginPackager.get_plugin_path(configuration[:target]), plugin_type)) end # Identify plugin type if not provided. def set_plugin_type if plugin_directory_exists?("agent") || plugin_directory_exists?("application") configuration[:plugintype] = "AgentDefinition" return "Agent" elsif plugin_directory_exists?(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. # Return the name of the type of plugin as a string 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.12.1/lib/mcollective/application/ping.rb0000644005276200011600000000451213265671724022653 0ustar jenkinsjenkins# 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 # If the user did not override the default timeout include the discovery timeout if options[:timeout] == 5 discovery_timeout = options[:disctimeout] || Config.instance.discovery_timeout || 0 options[:timeout] = options[:timeout] + discovery_timeout end client = MCollective::Client.new(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.12.1/lib/mcollective/application/completion.rb0000644005276200011600000000616513265671724024075 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/application/describe_filter.rb0000644005276200011600000000467613265671724025056 0ustar jenkinsjenkinsmodule MCollective class Application::Describe_filter -F -C " def describe_s_filter(stack) indent = " " old_indent = " " puts "-S Query expands to the following instructions:" puts stack.each do |token| if token.keys[0] == "statement" if token.values[0] =~ /(<=|>=|=|=~|=)/ op = $1 k,v = token.values[0].split(op) puts indent + get_fact_string(k, v, op) else puts indent + get_class_string(token.values[0]) end elsif token.keys[0] == "fstatement" v = token.values[0] result_string = indent + "Execute the Data Query '#{v["name"]}'" if v["params"] result_string += " with parameters (#{v["params"]})" end result_string += ". " result_string += "Check if the query's '#{v["value"]}' value #{v["operator"]} '#{v["r_compare"]}' " puts result_string elsif token.keys[0] == "(" puts indent + "(" old_indent = indent indent *= 2 elsif token.keys[0] == ")" indent = old_indent puts indent + ")" else puts indent + token.keys[0].upcase end end end def describe_f_filter(facts) puts "-F filter expands to the following fact comparisons:" puts facts.each do |f| puts " " + get_fact_string(f[:fact], f[:value], f[:operator]) end end def describe_c_filter(classes) puts "-C filter expands to the following class checks:" puts classes.each do |c| puts " " + get_class_string(c) end end def main if !(@options[:filter]["fact"].empty?) describe_f_filter(@options[:filter]["fact"]) puts end if !(@options[:filter]["cf_class"].empty?) describe_c_filter(@options[:filter]["cf_class"]) puts end if !(@options[:filter]["compound"].empty?) describe_s_filter(@options[:filter]["compound"][0]) puts end end private def get_fact_string(fact, value, op) "Check if fact '#{fact}' #{op} '#{value}'" end def get_class_string(classname) "Check if class '#{classname}' is present on the host" end end end mcollective-2.12.1/lib/mcollective/application/facts.rb0000644005276200011600000000277213265671724023024 0ustar jenkinsjenkinsclass 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 if facts.include?(value) facts[value] << resp[:senderid] else facts[value] = [ resp[:senderid] ] end end rescue Exception => e STDERR.puts "Could not parse facts for #{resp[:senderid]}: #{e.class}: #{e}" end end if facts.empty? puts "No values found for fact #{configuration[:fact]}\n" else show_single_fact_report(configuration[:fact], facts, options[:verbose]) end printrpcstats halt rpcutil.stats end end mcollective-2.12.1/lib/mcollective/application/help.rb0000644005276200011600000000127413265671724022650 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/application/inventory.rb0000644005276200011600000002274513265671724023763 0ustar jenkinsjenkinsclass 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.12.1/lib/mcollective/application/find.rb0000644005276200011600000000114113265671724022631 0ustar jenkinsjenkinsclass MCollective::Application::Find 0 ? exit(0) : exit(1) end end mcollective-2.12.1/lib/mcollective/validator/0000755005276200011600000000000013265671736021054 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/validator/regex_validator.ddl0000644005276200011600000000057413265671724024723 0ustar jenkinsjenkinsmetadata :name => "Regex", :description => "Validates that a string matches a supplied regular expression", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 mcollective-2.12.1/lib/mcollective/validator/typecheck_validator.ddl0000644005276200011600000000055613265671724025570 0ustar jenkinsjenkinsmetadata :name => "Typecheck", :description => "Validates that a value is of a certain type", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 mcollective-2.12.1/lib/mcollective/validator/shellsafe_validator.ddl0000644005276200011600000000054713265671724025557 0ustar jenkinsjenkinsmetadata :name => "Shellsafe", :description => "Validates that a string is shellsafe", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 mcollective-2.12.1/lib/mcollective/validator/ipv6address_validator.ddl0000644005276200011600000000055713265671724026044 0ustar jenkinsjenkinsmetadata :name => "IPv6 Address", :description => "Validates that a value is an ipv6 address", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 mcollective-2.12.1/lib/mcollective/validator/length_validator.ddl0000644005276200011600000000061313265671724025064 0ustar jenkinsjenkinsmetadata :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 => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 mcollective-2.12.1/lib/mcollective/validator/shellsafe_validator.rb0000644005276200011600000000061613265671724025414 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/validator/ipv4address_validator.ddl0000644005276200011600000000055713265671724026042 0ustar jenkinsjenkinsmetadata :name => "IPv4 Address", :description => "Validates that a value is an ipv4 address", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 mcollective-2.12.1/lib/mcollective/validator/ipv4address_validator.rb0000644005276200011600000000057713265671724025704 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/validator/array_validator.ddl0000644005276200011600000000055313265671724024724 0ustar jenkinsjenkinsmetadata :name => "Array", :description => "Validates that a value is included in a list", :author => "P. Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 mcollective-2.12.1/lib/mcollective/validator/length_validator.rb0000644005276200011600000000043513265671724024726 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/validator/regex_validator.rb0000644005276200011600000000033713265671724024560 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/validator/ipv6address_validator.rb0000644005276200011600000000057713265671724025706 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/validator/array_validator.rb0000644005276200011600000000036713265671724024567 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/validator/typecheck_validator.rb0000644005276200011600000000143313265671724025423 0ustar jenkinsjenkinsmodule 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?(Integer) 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.12.1/lib/mcollective/windows_daemon.rb0000644005276200011600000000227513265671724022434 0ustar jenkinsjenkinsrequire '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.12.1/lib/mcollective/aggregate/0000755005276200011600000000000013265671736021015 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/aggregate/sum.ddl0000644005276200011600000000126513265671724022307 0ustar jenkinsjenkinsmetadata :name => "Sum", :description => "Determine the total added value of a set of values", :author => "Pieter Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/plugin_directory/", :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.12.1/lib/mcollective/aggregate/average.ddl0000644005276200011600000000130313265671724023106 0ustar jenkinsjenkinsmetadata :name => "average", :description => "Displays the average of a set of numeric values", :author => "Pieter Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/plugin_directory/", :timeout => 5 usage <<-USAGE This aggregate plugin will determine the 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.12.1/lib/mcollective/aggregate/summary.ddl0000644005276200011600000000141213265671724023172 0ustar jenkinsjenkinsmetadata :name => "summary", :description => "Displays the summary of a set of results", :author => "Pieter Loubser ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/plugin_directory/", :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.12.1/lib/mcollective/aggregate/result/0000755005276200011600000000000013265671736022333 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/aggregate/result/collection_result.rb0000644005276200011600000000065013265671724026407 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/aggregate/result/numeric_result.rb0000644005276200011600000000036113265671724025715 0ustar jenkinsjenkinsmodule MCollective class Aggregate module Result class NumericResult 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.12.1/lib/mcollective/runner.rb0000644005276200011600000002121113265671724020717 0ustar jenkinsjenkinsmodule 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 @parent = Thread.current Thread.new do begin receiver_thread rescue Exception => e # When we are pausing the receiver thread will be killed. # If the thread raises an exception at any other time # reraise it in the main thread. if @state != :pausing @parent.raise(e) end end end 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.info(e) 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.info("Handling message #{request.description}: #{request.payload[:body]}") @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.12.1/lib/mcollective/optionparser.rb0000644005276200011600000001515413265671724022144 0ustar jenkinsjenkinsmodule 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 configuration 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 @parser.on("--connection-timeout TIMEOUT", Integer, "Set the timeout for establishing a connection to the middleware") do |v| @options[:connection_timeout] = Integer(v) 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.12.1/lib/mcollective/ssl.rb0000644005276200011600000002145213265671724020216 0ustar jenkinsjenkinsrequire '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.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.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 = [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8].map {|b| b.chr}.join 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.12.1/lib/mcollective/runnerstats.rb0000644005276200011600000000400413265671724021777 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/shell.rb0000644005276200011600000001030413265671724020516 0ustar jenkinsjenkinsmodule 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?(Integer) && 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?(Integer) # 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.12.1/lib/mcollective/validator.rb0000644005276200011600000000435013265671724021400 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/vendor/0000755005276200011600000000000013265671736020364 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/vendor/require_vendored.rb0000644005276200011600000000002213265671724024242 0ustar jenkinsjenkinsrequire 'systemu' mcollective-2.12.1/lib/mcollective/matcher/0000755005276200011600000000000013265671736020512 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/matcher/parser.rb0000644005276200011600000001104313265671724022327 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/matcher/scanner.rb0000644005276200011600000001711713265671724022474 0ustar jenkinsjenkinsmodule 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 escaped = false 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] =~ /\//) 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] != '/') if @arguments[j] == '\\' # eat the escape char current_token_value << @arguments[j] j += 1 escaped = true end current_token_value << @arguments[j] j += 1 end current_token_value << @arguments[j] if @arguments[j] break end if @arguments[j] == "(" func = true current_token_value << @arguments[j] j += 1 while j < @arguments.size current_token_value << @arguments[j] if @arguments[j] == ')' j += 1 break end j += 1 end elsif @arguments[j] == '"' || @arguments[j] == "'" escaped = true escaped_with = @arguments[j] j += 1 # step over first " or ' @white_spaces += 1 # identified "..." or '...' while j < @arguments.size if @arguments[j] == '\\' # eat the escape char but don't add it to the token, or we # end up with \\\" j += 1 @white_spaces += 1 unless j < @arguments.size break end elsif @arguments[j] == escaped_with j += 1 @white_spaces += 1 break end current_token_value << @arguments[j] j += 1 end else current_token_value << @arguments[j] 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 if escaped return "statement", current_token_value end 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.12.1/lib/mcollective/audit/0000755005276200011600000000000013265671736020175 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/audit/logfile.rb0000644005276200011600000000164413265671724022145 0ustar jenkinsjenkinsmodule 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 raise "Could not parse value for configuration option '#{key}' with value '#{val}'" end end end end read_plugin_config_dir("#{@configdir}/plugin.d") raise 'Identities can only match /\w\.\-/' unless @identity.match(/^[\w\.\-]+$/) @configured = true libdirs.each do |dir| unless File.directory?(dir) Log.debug("Cannot find libdir: #{dir}") end # remove the old one if it exists, we're moving it to the front $LOAD_PATH.reject! { |elem| elem == dir } $LOAD_PATH.unshift dir end 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 @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 @connection_timeout = nil @default_batch_size = 0 @default_batch_sleep_time = 1 end def libdir $LOAD_PATH 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.12.1/lib/mcollective/message.rb0000644005276200011600000002330113265671724021034 0ustar jenkinsjenkinsmodule 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 description cid = "" cid += payload[:callerid] + "@" if payload.include?(:callerid) cid += payload[:senderid] "#{requestid} for agent '#{agent}' in collective '#{collective}' from #{cid}" 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) begin @payload = PluginManager["security_plugin"].decodemsg(self) rescue Exception => e if type == :request # If we're a server receiving a request, reraise raise(e) else # We're in the client, log and carry on as best we can # Note: mc_sender is unverified. The verified identity is in the # payload we just failed to decode Log.warn("Failed to decode a message from '#{headers["mc_sender"]}': #{e}") return end end 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 PluginManager["global_stats"].ttlexpired raise(MsgTTLExpired, "Message #{description} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}. Rejecting message.") end raise(NotTargettedAtUs, "Message #{description} does not pass filters. Ignoring message.") 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.12.1/lib/mcollective/application.rb0000644005276200011600000002612613265671724021723 0ustar jenkinsjenkinsrequire 'mcollective/rpc' module 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.12.1/lib/mcollective/pluginpackager.rb0000644005276200011600000000522613265671724022412 0ustar jenkinsjenkinsmodule MCollective module PluginPackager # Plugin definition classes require "mcollective/pluginpackager/agent_definition" require "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 # Return the path to a plugin's core directories def self.get_plugin_path(target) if (File.exists?(File.join(target, "lib", "mcollective"))) return File.join(target, "lib", "mcollective") end return target end end end mcollective-2.12.1/lib/mcollective/facts.rb0000644005276200011600000000210613265671724020510 0ustar jenkinsjenkinsmodule 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 require "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.12.1/lib/mcollective/aggregate.rb0000644005276200011600000000563113265671724021344 0ustar jenkinsjenkinsmodule MCollective class Aggregate require 'mcollective/aggregate/result' require '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.12.1/lib/mcollective/cache.rb0000644005276200011600000001026613265671724020461 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/exceptions.rb0000644005276200011600000000147413265671724021600 0ustar jenkinsjenkinsmodule MCollective # Exceptions for the RPC system class DDLValidationError "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.12.1/lib/mcollective/discovery/0000755005276200011600000000000013265671736021076 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/discovery/mc.rb0000644005276200011600000000147113265671724022022 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/discovery/mc.ddl0000644005276200011600000000064713265671724022166 0ustar jenkinsjenkinsmetadata :name => "mc", :description => "MCollective Broadcast based discovery", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "0.1", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 2 discovery do capabilities [:classes, :facts, :identity, :agents, :compound] end mcollective-2.12.1/lib/mcollective/discovery/flatfile.ddl0000644005276200011600000000061413265671724023347 0ustar jenkinsjenkinsmetadata :name => "flatfile", :description => "Flatfile based discovery for node identities", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "0.1", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 0 discovery do capabilities :identity end mcollective-2.12.1/lib/mcollective/discovery/stdin.ddl0000644005276200011600000000062213265671724022701 0ustar jenkinsjenkinsmetadata :name => "stdin", :description => "STDIN based discovery for node identities", :author => "Tomas Doran ", :license => "ASL 2.0", :version => "0.1", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 0 discovery do capabilities :identity end mcollective-2.12.1/lib/mcollective/discovery/stdin.rb0000644005276200011600000000367513265671724022554 0ustar jenkinsjenkins# 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 Log.debug("Parsing STDIN input as type %s" % type) if type == 'json' hosts = 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.12.1/lib/mcollective/discovery/flatfile.rb0000644005276200011600000000277213265671724023216 0ustar jenkinsjenkins# 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.12.1/lib/mcollective/applications.rb0000644005276200011600000000774213265671724022111 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/generators.rb0000644005276200011600000000031513265671724021561 0ustar jenkinsjenkinsmodule MCollective module Generators require "mcollective/generators/base.rb" require "mcollective/generators/data_generator.rb" require "mcollective/generators/agent_generator.rb" end end mcollective-2.12.1/lib/mcollective/data/0000755005276200011600000000000013265671736020000 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/data/fact_data.ddl0000644005276200011600000000155713265671724022400 0ustar jenkinsjenkinsmetadata :name => "Fact", :description => "Structured fact query", :author => "Puppet Labs", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :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.12.1/lib/mcollective/data/agent_data.rb0000644005276200011600000000065513265671724022417 0ustar jenkinsjenkinsmodule MCollective module Data class Agent_data "Agent", :description => "Meta data about installed MColletive Agents", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :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.12.1/lib/mcollective/data/result.rb0000644005276200011600000000232313265671724021640 0ustar jenkinsjenkinsmodule 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) # checks using the string representation of the class name to avoid deprecations on Bignum and Fixnum raise "Can only store String, Integer, Float or Boolean data but got #{val.class} for key #{key}" unless ["String", "Integer", "Bignum", "Fixnum", "Float", "TrueClass", "FalseClass"].include?(val.class.to_s) @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.12.1/lib/mcollective/data/collective_data.ddl0000644005276200011600000000125113265671724023603 0ustar jenkinsjenkinsmetadata :name => "Collective", :description => "Collective membership", :author => "Puppet Labs", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :timeout => 1 dataquery :description => "Collective" do input :query, :prompt => 'Collective', :description => 'Collective name to ask about, eg mcollective', :type => :string, :validation => /./, :maxlength => 256 output :member, :description => 'Node is a member of the named collective', :display_as => 'member' end mcollective-2.12.1/lib/mcollective/data/fact_data.rb0000644005276200011600000000271513265671724022235 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/data/fstat_data.ddl0000644005276200011600000000504113265671724022574 0ustar jenkinsjenkinsmetadata :name => "File Stat", :description => "Retrieve file stat data for a given file", :author => "R.I.Pienaar ", :license => "ASL 2.0", :version => "1.0", :url => "https://docs.puppetlabs.com/mcollective/", :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.12.1/lib/mcollective/data/fstat_data.rb0000644005276200011600000000313113265671724022432 0ustar jenkinsjenkinsmodule MCollective module Data class Fstat_data 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.12.1/lib/mcollective/ddl/0000755005276200011600000000000013265671736017632 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/ddl/dataddl.rb0000644005276200011600000000363613265671724021561 0ustar jenkinsjenkinsmodule 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 => "https://docs.puppetlabs.com/mcollective/", # :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.12.1/lib/mcollective/ddl/discoveryddl.rb0000644005276200011600000000325713265671724022656 0ustar jenkinsjenkinsmodule MCollective module DDL # DDL for discovery plugins, a full example can be seen below # # metadata :name => "mc", # :description => "MCollective Broadcast based discovery", # :author => "R.I.Pienaar ", # :license => "ASL 2.0", # :version => "0.1", # :url => "https://docs.puppetlabs.com/mcollective/", # :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.12.1/lib/mcollective/ddl/validatorddl.rb0000644005276200011600000000011613265671724022623 0ustar jenkinsjenkinsmodule MCollective module DDL class ValidatorDDL "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 => "https://docs.puppetlabs.com/mcollective/", # :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 key.is_a?(Symbol) && arguments.include?(key.to_s) && !input.include?(key.to_s) compat_arg = key.to_s else compat_arg = key end if !arguments.include?(compat_arg) && !input[key][:default].nil? && !input[key][:optional] Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]]) arguments[compat_arg] = input[key][:default] end end end # Creates a new set of arguments with string arguments mapped to symbol ones # # This is to assist with moving to a JSON pure world where requests might come # in from REST or other languages, those languages and indeed JSON itself does # not support symbols. # # It ensures a backward compatible mode where for rpcutil both of these requests # are equivelant # # c.get_fact(:fact => "cluster") # c.get_fact("fact" => "cluster") # # The case where both :fact and "fact" is in the DDL cannot be handled correctly # and this code will assume the caller means "fact" in that case. There's no # way to represent such a request in JSON and just in general sounds like a bad # idea, so a warning is logged which would in default client configuration appear # on the clients display def symbolize_basic_input_arguments(input, arguments) warned = false Hash[arguments.map do |key, value| if input.include?(key.intern) && input.include?(key.to_s) && !warned Log.warn("String and Symbol versions of input %s found in the DDL for %s, ensure your DDL keys are unique." % [key, @pluginname]) warned = true end if key.is_a?(String) && input.include?(key.intern) && !input.include?(key) [key.intern, value] else [key, value] 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] || {} compatible_args = symbolize_basic_input_arguments(input, arguments) input.keys.each do |key| unless input[key][:optional] unless compatible_args.include?(key) raise DDLValidationError, "Action #{action} needs a #{key} argument" end end if compatible_args.include?(key) validate_input_argument(input, key, compatible_args[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.12.1/lib/mcollective/ddl/base.rb0000644005276200011600000001755113265671724021077 0ustar jenkinsjenkinsmodule 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.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.12.1/lib/mcollective/data.rb0000644005276200011600000000536013265671724020326 0ustar jenkinsjenkinsmodule MCollective module Data require "mcollective/data/base" require "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.12.1/lib/mcollective/logger/0000755005276200011600000000000013265671736020346 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/logger/file_logger.rb0000644005276200011600000000303113265671724023143 0ustar jenkinsjenkinsrequire '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.12.1/lib/mcollective/logger/console_logger.rb0000644005276200011600000000341013265671724023667 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/logger/syslog_logger.rb0000644005276200011600000000246513265671724023556 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/logger/base.rb0000644005276200011600000000437413265671724021612 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/registration.rb0000644005276200011600000000125113265671724022122 0ustar jenkinsjenkinsmodule MCollective # 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/.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 require "mcollective/registration/base" end end mcollective-2.12.1/lib/mcollective/discovery.rb0000644005276200011600000001032413265671724021420 0ustar jenkinsjenkinsmodule 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?(Integer) 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.12.1/lib/mcollective/util.rb0000755005276200011600000004401713265671724020377 0ustar jenkinsjenkinsmodule 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 # the set of acceptable config files config_paths = [] # user dotfile begin # File.expand_path will raise if HOME isn't set, catch it user_path = File.expand_path("~/.mcollective") config_paths << user_path rescue Exception end # standard locations if self.windows? config_paths << File.join(self.windows_prefix, 'etc', 'client.cfg') else config_paths << '/etc/puppetlabs/mcollective/client.cfg' config_paths << '/etc/mcollective/client.cfg' end # use the first readable config file, or if none are the first listed found = config_paths.find_index { |file| File.readable?(file) } || 0 return config_paths[found] 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 def self.get_hidden_input_on_windows() require 'Win32API' # Hook into getch from crtdll. Keep reading all keys till return # or newline is hit. # If key is backspace or delete, then delete the character and update # the buffer. input = '' while char = Win32API.new("crtdll", "_getch", [ ], "I").Call do break if char == 10 || char == 13 # return or newline if char == 127 || char == 8 # backspace and delete if input.length > 0 input.slice!(-1, 1) end else input << char.chr end end char = '' input end def self.get_hidden_input_on_unix() unless $stdin.tty? raise 'Could not hook to stdin to hide input. If using SSH, try using -t flag while connecting to server.' end unless system 'stty -echo -icanon' raise 'Could not hide input using stty command.' end input = $stdin.gets ensure unless system 'stty echo icanon' raise 'Could not enable echoing of input. Try executing `stty echo icanon` to debug.' end input end def self.get_hidden_input(message='Please enter data: ') unless message.nil? print message end if versioncmp(ruby_version, '1.9.3') >= 0 require 'io/console' input = $stdin.noecho(&:gets) else # Use hacks to get hidden input on Ruby <1.9.3 if self.windows? input = self.get_hidden_input_on_windows() else input = self.get_hidden_input_on_unix() end end input.chomp! if input input end end end mcollective-2.12.1/lib/mcollective/logger.rb0000644005276200011600000000012313265671724020664 0ustar jenkinsjenkinsmodule MCollective module Logger require "mcollective/logger/base" end end mcollective-2.12.1/lib/mcollective/pluginpackager/0000755005276200011600000000000013265671736022063 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/pluginpackager/agent_definition.rb0000644005276200011600000000761213265671724025721 0ustar jenkinsjenkinsmodule 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 = PluginPackager.get_plugin_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.12.1/lib/mcollective/pluginpackager/templates/0000755005276200011600000000000013265671736024061 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/pluginpackager/templates/debian/0000755005276200011600000000000013265671736025303 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/pluginpackager/templates/debian/compat.erb0000644005276200011600000000000213265671724027245 0ustar jenkinsjenkins7 mcollective-2.12.1/lib/mcollective/pluginpackager/templates/debian/control.erb0000644005276200011600000000071413265671724027454 0ustar jenkinsjenkinsSource: <%= @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.12.1/lib/mcollective/pluginpackager/templates/debian/rules.erb0000644005276200011600000000035013265671724027122 0ustar jenkinsjenkins#!/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.12.1/lib/mcollective/pluginpackager/templates/debian/changelog.erb0000644005276200011600000000045113265671724027721 0ustar jenkinsjenkins<%= @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.12.1/lib/mcollective/pluginpackager/templates/debian/Makefile.erb0000644005276200011600000000004313265671724027504 0ustar jenkinsjenkinsDESTDIR= build: clean: install: mcollective-2.12.1/lib/mcollective/pluginpackager/templates/debian/copyright.erb0000644005276200011600000000032213265671724027777 0ustar jenkinsjenkinsThis 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.12.1/lib/mcollective/pluginpackager/templates/module/0000755005276200011600000000000013265671736025346 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/pluginpackager/templates/module/README.md.erb0000644005276200011600000000222113265671724027366 0ustar jenkinsjenkins# <%= @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.12.1/lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb0000644005276200011600000000045313265671724030422 0ustar jenkinsjenkins# 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.12.1/lib/mcollective/pluginpackager/templates/module/Modulefile.erb0000644005276200011600000000035113265671724030121 0ustar jenkinsjenkinsname '<%= @plugin.vendor %>-<%= @package_name %>' version '<%= @plugin.metadata[:version] %>' description '<%= @plugin.metadata[:description] %>' project_page '<%= @plugin.metadata[:url]%>' dependency 'puppetlabs/mcollective', '1.x' mcollective-2.12.1/lib/mcollective/pluginpackager/templates/redhat/0000755005276200011600000000000013265671736025330 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/pluginpackager/templates/redhat/rpm_spec.erb0000644005276200011600000000375613265671724027642 0ustar jenkinsjenkinsName: <%= @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.12.1/lib/mcollective/pluginpackager/standard_definition.rb0000644005276200011600000000540613265671724026422 0ustar jenkinsjenkinsmodule 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 = PluginPackager.get_plugin_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.12.1/lib/mcollective/pluginpackager/rpmpackage_packager.rb0000644005276200011600000001346313265671724026363 0ustar jenkinsjenkinsmodule MCollective module PluginPackager class RpmpackagePackager require 'erb' def initialize(plugin, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = nil, module_template = nil) if @buildtool = select_command @plugin = plugin @package_name = "#{@plugin.mcname}-#{@plugin.metadata[:name]}" @package_name_and_version = "#{@package_name}-#{@plugin.metadata[:version]}" @verbose = verbose @libdir = pluginpath || '/usr/libexec/mcollective/mcollective/' @signature = signature @rpmdir = rpmdir @srpmdir = srpmdir @keep_artifacts = keep_artifacts else raise("Cannot build package. 'rpmbuild' or 'rpmbuild-md5' is not present on the system") end end # Determine the build tool present on the system def select_command if PluginPackager.command_available?('rpmbuild-md5') return 'rpmbuild-md5' elsif PluginPackager.command_available?('rpmbuild') return 'rpmbuild' else return nil end end def rpmdir `rpm --eval '%_rpmdir'`.chomp end def srpmdir `rpm --eval '%_srcrpmdir'`.chomp end # Build Process : # - create temporary buildroot # - create the spec file # - create the tarball # - run the build script # - move pacakges to cwd # - clean up def create_packages begin puts "Building packages for #{@package_name} plugin." @tmpdir = Dir.mktmpdir('mcollective_packager') prepare_tmpdirs make_spec_file 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 cleanup_tmpdirs end end end private def run_build begin tarfile = create_tar PluginPackager.execute_verbosely(@verbose) do PluginPackager.safe_system("#{@buildtool} -ta#{" --quiet" unless @verbose}#{" --sign" if @signature} #{tarfile}") end rescue => 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.12.1/lib/mcollective/pluginpackager/debpackage_packager.rb0000644005276200011600000002012513265671724026310 0ustar jenkinsjenkinsmodule 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]}) | puppet-agent" elsif dep[:version] dependencies << "#{dep[:name]} (>=#{dep[:version]}) | puppet-agent" else dependencies << "#{dep[:name]} | puppet-agent" 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.12.1/lib/mcollective/pluginpackager/ospackage_packager.rb0000644005276200011600000000451113265671724026200 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/pluginpackager/modulepackage_packager.rb0000644005276200011600000001005513265671724027044 0ustar jenkinsjenkinsmodule 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.expand_path(file).gsub(/#{@plugin.target_path}|^\.\//, '') 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.12.1/lib/mcollective/agent.rb0000644005276200011600000000005513265671724020507 0ustar jenkinsjenkinsmodule MCollective module Agent end end mcollective-2.12.1/lib/mcollective/agents.rb0000644005276200011600000001121113265671724020666 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/agent/0000755005276200011600000000000013265671736020165 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/agent/discovery.rb0000644005276200011600000000166013265671724022521 0ustar jenkinsjenkinsmodule 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 => "https://docs.puppetlabs.com/mcollective/", :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.12.1/lib/mcollective/agent/rpcutil.rb0000644005276200011600000000644113265671724022176 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/agent/rpcutil.ddl0000644005276200011600000001460013265671724022332 0ustar jenkinsjenkinsmetadata :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 => "https://docs.puppetlabs.com/mcollective/", :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.12.1/lib/mcollective/ddl.rb0000644005276200011600000001057013265671724020157 0ustar jenkinsjenkinsmodule 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 require "mcollective/ddl/base" require "mcollective/ddl/agentddl" require "mcollective/ddl/dataddl" require "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.12.1/lib/mcollective/connector/0000755005276200011600000000000013265671736021061 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/connector/activemq.rb0000644005276200011600000006127013265671724023222 0ustar jenkinsjenkinsrequire 'stomp' module MCollective module Connector # Handles sending and receiving messages over the Stomp protocol for ActiveMQ # servers specifically, we take advantages of ActiveMQ specific features and # enhancements to the Stomp protocol. For best results in a clustered environment # use ActiveMQ 5.5.0 at least. # # This plugin takes an entirely different approach to dealing with ActiveMQ # from the more generic stomp connector. # # - Agents use /topic/..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.1.ssl.ciphers = TLSv1:!MD5:!LOW:!EXPORT # # 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= params[:max_hbrlck_fails] # we're about to force a disconnect Log.error("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect]) else Log.warn("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect]) end else if params[:max_hbread_fails] == 0 # failure is disabled Log.debug("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) elsif ticker_data['read_fail_count'] >= params[:max_hbread_fails] # we're about to force a reconnect Log.error("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) else Log.warn("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) end end rescue Exception => 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 Log.info("ActiveMQ connector initialized. Using stomp-gem #{stomp_version}") 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 = [] middleware_user = '' middleware_password = '' prompt_for_username = get_bool_option("activemq.prompt_user", "false") prompt_for_password = get_bool_option("activemq.prompt_password", "false") if prompt_for_username Log.debug("No previous user exists and activemq.prompt-user is set to true") print "Please enter user to connect to middleware: " middleware_user = STDIN.gets.chomp end if prompt_for_password Log.debug("No previous password exists and activemq.prompt-password is set to true") middleware_password = MCollective::Util.get_hidden_input("Please enter password: ") print "\n" end 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[:ssl] = get_bool_option("activemq.pool.#{poolnum}.ssl", "false") # read user from config file host[:login] = get_env_or_option("STOMP_USER", "activemq.pool.#{poolnum}.user", middleware_user) if prompt_for_username and host[:login] != middleware_user Log.info("Using #{host[:login]} from config file to connect to #{host[:host]}. "+ "plugin.activemq.prompt_user should be set to false to remove the prompt.") end # read user from config file host[:passcode] = get_env_or_option("STOMP_PASSWORD", "activemq.pool.#{poolnum}.password", middleware_password) if prompt_for_password and host[:passcode] != middleware_password Log.info("Using password from config file to connect to #{host[:host]}. "+ "plugin.activemq.prompt_password should be set to false to remove the prompt.") end # 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", 0)) connection[:max_hbread_fails] = Integer(get_option("activemq.max_hbread_fails", 2)) connection[:logger] = EventLogger.new @connection = connector.new(connection) rescue ClientTimeoutError => e raise e 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), :ciphers => get_option("activemq.pool.#{poolnum}.ssl.ciphers", 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.warn("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 headers["mc_sender"] = Config.instance.identity 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) agents_multiplex = get_bool_option("activemq.agents_multiplex", "false") target = {:name => nil, :headers => {}} case type when :reply target[:name] = ["/queue/" + collective, :reply, "#{Config.instance.identity}_#{$$}", Client.request_sequence].join(".") when :broadcast if agents_multiplex target[:name] = ["/topic/" + collective, :agents].join(".") else target[:name] = ["/topic/" + collective, agent, :agent].join(".") end when :request if agents_multiplex target[:name] = ["/topic/" + collective, :agents].join(".") else target[:name] = ["/topic/" + collective, agent, :agent].join(".") end 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.12.1/lib/mcollective/connector/rabbitmq.rb0000644005276200011600000005074013265671724023212 0ustar jenkinsjenkinsrequire 'stomp' module MCollective module Connector class Rabbitmq= params[:max_hbrlck_fails] # we're about to force a disconnect Log.error("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect]) else Log.warn("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect]) end else if params[:max_hbread_fails] == 0 # failure is disabled Log.debug("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) elsif ticker_data['read_fail_count'] >= params[:max_hbread_fails] # we're about to force a reconnect Log.error("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) else Log.warn("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect]) end end rescue Exception => 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 Log.info("RabbitMQ connector initialized. Using stomp-gem #{stomp_version}") 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 = [] middleware_user = '' middleware_password = '' prompt_for_username = get_bool_option("rabbitmq.prompt_user", "false") prompt_for_password = get_bool_option("rabbitmq.prompt_password", "false") if prompt_for_username Log.debug("No previous user exists and rabbitmq.prompt-user is set to true") print "Please enter user to connect to middleware: " middleware_user = STDIN.gets.chomp end if prompt_for_password Log.debug("No previous password exists and rabbitmq.prompt-password is set to true") middleware_password = MCollective::Util.get_hidden_input("Please enter password: ") print "\n" end 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[:ssl] = get_bool_option("rabbitmq.pool.#{poolnum}.ssl", "false") # read user from config file host[:login] = get_env_or_option("STOMP_USER", "rabbitmq.pool.#{poolnum}.user", middleware_user) if prompt_for_username and host[:login] != middleware_user Log.info("Using #{host[:login]} from config file to connect to #{host[:host]}. "+ "plugin.rabbitmq.prompt_user should be set to false to remove the prompt.") end # read password from config file host[:passcode] = get_env_or_option("STOMP_PASSWORD", "rabbitmq.pool.#{poolnum}.password", middleware_password) if prompt_for_password and host[:passcode] != middleware_password Log.info("Using password from config file to connect to #{host[:host]}. "+ "plugin.rabbitmq.prompt_password should be set to false to remove the prompt.") end # 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", 0)) 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 ClientTimeoutError => e raise e 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), :ciphers => get_option("rabbitmq.pool.#{poolnum}.ssl.ciphers", 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 target[:headers]["mc_sender"] = Config.instance.identity 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) agents_multiplex = get_bool_option("rabbitmq.agents_multiplex", "false") 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 if agents_multiplex target[:name] = "/exchange/%s_broadcast" % collective target[:id] = "%s_broadcast" % collective else target[:name] = "/exchange/%s_broadcast/%s" % [collective, agent] target[:id] = "%s_broadcast_%s" % [collective, agent] end if reply_to target[:headers]["reply-to"] = reply_to else target[:headers]["reply-to"] = reply_path end 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.12.1/lib/mcollective/connector/rabbitmq.ddl0000644005276200011600000000061313265671724023344 0ustar jenkinsjenkinsmetadata :name => "RabbitMQ Connector", :description => "Connector plugin for RabbitMQ middleware", :author => "Puppet Labs", :license => "ASL 2.0", :version => "1.0.0", :url => "https://docs.puppetlabs.com/mcollective/plugin_directory/", :timeout => 60 requires :mcollective => "2.6.0" mcollective-2.12.1/lib/mcollective/connector/activemq.ddl0000644005276200011600000000061313265671724023354 0ustar jenkinsjenkinsmetadata :name => "ActiveMQ Connector", :description => "Connector plugin for ActiveMQ middleware", :author => "Puppet Labs", :license => "ASL 2.0", :version => "1.0.0", :url => "https://docs.puppetlabs.com/mcollective/plugin_directory/", :timeout => 60 requires :mcollective => "2.6.0" mcollective-2.12.1/lib/mcollective/connector/base.rb0000644005276200011600000000263013265671724022316 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/generators/0000755005276200011600000000000013265671736021240 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/generators/agent_generator.rb0000644005276200011600000000316213265671724024730 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/generators/templates/0000755005276200011600000000000013265671736023236 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/generators/templates/action_snippet.erb0000644005276200011600000000060413265671724026744 0ustar jenkinsjenkins # 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.12.1/lib/mcollective/generators/templates/ddl.erb0000644005276200011600000000064713265671724024477 0ustar jenkinsjenkinsmetadata :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.12.1/lib/mcollective/generators/templates/data_input_snippet.erb0000644005276200011600000000027013265671724027616 0ustar jenkinsjenkins input :query, :prompt => "%PROMP%", :description => "%DESCRIPTION%", :type => %TYPE%, :validation => %VALIDATION%, :maxlength => %MAXLENGTH% mcollective-2.12.1/lib/mcollective/generators/templates/plugin.erb0000644005276200011600000000020513265671724025220 0ustar jenkinsjenkinsmodule MCollective module <%= @mod_name%> class <%= @plugin_name.capitalize -%><<%= @pclass%> <%= @content%> end end end mcollective-2.12.1/lib/mcollective/generators/data_generator.rb0000644005276200011600000000320013265671724024534 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/generators/base.rb0000644005276200011600000000326713265671724022504 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/vendor.rb0000644005276200011600000000230013265671724020701 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/security/0000755005276200011600000000000013265671736020736 5ustar jenkinsjenkinsmcollective-2.12.1/lib/mcollective/security/ssl.rb0000644005276200011600000003062513265671724022067 0ustar jenkinsjenkinsrequire '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" if YAML.respond_to? :safe_load return YAML.safe_load(msg, [Symbol]) else raise "YAML.safe_load not supported by Ruby #{RUBY_VERSION}. Please update to Ruby 2.1+." end 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.12.1/lib/mcollective/security/aes_security.rb0000644005276200011600000003747113265671724023773 0ustar jenkinsjenkinsmodule 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" if YAML.respond_to? :safe_load return YAML.safe_load(msg, [Symbol]) else raise "YAML.safe_load not supported by Ruby #{RUBY_VERSION}. Please update to Ruby 2.1+." end 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.12.1/lib/mcollective/security/psk.rb0000644005276200011600000000665113265671724022065 0ustar jenkinsjenkinsmodule 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.12.1/lib/mcollective/security/base.rb0000644005276200011600000002170613265671724022200 0ustar jenkinsjenkinsmodule MCollective module Security # 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. class Base attr_reader :stats attr_accessor :initiated_by # Register plugins that inherits base def self.inherited(klass) PluginManager << {:type => "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.12.1/lib/mcollective.rb0000644005276200011600000000355713265671724017423 0ustar jenkinsjenkinsrequire 'rubygems' require 'json' 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 require "mcollective/agent" require "mcollective/agents" require "mcollective/aggregate" require "mcollective/application" require "mcollective/applications" require "mcollective/client" require "mcollective/config" require "mcollective/connector" require "mcollective/data" require "mcollective/ddl" require "mcollective/discovery" require "mcollective/facts" require "mcollective/logger" require "mcollective/log" require "mcollective/matcher" require "mcollective/message" require "mcollective/optionparser" require "mcollective/generators" require "mcollective/pluginmanager" require "mcollective/pluginpackager" require "mcollective/registration" require "mcollective/rpc" require "mcollective/runnerstats" require "mcollective/security" require "mcollective/shell" require "mcollective/ssl" require "mcollective/util" require "mcollective/validator" require "mcollective/vendor" MCollective::Vendor.load_vendored VERSION="2.12.1" def self.version VERSION end end mcollective-2.12.1/install.rb0000755005276200011600000002365613265671724016022 0ustar jenkinsjenkins#! /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 WINDOWS = TRUE else WINDOWS = FALSE 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 InstallOptions.batch_files = 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('--no-batch-files', 'Prevents installation of batch files for windows', 'Default off') do |batch_files| InstallOptions.batch_files = false 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 if WINDOWS InstallOptions.service_files = true opts.on('--[no-]service-files', 'Installation of windows service files', 'Default is to install the windows service files') do |service_files| InstallOptions.service_files = service_files end 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 unless destdir.empty? 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) end 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}" unless WINDOWS 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 prepare_installation # 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/**/*}) if WINDOWS && InstallOptions.batch_files if InstallOptions.service_files windows_bins = glob(%w{ext/windows/*.bat ext/windows/*.rb}) else windows_bins = glob(%w{ext/windows/mco.bat}) end end check_prereqs 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_bins(windows_bins, InstallOptions.bindir, 'ext/windows/') if WINDOWS && InstallOptions.batch_files do_libs(libs, InstallOptions.sitelibdir) do_libs(plugins, InstallOptions.plugindir, 'plugins/') end mcollective-2.12.1/bin/0000755005276200011600000000000013265671736014563 5ustar jenkinsjenkinsmcollective-2.12.1/bin/mco0000755005276200011600000000301313265671724015261 0ustar jenkinsjenkins#!/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' # assume everything will all be fine exitcode = 0 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) # it may be a config option rather than an application name. In this # case pretend we had no application name if app_name =~ /^--/ app_name = nil end 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 if app_name puts "Unknown command '#{app_name}', searched for applications in:" puts puts MCollective::Config.instance.libdir.map { |path| " #{path}\n" } puts # exit with an error as we didn't find a command exitcode = 1 else puts "usage: #{$0} command " puts end 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 exit exitcode mcollective-2.12.1/bin/mcollectived0000755005276200011600000000423113265671724017160 0ustar jenkinsjenkins#!/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 'mcollective/runner' 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 # search for the server.cfg in the AIO path then the traditional one configfiles = ['/etc/puppetlabs/mcollective/server.cfg', '/etc/mcollective/server.cfg' ] found = configfiles.find_index { |file| File.readable?(file) } # didn't find any? default to the first if found.nil? found = 0 end configfile = configfiles[found] 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.12.1/doc/0000755005276200011600000000000013265671736014560 5ustar jenkinsjenkinsmcollective-2.12.1/doc/GetoptLong.html0000644005276200011600000000430113265671731017521 0ustar jenkinsjenkins module GetoptLong - mcollective version 2.12.1

module GetoptLong

mcollective-2.12.1/doc/README_md.html0000644005276200011600000003641613265671734017073 0ustar jenkinsjenkins README - mcollective version 2.12.1

The Marionette Collective

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

For documentation please see docs.puppet.com/mcollective

Developing

The documentation above details how MCollective works and many of its extension points.

Spec Tests

To run spec tests bundle install bundle exec rake test

Acceptance Tests

To run acceptance tests, see this.

Development Environment (MacOS)

Setup ActiveMQ using acceptance config: brew install activemq cp acceptance/files/activemq.* /usr/local/opt/activemq/libexec/conf activemq start

ActiveMQ can later by stopped with activemq stop. ActiveMQ logs are located at /usr/local/opt/activemq/libexec/data/activemq.log.

Setup MCollective with acceptance config: mkdir -p ~/.puppetlabs/etc/mcollective/ssl-clients cp acceptance/files/client.* ~/.puppetlabs/etc/mcollective cp acceptance/files/server.* ~/.puppetlabs/etc/mcollective cp acceptance/files/ca_crt.pem ~/.puppetlabs/etc/mcollective cp acceptance/files/client.crt ~/.puppetlabs/etc/mcollective/ssl-clients/client.pem ln -s ~/.puppetlabs/etc/mcollective/client.cfg ~/.mcollective

Modify client.cfg to work on the local machine: * Change the ssl_server_public, ssl_client_private, ssl_client_public paths to point to ~/.puppetlabs/etc/mcollective/{server.crt,client.key,client.pem}. * Change the activemq.pool.1.ssl.{ca,cert,key} paths to ~/.puppetlabs/etc/mcollective/{ca_crt.pem,client.crt,client.key}. Note that ~ needs to be expanded to the full path. Also, that client.pem doesn’t point to an actual file is intentional (I don’t fully understand why).

Create server.cfg, updating <user>: “` main_collective = mcollective collectives = mcollective logger_type = console loglevel = info daemonize = 0

securityprovider = ssl plugin.ssl_server_private = /Users/<user>/.puppetlabs/etc/mcollective/server.key plugin.ssl_server_public = /Users/<user>/.puppetlabs/etc/mcollective/server.crt plugin.ssl_client_cert_dir = /Users/<user>/.puppetlabs/etc/mcollective/ssl-clients

connector = activemq plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = activemq plugin.activemq.pool.1.port = 61613 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = marionette plugin.activemq.pool.1.ssl = true plugin.activemq.pool.1.ssl.ca = /Users/<user>/.puppetlabs/etc/mcollective/ca_crt.pem plugin.activemq.pool.1.ssl.cert = /Users/<user>/.puppetlabs/etc/mcollective/server.crt plugin.activemq.pool.1.ssl.key = /Users/<user>/.puppetlabs/etc/mcollective/server.key “`

The configuration above uses activemq as the name of the ActiveMQ broker. MCollective will enforce that the SSL certificate presented by the server matches the name it’s trying to connect to. To use the configuration above, traffic to activemq must be redirected to the local host. On most machines, that can be accomplished with sudo echo "127.0.0.1   activemq" >> /etc/hosts

From the root of this repository, test the setup by running a server RUBYLIB=lib bundle exec bin/mcollectived --config ~/.puppetlabs/etc/mcollective/server.cfg and client RUBYLIB=lib bundle exec bin/mco ping

Note that it may be useful to change the loglevel in client.cfg to debug issues with mco ping.

To enable specific plugins, you may need to set libdir in server.cfg and add plugin-specific configuration.

Contributing

Please refer to this document.

mcollective-2.12.1/doc/lib/0000755005276200011600000000000013265671736015326 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/0000755005276200011600000000000013265671736017634 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/validator/0000755005276200011600000000000013265671736021621 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/validator/regex_validator_ddl.html0000644005276200011600000002622213265671735026514 0ustar jenkinsjenkins regex_validator.ddl - mcollective version 2.12.1

metadata :name => “Regexâ€,

:description => "Validates that a string matches a supplied regular expression",
:author      => "P. Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1
mcollective-2.12.1/doc/lib/mcollective/validator/typecheck_validator_ddl.html0000644005276200011600000002621413265671735027362 0ustar jenkinsjenkins typecheck_validator.ddl - mcollective version 2.12.1

metadata :name => “Typecheckâ€,

:description => "Validates that a value is of a certain type",
:author      => "P. Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1
mcollective-2.12.1/doc/lib/mcollective/validator/length_validator_ddl.html0000644005276200011600000002624313265671735026666 0ustar jenkinsjenkins length_validator.ddl - mcollective version 2.12.1

metadata :name => “Lengthâ€,

:description => "Validates that the length of a string is less or equal to a specified value",
:author      => "P. Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1
mcollective-2.12.1/doc/lib/mcollective/validator/ipv6address_validator_ddl.html0000644005276200011600000002622113265671735027633 0ustar jenkinsjenkins ipv6address_validator.ddl - mcollective version 2.12.1

metadata :name => “IPv6 Addressâ€,

:description => "Validates that a value is an ipv6 address",
:author      => "P. Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1
mcollective-2.12.1/doc/lib/mcollective/validator/shellsafe_validator_ddl.html0000644005276200011600000002620513265671735027351 0ustar jenkinsjenkins shellsafe_validator.ddl - mcollective version 2.12.1

metadata :name => “Shellsafeâ€,

:description => "Validates that a string is shellsafe",
:author      => "P. Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1
mcollective-2.12.1/doc/lib/mcollective/validator/ipv4address_validator_ddl.html0000644005276200011600000002622113265671735027631 0ustar jenkinsjenkins ipv4address_validator.ddl - mcollective version 2.12.1

metadata :name => “IPv4 Addressâ€,

:description => "Validates that a value is an ipv4 address",
:author      => "P. Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1
mcollective-2.12.1/doc/lib/mcollective/validator/array_validator_ddl.html0000644005276200011600000002620113265671735026515 0ustar jenkinsjenkins array_validator.ddl - mcollective version 2.12.1

metadata :name => “Arrayâ€,

:description => "Validates that a value is included in a list",
:author      => "P. Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1
mcollective-2.12.1/doc/lib/mcollective/aggregate/0000755005276200011600000000000013265671736021562 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/aggregate/summary_ddl.html0000644005276200011600000002706213265671735024776 0ustar jenkinsjenkins summary.ddl - mcollective version 2.12.1

metadata :name => “summaryâ€,

:description => "Displays the summary of a set of results",
:author      => "Pieter Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/plugin_directory/",
: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.12.1/doc/lib/mcollective/aggregate/average_ddl.html0000644005276200011600000002673213265671735024716 0ustar jenkinsjenkins average.ddl - mcollective version 2.12.1

metadata :name => “averageâ€,

:description => "Displays the average of a set of numeric values",
:author      => "Pieter Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/plugin_directory/",
:timeout     => 5

usage <<-USAGE

This aggregate plugin will determine the 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.12.1/doc/lib/mcollective/aggregate/sum_ddl.html0000644005276200011600000002670413265671735024107 0ustar jenkinsjenkins sum.ddl - mcollective version 2.12.1

metadata :name => “Sumâ€,

:description => "Determine the total added value of a set of values",
:author      => "Pieter Loubser <pieter.loubser@puppetlabs.com>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/plugin_directory/",
: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.12.1/doc/lib/mcollective/discovery/0000755005276200011600000000000013265671736021643 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/discovery/mc_ddl.html0000644005276200011600000002664613265671735023770 0ustar jenkinsjenkins mc.ddl - mcollective version 2.12.1

metadata :name => “mcâ€,

:description => "MCollective Broadcast based discovery",
:author      => "R.I.Pienaar <rip@devco.net>",
:license     => "ASL 2.0",
:version     => "0.1",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 2

discovery do

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

end

mcollective-2.12.1/doc/lib/mcollective/discovery/flatfile_ddl.html0000644005276200011600000002640313265671735025146 0ustar jenkinsjenkins flatfile.ddl - mcollective version 2.12.1

metadata :name => “flatfileâ€,

:description => "Flatfile based discovery for node identities",
:author      => "R.I.Pienaar <rip@devco.net>",
:license     => "ASL 2.0",
:version     => "0.1",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 0

discovery do

capabilities :identity

end

mcollective-2.12.1/doc/lib/mcollective/discovery/stdin_ddl.html0000644005276200011600000002640313265671735024501 0ustar jenkinsjenkins stdin.ddl - mcollective version 2.12.1

metadata :name => “stdinâ€,

:description => "STDIN based discovery for node identities",
:author      => "Tomas Doran <bobtfish@bobtfish.net.net>",
:license     => "ASL 2.0",
:version     => "0.1",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 0

discovery do

capabilities :identity

end

mcollective-2.12.1/doc/lib/mcollective/data/0000755005276200011600000000000013265671736020545 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/data/collective_data_ddl.html0000644005276200011600000003053513265671735025405 0ustar jenkinsjenkins collective_data.ddl - mcollective version 2.12.1

metadata :name => “Collectiveâ€,

:description => "Collective membership",
:author      => "Puppet Labs",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
:timeout     => 1

dataquery :description => “Collective†do

input :query,
      :prompt => 'Collective',
      :description => 'Collective name to ask about, eg mcollective',
      :type => :string,
      :validation => /./,
      :maxlength => 256

output :member,
      :description => 'Node is a member of the named collective',
      :display_as => 'member'

end

mcollective-2.12.1/doc/lib/mcollective/data/agent_data_ddl.html0000644005276200011600000003171313265671735024351 0ustar jenkinsjenkins agent_data.ddl - mcollective version 2.12.1

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         => "https://docs.puppetlabs.com/mcollective/",
: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.12.1/doc/lib/mcollective/data/fact_data_ddl.html0000644005276200011600000003211713265671735024167 0ustar jenkinsjenkins fact_data.ddl - mcollective version 2.12.1

metadata :name => “Factâ€,

:description => "Structured fact query",
:author      => "Puppet Labs",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
: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.12.1/doc/lib/mcollective/data/fstat_data_ddl.html0000644005276200011600000004626013265671735024377 0ustar jenkinsjenkins fstat_data.ddl - mcollective version 2.12.1

metadata :name => “File Statâ€,

:description => "Retrieve file stat data for a given file",
:author      => "R.I.Pienaar <rip@devco.net>",
:license     => "ASL 2.0",
:version     => "1.0",
:url         => "https://docs.puppetlabs.com/mcollective/",
: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.12.1/doc/lib/mcollective/agent/0000755005276200011600000000000013265671736020732 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/agent/rpcutil_ddl.html0000644005276200011600000007651613265671735024143 0ustar jenkinsjenkins rpcutil.ddl - mcollective version 2.12.1

metadata :name => “rpcutilâ€,

: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         => "https://docs.puppetlabs.com/mcollective/",
: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.12.1/doc/lib/mcollective/connector/0000755005276200011600000000000013265671736021626 5ustar jenkinsjenkinsmcollective-2.12.1/doc/lib/mcollective/connector/rabbitmq_ddl.html0000644005276200011600000002623313265671735025145 0ustar jenkinsjenkins rabbitmq.ddl - mcollective version 2.12.1

metadata :name => “RabbitMQ Connectorâ€,

:description => "Connector plugin for RabbitMQ middleware",
:author      => "Puppet Labs",
:license     => "ASL 2.0",
:version     => "1.0.0",
:url         => "https://docs.puppetlabs.com/mcollective/plugin_directory/",
:timeout     => 60

requires :mcollective => “2.6.0â€

mcollective-2.12.1/doc/lib/mcollective/connector/activemq_ddl.html0000644005276200011600000002623313265671735025155 0ustar jenkinsjenkins activemq.ddl - mcollective version 2.12.1

metadata :name => “ActiveMQ Connectorâ€,

:description => "Connector plugin for ActiveMQ middleware",
:author      => "Puppet Labs",
:license     => "ASL 2.0",
:version     => "1.0.0",
:url         => "https://docs.puppetlabs.com/mcollective/plugin_directory/",
:timeout     => 60

requires :mcollective => “2.6.0â€

mcollective-2.12.1/doc/Puppet.html0000644005276200011600000000426113265671734016724 0ustar jenkinsjenkins module Puppet - mcollective version 2.12.1

module Puppet

mcollective-2.12.1/doc/css/0000755005276200011600000000000013265671736015350 5ustar jenkinsjenkinsmcollective-2.12.1/doc/css/fonts.css0000644005276200011600000001434213265671731017212 0ustar jenkinsjenkins/* * Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), * with Reserved Font Name "Source". All Rights Reserved. Source is a * trademark of Adobe Systems Incorporated in the United States and/or other * countries. * * This Font Software is licensed under the SIL Open Font License, Version * 1.1. * * This license is copied below, and is also available with a FAQ at: * http://scripts.sil.org/OFL */ @font-face { font-family: "Source Code Pro"; font-style: normal; font-weight: 400; src: local("Source Code Pro"), local("SourceCodePro-Regular"), url("fonts/SourceCodePro-Regular.ttf") format("truetype"); } @font-face { font-family: "Source Code Pro"; font-style: normal; font-weight: 700; src: local("Source Code Pro Bold"), local("SourceCodePro-Bold"), url("fonts/SourceCodePro-Bold.ttf") format("truetype"); } /* * Copyright (c) 2010, Åukasz Dziedzic (dziedzic@typoland.com), * with Reserved Font Name Lato. * * This Font Software is licensed under the SIL Open Font License, Version * 1.1. * * This license is copied below, and is also available with a FAQ at: * http://scripts.sil.org/OFL */ @font-face { font-family: "Lato"; font-style: normal; font-weight: 300; src: local("Lato Light"), local("Lato-Light"), url("fonts/Lato-Light.ttf") format("truetype"); } @font-face { font-family: "Lato"; font-style: italic; font-weight: 300; src: local("Lato Light Italic"), local("Lato-LightItalic"), url("fonts/Lato-LightItalic.ttf") format("truetype"); } @font-face { font-family: "Lato"; font-style: normal; font-weight: 700; src: local("Lato Regular"), local("Lato-Regular"), url("fonts/Lato-Regular.ttf") format("truetype"); } @font-face { font-family: "Lato"; font-style: italic; font-weight: 700; src: local("Lato Italic"), local("Lato-Italic"), url("fonts/Lato-RegularItalic.ttf") format("truetype"); } /* * ----------------------------------------------------------- * SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 * ----------------------------------------------------------- * * PREAMBLE * The goals of the Open Font License (OFL) are to stimulate worldwide * development of collaborative font projects, to support the font creation * efforts of academic and linguistic communities, and to provide a free and * open framework in which fonts may be shared and improved in partnership * with others. * * The OFL allows the licensed fonts to be used, studied, modified and * redistributed freely as long as they are not sold by themselves. The * fonts, including any derivative works, can be bundled, embedded, * redistributed and/or sold with any software provided that any reserved * names are not used by derivative works. The fonts and derivatives, * however, cannot be released under any other type of license. The * requirement for fonts to remain under this license does not apply * to any document created using the fonts or their derivatives. * * DEFINITIONS * "Font Software" refers to the set of files released by the Copyright * Holder(s) under this license and clearly marked as such. This may * include source files, build scripts and documentation. * * "Reserved Font Name" refers to any names specified as such after the * copyright statement(s). * * "Original Version" refers to the collection of Font Software components as * distributed by the Copyright Holder(s). * * "Modified Version" refers to any derivative made by adding to, deleting, * or substituting -- in part or in whole -- any of the components of the * Original Version, by changing formats or by porting the Font Software to a * new environment. * * "Author" refers to any designer, engineer, programmer, technical * writer or other person who contributed to the Font Software. * * PERMISSION & CONDITIONS * Permission is hereby granted, free of charge, to any person obtaining * a copy of the Font Software, to use, study, copy, merge, embed, modify, * redistribute, and sell modified and unmodified copies of the Font * Software, subject to the following conditions: * * 1) Neither the Font Software nor any of its individual components, * in Original or Modified Versions, may be sold by itself. * * 2) Original or Modified Versions of the Font Software may be bundled, * redistributed and/or sold with any software, provided that each copy * contains the above copyright notice and this license. These can be * included either as stand-alone text files, human-readable headers or * in the appropriate machine-readable metadata fields within text or * binary files as long as those fields can be easily viewed by the user. * * 3) No Modified Version of the Font Software may use the Reserved Font * Name(s) unless explicit written permission is granted by the corresponding * Copyright Holder. This restriction only applies to the primary font name as * presented to the users. * * 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font * Software shall not be used to promote, endorse or advertise any * Modified Version, except to acknowledge the contribution(s) of the * Copyright Holder(s) and the Author(s) or with their explicit written * permission. * * 5) The Font Software, modified or unmodified, in part or in whole, * must be distributed entirely under this license, and must not be * distributed under any other license. The requirement for fonts to * remain under this license does not apply to any document created * using the Font Software. * * TERMINATION * This license becomes null and void if any of the above conditions are * not met. * * DISCLAIMER * THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT * OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE * COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL * DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM * OTHER DEALINGS IN THE FONT SOFTWARE. */ mcollective-2.12.1/doc/css/rdoc.css0000644005276200011600000002216413265671731017011 0ustar jenkinsjenkins/* * "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: #fafafa; font-family: Lato, sans-serif; font-weight: 300; } h1 span, h2 span, h3 span, h4 span, h5 span, h6 span { position: relative; display: none; padding-left: 1em; line-height: 0; vertical-align: baseline; font-size: 10px; } h1 span { top: -1.3em; } h2 span { top: -1.2em; } h3 span { top: -1.0em; } h4 span { top: -0.8em; } h5 span { top: -0.5em; } h6 span { top: -0.5em; } 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; } code, pre { font-family: "Source Code Pro", Monaco, monospace; } /* @group Generic Classes */ .initially-hidden { display: none; } #search-field { width: 98%; background: white; border: none; height: 1.5em; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; text-align: left; } #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 */ .table-of-contents ul { margin: 1em; list-style: none; } .table-of-contents ul ul { margin-top: 0.25em; } .table-of-contents ul :link, .table-of-contents ul :visited { font-size: 16px; } .table-of-contents li { margin-bottom: 0.25em; } .table-of-contents li .toc-toggle { width: 16px; height: 16px; background: url(images/add.png) no-repeat; } .table-of-contents li .toc-toggle.open { background: url(images/delete.png) no-repeat; } /* @end */ /* @group Top-Level Structure */ nav { float: left; width: 260px; font-family: Helvetica, sans-serif; font-size: 14px; } main { display: block; margin: 0 2em 5em 260px; padding-left: 20px; min-width: 340px; font-size: 16px; } main h1, main h2, main h3, main h4, main h5, main h6 { font-family: Helvetica, sans-serif; } .table-of-contents main { margin-left: 2em; } #validator-badges { clear: both; margin: 1em 1em 2em; font-size: smaller; } /* @end */ /* @group navigation */ nav { margin-bottom: 1em; } nav .nav-section { margin-top: 2em; border-top: 2px solid #aaa; font-size: 90%; overflow: hidden; } nav h2 { margin: 0; padding: 2px 8px 2px 8px; background-color: #e8e8e8; color: #555; font-size: 125%; text-align: center; } nav h3, #table-of-contents-navigation { margin: 0; padding: 2px 8px 2px 8px; text-align: right; background-color: #e8e8e8; color: #555; } nav ul, nav dl, nav p { padding: 4px 8px 0; list-style: none; } #project-navigation .nav-section { margin: 0; border-top: 0; } #home-section h2 { text-align: center; } #table-of-contents-navigation { font-size: 1.2em; font-weight: bold; text-align: center; } #search-section { margin-top: 0; border-top: 0; } #search-field-wrapper { border-top: 1px solid #aaa; border-bottom: 1px solid #aaa; padding: 3px 8px; background-color: #e8e8e8; color: #555; } ul.link-list li { white-space: nowrap; line-height: 1.4em; } 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 Documentation Section */ main { color: #333; } main > h1:first-child, main > h2:first-child, main > h3:first-child, main > h4:first-child, main > h5:first-child, main > h6:first-child { margin-top: 0px; } main sup { vertical-align: super; font-size: 0.8em; } /* The heading with the class name */ main h1[class] { margin-top: 0; margin-bottom: 1em; font-size: 2em; color: #6C8C22; } main h1 { margin: 2em 0 0.5em; font-size: 1.7em; } main h2 { margin: 2em 0 0.5em; font-size: 1.5em; } main h3 { margin: 2em 0 0.5em; font-size: 1.2em; } main h4 { margin: 2em 0 0.5em; font-size: 1.1em; } main h5 { margin: 2em 0 0.5em; font-size: 1em; } main h6 { margin: 2em 0 0.5em; font-size: 1em; } main p { margin: 0 0 0.5em; line-height: 1.4em; } main pre { margin: 1.2em 0.5em; padding: 1em; font-size: 0.8em; } main hr { margin: 1.5em 1em; border: 2px solid #ddd; } main blockquote { margin: 0 2em 1.2em 1.2em; padding-left: 0.5em; border-left: 2px solid #ddd; } main ol, main ul { margin: 1em 2em; } main li > p { margin-bottom: 0.5em; } main dl { margin: 1em 0.5em; } main dt { margin-bottom: 0.5em; font-weight: bold; } main dd { margin: 0 1em 1em 0.5em; } main header h2 { margin-top: 2em; border-width: 0; border-top: 4px solid #bbb; font-size: 130%; } main header h3 { margin: 2em 0 1.5em; border-width: 0; border-top: 3px solid #bbb; font-size: 120%; } .documentation-section-title { position: relative; } .documentation-section-title .section-click-top { position: absolute; top: 6px; left: 12px; font-size: 10px; color: #9b9877; visibility: hidden; padding-left: 0.5px; } .documentation-section-title:hover .section-click-top { visibility: visible; } .constants-list > dl { margin: 1em 0 2em; border: 0; } .constants-list > dl dt { margin-bottom: 0.75em; padding-left: 0; font-family: "Source Code Pro", Monaco, monospace; font-size: 110%; } .constants-list > dl dt a { color: inherit; } .constants-list > dl dd { margin: 0 0 2em 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 */ main .method-source-code { display: none; } main .method-description .method-calls-super { color: #333; font-weight: bold; } main .method-detail { margin-bottom: 2.5em; cursor: pointer; } main .method-detail:target { margin-left: -10px; border-left: 10px solid #f1edba; } main .method-heading { position: relative; font-family: "Source Code Pro", Monaco, monospace; font-size: 110%; font-weight: bold; color: #333; } main .method-heading :link, main .method-heading :visited { color: inherit; } main .method-click-advice { position: absolute; top: 2px; right: 5px; font-size: 12px; color: #9b9877; visibility: hidden; padding-right: 20px; line-height: 20px; background: url(images/zoom.png) no-repeat right top; } main .method-heading:hover .method-click-advice { visibility: visible; } main .method-alias .method-heading { color: #666; } main .method-description, main .aliases { margin-top: 0.75em; color: #333; } main .aliases { padding-top: 4px; font-style: italic; cursor: default; } main .method-description ul { margin-left: 1.5em; } main #attribute-method-details .method-detail:hover { background-color: transparent; cursor: default; } main .attribute-access-type { text-transform: uppercase; padding: 0 1em; } /* @end */ /* @end */ /* @group Source Code */ pre { margin: 0.5em 0; border: 1px dashed #999; padding: 0.5em; background: #262626; color: white; overflow: auto; } .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; background: transparent; } .ruby-regexp { color: #ffa07a; background: transparent; } .ruby-value { color: #7fffd4; background: transparent; } /* @end */ /* @group search results */ #search-results { font-family: Lato, sans-serif; font-weight: 300; } #search-results .search-match { font-family: Helvetica, sans-serif; font-weight: normal; } #search-results .search-selected { background: #e8e8e8; border-bottom: 1px solid transparent; } #search-results li { list-style: none; border-bottom: 1px solid #aaa; 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; font-family: "Source Code Pro", Monaco, monospace; } /* @end */ mcollective-2.12.1/doc/CONTRIBUTING_md.html0000644005276200011600000002525613265671734020045 0ustar jenkinsjenkins CONTRIBUTING - mcollective version 2.12.1

How to contribute

Third-party patches are essential for keeping Puppet open-source projects great. We want to keep it as easy as possible to contribute changes that allow you to get the most out of our projects. There are a few guidelines that we need contributors to follow so that we can have a chance of keeping on top of things. For more info, see our canonical guide to contributing:

github.com/puppetlabs/puppet/blob/master/CONTRIBUTING.md

mcollective-2.12.1/doc/images/0000755005276200011600000000000013265671736016025 5ustar jenkinsjenkinsmcollective-2.12.1/doc/images/bug.png0000644005276200011600000000140613265671731017304 0ustar jenkinsjenkins‰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.12.1/doc/images/find.png0000644005276200011600000000122313265671731017444 0ustar jenkinsjenkins‰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ž2hZÒ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.12.1/doc/images/bullet_black.png0000644005276200011600000000032313265671731021147 0ustar jenkinsjenkins‰PNG  IHDRµú7êgAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<eIDAT(ÏcøÏ€2 .ŠRŠ òWe®Š7JaWÐõ¿ëÛÿäÿ\ XÈ]íúŸþ?öãæ«XH^íøŸô?ìÝì „þ7¥Ãÿ3`·‚[Š­á*60H ÚÄÑþs–õàVIEND®B`‚mcollective-2.12.1/doc/images/wrench.png0000644005276200011600000000114213265671731020012 0ustar jenkinsjenkins‰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.12.1/doc/images/page_white_width.png0000644005276200011600000000046513265671731022046 0ustar jenkinsjenkins‰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.12.1/doc/images/loadingAnimation.gif0000644005276200011600000001337613265671731021776 0ustar jenkinsjenkinsGIF89aÐ Äûûû÷÷÷óóóïïïêêêæææâââÞÞÞÚÚÚÖÖÖÒÒÒÎÎÎÊÊÊÅÅÅÁÁÁ½½½¹¹¹µµµ±±±©©©ÿÿÿ!ÿ 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.12.1/doc/images/brick_link.png0000644005276200011600000000137413265671731020642 0ustar jenkinsjenkins‰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.12.1/doc/images/plugin.png0000644005276200011600000000111713265671731020024 0ustar jenkinsjenkins‰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¯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.12.1/doc/images/zoom.png0000644005276200011600000000126413265671731017515 0ustar jenkinsjenkins‰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.12.1/doc/images/brick.png0000644005276200011600000000070413265671731017621 0ustar jenkinsjenkins‰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.12.1/doc/images/tag_blue.png0000644005276200011600000000353013265671731020311 0ustar jenkinsjenkins‰PNG  IHDRóÿagAMA¯È7ŠéiCCPICC Profilex…TßkÓPþÚe°á‹:g >h‘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.12.1/doc/images/bullet_toggle_minus.png0000644005276200011600000000031713265671731022572 0ustar jenkinsjenkins‰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.12.1/doc/images/page_green.png0000644005276200011600000000115513265671731020624 0ustar jenkinsjenkins‰PNG  IHDRóÿagAMA¯È7ŠétEXtSoftwareAdobe ImageReadyqÉe<ÿIDATÁ?Œßsàçýù~~¿ëõêTI¤!!âbFÄ ,H8ƒ„Éb±1a5Úl’ ’NƒQBË b`‘^8MµAôî~ßïçåy*‰çÞ¿ôÆöîÖë­\À)(¤È0ñëkù¢þ¾þÞåý½€Jâž¹üÉÛi­Î†IH ®þ»qñ»þøíø¿Ÿ~øùÜ×û{ÇСMõкOgüÓÔª(2X2ÌsÜÜ,ž}à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.12.1/doc/images/macFFBgHack.png0000644005276200011600000000031713265671731020543 0ustar jenkinsjenkins‰PNG  IHDRÄé…csBIT|dˆ pHYs  ÒÝ~ütEXtSoftwareAdobe Fireworks CS3˜ÖFtEXtCreation Time7/16/07Z§ÿ (IDATH‰íÍA0!´§U[ Gϱ½JJJJJJJJJÀYÑñüŸ…IEND®B`‚mcollective-2.12.1/doc/images/delete.png0000644005276200011600000000131313265671731017766 0ustar jenkinsjenkins‰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.12.1/doc/images/wrench_orange.png0000644005276200011600000000111013265671731021340 0ustar jenkinsjenkins‰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.12.1/doc/images/bullet_toggle_plus.png0000644005276200011600000000032113265671731022415 0ustar jenkinsjenkins‰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.12.1/doc/images/arrow_up.png0000644005276200011600000000056413265671731020371 0ustar jenkinsjenkins‰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.12.1/doc/images/tag_green.png0000644005276200011600000000114513265671731020462 0ustar jenkinsjenkins‰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.12.1/doc/images/add.png0000644005276200011600000000133513265671731017260 0ustar jenkinsjenkins‰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.12.1/doc/Beaker.html0000644005276200011600000000426113265671731016635 0ustar jenkinsjenkins module Beaker - mcollective version 2.12.1

module Beaker

mcollective-2.12.1/doc/Beaker/0000755005276200011600000000000013265671736015751 5ustar jenkinsjenkinsmcollective-2.12.1/doc/Beaker/DSL.html0000644005276200011600000000431613265671731017260 0ustar jenkinsjenkins module Beaker::DSL - mcollective version 2.12.1

module Beaker::DSL

mcollective-2.12.1/doc/Rakefile.html0000644005276200011600000005011213265671734017165 0ustar jenkinsjenkins Rakefile - mcollective version 2.12.1

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 %Q{/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 %Q{/usr/bin/rpm --sign pkg/   *.rpm}
end
move_artifacts

end

desc “Run spec tests†task :test do

sh "cd spec && rake"

end

desc “Run spec tests†task :test => :spec

namespace :ci do

desc "Run the specs with CI options"
task :spec do
  ENV["LOG_SPEC_ORDER"] = "true"
  sh %Q{rspec -r yarjuf -f JUnit -o result.xml -fp spec}
end

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

desc ‘run static analysis with rubocop’ task(:rubocop) do

if RUBY_VERSION !~ /1.8/
  require 'rubocop'
  cli = RuboCop::CLI.new
  exit cli.run(%w(-D -f s))
else
  puts "Rubocop is disabled in ruby 1.8"
end

end

mcollective-2.12.1/doc/mcollective_init.html0000644005276200011600000003233513265671735021004 0ustar jenkinsjenkins mcollective.init - mcollective version 2.12.1

#!/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.12.1/doc/String.html0000644005276200011600000001252513265671734016717 0ustar jenkinsjenkins class String - mcollective version 2.12.1

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.12.1/doc/index.html0000644005276200011600000005572713265671731016570 0ustar jenkinsjenkins mcollective version 2.12.1

This is the API documentation for mcollective version 2.12.1.

mcollective-2.12.1/doc/acceptance/0000755005276200011600000000000013265671736016646 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/README_md.html0000644005276200011600000004036013265671735021153 0ustar jenkinsjenkins README - mcollective version 2.12.1

beaker tests for validation of MCollective in puppet-agent

WARNING

WARNING: Under NO circumstances should you use any of the certificate files located in the /acceptance directory or any of its subdirectories in a production system. The private keys are publicly available and will result in an insecure environment

Files directory

/files contains pre-generated certificates and configuration files that are used by the acceptance test pre-suites in order to quickly facilitate a running environment on the system under test. The certificates in the /files directory are for testing purposes only and are publicly available.

These files were generated using the command outlined below, in the SSL setup section.

SSL setup

/ssl is a puppet master’s ssl directory. Selected files from this have been copied into the files/ directory, either directly as .pem files, or combined into java truststores.

Commands used to set it up:

puppet master --ssldir=`pwd`/ssl
puppet cert --ssldir=`pwd`/ssl generate activemq
puppet cert --ssldir=`pwd`/ssl generate mcollective-client
puppet cert --ssldir=`pwd`/ssl generate mcollective-server

keytool -storepasswd -storepass notsecret -import -alias 'puppet ca' -file ssl/ca/ca_crt.pem -keystore files/activemq.truststore
cat ssl/private_keys/activemq.pem ssl/certs/activemq.pem > activemq.combined.pem
openssl pkcs12 -password pass:notsecret -export -in activemq.combined.pem -out activemq.p12 -name activemq.example.com
keytool -importkeystore -destkeystore files/activemq.keystore -deststorepass notsecret -srckeystore activemq.p12 -srcstoretype PKCS12 -srcstorepass notsecret -alias activemq.example.com
rm activemq.combined.pem activemq.p12

cp ssl/ca/ca_crt.pem files/ca_crt.pem
cp ssl/certs/mcollective-server.pem files/server.crt
cp ssl/private_keys/mcollective-server.pem files/server.key
cp ssl/certs/mcollective-client.pem files/client.crt
cp ssl/private_keys/mcollective-client.pem files/client.key

Running with Rake

The rake task ci:test:aio will provision, install, and execute the tests for you. It requires the environment variable for the sha of the puppet-agent version that should be installed during testing.

The minimal invocation of this task would be: bundle exec rake ci:test:aio SHA=1.8.2

Typically, this task would be invoked against a development build with a specific host target in mind. Given that we would like to test the MCO functionality in the latest nightly build of puppet-agent on an Ubuntu 16.04 x86_64 instance, the command would be: bundle exec rake ci:test:aio SHA=nightly TEST_TARGET=ubuntu1604-64mco_master.a

Environment variables

The following environment variables are used in conjunction with the ci:test:aio rake task:

SHA required : Build identifier of puppet-agent version to be installed, release tag or full SHA, (e.g. nightly, 1.8.2, aa3068e6859a695167a4b7ac06584b4d4ace525f).

SUITE_VERSION : If the SHA used is a development build, then this variable must be specified, (e.g. 1.8.2.62.gaa3068e).

TEST_TARGET : Beaker-hostgenerator string used to dynamically create a Beaker hosts file. The mco_master role must be part of this string. If left unspecified, this will default to windows2012r2-64mco_master.a.

MASTER_TEST_TARGET : Beaker-hostgenerator string used to dynamically create a Beaker hosts file. If unspecified, this will default to redhat7-64ma.

BEAKER_HOSTS : Path to an existing Beaker hosts file to be used.

ACTIVEMQ_SOURCE : A url from which to download pre-built ActiveMQ binaries. Together with ACTIVEMQ_VERSION, specifies where to get binaries. The default uses an internal Puppet mirror, externally you should use apache.osuosl.org. Note that osuosl only hosts the latest releases. Setting this will attempt to fetch a package from $ACTIVEMQ_SOURCE/activemq/$ACTIVEMQ_VERSION/apache-activemq-$ACTIVEMQ_VERSION-bin.tar.gz (or .zip on Windows).

ACTIVEMQ_VERSION : The version of ActiveMQ requested.

JDK_SOURCE : A url from which to download pre-built JDK binaries for Windows (other platforms use the package manager for the OS). Together with JDK_VERSION_FULL, specifies where to get binaries. The default uses an internal Puppet mirror, externally you should use download.oracle.com/otn-pub/java/jdk

JDK_VERSION_FULL : The full version of JDK requested. From Oracle, this should include the build number (e.g. 8u111-b14). See the Oracle JDK downoad page for details.

mcollective-2.12.1/doc/acceptance/config/0000755005276200011600000000000013265671736020113 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/config/nodes/0000755005276200011600000000000013265671736021223 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/config/nodes/win2008r2-rubyx64_yaml.html0000644005276200011600000002636013265671735026035 0ustar jenkinsjenkins win2008r2-rubyx64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2008r2-x86_64-rubyx64:
  roles:
  - agent
  - mco_master
  platform: windows-2008r2-64
  ruby_arch: x64
  hypervisor: vcloud
  template: win-2008r2-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/debian-6-x86_64_yaml.html0000644005276200011600000002630713265671735025463 0ustar jenkinsjenkins debian-6-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: debian-squeeze-x86_64
  hypervisor: vcloud
  template: debian-6-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/debian-7-x86_64_yaml.html0000644005276200011600000002630613265671735025463 0ustar jenkinsjenkins debian-7-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: debian-wheezy-x86_64
  hypervisor: vcloud
  template: debian-7-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2008r2-rubyx86_yaml.html0000644005276200011600000002636013265671735026041 0ustar jenkinsjenkins win2008r2-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2008r2-x86_64-rubyx86:
  roles:
  - agent
  - mco_master
  platform: windows-2008r2-64
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2008r2-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/ubuntu-1204-x86_64_yaml.html0000644005276200011600000002632013265671735025777 0ustar jenkinsjenkins ubuntu-1204-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: ubuntu-precise-x86_64
  hypervisor: vcloud
  template: ubuntu-1204-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2008-rubyx64_yaml.html0000644005276200011600000002634613265671735025575 0ustar jenkinsjenkins win2008-rubyx64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2008-x86_64-rubyx64:
  roles:
  - agent
  - mco_master
  platform: windows-2008-64
  ruby_arch: x64
  hypervisor: vcloud
  template: win-2008-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/redhat-7-x86_64_yaml.html0000644005276200011600000002627513265671735025515 0ustar jenkinsjenkins redhat-7-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2003x64-rubyx64_yaml.html0000644005276200011600000002635413265671735026131 0ustar jenkinsjenkins win2003x64-rubyx64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2003-x86_64-rubyx64:
  roles:
  - agent
  - mco_master
  platform: windows-2003-64
  ruby_arch: x64
  hypervisor: vcloud
  template: win-2003-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/fedora-20-x86_64_yaml.html0000644005276200011600000002630513265671735025553 0ustar jenkinsjenkins fedora-20-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: fedora-20-x86_64
  hypervisor: vcloud
  template: fedora-20-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2012-rubyx86_yaml.html0000644005276200011600000002634613265671735025574 0ustar jenkinsjenkins win2012-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2012-x86_64-rubyx86:
  roles:
  - agent
  - mco_master
  platform: windows-2012-64
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2012-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2012-rubyx64_yaml.html0000644005276200011600000002634613265671735025570 0ustar jenkinsjenkins win2012-rubyx64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2012-x86_64-rubyx64:
  roles:
  - agent
  - mco_master
  platform: windows-2012-64
  ruby_arch: x64
  hypervisor: vcloud
  template: win-2012-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/fedora-21-x86_64_yaml.html0000644005276200011600000002630513265671735025554 0ustar jenkinsjenkins fedora-21-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: fedora-21-x86_64
  hypervisor: vcloud
  template: fedora-21-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/redhat-6-x86_64_yaml.html0000644005276200011600000002627513265671735025514 0ustar jenkinsjenkins redhat-6-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: el-6-x86_64
  hypervisor: vcloud
  template: redhat-6-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/redhat-5-x86_64_yaml.html0000644005276200011600000002627513265671735025513 0ustar jenkinsjenkins redhat-5-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: el-5-x86_64
  hypervisor: vcloud
  template: redhat-5-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/ubuntu-1404-x86_64_yaml.html0000644005276200011600000002631713265671735026007 0ustar jenkinsjenkins ubuntu-1404-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: ubuntu-trusty-x86_64
  hypervisor: vcloud
  template: ubuntu-1404-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2008-rubyx86_yaml.html0000644005276200011600000002634613265671735025601 0ustar jenkinsjenkins win2008-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2008-x86_64-rubyx86:
  roles:
  - agent
  - mco_master
  platform: windows-2008-64
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2008-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2003r2x86-rubyx86_yaml.html0000644005276200011600000002635213265671735026403 0ustar jenkinsjenkins win2003r2x86-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2003r2-i386:
  roles:
  - agent
  - mco_master
  platform: windows-2003r2-32
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2003r2-i386

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2012r2-rubyx64_yaml.html0000644005276200011600000002636013265671735026030 0ustar jenkinsjenkins win2012r2-rubyx64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2012r2-x86_64-rubyx64:
  roles:
  - agent
  - mco_master
  platform: windows-2012r2-64
  ruby_arch: x64
  hypervisor: vcloud
  template: win-2012r2-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2003x86-rubyx86_yaml.html0000644005276200011600000002634013265671735026134 0ustar jenkinsjenkins win2003x86-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2003-i386:
  roles:
  - agent
  - mco_master
  platform: windows-2003-32
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2003-i386

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2003x64-rubyx86_yaml.html0000644005276200011600000002635413265671735026135 0ustar jenkinsjenkins win2003x64-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2003-x86_64-rubyx86:
  roles:
  - agent
  - mco_master
  platform: windows-2003-64
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2003-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/ubuntu-1410-x86_64_yaml.html0000644005276200011600000002631713265671735026004 0ustar jenkinsjenkins ubuntu-1410-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: ubuntu-utopic-x86_64
  hypervisor: vcloud
  template: ubuntu-1410-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2003r2x64-rubyx86_yaml.html0000644005276200011600000002636613265671735026404 0ustar jenkinsjenkins win2003r2x64-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2003r2-x86_64-rubyx86:
  roles:
  - agent
  - mco_master
  platform: windows-2003r2-64
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2003r2-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/debian-8-x86_64_yaml.html0000644005276200011600000002630613265671735025464 0ustar jenkinsjenkins debian-8-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: debian-jessie-x86_64
  hypervisor: vcloud
  template: debian-8-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2003r2x64-rubyx64_yaml.html0000644005276200011600000002636613265671735026400 0ustar jenkinsjenkins win2003r2x64-rubyx64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2003r2-x86_64-rubyx64:
  roles:
  - agent
  - mco_master
  platform: windows-2003r2-64
  ruby_arch: x64
  hypervisor: vcloud
  template: win-2003r2-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/centos-5-x86_64_yaml.html0000644005276200011600000002630113265671735025525 0ustar jenkinsjenkins centos-5-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: centos-5-x86_64
  hypervisor: vcloud
  template: centos-5-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/centos-6-x86_64_yaml.html0000644005276200011600000002630113265671735025526 0ustar jenkinsjenkins centos-6-x86_64.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent:
  roles:
  - agent
  - mco_master
  platform: centos-6-x86_64
  hypervisor: vcloud
  template: centos-6-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/config/nodes/win2012r2-rubyx86_yaml.html0000644005276200011600000002636013265671735026034 0ustar jenkinsjenkins win2012r2-rubyx86.yaml - mcollective version 2.12.1

HOSTS:

master:
  roles:
  - master
  platform: el-7-x86_64
  hypervisor: vcloud
  template: redhat-7-x86_64
agent-2012r2-x86_64-rubyx86:
  roles:
  - agent
  - mco_master
  platform: windows-2012r2-64
  ruby_arch: x86
  hypervisor: vcloud
  template: win-2012r2-x86_64

CONFIG:

datastore: instance0
resourcepool: delivery/Quality Assurance/FOSS/Dynamic
folder: Delivery/Quality Assurance/FOSS/Dynamic
pooling_api: http://vmpooler.delivery.puppetlabs.net/
mcollective-2.12.1/doc/acceptance/Rakefile.html0000644005276200011600000012233013265671735021256 0ustar jenkinsjenkins Rakefile - mcollective version 2.12.1

require ‘rake/clean’ require ‘pp’ require ‘yaml’ require ‘securerandom’ require ‘fileutils’ require ‘beaker-hostgenerator’

$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), ‘lib’)) require ‘puppet/acceptance/git_utils’ extend Puppet::Acceptance::GitUtils

ONE_DAY_IN_SECS = 24 * 60 * 60 REPO_CONFIGS_DIR = “repo-configs†CLEAN.include(‘*.tar’, REPO_CONFIGS_DIR, ‘merged_options.rb’)

# If elsewhere we’re depending on internal network resources # then assume we can depend on them here EPEL_MIRROR = ENV

# Default test target if none specified DEFAULT_MASTER_TEST_TARGET = ‘redhat7-64m’ DEFAULT_TEST_TARGETS = “#{DEFAULT_MASTER_TEST_TARGET}a-windows2012r2-64mco_master.a†module HarnessOptions

DEFAULTS = {
  :type => 'git',
  :helper => ['lib/helper.rb'],
  :tests  => ['tests'],
  :log_level => 'debug',
  :color => false,
  :root_keys => true,
  :ssh => {
    :keys => ["id_rsa_acceptance", "#{ENV['HOME']}/.ssh/id_rsa-acceptance"],
  },
  :xml => true,
  :timesync => false,
  :repo_proxy => true,
  :add_el_extras => true,
  :preserve_hosts => 'onfail',
  :forge_host => 'forge-aio01-petest.puppetlabs.com',
  :'master-start-curl-retries' => 30,
}

class Aggregator
  attr_reader :mode

  def initialize(mode)
    @mode = mode
  end

  def get_options(file_path)
    puts file_path
    if File.exists? file_path
      options = eval(File.read(file_path), binding)
    else
      puts "No options file found at #{File.expand_path(file_path)}"
    end
    options || {}
  end

  def get_mode_options
    get_options("./config/#{mode}/options.rb")
  end

  def get_local_options
    get_options("./local_options.rb")
  end

  def final_options(intermediary_options = {})
    mode_options = get_mode_options
    local_overrides = get_local_options
    final_options = DEFAULTS.merge(mode_options)
    final_options.merge!(intermediary_options)
    final_options.merge!(local_overrides)
    return final_options
  end
end

def self.options(mode, options)
  final_options = Aggregator.new(mode).final_options(options)
  final_options
end

end

def beaker_test(mode = :packages, options = {})

delete_options = options.delete(:__delete_options__) || []
final_options = HarnessOptions.options(mode, options)
preserve_config = final_options.delete(:__preserve_config__)

if mode == :git
  # Build up project git urls based on git server and fork env variables or defaults
  final_options[:install].map! do |install|
    raise(ArgumentError, "Missing Git URL within options hash. Install URL is nil.") if install.nil?
    if md = /^(\w+)#(\w+)$/.match(install)
      project, project_sha = md.captures
      "#{build_giturl(project)}##{project_sha}"
    elsif md = /^(\w+)$/.match(install)
      project = md[1]
      "#{build_giturl(project)}##{sha}"
    else
      install
    end
  end
end

delete_options.each do |delete_me|
  final_options.delete(delete_me)
end

options_file = 'merged_options.rb'
File.open(options_file, 'w') do |merged|
  merged.puts <<-EOS

# Copy this file to local_options.rb and adjust as needed if you wish to run # with some local overrides. EOS

  merged.puts(final_options.pretty_inspect)
end

tests = ENV['TESTS'] || ENV['TEST']
tests_opt = "--tests=#{tests}" if tests

target = ENV['TEST_TARGET']
if config and File.exists?(config)
  config_opt = "--hosts=#{config}"
else
  if agent_target = ENV['TEST_TARGET']
    master_target = ENV['MASTER_TEST_TARGET'] || DEFAULT_MASTER_TEST_TARGET
    targets = "#{master_target}-#{agent_target}"
  else
    targets = DEFAULT_TEST_TARGETS
  end

  if !targets.include? 'mco_master'
    targets = targets.gsub( /([^\d]+)$/, 'mco_master.\1')
  end

  cli = BeakerHostGenerator::CLI.new([targets, '--disable-default-role', '--osinfo-version', '1'])
  ENV['BEAKER_HOSTS'] = "tmp/#{targets}-#{SecureRandom.uuid}.yaml"
  FileUtils.mkdir_p('tmp')
  File.open(config, 'w') do |fh|
    fh.print(cli.execute)
  end
  config_opt = "--hosts=#{config}"
end

overriding_options = ENV['OPTIONS']

args = ["--options-file", options_file, config_opt, tests_opt, overriding_options].compact

begin
  sh("beaker", *args)
ensure
  preserve_configuration(final_options, options_file) if preserve_config
end

end

def preserve_configuration(final_options, options_file)

if (hosts_file = config || final_options[:hosts_file]) && hosts_file !~ /preserved_config/
  cp(hosts_file, "log/latest/config.yml")
  generate_config_for_latest_hosts
end
mv(options_file, "log/latest")

end

def generate_config_for_latest_hosts

preserved_config_hash = { 'HOSTS' => {} }

puts "\nPreserving configuration so that any preserved nodes can be tested again locally..."

config_hash = YAML.load_file('log/latest/config.yml')
if !config_hash || !config_hash.include?('HOSTS')
  puts "Warning: No HOSTS configuration found in log/latest/config.yml"
  return
else
  nodes = config_hash['HOSTS'].map do |node_label,hash|
    {
      :node_label => node_label,
      :roles => hash['roles'],
      :platform => hash['platform']
    }
  end

  pre_suite_log = File.read('log/latest/pre_suite-run.log')
  nodes.each do |node_info|
    host_regex = /^([\w.]+) \(#{node_info[:node_label]}\)/
    if matched = host_regex.match(pre_suite_log)
      hostname = matched[1]
      fqdn = (hostname =~ /\./) ?
        hostname :
        "#{hostname}.delivery.puppetlabs.net"
    elsif /^#{node_info[:node_label]} /.match(pre_suite_log)
      fqdn = "#{node_info[:node_label]}"
      puts "* Couldn't find any log lines for #{host_regex}, assuming #{fqdn} is the fqdn"
    end
    if fqdn
      preserved_config_hash['HOSTS'][fqdn] = {
        'roles' => node_info[:roles],
        'platform' => node_info[:platform],
      }
    else
      puts "* Couldn't match #{node_info[:node_label]} in pre_suite-run.log"
    end
  end
  pp preserved_config_hash

  File.open('log/latest/preserved_config.yaml', 'w') do |config_file|
    YAML.dump(preserved_config_hash, config_file)
  end
end

rescue Errno::ENOENT => e

puts "Warning: Couldn't generate preserved_config.yaml #{e}"

end

def list_preserved_configurations(secs_ago = ONE_DAY_IN_SECS)

preserved = {}
Dir.glob('log  _*').each do |dir|
  preserved_config_path = "#{dir}/preserved_config.yaml"
  yesterday = Time.now - secs_ago.to_i
  if preserved_config = File.exists?(preserved_config_path)
    directory = File.new(dir)
    if directory.ctime > yesterday
      hosts = []
      preserved_config = YAML.load_file(preserved_config_path).to_hash
      preserved_config['HOSTS'].each do |hostname,values|
        hosts << "#{hostname}: #{values['platform']}, #{values['roles']}"
      end
      preserved[hosts] = directory.to_path
    end
  end
end
preserved.map { |k,v| [v,k] }.sort { |a,b| a[0] <=> b[0] }.reverse

end

def list_preserved_hosts(secs_ago = ONE_DAY_IN_SECS)

hosts = Set.new
Dir.glob('log/   pre*suite*run.log').each do |log|
  yesterday = Time.now - secs_ago.to_i
  File.open(log, 'r') do |file|
    if file.ctime > yesterday
      file.each_line do |line|
        matchdata = /^(\w+)(?:\.[\w.]+)? \(.*?\) \d\d:\d\d:\d\d\$/.match(line.encode!('UTF-8', 'UTF-8', :invalid => :replace))
        hosts.add(matchdata[1]) if matchdata
      end
    end
  end
end
hosts

end

def release_hosts(hosts = nil, secs_ago = ONE_DAY_IN_SECS)

secs_ago ||= ONE_DAY_IN_SECS
hosts ||= list_preserved_hosts(secs_ago)

hosts.each do |h|
  hostname = h.split('.').first
  puts "Releaseing '#{hostname}'"
  puts %x`curl -X DELETE --url http://vcloud.delivery.puppetlabs.net/vm/#{hostname}`
end

end

def print_preserved(preserved)

preserved.each_with_index do |entry,i|
  puts "##{i}: #{entry[0]}"
  entry[1].each { |h| puts "  #{h}" }
end

end

def beaker_run_type

type = ENV['TYPE'] || :packages
type = type.to_sym

end

def sha

ENV['SHA']

end

def config

ENV['BEAKER_HOSTS']

end

namespace :ci do

task :check_env do
  raise(USAGE) unless sha
end

namespace :test do
  USAGE = <<-EOS

Requires commit SHA to be put under test as environment variable: SHA=‘<sha>’. Also must set BEAKER_HOSTS=config/nodes/foo.yaml or include it in an options.rb for Beaker, or specify TEST_TARGET in a form beaker-hostgenerator accepts, e.g. `ubuntu1604-64a`. The TEST_TARGET must include the `mco_master` role, e.g. `ubuntu1604-64mco_master.a`. You may override the default master test target by specifying MASTER_TEST_TARGET. You may set TESTS=path/to/test,and/more/tests. You may set additional Beaker OPTIONS=‘–more –options’ If testing from git checkouts, you may optionally set the github fork to checkout from using PUPPET_FORK=‘some-other-puppet-fork’ (you may change the HIERA_FORK and FACTER_FORK as well if you wish). You may also optionally set the git server to checkout repos from using GIT_SERVER=‘some.git.mirror’. Or you may set PUPPET_GIT_SERVER=‘my.host.with.git.daemon’, specifically, if you have set up a `git daemon` to pull local commits from. (You will need to allow the git daemon to serve the repo (see `git help daemon` and the docs/acceptance_tests.md for more details)). If there is a Beaker options hash in a ./local_options.rb, it will be included. Commandline options set through the above environment variables will override settings in this file. EOS

desc <<-EOS

Run the acceptance tests through Beaker and install packages on the configuration targets. #{USAGE} EOS

task :packages => 'ci:check_env' do
  beaker_test
end

desc <<-EOS

Run the acceptance tests through Beaker and install packages as part of the AIO puppet-agent installation. #{USAGE} EOS

task :aio => 'ci:check_env' do
  beaker_test(:aio)
end

desc <<-EOS

Run the acceptance tests through Beaker and install packages as part of the AIO puppet-agent installation, testing against puppet-master-passenger. #{USAGE} EOS

task :passenger => 'ci:check_env' do
  beaker_test(:passenger)
end

desc <<-EOS

Run the acceptance tests through Beaker and install from git on the configuration targets. #{USAGE} EOS

  task :git => 'ci:check_env' do
    beaker_test(:git)
  end
end

desc "Capture the master and agent hostname from the latest log and construct a preserved_config.yaml for re-running against preserved hosts without provisioning."
task :extract_preserved_config do
  generate_config_for_latest_hosts
end

desc <<-EOS

Run an acceptance test for a given node configuration and preserve the hosts. Defaults to a packages run, but you can set it to ‘git’ with TYPE=‘git’. #{USAGE}

EOS
task :test_and_preserve_hosts => 'ci:check_env'  do
  beaker_test(beaker_run_type, :preserve_hosts => 'always', :__preserve_config__ => true)
end

desc "List acceptance runs from the past day which had hosts preserved."
task :list_preserved do
  preserved = list_preserved_configurations
  print_preserved(preserved)
end

desc <<-EOS

Shutdown and destroy any hosts that we have preserved for testing. These should be reaped daily by scripts, but this will free up resources immediately. Specify a list of comma separated HOST_NAMES if you have a set of dynamic vcloud host names you want to purge outside of what can be grepped from the logs. You can go back through the last SECS_AGO logs. Default is one day ago in secs.

EOS
task :release_hosts do
  host_names = ENV['HOST_NAMES'].split(',') if ENV['HOST_NAMES']
  secs_ago = ENV['SECS_AGO']
  release_hosts(host_names, secs_ago)
end

task :destroy_preserved_hosts => 'ci:release_hosts' do
  puts "Note: we are now releasing hosts back to the vcloud pooling api rather than destroying them directly.  The rake task for this is ci:release_hosts"
end

desc <<-EOS

Rerun an acceptance test using the last captured preserved_config.yaml to skip provisioning. Or specify a CONFIG_NUMBER from `rake ci:list_preserved`. Defaults to a packages run, but you can set it to ‘git’ with TYPE=‘git’.

EOS
task :test_against_preserved_hosts do
  config_number = (ENV['CONFIG_NUMBER'] || 0).to_i
  preserved = list_preserved_configurations
  print_preserved(preserved)
  config_path = preserved[config_number][0]

  puts "Using ##{config_number}: #{config_path}"

  options = {
    :hosts_file => "#{config_path}/preserved_config.yaml",
    :no_provision => true,
    :preserve_hosts => 'always',
  }
  run_type = beaker_run_type
  if run_type == :packages
    options.merge!(:pre_suite => [
      'setup/packages/pre-suite/015_PackageHostsPresets.rb',
      'setup/packages/pre-suite/045_EnsureMasterStartedOnPassenger.rb',
    ])
  else
    options.merge!(:__delete_options__ => [:pre_suite])
  end
  beaker_test(beaker_run_type, options)
end

end

task :default do

sh('rake -T')

end

task :spec do

sh('rspec lib')

end

mcollective-2.12.1/doc/acceptance/files/0000755005276200011600000000000013265671736017750 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/files/activemq_xml.html0000644005276200011600000004651613265671735023342 0ustar jenkinsjenkins activemq.xml - mcollective version 2.12.1

<!–

Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements.  See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You 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.

–> <!– START SNIPPET: example –> <beans

xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://activemq.apache.org/schema/core http://activemq.apache.org/schema/core/activemq-core.xsd">

  <!-- Allows us to use system properties as variables in this configuration file -->
  <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
      <property name="locations">
          <value>file:${activemq.conf}/credentials.properties</value>
      </property>
  </bean>

  <!--
      The <broker> element is used to configure the ActiveMQ broker.
  -->
  <broker xmlns="http://activemq.apache.org/schema/core" brokerName="localhost" dataDirectory="${activemq.data}">

      <destinationPolicy>
          <policyMap>
            <policyEntries>
              <policyEntry topic=">" >
                  <!-- The constantPendingMessageLimitStrategy is used to prevent
                       slow topic consumers to block producers and affect other consumers
                       by limiting the number of messages that are retained
                       For more information, see:

                       http://activemq.apache.org/slow-consumer-handling.html

                  -->
                <pendingMessageLimitStrategy>
                  <constantPendingMessageLimitStrategy limit="1000"/>
                </pendingMessageLimitStrategy>
              </policyEntry>
            </policyEntries>
          </policyMap>
      </destinationPolicy>

      <!--
          The managementContext is used to configure how ActiveMQ is exposed in
          JMX. By default, ActiveMQ uses the MBean server that is started by
          the JVM. For more information, see:

          http://activemq.apache.org/jmx.html
      -->
      <managementContext>
          <managementContext createConnector="false"/>
      </managementContext>

      <!--
          Configure message persistence for the broker. The default persistence
          mechanism is the KahaDB store (identified by the kahaDB tag).
          For more information, see:

          http://activemq.apache.org/persistence.html
      -->
      <persistenceAdapter>
          <kahaDB directory="${activemq.data}/kahadb"/>
      </persistenceAdapter>

      <plugins>
        <statisticsBrokerPlugin/>

        <!--
          This configures the users and groups used by this broker. Groups
          are referenced below, in the write/read/admin attributes
          of each authorizationEntry element.
        -->
        <simpleAuthenticationPlugin>
          <users>
            <authenticationUser username="mcollective" password="marionette" groups="mcollective,everyone"/>
            <authenticationUser username="admin" password="secret" groups="mcollective,admins,everyone"/>
          </users>
        </simpleAuthenticationPlugin>

        <!--
          Configure which users are allowed to read and write where. Permissions
          are organized by group; groups are configured above, in the
          authentication plugin.

          With the rules below, both servers and admin users belong to group
          mcollective, which can both issue and respond to commands. For an
          example that splits permissions and doesn't allow servers to issue
          commands, see:
          https://docs.puppetlabs.com/mcollective/deploy/middleware/activemq.html#detailed-restrictions
        -->
        <authorizationPlugin>
          <map>
            <authorizationMap>
              <authorizationEntries>
                <authorizationEntry queue=">" write="admins" read="admins" admin="admins" />
                <authorizationEntry topic=">" write="admins" read="admins" admin="admins" />
                <authorizationEntry topic="mcollective.>" write="mcollective" read="mcollective" admin="mcollective" />
                <authorizationEntry queue="mcollective.>" write="mcollective" read="mcollective" admin="mcollective" />
                <!--
                  The advisory topics are part of ActiveMQ, and all users need access to them.
                  The "everyone" group is not special; you need to ensure every user is a member.
                -->
                <authorizationEntry topic="ActiveMQ.Advisory.>" read="everyone" write="everyone" admin="everyone"/>
              </authorizationEntries>
            </authorizationMap>
          </map>
        </authorizationPlugin>
      </plugins>

      <sslContext>
        <sslContext
            keyStore="activemq.keystore" keyStorePassword="notsecret"
            trustStore="activemq.truststore" trustStorePassword="notsecret"
            />
      </sslContext>

        <!--
          The systemUsage controls the maximum amount of space the broker will
          use before disabling caching and/or slowing down producers. For more information, see:
          http://activemq.apache.org/producer-flow-control.html
        -->
        <systemUsage>
          <systemUsage>
              <memoryUsage>
                  <memoryUsage percentOfJvmHeap="70" />
              </memoryUsage>
              <storeUsage>
                  <storeUsage limit="100 gb"/>
              </storeUsage>
              <tempUsage>
                  <tempUsage limit="50 gb"/>
              </tempUsage>
          </systemUsage>
      </systemUsage>

      <!--
          The transport connectors expose ActiveMQ over a given protocol to
          clients and other brokers. For more information, see:

          http://activemq.apache.org/configuring-transports.html
      -->
      <transportConnectors>
          <!-- DOS protection, limit concurrent connections to 1000 and frame size to 100MB -->
          <transportConnector name="openwire" uri="tcp://0.0.0.0:61616?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
          <transportConnector name="amqp" uri="amqp://0.0.0.0:5672?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
          <transportConnector name="stomp+ssl" uri="stomp+ssl://0.0.0.0:61613?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
          <transportConnector name="mqtt" uri="mqtt://0.0.0.0:1883?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
          <transportConnector name="ws" uri="ws://0.0.0.0:61614?maximumConnections=1000&amp;wireFormat.maxFrameSize=104857600"/>
      </transportConnectors>

      <!-- destroy the spring context on shutdown to stop jetty -->
      <shutdownHooks>
          <bean xmlns="http://www.springframework.org/schema/beans" class="org.apache.activemq.hooks.SpringContextHook" />
      </shutdownHooks>

  </broker>

  <!--
      Enable web consoles, REST and Ajax APIs and demos
      The web consoles requires by default login, you can disable this in the jetty.xml file

      Take a look at ${ACTIVEMQ_HOME}/conf/jetty.xml for more details
  -->
  <import resource="jetty.xml"/>

</beans> <!– END SNIPPET: example –>

mcollective-2.12.1/doc/acceptance/files/server_crt.html0000644005276200011600000003052213265671735023015 0ustar jenkinsjenkins server.crt - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFdDCCA1ygAwIBAgIBBTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjEyMTgzNVoXDTIwMDMwMTEyMTgz NVowHTEbMBkGA1UEAwwSbWNvbGxlY3RpdmUtc2VydmVyMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAmb8vabpZIw3WHUXqV6HkOjmKXz5IgjhXBg5/XA6A TGrvFlo5mj6W2JpE7CxI+DhHYZ4+Z5NLkzIAYoSXuV/0umuPpXOAH5ma5JYudzKd 5RV7RiffSj5LfzBnGZwn3dVVhaZiR6vUM4cmP8CqUqfoxvpweh9nGDbkEKoHdC4p ENerwvuheAnPGflonDZFxaWEqXn5oXxSiuf88XZ0tgn1mMBn/JF0rXtCKYFfOAr7 Evw1eUak+4wEHym26q+BCogKpeW+lT0C/v/TH5XG63ycyhmqiWjTI2vPab6BC7t2 Bmgd5Grcr7cofvt4QYwIsIwL6ZZWahyLynHMBs85AAm1bfPdnMeia5WP+J98euAJ 7MK8TuX4sEjHt/yAiXT75aD0rsViP9BkKstBudMALokywnMHLa0/KdJCwXP5JH/Z D8v4LJvpfGxDodC+tyUCsr82Wn67AAculvMZDjY6SD9LtaNrtiNZeNj2PXYBYWWs CQ4aR2LzelDPDZ3TUu0BqMjNQ07zz/Daol6DASuF1TLUv6YW2tLZ5nggt1rkARx6 m2BTEpa1Jl6j8KkE2l+7KR6EaUCwz3bqlvAweqY/8mnHrwhXkeeQ6Bta53o5YjXD WFXTmZD/iSlT8hbnWmoww/EgRjsQyXZ9dS3OelsAPYBTKTwXsKIGdVgj4t8Xd9CK gicCAwEAAaOBujCBtzA1BglghkgBhvhCAQ0EKFB1cHBldCBSdWJ5L09wZW5TU0wg SW50ZXJuYWwgQ2VydGlmaWNhdGUwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQW MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRm iqXTvKMtefbQHDRT3F9MVe9xtjAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7 nhV46zANBgkqhkiG9w0BAQsFAAOCAgEAWSNWAshqJtEeV1vfdlU581ppNmodUs7Q 6Ypdx0eot6S9lvTl69GFYonuOvwf8U6chs5Hp051oMB8M1PWzD7CZHj+wMwRKqvG PWymZUsZQHFpb01tnABLL62fyqxEnaVPqUyRwNQMsXoD6OiW495kY+o17i2k6oYF pE2ofyREQd40i3wGk7u0MlpWvSfdpcUgzVTTlruOuHh+E2HTf53PH3AuFDgqd8Ee +2JCO2hcAT7JXfmlxzE6XtbalnaorBnJD09vRR/MshppN6l/11+yMDg3CpfkARBJ OqSVLd8PD3BZm4jUWd3w7tBMs1WUYrtMtUKVGc2Ct4QyxCpi1bKKZRcrnROo3lLH ZZGEYo+19KpCff/kOoBiyqkim8SN9cdy5nzEllGsIj+72mJuqRhkh58tlrTBUDl1 8Sc1rRLZ+T6k2A/UWybkPMVFw+e1DFOtK8QvjwXPiZyNTDmf05uesgp8sJ81iebv 1llZu24x5gVobMHEeXGmKGGs6vquwTrs0/mAy4ujHhkEXZPRkrdv1uBt0sG969/5 0Bnk+Lq0xxGDbgTt+8TOpV++cE3dU6K3Fb7JCJT8S6dzd/78+T+m13maW6WKdiZc QAzFNkiw4D0qvdCoL8bu45P58tPFGdJtRbIQ83Ik1Ie6M8nXxCcq0qIttw83Od+H qDxqCwAZL8E= —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/files/windows-client_cfg.html0000644005276200011600000002655613265671735024440 0ustar jenkinsjenkins windows-client.cfg - mcollective version 2.12.1

main_collective = mcollective collectives = mcollective libdir = C:ProgramDataPuppetLabsmcollectiveetcplugins logger_type = console loglevel = warn

# Plugins securityprovider = ssl plugin.ssl_server_public = C:ProgramDataPuppetLabsmcollectiveetcserver.crt plugin.ssl_client_private = C:ProgramDataPuppetLabsmcollectiveetcclient.key plugin.ssl_client_public = C:ProgramDataPuppetLabsmcollectiveetcclient.pem

connector = activemq plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = activemq plugin.activemq.pool.1.port = 61613 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = marionette plugin.activemq.pool.1.ssl = true plugin.activemq.pool.1.ssl.ca = C:ProgramDataPuppetLabsmcollectiveetcca_crt.pem plugin.activemq.pool.1.ssl.cert = C:ProgramDataPuppetLabsmcollectiveetcclient.crt plugin.activemq.pool.1.ssl.key = C:ProgramDataPuppetLabsmcollectiveetcclient.key

connection_timeout = 3

mcollective-2.12.1/doc/acceptance/files/server_key.html0000644005276200011600000003313513265671735023020 0ustar jenkinsjenkins server.key - mcollective version 2.12.1

—–BEGIN RSA PRIVATE KEY—– MIIJKQIBAAKCAgEAmb8vabpZIw3WHUXqV6HkOjmKXz5IgjhXBg5/XA6ATGrvFlo5 mj6W2JpE7CxI+DhHYZ4+Z5NLkzIAYoSXuV/0umuPpXOAH5ma5JYudzKd5RV7Riff Sj5LfzBnGZwn3dVVhaZiR6vUM4cmP8CqUqfoxvpweh9nGDbkEKoHdC4pENerwvuh eAnPGflonDZFxaWEqXn5oXxSiuf88XZ0tgn1mMBn/JF0rXtCKYFfOAr7Evw1eUak +4wEHym26q+BCogKpeW+lT0C/v/TH5XG63ycyhmqiWjTI2vPab6BC7t2Bmgd5Grc r7cofvt4QYwIsIwL6ZZWahyLynHMBs85AAm1bfPdnMeia5WP+J98euAJ7MK8TuX4 sEjHt/yAiXT75aD0rsViP9BkKstBudMALokywnMHLa0/KdJCwXP5JH/ZD8v4LJvp fGxDodC+tyUCsr82Wn67AAculvMZDjY6SD9LtaNrtiNZeNj2PXYBYWWsCQ4aR2Lz elDPDZ3TUu0BqMjNQ07zz/Daol6DASuF1TLUv6YW2tLZ5nggt1rkARx6m2BTEpa1 Jl6j8KkE2l+7KR6EaUCwz3bqlvAweqY/8mnHrwhXkeeQ6Bta53o5YjXDWFXTmZD/ iSlT8hbnWmoww/EgRjsQyXZ9dS3OelsAPYBTKTwXsKIGdVgj4t8Xd9CKgicCAwEA AQKCAgEAjJJqdl/kehF/kHpJXmBt/PJ5WsXmo/GBV89PkUrM8ZHgEm7iNe4+G8NJ eyqueQ1z7oQyCJ97PRU9ltYmp15ds0j/KMZnAf1+yruptmB4T2mJscZo+Ufl3V/T FG5bYQ9aR32uZFtuBMLwLOAqmrJdOfjneDFYIKKnebkMYaSG9ZhLulY59zf4vIX2 qMVSm/jPR+l3Xbf+/HRKKDgnU/3dKRHawen4ZVnKT4qzFdmsYFprRfxagogtiJSq Yv+em5NxMOfTjj4fjCk5zqoyG0NvU7WN4F8QlVVQah29fY2jxw3RLvSp3Js5koi4 Fi5ED6+sgKFtV03MXogXhK5ZLniyAXBBMdHqUeIDulg4LLiIJaB3ZcWPYVz09n4W hr/L6RVGroFIGL62PV2dzoDOoJYuOla/qQr2kMVLjiq6+PPM23wY6K90ISynCqDV TbKLgyHSEFtcfPMdv9gLGEyUeXSrKoizvcRB0DpqFdgS+Advd76GJDQK/t9ZvSha eF3uMTjZYeMNY+BKdZ2XEXQ/VUwy9TapkCqvvvDJP4LRMMXtu2aLy3cLv7ose/Qv Lqyf2v9VDIWMpbaArGAQ/E8tqVCONsxfjs6GxiyODe2Q4dZcq5D8dalOsgK0hTCh qG3krm9EDlg6jBioEJSY6HCFWcIhKZwVIO5zvwrhKMj8x/kTeskCggEBAMxqhDs+ n9iiyqxA/TMgWYV4Vxdtz9UeWTT89vT8Anj+JIgTXNTiiMGiMJfCheuWgDUikpTW g2boK8W1UmYuZxOVcAFAx3OKmcusryg+zgZzophlLMVH/stfRS/K4xw5KPCTfRcF cNscOQ7Skvny9eX5rkdT3MXyuhMz2V+5jJRZbd/Ln5KrYU2iPF9ZHdwCv/eL+KBu 6WBWt2+U8077Ry1s33bCKGGJoW8TlncLRgcZ3o8m7wKuBK02FgfRr/UNOFCvzyNC tGbP/NKnY0AJ2Pxy3kWSiQWwXv45ZVKbwe8n8dBMyye8ziFjYphdKB5Pkh/NYJNM /44XmeuJMIWcmAsCggEBAMCLYjB+AvFwWrpuF5pkgEoBX/CYpY1zuJzUU+wfyf7r jo8V9me03a8FG/pNLIcb6Ilb15WFL5Pd2VBsZnlBdhFyz13ShpRmX8TZgarKtqF9 FWFET9BCUyz2vJByvpBiiwqhaEdB/LzHbWcJ0y7Dhiq4mmW4pameWJUYjpmJV9iN io5gsc+o1Me7ww/I24k3FWzU0yp8EiPmc65Qh5RiGuKE/Oj8OKSv0VpskYh0qFru VpyZb3JvssiO7Rv+VPz9qT5IQdmc+cwU/aI7EfZD19abbzXblDPLZI7ELWTwomB/ JdolC7AE5akPULZfR/Ox+2j5imxPdUp+W0mVoZrzo9UCggEBAIBg+RDYIL2GZ7DM 0/fy/iYD0PaG4brf53iO0m5Dgy4Htlu7hVaxut+ZA9mbsk2l5Hj6cIKHQlkzwKHX YZuI0vWKqaAv1dNrnXE7z3mAEBYfM2NwTzDLKWsCN0pvqjiEcYC9sBbwNNN3IyJ6 /xF5FBPNvjVPptyut0vuCTvEJVTZ4VZm5J3RgjemhzH+nvEYzQUj5A6l+W2mqGes bS4SQ750nNdR0tiG/lrrO1kiPGWjSpIvCnZtYcSCrk1U1KQbHF4YPAfSEchsIUUP GRcT2DGb1pptEB8BiHczBr5d0etn88c1I3WebvqrFzXHc7WtlYDhlCCOgAT2L5Ws QEv5b8cCggEATCeU+XsNrPL0X6JloYI4n4y1ppKpgC4SCa2NV+om2bXPV9am89O8 jpnlu/VENFgcfB+gQBpCfCiV6FJHoZP7n+EaFZDL++wV2uTwfV3aKDsx9puvOb7R 6QnaAQPxTWg8eELo3K5DyWLTaZqgNXHOIh2Lq8oBc/oWLTpeRGnnHda1w6SJD4pG Ro2EFD0sX7QyvC3dK4ORJTPj80EUruKyoX9aDMidmr2Tf0FLun3xjK2SAjRJuShm vR6St6y7bqjhhKnNqcWFo2t1+fGFJDMnLlGiBdpIXsgGiNUc4zyt7J69mO9oFQGK 2HgfQ1KiQcfWmWmBj19KE+GwN4WAOqo81QKCAQBZOJhwvmRZ1ajlSHvL7PLQfJ6O OQQZ3qw1yn+X+RJV3WQXacK/wXXDZ9l6ytpnUJwaamx3j6Vqk/wevM8LG7lXbXoV sYGw80K9R+j8CNZa0LWEeemvlynR3shUwNmJjLec3zD+T3zGsimv7SlnTOGr8/uM 8GaksGOdjigVcMFUJ/YHEwrAnz3LnahkD4ZK4y5SW3ERTD6LCjWprPAZoQZdpkvl DMJwVzaCH0yWnEXDq7QCYfEzp3Z+gr7ZWkNCpTKGGcBL8y1m+u0mwsIFbiG0Eb21 WM2I4CA+OhbHCv741ptBSTzgo9/d8jxjU2qiVjYNyIIY7bKdny3ldfVbXekc —–END RSA PRIVATE KEY—–

mcollective-2.12.1/doc/acceptance/files/client_cfg.html0000644005276200011600000002643713265671735022746 0ustar jenkinsjenkins client.cfg - mcollective version 2.12.1

main_collective = mcollective collectives = mcollective libdir = /opt/puppetlabs/mcollective/plugins logger_type = console loglevel = warn

# Plugins securityprovider = ssl plugin.ssl_server_public = /etc/puppetlabs/mcollective/server.crt plugin.ssl_client_private = /etc/puppetlabs/mcollective/client.key plugin.ssl_client_public = /etc/puppetlabs/mcollective/client.pem

connector = activemq plugin.activemq.pool.size = 1 plugin.activemq.pool.1.host = activemq plugin.activemq.pool.1.port = 61613 plugin.activemq.pool.1.user = mcollective plugin.activemq.pool.1.password = marionette plugin.activemq.pool.1.ssl = true plugin.activemq.pool.1.ssl.ca = /etc/puppetlabs/mcollective/ca_crt.pem plugin.activemq.pool.1.ssl.cert = /etc/puppetlabs/mcollective/client.crt plugin.activemq.pool.1.ssl.key = /etc/puppetlabs/mcollective/client.key

connection_timeout = 3

mcollective-2.12.1/doc/acceptance/files/ca_crt_pem.html0000644005276200011600000003051213265671735022732 0ustar jenkinsjenkins ca_crt.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFbTCCA1WgAwIBAgIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyMFoXDTIwMDMwMTExNTYy MFowITEfMB0GA1UEAwwWUHVwcGV0IENBOiBzb2Nrcy5sb2NhbDCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBAL0qDiSB8u/6dxihgLSycKXGMeH+xzASvIWk tsK6oPZ8vwBeEoz4A1iw6Lf7Xz9G9qUx0NlmLryN7HLFmidQ4aTwgYE/+/Q/xwa1 FJaFl9yy49vU8/rfZ6m6/xNfBMP3MzDjqtlDYouo1A7h+VTN9sXMmltI9Qu8zwvT 0UYRy9J8wAMri7y0yVdiYZ4IjhzhwGaB4yrCZkFN6fsuoW3u2Yb1BtewffqXoav2 R5G6m/tcgMzNkyJ9cG+GUM1bk5lEE8KOf360mHC8RsOu/7ZWLJmIUx5JKB+5xhGX 1nNd71mNB36f6JJ5ZlNSdVa4Yw5bv2HPyqk3BXbxfZlov/8ECfyEsW4LIANgTQbm 9Eep6wZE8inogDiGB5hMH1VgrbLjiNriScF3yeMVfdyzRdSp/OjyuE1CmOcBToIu Bse7RDfpuJNDQs9ycqaa1ncFVA0sO6e06FszlznaioejELjCULT6qxVNgwTLTPqH e1vTZe+iCtoVAIw0RFUhHltAZE4hUXAm+trgAy1FIpeG90TTtfHQEwpR9IgdzDaI b2Oc4WeOG7pYxBRFz9NeESyBuKxXmCVfbprbtunpP/F0N4TgUc2zxdSZFN03GKL+ jKYYDUcFq4Iune/uxdGudcUHfyx0XaRFmRAjtqwFoFzuebnuJuXKcCuh7x5OTB8i 4sF64VQJAgMBAAGjga8wgawwNQYJYIZIAYb4QgENBChQdXBwZXQgUnVieS9PcGVu U1NMIEludGVybmFsIENlcnRpZmljYXRlMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBRqr/VzStqL/9HXVt6Qy5P7nhV46zAzBgNVHSME LDAqoSWkIzAhMR8wHQYDVQQDDBZQdXBwZXQgQ0E6IHNvY2tzLmxvY2FsggEBMA0G CSqGSIb3DQEBCwUAA4ICAQAWW4sT+13YO3E76jLgjhIjOmmQCSGJiJAHe8gxZxrZ xbsybkImc+bR0DIJXMQzWpm183Wx0K3YKVD50zfoS5hTThU8OLuMHvXfmMwNy7Hi 0vwOaREyrfIzuCRhY3PUz7HXlncdmzAT/1Q9n07d7VCmsFvHtksSJUVcTAMsL9xv D6fzO2or1ZtpJbckww6NmoOw+ofdn4vutn06do5SZbbPDfzEfdyPbeWFUXqFlCGk /IO8RWjMt6XRBfc6z9HFqj4HI2n5t8gSVN8MTUo1vmzaR0rErpO+JGw8xm2XVvBW jHwG8onTGErHYk+04M+woL8Q294SglpfvLONuJTBaKGaCtVnQvhDMvITJ7rnrBz3 r1x1Fx635ofY+FGpVMX0EGLA79gaya6zeyBIlvtUcTN89UfQ0sCEClme9Oe48Scy bSJKsJNsNqEljCWQ8sqlc4zGXGk2tysAHWBEPoPcfPeIyWKNx0KQ5dz8zf+adV+3 JIZC70qF3fEnyhpv0Z6B7VxRR/EZUVAyLIkucQUNVcMR7uTuAWv67CciCS9EOfit eYNnpfGRXYoiEgaSnv6oOCtgDSXd6nJgtfe9m4K7200KNPFJIWREFm21UCseHsRc AC+KbxNdwopBY8IFS2PLJQPQfBnf4q5uPZagBg4r0mHH5TgiESpa15fI25fjxAHB Zw== —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/files/client_crt.html0000644005276200011600000003052213265671735022765 0ustar jenkinsjenkins client.crt - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFdDCCA1ygAwIBAgIBBDANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyOVoXDTIwMDMwMTExNTYy OVowHTEbMBkGA1UEAwwSbWNvbGxlY3RpdmUtY2xpZW50MIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAopc/qeMD7V4sQSeLjI71Na9gabTXC5dxDV5T/qoM ZOGVInuMUVEjWWYM/Pjsj+/rEqjcN+0N8sZHpY9sMkbqB97I9c0A/zdVskF8EGVe 7vS/ytUIUtKwONffD0/FEPNpIDUc++rSj5PPgJvn35ObhB7Q2MpXLx6iamD1gnyF fmrssnv6/f5esjV/n61YaskLd1zQD6t7Zghdkzjy6pFz/AfGqWdzq4r2D5ANnsTO WAH1qWyphv8Jbwf7vE+H8x7jySmwrxmHu/PDiqZMz28ZRTVjWBWY67hI7Z/p1otQ kzoFFf7XLNfNuyajcsG1YCaGXwbBHdb5aa+leLKLzPd1Xy1X0/h4MEG63xFzkrml B7PQZHbzVHdi1lI4tI6erUyb3RjsIT2EE8ED732iDIPZduDg5I1yOUKa7R8wEYVE RyHv7V6Hbo2ydJu/oIMwJZ0oGlzK1T1xpEsg8t7wgcLKUY7n/+zG23dLOh49/ubm +ecC3QbN1z3pMc9pOceTwkWCi6vGf0Otd1TrraHAlK22dzkfQqFK2mKMegoQZXSW HPd5JAdSy4qnXCJPxGJaStQGDii5SndFWiuduKlQpSE6WSgMdiBwncAFmdYsnlNM gY0wHL/ufjWhsVdePVrEagNux6PdLdmolWawoHxtNh4UuwyByiyJU8gF7A6f/DoA H8cCAwEAAaOBujCBtzA1BglghkgBhvhCAQ0EKFB1cHBldCBSdWJ5L09wZW5TU0wg SW50ZXJuYWwgQ2VydGlmaWNhdGUwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQW MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQV Bygt19XIerwYMgLimQ1LAQjwATAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7 nhV46zANBgkqhkiG9w0BAQsFAAOCAgEANw3dHIu3J71utQB8eiCwfhNKxBhM8Yj3 i+eeAvyo28ZN3knyCbXRQmaegVRbeRLHi3YC+9yG0MFTBeOasxVPK508WosaaQJA yOuP/++IYbPiqB/xZArAbe5lA/bmp7xekS/hB9PA0hp6eS2tj13GQBOfJ+tNDEFf su8H1w+OIA626LfR1PwaqkKCTQBHEtX+Cv8KyvDeYLt0cP2tMi4MQbqdqO093AHO 4o8ZdyBOJUwzjYzt+4Y3M5Lh7i/WXh4XxnH59F0RfhVmztf2cSrjRKUvop+IDP7b b1bx4vtnorFSFqcngb9jg6Om21+x1Akhew26ZsvlazSWwAxCrlPOSCPy/taW680K gA02sbAonkanLe44E3UE5puugWz8ImDp28gOGt1PWiCyNxtJcJ3j3OTw/L5jur8Q OEZNhTrZ5j7WdZrzEFMiu6K7C2c0RL2Gz1TWd4MocGTe/vs+0ebbh6D1w9EGci8V +FBIavrOc9ZmI03wGIHLnHD+QgxLeTvQOnFENr0k9ah320b8UHdpbbvr3pRf8an5 Q8GdVahd5gTY1CbkZGeaIlY8OWUG7s9mvGGDEswSsLIah0UjTNDpzexw1ibr+LDZ tSH7GPdtwSniix47ywtv/qzVa8erCpJQ6rucUThR7o4jn3BcZJm0ZDGzZhWhpxjt cCoGKqzr1xk= —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/files/client_key.html0000644005276200011600000003313513265671735022770 0ustar jenkinsjenkins client.key - mcollective version 2.12.1

—–BEGIN RSA PRIVATE KEY—– MIIJKQIBAAKCAgEAopc/qeMD7V4sQSeLjI71Na9gabTXC5dxDV5T/qoMZOGVInuM UVEjWWYM/Pjsj+/rEqjcN+0N8sZHpY9sMkbqB97I9c0A/zdVskF8EGVe7vS/ytUI UtKwONffD0/FEPNpIDUc++rSj5PPgJvn35ObhB7Q2MpXLx6iamD1gnyFfmrssnv6 /f5esjV/n61YaskLd1zQD6t7Zghdkzjy6pFz/AfGqWdzq4r2D5ANnsTOWAH1qWyp hv8Jbwf7vE+H8x7jySmwrxmHu/PDiqZMz28ZRTVjWBWY67hI7Z/p1otQkzoFFf7X LNfNuyajcsG1YCaGXwbBHdb5aa+leLKLzPd1Xy1X0/h4MEG63xFzkrmlB7PQZHbz VHdi1lI4tI6erUyb3RjsIT2EE8ED732iDIPZduDg5I1yOUKa7R8wEYVERyHv7V6H bo2ydJu/oIMwJZ0oGlzK1T1xpEsg8t7wgcLKUY7n/+zG23dLOh49/ubm+ecC3QbN 1z3pMc9pOceTwkWCi6vGf0Otd1TrraHAlK22dzkfQqFK2mKMegoQZXSWHPd5JAdS y4qnXCJPxGJaStQGDii5SndFWiuduKlQpSE6WSgMdiBwncAFmdYsnlNMgY0wHL/u fjWhsVdePVrEagNux6PdLdmolWawoHxtNh4UuwyByiyJU8gF7A6f/DoAH8cCAwEA AQKCAgEAnMXqBQ14Q/CCC3DuBeHyXol0FXjhwbOMtq81nmCpArzg6Bbo/Z+WziSw cVwz/bYAnEVl0icpQ+YcP29DjFcEYgJXE2tQgsYAQ5kQ315fY1lrdVBkbjfo42aD Fmh8RBPwUbXk9KM/1GFZu1CZC+NwGLz984tm9XA8ewZytBcNQRAomxEgurgC5yLe pECo+I5SGA3OMM3QdlmqoMJQuAz4IRP+Ymn2Bno/OFJWT7jEneeF0I+OBzTzC7RO 7wxgsfQe/2DLlIqxbnFlItOnTHx7iVgpKk3o3aEb8MQ43T4Hu24+aS8Im5yaKtLe 11Aga026UvZYT0VxyI4sp2zBkCkrackFA0GxprFK2QohopsW05iJVzqUCHyIEPyc YRjtw2xpe4yqK2Kiiv9qgWLjoDrO6AjdJ5gcBfAmZokysqSZsfdVl63uCl6du54I OQ4vIIVC7/VdrgiMUhCu5sDNbBq0K+MKZC93KSHrif8a/bCKnjpWbPoQXYAfkxXc PdQe6DDivdKj2qRjUfZEmrT6jIz7lrVjHu8rpB3Z2rBxqBj0fMEiNGU2lrrZ/yX7 UhoECfOYKMw5LDe0BltOkgjm4gizM2KJkAPthujAJuFw77bwF5IrEMxYl0biywd7 +LUA6Ga2ckLieNbGJ406mBDg7mO3brHVK6Oa5R2GQpqgJBnOIAECggEBAMzZ4svY 5NeVyu3IgSXsv2Ou/lNQM3Aa3C6Q0kvqK4W1K9mlzgD7UM8fbLVzacezM14OKuPp 3DR227sKhOcnKF4B61q3SvGx9Vh5CXL6aX5Jmkbv0iiKwu+gY1pwbpiKRP/L9iAK yfZBT7r4qE993W+5KRxSA6bTbdNgkxHd6IijiWsQ3b7dCs9mRlHMvAGGTouY4POV oy6pik5vjIHytE3GRPFuQo94Q0yRkQm29Wfv0pKm9Bjc5ItjYKWr6njfRxQL6P2T IkYRoYgYVAbDcRcHx/3PcPJt6xwdUV61HTkF5lnh16IZ4z2/f/emeUaLP1+hfuQS 5lyDKTnVs7d5kgECggEBAMswE/RfWYh5PF8QOxs/yl+ia7ro9Vaubrp/rGAB8ny6 pgI5ntwoYuBN95APxP9vNIn27Kcm8w9zvCgesgVJdjN73eWVo6vqF+UL9zIDDVpC 2bILe30nEoB6JViqixxgwXARinTkalVMwoOHuabmowqJStfsjCBAoUCU98vLUgJP 5SjyYHCyQfY++XijG+QgMtmw8x8hKEGJ+JqNqLZfAJtQMg4q0IjoA/71GfLASpla mSqHsG4obURcQSr8Bww4Ntbbwa5+ai1G13fSsHxOq0Ve/ksDEWm6dYX+QWD5sY1j TT0QUqWffZxoNZIPXpwTNljS0nVjPP+8lBf2l3utoccCggEANMGQZQswtEzBfEd8 6zVzfMqeePpYpPBl8CAf6KkgGEk2R9EpopsWjqD7MCfPAD0oZ3qilRuvOFu77wmg fJ9bCAraf/xgcqpwEx7ozhGrhXiTIN22c/Z4iZn2vF3kPaiuaGowssPUi3T6gZ50 SjSSCKnY6pA8nIQq7psahSlvL3FefJJVaUgn9o3SYlKlwIbER7NRi6nn4qaLFfQo bXcB9F2xd7P7tFM2kcuTXLsfrGrZAie5CYYp3bP0OfZyZjqqZYFDubpgw0bbIN+T FELVJyc73CPGZMjmGdF7GTbvlbXtQwykqfycx1RAMplO9ln6Mji3Iymy5fRpc5mT tRa8AQKCAQEAyajJ5TvNDJnmxSLCxuSk4g29hiA3fvRYiVi1qAPGuuw0Xvj5JeAf Yid/kMdV6X5hUpxze+I4Uhm3oMn2PTEP00EYlgfSDYmkdXtOt6A33GGE1iR3R1tE Dacs8bcxodSVp7iBBkq9DjPEye4m3/L/1jE0yuDGoiwC3qn61ZTRq+HHur/z32XL +a2+w3B++gbK8Suh4D90SLe6uQnnbDkVzQ/m3hC4L2i2rpBsVHVslI9KfN92x53v 9GzrcNH53LLeGpi4vYpRruYka5P7/SPG8G67S4+b9KdOhNI36RtokL8BpZ1RqXeF n3n90RzX9WdMBRQYNNFtdROgl7fx2JoXeQKCAQAuwhhzlDBTm7x3J8OGkPVYHCFE rrkSooAbQUH7EK3Ns5ZeDrDNQ/193zELtCBnJ2PSlpcNIry9jI0NkdBniBzvQ8Kn CkSmw73HRVfg+BIP5CPj2L8jDdbiZlu4Y7cn1u5Q5uz/A8Q8/7VxwaC/hCzFJ3GP kSbpvlAxpoQ5QofK0EW/c/5EXYG1soITKvv6+Z4b/yZg91bPFSVTRFdHLVHSs2Q7 cz+QquxFfkS2jhouXtgxQNA5fy45EHjZgnUf3nFWx8eohAEZd4a+KkDmFW3foDS6 Yo3ydTc1R68U0ssdwbLW28OJuXIfUHWMavLY1OOJ5OBYWL1ML9uuJFmCy8/H —–END RSA PRIVATE KEY—–

mcollective-2.12.1/doc/acceptance/Gemfile.html0000644005276200011600000003217713265671734021114 0ustar jenkinsjenkins Gemfile - mcollective version 2.12.1

# Specifies a gem mirror; duplicated in acceptance setup # to ensure a similar environment on acceptance hosts. source ENV || ‘rubygems.org’

def location_for(place, fake_version = nil)

if place =~ /^(git:[^#]*)#(.*)/
  [fake_version, { :git => $1, :branch => $2, :require => false }].compact
elsif place =~ /^file:\/\/(.*)/
  ['>= 0', { :path => File.expand_path($1), :require => false }]
else
  [place, { :require => false }]
end

end

gem “beakerâ€, *location_for(ENV || “~> 3.6â€) gem “beaker-hostgeneratorâ€, *location_for(ENV || “~> 0.8â€) gem “beaker-absâ€, *location_for(ENV || “~> 0.2â€) gem “rake†gem “httpartyâ€, :require => false gem ‘uuidtools’, :require => false

group(:test) do

gem "rspec", "~> 2.14.0", :require => false
gem "mocha", "~> 0.10.5", :require => false

end

if File.exists? “#{__FILE__}.localâ€

eval(File.read("#{__FILE__}.local"), binding)

end

mcollective-2.12.1/doc/acceptance/ssl/0000755005276200011600000000000013265671736017447 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/ssl/crl_pem.html0000644005276200011600000002655013265671735021765 0ustar jenkinsjenkins crl.pem - mcollective version 2.12.1

—–BEGIN X509 CRL—– MIICmzCBhAIBATANBgkqhkiG9w0BAQUFADAhMR8wHQYDVQQDDBZQdXBwZXQgQ0E6 IHNvY2tzLmxvY2FsFw0xNTAzMDMxMTU2MTlaFw0yMDAzMDExMTU2MjBaoC8wLTAf BgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7nhV46zAKBgNVHRQEAwIBADANBgkq hkiG9w0BAQUFAAOCAgEAOuf7KWxGzusauTsjVKFAc9/vZhMC9JcZUGPvlGENLMFz Fn9kGh04Duq/HcO4KG4wS/VjO54wawQ8tcPXPOouVOXgoFLOUuEN5LQZZ7o9cOaF pQ5e/1m6kveABW9kbwioZCXx1GivBEEuEaF2ZfneKqO3p6eTlnwaM400PypL3c4c L/CvDqTgH7F9tPC6rhNJ8iQQHRRKfmpEAF2Ej5i6FPJMDT4CIq13gQkJVlXVp6xB AxfKXcMCc129NyRq3/Jjrc58/PRdkFxR/0MdN1EX7q2DnlzVDw4TU1ow4IVexFXK SQKJZmBgGYckIrQRumJatkeNB4k4aeSEesHkppZ7w57wAhsW1GTXZCdPs6Saqs3v I44btc3N8kTk0syw7nvl6ifJpa3UNJdJ3HnNrPyDEnKJQ3eMRG1kslRJnFnKC/y/ 7C55cxL1wzCm+O/ks0MVG8QwK59Ooowlztimm7X9EZPOiga010lLgWXNN54Cha9u AYo4rprqY/sK12eayhp0nodOGrpHV6PqhvtDfymoIT/hvocpxpPl80pYYO86+72d G012NdlG1Pa3/okBIfSw6/JZLiv/MiE0JFaob/xdnWPCD8UpZq8LYjIhOv5RnYBj VMisIczo6JRCwHhUB0mvK+afrEWgAodIve0ZNYAdeZ90NusDXBcA7ka9lUpau1E= —–END X509 CRL—–

mcollective-2.12.1/doc/acceptance/ssl/private_keys/0000755005276200011600000000000013265671736022154 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/ssl/private_keys/socks_local_pem.html0000644005276200011600000003365613265671735026213 0ustar jenkinsjenkins socks.local.pem - mcollective version 2.12.1

—–BEGIN RSA PRIVATE KEY—– MIIJKgIBAAKCAgEA3j3nhic+6M1yv4+EiSr8ouksO4umpT4syNrkf7J8fLWL/1e7 o6d7IGeurrwQVtVM+5WaTcQ9fRjiAmVxept1KM2n3hrI4n3ywOtCqBMy9jSqHsSM /FA1c0LS37ukbKp3ysxW6vL4xZsJHfpoOuL+m5Ft1AaSl8kqxBBqfzFIgChF8ovm m0exTR/a75sq9Ey9MQy6VA3UfSjLnspbX1w5RsniO/thjatL3Us6o37SIQe0CFHn ldOV4eoqFkBAqF9qEH04N7bLH0ZEOx/4QUbyIPXXJ/3oQLo3ReI7gExVM4c14AZX J+EJGKrDwCY/gMfhSB5WaAopdIBs/EJ9MXXxrPllH3vaeMPQsMW0/bO4SoCcxeMr sYbG/tbg2pb5W5TjBwT2eZwJ2McDxC5nbWmg8CN6TfjBU1Rb4JpKD3cm0GPHU7tp 6YSS348g7NZ/XNbKTW9Wqr/EUgMPJVsye1wQnAEQ/dLXHW8Xyi8rcM+iVAvocagt pkmy/HBz3BUS6AzZOk+Zrla7M8qTAWGaZoIdQbpt5D8Jv50kbDN0cHgVEn7Kq/MR VlOgKTa3VHJRoWv8ozkRzToiIiRaxHayv64jusmMlP7PwJ/FZqOR1bT+C3ntSdlX DubxlWq4iYRCLKvl8Gy0nunTJNIDjwwB7o48QQX/Jv9so6W/kbtAlB1CUgcCAwEA AQKCAgEAolTZTlZQTz/x9xZfmsm5a02iL4RcHwsBL10j1lqLL5R0Q/jTaECnYQ4a vCtGTAbVilbGNNOIjl2kiFeMwh/XPgyp90PdRAL3VA8Dyzzq1oe1U3ACAzKbOCux WBb5jR4fDhSf0FHVgJEz4Z/MjmbLzto1PM/oN9KK2FedUnavHF1OalHiCb4eNdV0 19uhTnzXHILNfyPy99Damov4nfkBJUHFbEa8BP0AFw4iMNYrqlC0IfehjwDT1AZ0 43ffQp0fHhg4/gkY6SksIXvCm/u0VHr9w4plIRJj2TnceGRJP7AdnBtFgi2PtWLW RP4rGGfnmAQA3VG+4ggEk2YFiCE8et0oqwH5KcbdEcH3aa1GW3H+Y9AEa5ZQh5jA PtdWIJLgAWT1lML4jFcm/Qy1YFcDmDhs3YqGqiQ74kwKHrGurvOT2sGF74A0sS1+ r8jWis7pzowbDobVkywe2D1ceoiVeWP0wbr/uL7tEkhgn0HVCYvPjvBySX2hkv4l G3JsThoTxKNX3i3Ak3QY6u0Hkmj/gWzhV82zPer5GjVlcISkVxeOBf2JsIpH8TWF kl5yp33N694Dh8/s6fQBtX/CEav7OWlH9Pd8bR0hS3fUw1mfX2/TX9HnLV+JB6o1 coHQCdR4Xb5xnYP7pkQmDgbgwEhEtM6Fhyg59yYA1M3xiUeXGmECggEBAP5534WY P2TyU9M/37TvPLa342RGKsnv8BpnGPSfrGAwhxE8L40hWtnzyQfoR8NwI4Nzp8t8 mMU1hwXqa+HzrUsZFHN5OQ8Ld+aGbol5O71+N38pmBdiEtGZwgf5traA+UMsSrIj QNoFksKz1UxiHXNRESKQtqy56Usou25B7+xygIUhreaJk0NRGElsLzrXE4gnCA7X r3I2kZ7mlOXbzd+ypwApOIBBV/pFsMThNwhC9HIU7zUp/u6a2hJpmM8Kz/kTSq/k jFKnsN0VIcGuYN6Gd4JKbbIKCck51yJ2K3Gs81dtP/6e0bhjLKG1poe9Z50wT20B HYopedmttwwihx8CggEBAN+SnT7vQmiC9HH5q4mgDbu5Jr4K2HLIamxNTrj3Zxcs mYfmWJnxxMlEic90DNKRMUVacFwSS5ZQ1IP/EG7vIYBZYCQuVV0Wn2y1awpjbd// VjKxv7P3c26raOK+HHaAECEKhWp5KlYK8pxmdCEChjA9IktxkMaCVBoNRCRqm/XS mRwbF18pkRoFXiXTU9JvfBczuNKq1x20L/X7h/v/uhpVain68L5kxwvJCLKZgi9U nxZPT2Vi9JqqL0kA5Sr9H1636XDetWZggEfLzTCoUR723JRiKqRJYfb37ZCelRTG saR5sabSK2CqSaOfVmJYJY5F4t4r0vIDMfP1KRtY4BkCggEBAJ5yd/CQ2XgbQoJY VQwtodoLKXS4E9Bkq1VlOC6odQX3cUIT/W+cgxsOJe/ce0J/Qj2I98NFs0GnkIjo a3/m39HQreEwa4yokWFh9tV2PPJB8oAk/+QfVqRXXm7k1xVPvORMYb4FIU4fBkBh BMBonvm8UjT5ciSeJVp85CdEuwS4TrmWPNc8dgsGJV8iE05JuImf8+558W3l+Em+ PJvafpd4f28kO1/wT70vQ19y83cbaWTXEBJEMc2TcZDhWxofG1t03Z4jahPVAdTG YZxc3Ufw5dykRyUCBxHaB9AXT/mZvHKFPcFmViK+7X1Kw4PlNOy0td+jQCdUp+AX v8+4d/UCggEANRvO3rRXQhxZlPmKFhc/K+i/m0VYRWv2W+PBX47lnkup7O56/ZbA +E6frfgTU7HJ0n+5USXHbt2knfZpIPENNEAG5pFbFdHZ7SjrVHxqDVvKTiCLOWT+ trthHvuqJzwkmLS3HKPLusS+/QgZbf37gloDW4iOq5kqAUZFeaEZizuLw3FsC98D 6Pp9ct7QepyhDaHrtZ9/mj7DZdtqYiJNQxQnM/G4XoHyBoqq1kJA+p1PbS1J3vuy v0rOELl1ttCKMyXc/kLpNHiYAHYZOAXDv2dE3rqzBnOoIdKjTEdfgp/wcvG1kbs7 MqtA8u1Wx+XIhhNz4PLoGTVJGHqUWTqzgQKCAQEAgwNSwhdUz3v5U4PJzqcxW+jY t8828kIdNQRisx3NvgQQ8B5E1GnBvW2DoMvwTXhX4VxInaVxcx/98p1dBhv8TCq3 xatlfHj05DlNL+YKQd+4z0MR9D56oteuE7dmtFEiESYv3lYmNGGr5RdUcFCTlVfD Go4bewtTR1HcGLn5HbuL0eh4BqZcyKZzkimix516a5o+7nTbqqKek4NZ3JUI1oes jSGTJ7hadUyWwbm/PDGdpgutoqTR1MdaL39U13GpGI988dIQPpZU7gTgx1O+Ufns RSdjEkSUVKw+UCKiIRLDWz6e6l5+NGf3tHhwoO2ISKCPLL83VK8+qQxVMuBl4w== —–END RSA PRIVATE KEY—–

mcollective-2.12.1/doc/acceptance/ssl/private_keys/mcollective-client_pem.html0000644005276200011600000003367013265671735027475 0ustar jenkinsjenkins mcollective-client.pem - mcollective version 2.12.1

—–BEGIN RSA PRIVATE KEY—– MIIJKQIBAAKCAgEAopc/qeMD7V4sQSeLjI71Na9gabTXC5dxDV5T/qoMZOGVInuM UVEjWWYM/Pjsj+/rEqjcN+0N8sZHpY9sMkbqB97I9c0A/zdVskF8EGVe7vS/ytUI UtKwONffD0/FEPNpIDUc++rSj5PPgJvn35ObhB7Q2MpXLx6iamD1gnyFfmrssnv6 /f5esjV/n61YaskLd1zQD6t7Zghdkzjy6pFz/AfGqWdzq4r2D5ANnsTOWAH1qWyp hv8Jbwf7vE+H8x7jySmwrxmHu/PDiqZMz28ZRTVjWBWY67hI7Z/p1otQkzoFFf7X LNfNuyajcsG1YCaGXwbBHdb5aa+leLKLzPd1Xy1X0/h4MEG63xFzkrmlB7PQZHbz VHdi1lI4tI6erUyb3RjsIT2EE8ED732iDIPZduDg5I1yOUKa7R8wEYVERyHv7V6H bo2ydJu/oIMwJZ0oGlzK1T1xpEsg8t7wgcLKUY7n/+zG23dLOh49/ubm+ecC3QbN 1z3pMc9pOceTwkWCi6vGf0Otd1TrraHAlK22dzkfQqFK2mKMegoQZXSWHPd5JAdS y4qnXCJPxGJaStQGDii5SndFWiuduKlQpSE6WSgMdiBwncAFmdYsnlNMgY0wHL/u fjWhsVdePVrEagNux6PdLdmolWawoHxtNh4UuwyByiyJU8gF7A6f/DoAH8cCAwEA AQKCAgEAnMXqBQ14Q/CCC3DuBeHyXol0FXjhwbOMtq81nmCpArzg6Bbo/Z+WziSw cVwz/bYAnEVl0icpQ+YcP29DjFcEYgJXE2tQgsYAQ5kQ315fY1lrdVBkbjfo42aD Fmh8RBPwUbXk9KM/1GFZu1CZC+NwGLz984tm9XA8ewZytBcNQRAomxEgurgC5yLe pECo+I5SGA3OMM3QdlmqoMJQuAz4IRP+Ymn2Bno/OFJWT7jEneeF0I+OBzTzC7RO 7wxgsfQe/2DLlIqxbnFlItOnTHx7iVgpKk3o3aEb8MQ43T4Hu24+aS8Im5yaKtLe 11Aga026UvZYT0VxyI4sp2zBkCkrackFA0GxprFK2QohopsW05iJVzqUCHyIEPyc YRjtw2xpe4yqK2Kiiv9qgWLjoDrO6AjdJ5gcBfAmZokysqSZsfdVl63uCl6du54I OQ4vIIVC7/VdrgiMUhCu5sDNbBq0K+MKZC93KSHrif8a/bCKnjpWbPoQXYAfkxXc PdQe6DDivdKj2qRjUfZEmrT6jIz7lrVjHu8rpB3Z2rBxqBj0fMEiNGU2lrrZ/yX7 UhoECfOYKMw5LDe0BltOkgjm4gizM2KJkAPthujAJuFw77bwF5IrEMxYl0biywd7 +LUA6Ga2ckLieNbGJ406mBDg7mO3brHVK6Oa5R2GQpqgJBnOIAECggEBAMzZ4svY 5NeVyu3IgSXsv2Ou/lNQM3Aa3C6Q0kvqK4W1K9mlzgD7UM8fbLVzacezM14OKuPp 3DR227sKhOcnKF4B61q3SvGx9Vh5CXL6aX5Jmkbv0iiKwu+gY1pwbpiKRP/L9iAK yfZBT7r4qE993W+5KRxSA6bTbdNgkxHd6IijiWsQ3b7dCs9mRlHMvAGGTouY4POV oy6pik5vjIHytE3GRPFuQo94Q0yRkQm29Wfv0pKm9Bjc5ItjYKWr6njfRxQL6P2T IkYRoYgYVAbDcRcHx/3PcPJt6xwdUV61HTkF5lnh16IZ4z2/f/emeUaLP1+hfuQS 5lyDKTnVs7d5kgECggEBAMswE/RfWYh5PF8QOxs/yl+ia7ro9Vaubrp/rGAB8ny6 pgI5ntwoYuBN95APxP9vNIn27Kcm8w9zvCgesgVJdjN73eWVo6vqF+UL9zIDDVpC 2bILe30nEoB6JViqixxgwXARinTkalVMwoOHuabmowqJStfsjCBAoUCU98vLUgJP 5SjyYHCyQfY++XijG+QgMtmw8x8hKEGJ+JqNqLZfAJtQMg4q0IjoA/71GfLASpla mSqHsG4obURcQSr8Bww4Ntbbwa5+ai1G13fSsHxOq0Ve/ksDEWm6dYX+QWD5sY1j TT0QUqWffZxoNZIPXpwTNljS0nVjPP+8lBf2l3utoccCggEANMGQZQswtEzBfEd8 6zVzfMqeePpYpPBl8CAf6KkgGEk2R9EpopsWjqD7MCfPAD0oZ3qilRuvOFu77wmg fJ9bCAraf/xgcqpwEx7ozhGrhXiTIN22c/Z4iZn2vF3kPaiuaGowssPUi3T6gZ50 SjSSCKnY6pA8nIQq7psahSlvL3FefJJVaUgn9o3SYlKlwIbER7NRi6nn4qaLFfQo bXcB9F2xd7P7tFM2kcuTXLsfrGrZAie5CYYp3bP0OfZyZjqqZYFDubpgw0bbIN+T FELVJyc73CPGZMjmGdF7GTbvlbXtQwykqfycx1RAMplO9ln6Mji3Iymy5fRpc5mT tRa8AQKCAQEAyajJ5TvNDJnmxSLCxuSk4g29hiA3fvRYiVi1qAPGuuw0Xvj5JeAf Yid/kMdV6X5hUpxze+I4Uhm3oMn2PTEP00EYlgfSDYmkdXtOt6A33GGE1iR3R1tE Dacs8bcxodSVp7iBBkq9DjPEye4m3/L/1jE0yuDGoiwC3qn61ZTRq+HHur/z32XL +a2+w3B++gbK8Suh4D90SLe6uQnnbDkVzQ/m3hC4L2i2rpBsVHVslI9KfN92x53v 9GzrcNH53LLeGpi4vYpRruYka5P7/SPG8G67S4+b9KdOhNI36RtokL8BpZ1RqXeF n3n90RzX9WdMBRQYNNFtdROgl7fx2JoXeQKCAQAuwhhzlDBTm7x3J8OGkPVYHCFE rrkSooAbQUH7EK3Ns5ZeDrDNQ/193zELtCBnJ2PSlpcNIry9jI0NkdBniBzvQ8Kn CkSmw73HRVfg+BIP5CPj2L8jDdbiZlu4Y7cn1u5Q5uz/A8Q8/7VxwaC/hCzFJ3GP kSbpvlAxpoQ5QofK0EW/c/5EXYG1soITKvv6+Z4b/yZg91bPFSVTRFdHLVHSs2Q7 cz+QquxFfkS2jhouXtgxQNA5fy45EHjZgnUf3nFWx8eohAEZd4a+KkDmFW3foDS6 Yo3ydTc1R68U0ssdwbLW28OJuXIfUHWMavLY1OOJ5OBYWL1ML9uuJFmCy8/H —–END RSA PRIVATE KEY—–

mcollective-2.12.1/doc/acceptance/ssl/private_keys/activemq_pem.html0000644005276200011600000003364413265671735025525 0ustar jenkinsjenkins activemq.pem - mcollective version 2.12.1

—–BEGIN RSA PRIVATE KEY—– MIIJKQIBAAKCAgEA7tZf24Tg3fkcIIdQH41a2rka3BJpll+DGvQIvbTs0hKh5AxX P2i3jeCPpkhlxQLjsjdr7lxVZRL+Ueck/DdSERUOTkeaFJrhHHvBNkDGVnZqLA5E wRlTBUY5C3uKKw8KRzgBhm59YiCZwU4impRgf9Vz3WkxkgjUO/7uR/wLXieAZP+c NPCHGLWodszzmdqV/6eXU/UsbSjX7mF025n9eDxel+N//ssXCNkHJUwy4rdQ4cV1 Rf4f1O33yTFhVtrf4JrX1znEYzB8urplSiOuyNUOxU50m0g5sPOtZpkBAhmvUxtp Q29zjksB9G1h4/mq28UuQeu4oRMATAxkchgGjHGm4lJZeAQ3ehzeNh+kNwQuaIaF IDzjgcTB5M7cDVJdPCP7LT8ONJ7nSVfykQus3OMGh+Yr2QIUM8smn3gw4Ta09aNo vyPlC7A2e0f6vmkPpYMhld3D2K6Kz5/2k1hluGaNbhBR7pklVH3KSteNQ8FPnyPI nRmw4lxC1e5kVjikqydcsKW08X4YErZzIy0cgHeYO7WikPCJlpwsqmDkAV2URhjc qBtHY884qPtnlntUEGTqSnjJuU9NKsgSYun0E7gDyyxiG2TEuVpli779NKN9dpcN mudyHJm8B9Xnnwb8gQxDZJWaYCodEQQy2eYiLZhbQz6hClc45xTG9rJvdiMCAwEA AQKCAgEAsXW5vcsKpgPtYUIJR5hpNHErQu+vYprPOFywjfx7pk9P2LlOrn+pOxLq yVurumm+M3I4kOAsVzB4pPSBJ6IXKTgi7AkJtFur/dT6J8yTUjUkCF4FuhCN/jJC OoTtkbbYJTi48WDxatLcS6Q50++BkBpSv+XkfiRvRbDS7cuZyrjBDspaD3/UcYiw hq0cJ2oh39Zve2OFQgqB/s6M3IqbqhX5FxRjyzatg9Z2F8F5YblhsI9d5sa+ciDq 5qP5Nmbkx+dljraOdEDXvIq4rhC7Ut/z3KjBvYs5yoAUjFINTLk26yJH4WJTUxvq dOo9PP4KdMca6sIx6gTLSLZAgAHYAlzbnqsQNNio4lHgBHIKXbhh/epQcA6IDk7v +QiUPjtND8ArSFGBZ5hPEcjtx7ayNRKme77LZwtJSC0Vi4UII0W3VzbFlqI+vqfw 80UrYVYTiZz65zyYDL8ViuTvJrVCf9Zc46SbQycMk8iY6HndVFkFrwnrLe8EQDpQ ofbFC5h9X+l5xTzS3KaJzFWb2kxnBkxY+pgkyhnbaR+dJ+uLU82T28auz2+2h2+j xCcgVLYN3mu1329naS25A3HtFWJcTOwYSyd8h4Ft+zzrHyb8cFsgJknDNFIUiE0O cK1jz7bauBDBjwpUTSA9GnozuBfc0lO63w0Dlk5gxsTtvuWu1xECggEBAP+WxN1E 0zBVp8YJ3aGx9EQxy6Znv80+Pj9MrcVBXxYnnZK5DkYJtiePVGn5CCNDaQZGBG3Z SNHEMC2YKgYcBnFwlf6/l75G+jxUHNmrvP7XSN7k/Tf87uQ/bxZiFGdbG30NPYjS v/stWOHhujjqXL4zAj128dVjZ0JUrEZ6i/ybIgcbaPgem/Z9uieueRutHdgl40m4 Mj/6C7uo1lKGPs2sqyK4zvSn+X4vA7JAD4CL0SD6BnLegSpyTzRexUYml0kJlit4 8sjx1Rvwkhq4dtrRCOZmn94UalVJnh2enGJzKJtT8mJZ15Yb11x3PmvbdeTtRNSB WS4ZUwtx9brgxxsCggEBAO84tWBvrEh5BkK8EPDlcz5p61EUdAkAzZ4qUMzbDQkg +rRBYI1ksfpVha0r3oY6hyWCSCbC5nS6bEYthJFOTG1SOgsDtQ8GHpTixx9WKx+N ePhOrhFuxjbFL9UD/Mq1xCnG4MZWNsJEEkWeQPq2UkijZElunwryC87xMdWCThkv 9WDIoOJ1YSAAfS671SuwG7498RnTzfyb3unW+/BcrjI5ExH3+I4BiP8r2aSV1xj3 UVYt9VZCp3sDrdmb+PM27FosZs/B6zhtqRG+ENoJQ8LQeZp1f9EHzVAJAdqfbC7b VQbXa/SJeD27kupze2ksyxN6WBprzI0H75DrClE91ZkCggEAdWE2lDARlXchEBBj 2uixN8husMOhEI9vYv7CJ384PiJ/eWzHo16+azimGe0m0ZsZ1qm9Sv2pNb0WKzpg wmGIi/yaxl4TQG20apRuk4EN0TPp4YPkzxu4UtbXG757hGQRIcM86WNwi9hto39Z WN45LZibhMLlmYyHVa0xWUTamW5dmEn8UUctdWyUW7kDIbTFqgjsc2TGnx8k2FBX 7Zp9qVONXOrnEPE74bmRxwEonW6Y4r4wy/xn8aiBu5lynB8JB6ABeYGYbrSxnJ1F XEQvQZsSrU2gSguyMe0XUp8PTx94HK3W+VgtbGDo7SxOXJZzdGmLXNTysfsramwx 4Ftu1QKCAQEAg/jZ/xC8j+LMdGaCRhOfR1BxtHI/LXRW+ynHF6zE/UNHa8ghdv+V PFCQBaANDCofwq6kFVYygzURZInZ97ulaC4TvV3XNimfF24gggYsliSbFwl4+D8P dMnGAJNJk59BT/barDCF1xhsqCGNIYMR0+bn/abccNJld04SuwI/CaAuk607LJOq +Fest+qByK9ILuTHLQNvQSvQh1rF9K18oFJiVehiJB1usa9ES4sqdiiCo7OJBIST FsdJ0qI8bhjfvUN/mKAXnzLIMfSUIWU5D7GpVG/8CppuRvghILYQnaz8tGCFuwtX 3V6sx1DcRYKjqeQ11KWh3DADlwRD1dvyCQKCAQBLCGr4zI/2zMJz6+rnZFnSAV24 MWr+OaKGDXMeYZLFKgEE7gu6BlE6VUvLSBJS3bz9cg/mzqzJBo6+OFUoUbZ6QPwN oPOzTlJCl913ngDppSjpd1Z3J8MgWL6oO0+5XAq4tJm4oPEt8AYj0ovG4l60myV8 5GLGsw9rj8qf3pDWE0opnpPiSiuPmJS104F0ac3P9F9GOqJoNaHwh9VJ2mhqrRxG 1oJwWyoB9yR7Z4/8lGPHL1jTwzaGQu3uIesor0QeYH1AOqBn0guE5P0sJdp2Q1hc uM2NYEh7KGhpsKtXg/X0mAuy0uM8ZqYqsE6mczqkGtdZwtV/nD3emHs2bRPc —–END RSA PRIVATE KEY—–

mcollective-2.12.1/doc/acceptance/ssl/private_keys/mcollective-server_pem.html0000644005276200011600000003367013265671735027525 0ustar jenkinsjenkins mcollective-server.pem - mcollective version 2.12.1

—–BEGIN RSA PRIVATE KEY—– MIIJKQIBAAKCAgEAmb8vabpZIw3WHUXqV6HkOjmKXz5IgjhXBg5/XA6ATGrvFlo5 mj6W2JpE7CxI+DhHYZ4+Z5NLkzIAYoSXuV/0umuPpXOAH5ma5JYudzKd5RV7Riff Sj5LfzBnGZwn3dVVhaZiR6vUM4cmP8CqUqfoxvpweh9nGDbkEKoHdC4pENerwvuh eAnPGflonDZFxaWEqXn5oXxSiuf88XZ0tgn1mMBn/JF0rXtCKYFfOAr7Evw1eUak +4wEHym26q+BCogKpeW+lT0C/v/TH5XG63ycyhmqiWjTI2vPab6BC7t2Bmgd5Grc r7cofvt4QYwIsIwL6ZZWahyLynHMBs85AAm1bfPdnMeia5WP+J98euAJ7MK8TuX4 sEjHt/yAiXT75aD0rsViP9BkKstBudMALokywnMHLa0/KdJCwXP5JH/ZD8v4LJvp fGxDodC+tyUCsr82Wn67AAculvMZDjY6SD9LtaNrtiNZeNj2PXYBYWWsCQ4aR2Lz elDPDZ3TUu0BqMjNQ07zz/Daol6DASuF1TLUv6YW2tLZ5nggt1rkARx6m2BTEpa1 Jl6j8KkE2l+7KR6EaUCwz3bqlvAweqY/8mnHrwhXkeeQ6Bta53o5YjXDWFXTmZD/ iSlT8hbnWmoww/EgRjsQyXZ9dS3OelsAPYBTKTwXsKIGdVgj4t8Xd9CKgicCAwEA AQKCAgEAjJJqdl/kehF/kHpJXmBt/PJ5WsXmo/GBV89PkUrM8ZHgEm7iNe4+G8NJ eyqueQ1z7oQyCJ97PRU9ltYmp15ds0j/KMZnAf1+yruptmB4T2mJscZo+Ufl3V/T FG5bYQ9aR32uZFtuBMLwLOAqmrJdOfjneDFYIKKnebkMYaSG9ZhLulY59zf4vIX2 qMVSm/jPR+l3Xbf+/HRKKDgnU/3dKRHawen4ZVnKT4qzFdmsYFprRfxagogtiJSq Yv+em5NxMOfTjj4fjCk5zqoyG0NvU7WN4F8QlVVQah29fY2jxw3RLvSp3Js5koi4 Fi5ED6+sgKFtV03MXogXhK5ZLniyAXBBMdHqUeIDulg4LLiIJaB3ZcWPYVz09n4W hr/L6RVGroFIGL62PV2dzoDOoJYuOla/qQr2kMVLjiq6+PPM23wY6K90ISynCqDV TbKLgyHSEFtcfPMdv9gLGEyUeXSrKoizvcRB0DpqFdgS+Advd76GJDQK/t9ZvSha eF3uMTjZYeMNY+BKdZ2XEXQ/VUwy9TapkCqvvvDJP4LRMMXtu2aLy3cLv7ose/Qv Lqyf2v9VDIWMpbaArGAQ/E8tqVCONsxfjs6GxiyODe2Q4dZcq5D8dalOsgK0hTCh qG3krm9EDlg6jBioEJSY6HCFWcIhKZwVIO5zvwrhKMj8x/kTeskCggEBAMxqhDs+ n9iiyqxA/TMgWYV4Vxdtz9UeWTT89vT8Anj+JIgTXNTiiMGiMJfCheuWgDUikpTW g2boK8W1UmYuZxOVcAFAx3OKmcusryg+zgZzophlLMVH/stfRS/K4xw5KPCTfRcF cNscOQ7Skvny9eX5rkdT3MXyuhMz2V+5jJRZbd/Ln5KrYU2iPF9ZHdwCv/eL+KBu 6WBWt2+U8077Ry1s33bCKGGJoW8TlncLRgcZ3o8m7wKuBK02FgfRr/UNOFCvzyNC tGbP/NKnY0AJ2Pxy3kWSiQWwXv45ZVKbwe8n8dBMyye8ziFjYphdKB5Pkh/NYJNM /44XmeuJMIWcmAsCggEBAMCLYjB+AvFwWrpuF5pkgEoBX/CYpY1zuJzUU+wfyf7r jo8V9me03a8FG/pNLIcb6Ilb15WFL5Pd2VBsZnlBdhFyz13ShpRmX8TZgarKtqF9 FWFET9BCUyz2vJByvpBiiwqhaEdB/LzHbWcJ0y7Dhiq4mmW4pameWJUYjpmJV9iN io5gsc+o1Me7ww/I24k3FWzU0yp8EiPmc65Qh5RiGuKE/Oj8OKSv0VpskYh0qFru VpyZb3JvssiO7Rv+VPz9qT5IQdmc+cwU/aI7EfZD19abbzXblDPLZI7ELWTwomB/ JdolC7AE5akPULZfR/Ox+2j5imxPdUp+W0mVoZrzo9UCggEBAIBg+RDYIL2GZ7DM 0/fy/iYD0PaG4brf53iO0m5Dgy4Htlu7hVaxut+ZA9mbsk2l5Hj6cIKHQlkzwKHX YZuI0vWKqaAv1dNrnXE7z3mAEBYfM2NwTzDLKWsCN0pvqjiEcYC9sBbwNNN3IyJ6 /xF5FBPNvjVPptyut0vuCTvEJVTZ4VZm5J3RgjemhzH+nvEYzQUj5A6l+W2mqGes bS4SQ750nNdR0tiG/lrrO1kiPGWjSpIvCnZtYcSCrk1U1KQbHF4YPAfSEchsIUUP GRcT2DGb1pptEB8BiHczBr5d0etn88c1I3WebvqrFzXHc7WtlYDhlCCOgAT2L5Ws QEv5b8cCggEATCeU+XsNrPL0X6JloYI4n4y1ppKpgC4SCa2NV+om2bXPV9am89O8 jpnlu/VENFgcfB+gQBpCfCiV6FJHoZP7n+EaFZDL++wV2uTwfV3aKDsx9puvOb7R 6QnaAQPxTWg8eELo3K5DyWLTaZqgNXHOIh2Lq8oBc/oWLTpeRGnnHda1w6SJD4pG Ro2EFD0sX7QyvC3dK4ORJTPj80EUruKyoX9aDMidmr2Tf0FLun3xjK2SAjRJuShm vR6St6y7bqjhhKnNqcWFo2t1+fGFJDMnLlGiBdpIXsgGiNUc4zyt7J69mO9oFQGK 2HgfQ1KiQcfWmWmBj19KE+GwN4WAOqo81QKCAQBZOJhwvmRZ1ajlSHvL7PLQfJ6O OQQZ3qw1yn+X+RJV3WQXacK/wXXDZ9l6ytpnUJwaamx3j6Vqk/wevM8LG7lXbXoV sYGw80K9R+j8CNZa0LWEeemvlynR3shUwNmJjLec3zD+T3zGsimv7SlnTOGr8/uM 8GaksGOdjigVcMFUJ/YHEwrAnz3LnahkD4ZK4y5SW3ERTD6LCjWprPAZoQZdpkvl DMJwVzaCH0yWnEXDq7QCYfEzp3Z+gr7ZWkNCpTKGGcBL8y1m+u0mwsIFbiG0Eb21 WM2I4CA+OhbHCv741ptBSTzgo9/d8jxjU2qiVjYNyIIY7bKdny3ldfVbXekc —–END RSA PRIVATE KEY—–

mcollective-2.12.1/doc/acceptance/ssl/public_keys/0000755005276200011600000000000013265671736021760 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/ssl/public_keys/socks_local_pem.html0000644005276200011600000002703613265671735026012 0ustar jenkinsjenkins socks.local.pem - mcollective version 2.12.1

—–BEGIN PUBLIC KEY—– MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3j3nhic+6M1yv4+EiSr8 ouksO4umpT4syNrkf7J8fLWL/1e7o6d7IGeurrwQVtVM+5WaTcQ9fRjiAmVxept1 KM2n3hrI4n3ywOtCqBMy9jSqHsSM/FA1c0LS37ukbKp3ysxW6vL4xZsJHfpoOuL+ m5Ft1AaSl8kqxBBqfzFIgChF8ovmm0exTR/a75sq9Ey9MQy6VA3UfSjLnspbX1w5 RsniO/thjatL3Us6o37SIQe0CFHnldOV4eoqFkBAqF9qEH04N7bLH0ZEOx/4QUby IPXXJ/3oQLo3ReI7gExVM4c14AZXJ+EJGKrDwCY/gMfhSB5WaAopdIBs/EJ9MXXx rPllH3vaeMPQsMW0/bO4SoCcxeMrsYbG/tbg2pb5W5TjBwT2eZwJ2McDxC5nbWmg 8CN6TfjBU1Rb4JpKD3cm0GPHU7tp6YSS348g7NZ/XNbKTW9Wqr/EUgMPJVsye1wQ nAEQ/dLXHW8Xyi8rcM+iVAvocagtpkmy/HBz3BUS6AzZOk+Zrla7M8qTAWGaZoId Qbpt5D8Jv50kbDN0cHgVEn7Kq/MRVlOgKTa3VHJRoWv8ozkRzToiIiRaxHayv64j usmMlP7PwJ/FZqOR1bT+C3ntSdlXDubxlWq4iYRCLKvl8Gy0nunTJNIDjwwB7o48 QQX/Jv9so6W/kbtAlB1CUgcCAwEAAQ== —–END PUBLIC KEY—–

mcollective-2.12.1/doc/acceptance/ssl/public_keys/mcollective-client_pem.html0000644005276200011600000002705413265671735027300 0ustar jenkinsjenkins mcollective-client.pem - mcollective version 2.12.1

—–BEGIN PUBLIC KEY—– MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAopc/qeMD7V4sQSeLjI71 Na9gabTXC5dxDV5T/qoMZOGVInuMUVEjWWYM/Pjsj+/rEqjcN+0N8sZHpY9sMkbq B97I9c0A/zdVskF8EGVe7vS/ytUIUtKwONffD0/FEPNpIDUc++rSj5PPgJvn35Ob hB7Q2MpXLx6iamD1gnyFfmrssnv6/f5esjV/n61YaskLd1zQD6t7Zghdkzjy6pFz /AfGqWdzq4r2D5ANnsTOWAH1qWyphv8Jbwf7vE+H8x7jySmwrxmHu/PDiqZMz28Z RTVjWBWY67hI7Z/p1otQkzoFFf7XLNfNuyajcsG1YCaGXwbBHdb5aa+leLKLzPd1 Xy1X0/h4MEG63xFzkrmlB7PQZHbzVHdi1lI4tI6erUyb3RjsIT2EE8ED732iDIPZ duDg5I1yOUKa7R8wEYVERyHv7V6Hbo2ydJu/oIMwJZ0oGlzK1T1xpEsg8t7wgcLK UY7n/+zG23dLOh49/ubm+ecC3QbN1z3pMc9pOceTwkWCi6vGf0Otd1TrraHAlK22 dzkfQqFK2mKMegoQZXSWHPd5JAdSy4qnXCJPxGJaStQGDii5SndFWiuduKlQpSE6 WSgMdiBwncAFmdYsnlNMgY0wHL/ufjWhsVdePVrEagNux6PdLdmolWawoHxtNh4U uwyByiyJU8gF7A6f/DoAH8cCAwEAAQ== —–END PUBLIC KEY—–

mcollective-2.12.1/doc/acceptance/ssl/public_keys/activemq_pem.html0000644005276200011600000002703013265671735025321 0ustar jenkinsjenkins activemq.pem - mcollective version 2.12.1

—–BEGIN PUBLIC KEY—– MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7tZf24Tg3fkcIIdQH41a 2rka3BJpll+DGvQIvbTs0hKh5AxXP2i3jeCPpkhlxQLjsjdr7lxVZRL+Ueck/DdS ERUOTkeaFJrhHHvBNkDGVnZqLA5EwRlTBUY5C3uKKw8KRzgBhm59YiCZwU4impRg f9Vz3WkxkgjUO/7uR/wLXieAZP+cNPCHGLWodszzmdqV/6eXU/UsbSjX7mF025n9 eDxel+N//ssXCNkHJUwy4rdQ4cV1Rf4f1O33yTFhVtrf4JrX1znEYzB8urplSiOu yNUOxU50m0g5sPOtZpkBAhmvUxtpQ29zjksB9G1h4/mq28UuQeu4oRMATAxkchgG jHGm4lJZeAQ3ehzeNh+kNwQuaIaFIDzjgcTB5M7cDVJdPCP7LT8ONJ7nSVfykQus 3OMGh+Yr2QIUM8smn3gw4Ta09aNovyPlC7A2e0f6vmkPpYMhld3D2K6Kz5/2k1hl uGaNbhBR7pklVH3KSteNQ8FPnyPInRmw4lxC1e5kVjikqydcsKW08X4YErZzIy0c gHeYO7WikPCJlpwsqmDkAV2URhjcqBtHY884qPtnlntUEGTqSnjJuU9NKsgSYun0 E7gDyyxiG2TEuVpli779NKN9dpcNmudyHJm8B9Xnnwb8gQxDZJWaYCodEQQy2eYi LZhbQz6hClc45xTG9rJvdiMCAwEAAQ== —–END PUBLIC KEY—–

mcollective-2.12.1/doc/acceptance/ssl/public_keys/mcollective-server_pem.html0000644005276200011600000002705413265671735027330 0ustar jenkinsjenkins mcollective-server.pem - mcollective version 2.12.1

—–BEGIN PUBLIC KEY—– MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmb8vabpZIw3WHUXqV6Hk OjmKXz5IgjhXBg5/XA6ATGrvFlo5mj6W2JpE7CxI+DhHYZ4+Z5NLkzIAYoSXuV/0 umuPpXOAH5ma5JYudzKd5RV7RiffSj5LfzBnGZwn3dVVhaZiR6vUM4cmP8CqUqfo xvpweh9nGDbkEKoHdC4pENerwvuheAnPGflonDZFxaWEqXn5oXxSiuf88XZ0tgn1 mMBn/JF0rXtCKYFfOAr7Evw1eUak+4wEHym26q+BCogKpeW+lT0C/v/TH5XG63yc yhmqiWjTI2vPab6BC7t2Bmgd5Grcr7cofvt4QYwIsIwL6ZZWahyLynHMBs85AAm1 bfPdnMeia5WP+J98euAJ7MK8TuX4sEjHt/yAiXT75aD0rsViP9BkKstBudMALoky wnMHLa0/KdJCwXP5JH/ZD8v4LJvpfGxDodC+tyUCsr82Wn67AAculvMZDjY6SD9L taNrtiNZeNj2PXYBYWWsCQ4aR2LzelDPDZ3TUu0BqMjNQ07zz/Daol6DASuF1TLU v6YW2tLZ5nggt1rkARx6m2BTEpa1Jl6j8KkE2l+7KR6EaUCwz3bqlvAweqY/8mnH rwhXkeeQ6Bta53o5YjXDWFXTmZD/iSlT8hbnWmoww/EgRjsQyXZ9dS3OelsAPYBT KTwXsKIGdVgj4t8Xd9CKgicCAwEAAQ== —–END PUBLIC KEY—–

mcollective-2.12.1/doc/acceptance/ssl/certs/0000755005276200011600000000000013265671736020567 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/ssl/certs/ca_pem.html0000644005276200011600000003117613265671735022710 0ustar jenkinsjenkins ca.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFbTCCA1WgAwIBAgIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyMFoXDTIwMDMwMTExNTYy MFowITEfMB0GA1UEAwwWUHVwcGV0IENBOiBzb2Nrcy5sb2NhbDCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBAL0qDiSB8u/6dxihgLSycKXGMeH+xzASvIWk tsK6oPZ8vwBeEoz4A1iw6Lf7Xz9G9qUx0NlmLryN7HLFmidQ4aTwgYE/+/Q/xwa1 FJaFl9yy49vU8/rfZ6m6/xNfBMP3MzDjqtlDYouo1A7h+VTN9sXMmltI9Qu8zwvT 0UYRy9J8wAMri7y0yVdiYZ4IjhzhwGaB4yrCZkFN6fsuoW3u2Yb1BtewffqXoav2 R5G6m/tcgMzNkyJ9cG+GUM1bk5lEE8KOf360mHC8RsOu/7ZWLJmIUx5JKB+5xhGX 1nNd71mNB36f6JJ5ZlNSdVa4Yw5bv2HPyqk3BXbxfZlov/8ECfyEsW4LIANgTQbm 9Eep6wZE8inogDiGB5hMH1VgrbLjiNriScF3yeMVfdyzRdSp/OjyuE1CmOcBToIu Bse7RDfpuJNDQs9ycqaa1ncFVA0sO6e06FszlznaioejELjCULT6qxVNgwTLTPqH e1vTZe+iCtoVAIw0RFUhHltAZE4hUXAm+trgAy1FIpeG90TTtfHQEwpR9IgdzDaI b2Oc4WeOG7pYxBRFz9NeESyBuKxXmCVfbprbtunpP/F0N4TgUc2zxdSZFN03GKL+ jKYYDUcFq4Iune/uxdGudcUHfyx0XaRFmRAjtqwFoFzuebnuJuXKcCuh7x5OTB8i 4sF64VQJAgMBAAGjga8wgawwNQYJYIZIAYb4QgENBChQdXBwZXQgUnVieS9PcGVu U1NMIEludGVybmFsIENlcnRpZmljYXRlMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBRqr/VzStqL/9HXVt6Qy5P7nhV46zAzBgNVHSME LDAqoSWkIzAhMR8wHQYDVQQDDBZQdXBwZXQgQ0E6IHNvY2tzLmxvY2FsggEBMA0G CSqGSIb3DQEBCwUAA4ICAQAWW4sT+13YO3E76jLgjhIjOmmQCSGJiJAHe8gxZxrZ xbsybkImc+bR0DIJXMQzWpm183Wx0K3YKVD50zfoS5hTThU8OLuMHvXfmMwNy7Hi 0vwOaREyrfIzuCRhY3PUz7HXlncdmzAT/1Q9n07d7VCmsFvHtksSJUVcTAMsL9xv D6fzO2or1ZtpJbckww6NmoOw+ofdn4vutn06do5SZbbPDfzEfdyPbeWFUXqFlCGk /IO8RWjMt6XRBfc6z9HFqj4HI2n5t8gSVN8MTUo1vmzaR0rErpO+JGw8xm2XVvBW jHwG8onTGErHYk+04M+woL8Q294SglpfvLONuJTBaKGaCtVnQvhDMvITJ7rnrBz3 r1x1Fx635ofY+FGpVMX0EGLA79gaya6zeyBIlvtUcTN89UfQ0sCEClme9Oe48Scy bSJKsJNsNqEljCWQ8sqlc4zGXGk2tysAHWBEPoPcfPeIyWKNx0KQ5dz8zf+adV+3 JIZC70qF3fEnyhpv0Z6B7VxRR/EZUVAyLIkucQUNVcMR7uTuAWv67CciCS9EOfit eYNnpfGRXYoiEgaSnv6oOCtgDSXd6nJgtfe9m4K7200KNPFJIWREFm21UCseHsRc AC+KbxNdwopBY8IFS2PLJQPQfBnf4q5uPZagBg4r0mHH5TgiESpa15fI25fjxAHB Zw== —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/certs/socks_local_pem.html0000644005276200011600000003131413265671735024613 0ustar jenkinsjenkins socks.local.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFmzCCA4OgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyMVoXDTIwMDMwMTExNTYy MVowFjEUMBIGA1UEAwwLc29ja3MubG9jYWwwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDePeeGJz7ozXK/j4SJKvyi6Sw7i6alPizI2uR/snx8tYv/V7uj p3sgZ66uvBBW1Uz7lZpNxD19GOICZXF6m3UozafeGsjiffLA60KoEzL2NKoexIz8 UDVzQtLfu6RsqnfKzFbq8vjFmwkd+mg64v6bkW3UBpKXySrEEGp/MUiAKEXyi+ab R7FNH9rvmyr0TL0xDLpUDdR9KMueyltfXDlGyeI7+2GNq0vdSzqjftIhB7QIUeeV 05Xh6ioWQECoX2oQfTg3tssfRkQ7H/hBRvIg9dcn/ehAujdF4juATFUzhzXgBlcn 4QkYqsPAJj+Ax+FIHlZoCil0gGz8Qn0xdfGs+WUfe9p4w9CwxbT9s7hKgJzF4yux hsb+1uDalvlblOMHBPZ5nAnYxwPELmdtaaDwI3pN+MFTVFvgmkoPdybQY8dTu2np hJLfjyDs1n9c1spNb1aqv8RSAw8lWzJ7XBCcARD90tcdbxfKLytwz6JUC+hxqC2m SbL8cHPcFRLoDNk6T5muVrszypMBYZpmgh1Bum3kPwm/nSRsM3RweBUSfsqr8xFW U6ApNrdUclGha/yjORHNOiIiJFrEdrK/riO6yYyU/s/An8Vmo5HVtP4Lee1J2VcO 5vGVariJhEIsq+XwbLSe6dMk0gOPDAHujjxBBf8m/2yjpb+Ru0CUHUJSBwIDAQAB o4HoMIHlMDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1YnkvT3BlblNTTCBJbnRlcm5h bCBDZXJ0aWZpY2F0ZTAsBgNVHREEJTAjggZwdXBwZXSCDHB1cHBldC5sb2NhbIIL c29ja3MubG9jYWwwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQGCCsGAQUF BwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRobiTuhmr7JT89 XappKM3PKSknKzAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7nhV46zANBgkq hkiG9w0BAQsFAAOCAgEAZ2Nm54dwCWnaAGiP+eR+rcrz+tmKfeuqHQMR6jihIFsy QSrTomBMaMmaw7wdSAyfusbAg6ZZ23lzjHUDUMdbzXNO8/zAKwt93JNdJdeGrpB9 TxR8Bhb5Xv4axSQszCP/X0iBWm3KjbmhCNXjMqKZ1UuEt8K80bRT+a3YzdRJFNm8 HmzWyZcN+ramkGIWQoAtjRmxYVOHOatlTOXnX3J5Yg8Z7BUTbtgPl4fGpTEu5fX+ 60uHJy2CB/jXUO1cPb9E7vVt/EPW1GjuYyQGfpG/2lNEGGkDDztbCL2Fn44BZrYO HuIcmPumTmTR6BKxx5TBnaql2KbX7bvJm9KbBUEwE36KbvwvefcYiTBAtCBzBNXR u/7BbfSvWHv2Mhtl6O4sF3sSUdhH+eilgO7tzvq1P5DcDGifdu6U8DWeBYGcaPFV 60QciJHZnyFjwoCcYthNIhfAaVjO89q2AFXCD0tlYGL+BLdxutlmHjM5WTau3pSW cdqF3FWM8dVGbc5gkWce3PL3Am745tpncySH0JGn9cvBPfUK7aoba8r2/Z7BGS/C EJWgJ+KWHJ1H7F91mImdr3lADvLIDvudb+NulloLdo8qE54csyOno4zPoM5G+5Nz vcpC7tLkC+kJ8vcn+cnVx41NYwGuonGGCPg053yrcsmvd/wQHFVhiq3RSHZK318= —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/certs/mcollective-client_pem.html0000644005276200011600000003124613265671735026105 0ustar jenkinsjenkins mcollective-client.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFdDCCA1ygAwIBAgIBBDANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyOVoXDTIwMDMwMTExNTYy OVowHTEbMBkGA1UEAwwSbWNvbGxlY3RpdmUtY2xpZW50MIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAopc/qeMD7V4sQSeLjI71Na9gabTXC5dxDV5T/qoM ZOGVInuMUVEjWWYM/Pjsj+/rEqjcN+0N8sZHpY9sMkbqB97I9c0A/zdVskF8EGVe 7vS/ytUIUtKwONffD0/FEPNpIDUc++rSj5PPgJvn35ObhB7Q2MpXLx6iamD1gnyF fmrssnv6/f5esjV/n61YaskLd1zQD6t7Zghdkzjy6pFz/AfGqWdzq4r2D5ANnsTO WAH1qWyphv8Jbwf7vE+H8x7jySmwrxmHu/PDiqZMz28ZRTVjWBWY67hI7Z/p1otQ kzoFFf7XLNfNuyajcsG1YCaGXwbBHdb5aa+leLKLzPd1Xy1X0/h4MEG63xFzkrml B7PQZHbzVHdi1lI4tI6erUyb3RjsIT2EE8ED732iDIPZduDg5I1yOUKa7R8wEYVE RyHv7V6Hbo2ydJu/oIMwJZ0oGlzK1T1xpEsg8t7wgcLKUY7n/+zG23dLOh49/ubm +ecC3QbN1z3pMc9pOceTwkWCi6vGf0Otd1TrraHAlK22dzkfQqFK2mKMegoQZXSW HPd5JAdSy4qnXCJPxGJaStQGDii5SndFWiuduKlQpSE6WSgMdiBwncAFmdYsnlNM gY0wHL/ufjWhsVdePVrEagNux6PdLdmolWawoHxtNh4UuwyByiyJU8gF7A6f/DoA H8cCAwEAAaOBujCBtzA1BglghkgBhvhCAQ0EKFB1cHBldCBSdWJ5L09wZW5TU0wg SW50ZXJuYWwgQ2VydGlmaWNhdGUwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQW MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQV Bygt19XIerwYMgLimQ1LAQjwATAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7 nhV46zANBgkqhkiG9w0BAQsFAAOCAgEANw3dHIu3J71utQB8eiCwfhNKxBhM8Yj3 i+eeAvyo28ZN3knyCbXRQmaegVRbeRLHi3YC+9yG0MFTBeOasxVPK508WosaaQJA yOuP/++IYbPiqB/xZArAbe5lA/bmp7xekS/hB9PA0hp6eS2tj13GQBOfJ+tNDEFf su8H1w+OIA626LfR1PwaqkKCTQBHEtX+Cv8KyvDeYLt0cP2tMi4MQbqdqO093AHO 4o8ZdyBOJUwzjYzt+4Y3M5Lh7i/WXh4XxnH59F0RfhVmztf2cSrjRKUvop+IDP7b b1bx4vtnorFSFqcngb9jg6Om21+x1Akhew26ZsvlazSWwAxCrlPOSCPy/taW680K gA02sbAonkanLe44E3UE5puugWz8ImDp28gOGt1PWiCyNxtJcJ3j3OTw/L5jur8Q OEZNhTrZ5j7WdZrzEFMiu6K7C2c0RL2Gz1TWd4MocGTe/vs+0ebbh6D1w9EGci8V +FBIavrOc9ZmI03wGIHLnHD+QgxLeTvQOnFENr0k9ah320b8UHdpbbvr3pRf8an5 Q8GdVahd5gTY1CbkZGeaIlY8OWUG7s9mvGGDEswSsLIah0UjTNDpzexw1ibr+LDZ tSH7GPdtwSniix47ywtv/qzVa8erCpJQ6rucUThR7o4jn3BcZJm0ZDGzZhWhpxjt cCoGKqzr1xk= —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/certs/activemq_pem.html0000644005276200011600000003120513265671735024127 0ustar jenkinsjenkins activemq.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFajCCA1KgAwIBAgIBAzANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyNFoXDTIwMDMwMTExNTYy NFowEzERMA8GA1UEAwwIYWN0aXZlbXEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDu1l/bhODd+Rwgh1AfjVrauRrcEmmWX4Ma9Ai9tOzSEqHkDFc/aLeN 4I+mSGXFAuOyN2vuXFVlEv5R5yT8N1IRFQ5OR5oUmuEce8E2QMZWdmosDkTBGVMF RjkLe4orDwpHOAGGbn1iIJnBTiKalGB/1XPdaTGSCNQ7/u5H/AteJ4Bk/5w08IcY tah2zPOZ2pX/p5dT9SxtKNfuYXTbmf14PF6X43/+yxcI2QclTDLit1DhxXVF/h/U 7ffJMWFW2t/gmtfXOcRjMHy6umVKI67I1Q7FTnSbSDmw861mmQECGa9TG2lDb3OO SwH0bWHj+arbxS5B67ihEwBMDGRyGAaMcabiUll4BDd6HN42H6Q3BC5ohoUgPOOB xMHkztwNUl08I/stPw40nudJV/KRC6zc4waH5ivZAhQzyyafeDDhNrT1o2i/I+UL sDZ7R/q+aQ+lgyGV3cPYrorPn/aTWGW4Zo1uEFHumSVUfcpK141DwU+fI8idGbDi XELV7mRWOKSrJ1ywpbTxfhgStnMjLRyAd5g7taKQ8ImWnCyqYOQBXZRGGNyoG0dj zzio+2eWe1QQZOpKeMm5T00qyBJi6fQTuAPLLGIbZMS5WmWLvv00o312lw2a53Ic mbwH1eefBvyBDENklZpgKh0RBDLZ5iItmFtDPqEKVzjnFMb2sm92IwIDAQABo4G6 MIG3MDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1YnkvT3BlblNTTCBJbnRlcm5hbCBD ZXJ0aWZpY2F0ZTAOBgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYIKwYBBQUH AwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOgvbtpFuQfBRhXM PG9EvkScrrehMB8GA1UdIwQYMBaAFGqv9XNK2ov/0ddW3pDLk/ueFXjrMA0GCSqG SIb3DQEBCwUAA4ICAQCuyi/sp1xzTC/nIcwg2k9CiEJbY4I7lBX68Gh1uVVAJtTN TEJNGa+ot0pEWp5OQiD6tTZboCvJdIgq3SsZ/YHjjPEGWDbGwmTEWxAI/Oy6WOn7 fK6EyAsuoMFfIDoZo45AnIsCLjXAjwEOyRYcVYElJtFJYa806RsPMAeOkdQQDk1G Hk9QB5Z37ESfYZ11ULVPJ/akpgWMXk994SwE+B3Nqsrk1Gc3C/78ZXz+shHrJq6L 1Eiy4FzrYbOGD2+6B2SduD17YOyhuv8205eZnMyRKQ2RCyOA9ginUIJmE/xeAxgT ITpxEKI/dCAEt4QqUM2ffmvSTaaWeftEvSsrRHBmU82MRWGPCkockCL9cU9dAa+F cpJUYLDmjGs/xXQJpY+is5ZwdXNZE8Onv2zMPV3nAMWrw6l/N+SlWyHjyAgba9k4 ZJcSUQ20ZFX6R4zK7/FAQGBOpTpIYmWEMfJCJoV20Bf31HlYE7qToOnazw424csV OdjvXbusZdADw5TdVPRvl2Y3Ej6NA4AEqgOi4fC8C512GYQIMFOj8bcq+c3CGhfB X0fk9AKhxaen0HmSGR+CWx3EdUnjHRp64d5UKLaw/190Ny7bKNhLFFLVuMNn86dg oCabruNoOGS/ITCnQ8xYmY7KuhNFuh5/F3Z9PYp8Ej3lDMhO3ltZ7h4d7NP3uQ== —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/certs/mcollective-server_pem.html0000644005276200011600000003124613265671735026135 0ustar jenkinsjenkins mcollective-server.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFdDCCA1ygAwIBAgIBBTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjEyMTgzNVoXDTIwMDMwMTEyMTgz NVowHTEbMBkGA1UEAwwSbWNvbGxlY3RpdmUtc2VydmVyMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAmb8vabpZIw3WHUXqV6HkOjmKXz5IgjhXBg5/XA6A TGrvFlo5mj6W2JpE7CxI+DhHYZ4+Z5NLkzIAYoSXuV/0umuPpXOAH5ma5JYudzKd 5RV7RiffSj5LfzBnGZwn3dVVhaZiR6vUM4cmP8CqUqfoxvpweh9nGDbkEKoHdC4p ENerwvuheAnPGflonDZFxaWEqXn5oXxSiuf88XZ0tgn1mMBn/JF0rXtCKYFfOAr7 Evw1eUak+4wEHym26q+BCogKpeW+lT0C/v/TH5XG63ycyhmqiWjTI2vPab6BC7t2 Bmgd5Grcr7cofvt4QYwIsIwL6ZZWahyLynHMBs85AAm1bfPdnMeia5WP+J98euAJ 7MK8TuX4sEjHt/yAiXT75aD0rsViP9BkKstBudMALokywnMHLa0/KdJCwXP5JH/Z D8v4LJvpfGxDodC+tyUCsr82Wn67AAculvMZDjY6SD9LtaNrtiNZeNj2PXYBYWWs CQ4aR2LzelDPDZ3TUu0BqMjNQ07zz/Daol6DASuF1TLUv6YW2tLZ5nggt1rkARx6 m2BTEpa1Jl6j8KkE2l+7KR6EaUCwz3bqlvAweqY/8mnHrwhXkeeQ6Bta53o5YjXD WFXTmZD/iSlT8hbnWmoww/EgRjsQyXZ9dS3OelsAPYBTKTwXsKIGdVgj4t8Xd9CK gicCAwEAAaOBujCBtzA1BglghkgBhvhCAQ0EKFB1cHBldCBSdWJ5L09wZW5TU0wg SW50ZXJuYWwgQ2VydGlmaWNhdGUwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQW MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRm iqXTvKMtefbQHDRT3F9MVe9xtjAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7 nhV46zANBgkqhkiG9w0BAQsFAAOCAgEAWSNWAshqJtEeV1vfdlU581ppNmodUs7Q 6Ypdx0eot6S9lvTl69GFYonuOvwf8U6chs5Hp051oMB8M1PWzD7CZHj+wMwRKqvG PWymZUsZQHFpb01tnABLL62fyqxEnaVPqUyRwNQMsXoD6OiW495kY+o17i2k6oYF pE2ofyREQd40i3wGk7u0MlpWvSfdpcUgzVTTlruOuHh+E2HTf53PH3AuFDgqd8Ee +2JCO2hcAT7JXfmlxzE6XtbalnaorBnJD09vRR/MshppN6l/11+yMDg3CpfkARBJ OqSVLd8PD3BZm4jUWd3w7tBMs1WUYrtMtUKVGc2Ct4QyxCpi1bKKZRcrnROo3lLH ZZGEYo+19KpCff/kOoBiyqkim8SN9cdy5nzEllGsIj+72mJuqRhkh58tlrTBUDl1 8Sc1rRLZ+T6k2A/UWybkPMVFw+e1DFOtK8QvjwXPiZyNTDmf05uesgp8sJ81iebv 1llZu24x5gVobMHEeXGmKGGs6vquwTrs0/mAy4ujHhkEXZPRkrdv1uBt0sG969/5 0Bnk+Lq0xxGDbgTt+8TOpV++cE3dU6K3Fb7JCJT8S6dzd/78+T+m13maW6WKdiZc QAzFNkiw4D0qvdCoL8bu45P58tPFGdJtRbIQ83Ik1Ie6M8nXxCcq0qIttw83Od+H qDxqCwAZL8E= —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/ca/0000755005276200011600000000000013265671736020032 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/ssl/ca/ca_key_pem.html0000644005276200011600000003362613265671735023025 0ustar jenkinsjenkins ca_key.pem - mcollective version 2.12.1

—–BEGIN RSA PRIVATE KEY—– MIIJKAIBAAKCAgEAvSoOJIHy7/p3GKGAtLJwpcYx4f7HMBK8haS2wrqg9ny/AF4S jPgDWLDot/tfP0b2pTHQ2WYuvI3scsWaJ1DhpPCBgT/79D/HBrUUloWX3LLj29Tz +t9nqbr/E18Ew/czMOOq2UNii6jUDuH5VM32xcyaW0j1C7zPC9PRRhHL0nzAAyuL vLTJV2JhngiOHOHAZoHjKsJmQU3p+y6hbe7ZhvUG17B9+pehq/ZHkbqb+1yAzM2T In1wb4ZQzVuTmUQTwo5/frSYcLxGw67/tlYsmYhTHkkoH7nGEZfWc13vWY0Hfp/o knlmU1J1VrhjDlu/Yc/KqTcFdvF9mWi//wQJ/ISxbgsgA2BNBub0R6nrBkTyKeiA OIYHmEwfVWCtsuOI2uJJwXfJ4xV93LNF1Kn86PK4TUKY5wFOgi4Gx7tEN+m4k0NC z3JypprWdwVUDSw7p7ToWzOXOdqKh6MQuMJQtPqrFU2DBMtM+od7W9Nl76IK2hUA jDREVSEeW0BkTiFRcCb62uADLUUil4b3RNO18dATClH0iB3MNohvY5zhZ44buljE FEXP014RLIG4rFeYJV9umtu26ek/8XQ3hOBRzbPF1JkU3TcYov6MphgNRwWrgi6d 7+7F0a51xQd/LHRdpEWZECO2rAWgXO55ue4m5cpwK6HvHk5MHyLiwXrhVAkCAwEA AQKCAgArf2YtIuyYHkIQmeKcqoOYuxBxZUKsXUKYgJ29/WhSBbFuBjPB0q4CUPvS gq0OQeUpa2EY/JQ2q3P5mFKwQ7r/UIaXD/2B1sCPXEhVrHaVO1WGEVvhDkoqD2Pk Mm9IGKnezhtVRld8wd+xZGkPBSV/qmK8JvSiNFJv588xi5oQ1rrYf8TsKa46t9a2 nkT/gzHY4Hk6NeWJkr8BEWHxg4DltaZTJrRxUTjN8B3Y9s4zaLv9XDA/OBUIsBwY fLxd+/eHP3k1y5VaJxEmojEO3pX24BAs4KmnUfKnzQB1Gt6U+wcEKQUz2VKcSlPN Ey1D+fjyJvP1IT/ScAlvo1Sy8VzSHPzkfF17Wje1ZVqISs2XLOfI6iV89XrUL4/o Az1NtjAzAqV4XIOSCXTUzyNWZ3VdWqQSeR2ATFEO1bZaridsHarwkNpPV7R0IYvE ErdSqXjxzWkHiBem3o9L1XNS5Fc/l7Hzof0oA+hmgE8kAfzBuenrXEx2VUS7zYoX 5DtkH1PGe0u+O4c4wJ/uaf1+FRIXLUAU8ncvI5jRqdGaNCntF+ndakssx7qK55KD qnccGriXSNQwG4tzybnG5Or3vf9JBa7kafVs6LyswsPaf7eynidixuh+a/RKW7A8 IWOTux6hFUfDx4zxfzQLu0YpHS7Qln1ltD6QR5X55XdTxwjZWQKCAQEA4YAsNqau kKrQONP0nLWRV8Rs+EyxlSVoxJohd+nYldevpDEvL9c2R8NsX0sfywh33kpjxo67 V3vxZmtCUEZ0x/AHPoc047RStBeBWfAM0wonGzPib1obVsbstqT8meG39L1ivrHK qUcN6mz3L168AbisK05xGsWZ7L3A/X67311t60ppr29A+UO2jKxXHhekQybavDUR ukSRm+pg3c8ajWnwyB6JljkwjgMuUtKyQFjdB8r4Z3DYyHs+XNW0/siLVcAJ3Znw rKhlqw96Zf9msfwZrQeJICVvjRSamhLuuWn1nqHnesQ67+Jgi0OF99juaHLvwbJX y0vpdgfSQYB2/wKCAQEA1r/BnysGMtJuLUuL3mTAAJiOzTwAhS/kRMIgdMlokIWg eaVHIQxIW1IHnkeZZ+CNe410qLeNyR/N/TvGB4LnoudS+nUjAATUBTBobZnLRCtu 5L27GapMPb0+xW6d9cZWs1fytApN/XWO94mDKjctziACy5AngUUXgmtQdmorO9+X 1wSjwbGCoyyYcWA6wTNp/6JSQ4+H315gn65bj3h5wA7jwcOgCmoeRm7EGojGP4eL /vlWSYp4L/8XeZsDrRh6yG7f0PR4lQL8NLs4nPyLHuNoZjdZ/bQVWQUqQ5IjUe4B R8mmCd1kv+VHYaaVwLGM4jXVJGuVcxildjP2FLV89wKCAQAYaoBfCn0sPNJ+1LRP o3kxP5ts5yDzPTGy19131mIVF111Mb4iN/MwogBB8ShoG2qfLv7OqPWv60OgC00K 1BYg7+RY6NrZLNUnmsPusQcyco75awzBccg0BSXsQMD5CG+amEbwzt9apM7k6xd4 kZFZvl0l80Bb3blk4Mbcq7Q74ynbqBr5W9p1ItfJM3/bSkQG4VYdusFIodQCRcZn Pd1qImZnxKaxFVAkkEObxCR4wyZriZaL3LQcQPvnoVwPmjc7+acXz2s3xqP7eZmP IbBE5T8CdSZrzKHfbLOwqwweF1L48h7WNBkNkD1T1uPSijKGLKu5FU5cPMPye7ZQ UVCRAoIBAQCiYuwxxOHyCkd//tFsQPkjOjk5nnayP+23xKewSCDGsBUSB1XK+rO4 QkY6fN/WuemilOuzLOGNRXyJUerEAUvRVmTbuTnXKM9+gQVmY1ZeXQ2E8KeLl8gB I5pUxvmxQOKMySNg2y3wTDXontZNVN5RMMmMTpxg1vMZDrcQY+X/Z2s1D3AY5nbn lrBLgz3KnGs5/+9+4QkYchBUjw7zZWGDRn8ZJSle1rHABsBJZEWtAn1tN/P0tj19 5cJFlV9pQ8qMx7J9GnAYFeLKqQv9Qcade86VBKQLAWLnJs23vMjyiivzsdrZOM2X gfPBNqukw13KLHTZDnU7TFbKFC6vqDEvAoIBADwZOSWbBwM3ONiQQcJNaBdLcJNb TAfLP9vdCgF/hy8hwXnNpuYsa7uGb/Ob71hCMFktcQJ/G9k3whssXiP67r7XLkuu AYXIzkdaMyYXZIGuGsNCw9zLOG26OgoAoDfFVxUZ3HTnqcGBH2PWMCaAtOotdedg jATFEfvoVHZ0kU/g/8l+oQAZfFLI3bEff8ueN4IDgT0ytQfCDKH+nTk7OiXbHq9r 531z5WfZ922ahHFx8jCfmkqrg+aUbadH6HerrFTnsgQ7bB2Bg5Up6XYkuugupMrS gdEpFexFb/VtCoJDBk+Uh9xANAUwrQ0IXKSGbNff1HriBStu2H2Y2LOKT/g= —–END RSA PRIVATE KEY—–

mcollective-2.12.1/doc/acceptance/ssl/ca/serial.html0000644005276200011600000002534413265671735022206 0ustar jenkinsjenkins serial - mcollective version 2.12.1

0006

mcollective-2.12.1/doc/acceptance/ssl/ca/ca_pub_pem.html0000644005276200011600000002701313265671735023014 0ustar jenkinsjenkins ca_pub.pem - mcollective version 2.12.1

—–BEGIN PUBLIC KEY—– MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvSoOJIHy7/p3GKGAtLJw pcYx4f7HMBK8haS2wrqg9ny/AF4SjPgDWLDot/tfP0b2pTHQ2WYuvI3scsWaJ1Dh pPCBgT/79D/HBrUUloWX3LLj29Tz+t9nqbr/E18Ew/czMOOq2UNii6jUDuH5VM32 xcyaW0j1C7zPC9PRRhHL0nzAAyuLvLTJV2JhngiOHOHAZoHjKsJmQU3p+y6hbe7Z hvUG17B9+pehq/ZHkbqb+1yAzM2TIn1wb4ZQzVuTmUQTwo5/frSYcLxGw67/tlYs mYhTHkkoH7nGEZfWc13vWY0Hfp/oknlmU1J1VrhjDlu/Yc/KqTcFdvF9mWi//wQJ /ISxbgsgA2BNBub0R6nrBkTyKeiAOIYHmEwfVWCtsuOI2uJJwXfJ4xV93LNF1Kn8 6PK4TUKY5wFOgi4Gx7tEN+m4k0NCz3JypprWdwVUDSw7p7ToWzOXOdqKh6MQuMJQ tPqrFU2DBMtM+od7W9Nl76IK2hUAjDREVSEeW0BkTiFRcCb62uADLUUil4b3RNO1 8dATClH0iB3MNohvY5zhZ44buljEFEXP014RLIG4rFeYJV9umtu26ek/8XQ3hOBR zbPF1JkU3TcYov6MphgNRwWrgi6d7+7F0a51xQd/LHRdpEWZECO2rAWgXO55ue4m 5cpwK6HvHk5MHyLiwXrhVAkCAwEAAQ== —–END PUBLIC KEY—–

mcollective-2.12.1/doc/acceptance/ssl/ca/inventory_txt.html0000644005276200011600000002613013265671735023655 0ustar jenkinsjenkins inventory - mcollective version 2.12.1

0x0001 2015-03-02T11:56:20UTC 2020-03-01T11:56:20UTC /CN=Puppet CA: socks.local 0x0002 2015-03-02T11:56:21UTC 2020-03-01T11:56:21UTC /CN=socks.local 0x0003 2015-03-02T11:56:24UTC 2020-03-01T11:56:24UTC /CN=activemq 0x0004 2015-03-02T11:56:29UTC 2020-03-01T11:56:29UTC /CN=mcollective-client 0x0005 2015-03-02T12:18:35UTC 2020-03-01T12:18:35UTC /CN=mcollective-server

mcollective-2.12.1/doc/acceptance/ssl/ca/ca_crl_pem.html0000644005276200011600000002725113265671735023012 0ustar jenkinsjenkins ca_crl.pem - mcollective version 2.12.1

—–BEGIN X509 CRL—– MIICmzCBhAIBATANBgkqhkiG9w0BAQUFADAhMR8wHQYDVQQDDBZQdXBwZXQgQ0E6 IHNvY2tzLmxvY2FsFw0xNTAzMDMxMTU2MTlaFw0yMDAzMDExMTU2MjBaoC8wLTAf BgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7nhV46zAKBgNVHRQEAwIBADANBgkq hkiG9w0BAQUFAAOCAgEAOuf7KWxGzusauTsjVKFAc9/vZhMC9JcZUGPvlGENLMFz Fn9kGh04Duq/HcO4KG4wS/VjO54wawQ8tcPXPOouVOXgoFLOUuEN5LQZZ7o9cOaF pQ5e/1m6kveABW9kbwioZCXx1GivBEEuEaF2ZfneKqO3p6eTlnwaM400PypL3c4c L/CvDqTgH7F9tPC6rhNJ8iQQHRRKfmpEAF2Ej5i6FPJMDT4CIq13gQkJVlXVp6xB AxfKXcMCc129NyRq3/Jjrc58/PRdkFxR/0MdN1EX7q2DnlzVDw4TU1ow4IVexFXK SQKJZmBgGYckIrQRumJatkeNB4k4aeSEesHkppZ7w57wAhsW1GTXZCdPs6Saqs3v I44btc3N8kTk0syw7nvl6ifJpa3UNJdJ3HnNrPyDEnKJQ3eMRG1kslRJnFnKC/y/ 7C55cxL1wzCm+O/ks0MVG8QwK59Ooowlztimm7X9EZPOiga010lLgWXNN54Cha9u AYo4rprqY/sK12eayhp0nodOGrpHV6PqhvtDfymoIT/hvocpxpPl80pYYO86+72d G012NdlG1Pa3/okBIfSw6/JZLiv/MiE0JFaob/xdnWPCD8UpZq8LYjIhOv5RnYBj VMisIczo6JRCwHhUB0mvK+afrEWgAodIve0ZNYAdeZ90NusDXBcA7ka9lUpau1E= —–END X509 CRL—–

mcollective-2.12.1/doc/acceptance/ssl/ca/private/0000755005276200011600000000000013265671736021504 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/ssl/ca/private/ca_pass.html0000644005276200011600000002606613265671735024014 0ustar jenkinsjenkins ca.pass - mcollective version 2.12.1

BHSQo_BL3oI?81FWI5lG

mcollective-2.12.1/doc/acceptance/ssl/ca/ca_crt_pem.html0000644005276200011600000003120313265671735023012 0ustar jenkinsjenkins ca_crt.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFbTCCA1WgAwIBAgIBATANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyMFoXDTIwMDMwMTExNTYy MFowITEfMB0GA1UEAwwWUHVwcGV0IENBOiBzb2Nrcy5sb2NhbDCCAiIwDQYJKoZI hvcNAQEBBQADggIPADCCAgoCggIBAL0qDiSB8u/6dxihgLSycKXGMeH+xzASvIWk tsK6oPZ8vwBeEoz4A1iw6Lf7Xz9G9qUx0NlmLryN7HLFmidQ4aTwgYE/+/Q/xwa1 FJaFl9yy49vU8/rfZ6m6/xNfBMP3MzDjqtlDYouo1A7h+VTN9sXMmltI9Qu8zwvT 0UYRy9J8wAMri7y0yVdiYZ4IjhzhwGaB4yrCZkFN6fsuoW3u2Yb1BtewffqXoav2 R5G6m/tcgMzNkyJ9cG+GUM1bk5lEE8KOf360mHC8RsOu/7ZWLJmIUx5JKB+5xhGX 1nNd71mNB36f6JJ5ZlNSdVa4Yw5bv2HPyqk3BXbxfZlov/8ECfyEsW4LIANgTQbm 9Eep6wZE8inogDiGB5hMH1VgrbLjiNriScF3yeMVfdyzRdSp/OjyuE1CmOcBToIu Bse7RDfpuJNDQs9ycqaa1ncFVA0sO6e06FszlznaioejELjCULT6qxVNgwTLTPqH e1vTZe+iCtoVAIw0RFUhHltAZE4hUXAm+trgAy1FIpeG90TTtfHQEwpR9IgdzDaI b2Oc4WeOG7pYxBRFz9NeESyBuKxXmCVfbprbtunpP/F0N4TgUc2zxdSZFN03GKL+ jKYYDUcFq4Iune/uxdGudcUHfyx0XaRFmRAjtqwFoFzuebnuJuXKcCuh7x5OTB8i 4sF64VQJAgMBAAGjga8wgawwNQYJYIZIAYb4QgENBChQdXBwZXQgUnVieS9PcGVu U1NMIEludGVybmFsIENlcnRpZmljYXRlMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB Af8EBTADAQH/MB0GA1UdDgQWBBRqr/VzStqL/9HXVt6Qy5P7nhV46zAzBgNVHSME LDAqoSWkIzAhMR8wHQYDVQQDDBZQdXBwZXQgQ0E6IHNvY2tzLmxvY2FsggEBMA0G CSqGSIb3DQEBCwUAA4ICAQAWW4sT+13YO3E76jLgjhIjOmmQCSGJiJAHe8gxZxrZ xbsybkImc+bR0DIJXMQzWpm183Wx0K3YKVD50zfoS5hTThU8OLuMHvXfmMwNy7Hi 0vwOaREyrfIzuCRhY3PUz7HXlncdmzAT/1Q9n07d7VCmsFvHtksSJUVcTAMsL9xv D6fzO2or1ZtpJbckww6NmoOw+ofdn4vutn06do5SZbbPDfzEfdyPbeWFUXqFlCGk /IO8RWjMt6XRBfc6z9HFqj4HI2n5t8gSVN8MTUo1vmzaR0rErpO+JGw8xm2XVvBW jHwG8onTGErHYk+04M+woL8Q294SglpfvLONuJTBaKGaCtVnQvhDMvITJ7rnrBz3 r1x1Fx635ofY+FGpVMX0EGLA79gaya6zeyBIlvtUcTN89UfQ0sCEClme9Oe48Scy bSJKsJNsNqEljCWQ8sqlc4zGXGk2tysAHWBEPoPcfPeIyWKNx0KQ5dz8zf+adV+3 JIZC70qF3fEnyhpv0Z6B7VxRR/EZUVAyLIkucQUNVcMR7uTuAWv67CciCS9EOfit eYNnpfGRXYoiEgaSnv6oOCtgDSXd6nJgtfe9m4K7200KNPFJIWREFm21UCseHsRc AC+KbxNdwopBY8IFS2PLJQPQfBnf4q5uPZagBg4r0mHH5TgiESpa15fI25fjxAHB Zw== —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/ca/signed/0000755005276200011600000000000013265671736021303 5ustar jenkinsjenkinsmcollective-2.12.1/doc/acceptance/ssl/ca/signed/socks_local_pem.html0000644005276200011600000003201013265671735025321 0ustar jenkinsjenkins socks.local.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFmzCCA4OgAwIBAgIBAjANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyMVoXDTIwMDMwMTExNTYy MVowFjEUMBIGA1UEAwwLc29ja3MubG9jYWwwggIiMA0GCSqGSIb3DQEBAQUAA4IC DwAwggIKAoICAQDePeeGJz7ozXK/j4SJKvyi6Sw7i6alPizI2uR/snx8tYv/V7uj p3sgZ66uvBBW1Uz7lZpNxD19GOICZXF6m3UozafeGsjiffLA60KoEzL2NKoexIz8 UDVzQtLfu6RsqnfKzFbq8vjFmwkd+mg64v6bkW3UBpKXySrEEGp/MUiAKEXyi+ab R7FNH9rvmyr0TL0xDLpUDdR9KMueyltfXDlGyeI7+2GNq0vdSzqjftIhB7QIUeeV 05Xh6ioWQECoX2oQfTg3tssfRkQ7H/hBRvIg9dcn/ehAujdF4juATFUzhzXgBlcn 4QkYqsPAJj+Ax+FIHlZoCil0gGz8Qn0xdfGs+WUfe9p4w9CwxbT9s7hKgJzF4yux hsb+1uDalvlblOMHBPZ5nAnYxwPELmdtaaDwI3pN+MFTVFvgmkoPdybQY8dTu2np hJLfjyDs1n9c1spNb1aqv8RSAw8lWzJ7XBCcARD90tcdbxfKLytwz6JUC+hxqC2m SbL8cHPcFRLoDNk6T5muVrszypMBYZpmgh1Bum3kPwm/nSRsM3RweBUSfsqr8xFW U6ApNrdUclGha/yjORHNOiIiJFrEdrK/riO6yYyU/s/An8Vmo5HVtP4Lee1J2VcO 5vGVariJhEIsq+XwbLSe6dMk0gOPDAHujjxBBf8m/2yjpb+Ru0CUHUJSBwIDAQAB o4HoMIHlMDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1YnkvT3BlblNTTCBJbnRlcm5h bCBDZXJ0aWZpY2F0ZTAsBgNVHREEJTAjggZwdXBwZXSCDHB1cHBldC5sb2NhbIIL c29ja3MubG9jYWwwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQGCCsGAQUF BwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRobiTuhmr7JT89 XappKM3PKSknKzAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7nhV46zANBgkq hkiG9w0BAQsFAAOCAgEAZ2Nm54dwCWnaAGiP+eR+rcrz+tmKfeuqHQMR6jihIFsy QSrTomBMaMmaw7wdSAyfusbAg6ZZ23lzjHUDUMdbzXNO8/zAKwt93JNdJdeGrpB9 TxR8Bhb5Xv4axSQszCP/X0iBWm3KjbmhCNXjMqKZ1UuEt8K80bRT+a3YzdRJFNm8 HmzWyZcN+ramkGIWQoAtjRmxYVOHOatlTOXnX3J5Yg8Z7BUTbtgPl4fGpTEu5fX+ 60uHJy2CB/jXUO1cPb9E7vVt/EPW1GjuYyQGfpG/2lNEGGkDDztbCL2Fn44BZrYO HuIcmPumTmTR6BKxx5TBnaql2KbX7bvJm9KbBUEwE36KbvwvefcYiTBAtCBzBNXR u/7BbfSvWHv2Mhtl6O4sF3sSUdhH+eilgO7tzvq1P5DcDGifdu6U8DWeBYGcaPFV 60QciJHZnyFjwoCcYthNIhfAaVjO89q2AFXCD0tlYGL+BLdxutlmHjM5WTau3pSW cdqF3FWM8dVGbc5gkWce3PL3Am745tpncySH0JGn9cvBPfUK7aoba8r2/Z7BGS/C EJWgJ+KWHJ1H7F91mImdr3lADvLIDvudb+NulloLdo8qE54csyOno4zPoM5G+5Nz vcpC7tLkC+kJ8vcn+cnVx41NYwGuonGGCPg053yrcsmvd/wQHFVhiq3RSHZK318= —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/ca/signed/mcollective-client_pem.html0000644005276200011600000003174213265671735026622 0ustar jenkinsjenkins mcollective-client.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFdDCCA1ygAwIBAgIBBDANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyOVoXDTIwMDMwMTExNTYy OVowHTEbMBkGA1UEAwwSbWNvbGxlY3RpdmUtY2xpZW50MIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAopc/qeMD7V4sQSeLjI71Na9gabTXC5dxDV5T/qoM ZOGVInuMUVEjWWYM/Pjsj+/rEqjcN+0N8sZHpY9sMkbqB97I9c0A/zdVskF8EGVe 7vS/ytUIUtKwONffD0/FEPNpIDUc++rSj5PPgJvn35ObhB7Q2MpXLx6iamD1gnyF fmrssnv6/f5esjV/n61YaskLd1zQD6t7Zghdkzjy6pFz/AfGqWdzq4r2D5ANnsTO WAH1qWyphv8Jbwf7vE+H8x7jySmwrxmHu/PDiqZMz28ZRTVjWBWY67hI7Z/p1otQ kzoFFf7XLNfNuyajcsG1YCaGXwbBHdb5aa+leLKLzPd1Xy1X0/h4MEG63xFzkrml B7PQZHbzVHdi1lI4tI6erUyb3RjsIT2EE8ED732iDIPZduDg5I1yOUKa7R8wEYVE RyHv7V6Hbo2ydJu/oIMwJZ0oGlzK1T1xpEsg8t7wgcLKUY7n/+zG23dLOh49/ubm +ecC3QbN1z3pMc9pOceTwkWCi6vGf0Otd1TrraHAlK22dzkfQqFK2mKMegoQZXSW HPd5JAdSy4qnXCJPxGJaStQGDii5SndFWiuduKlQpSE6WSgMdiBwncAFmdYsnlNM gY0wHL/ufjWhsVdePVrEagNux6PdLdmolWawoHxtNh4UuwyByiyJU8gF7A6f/DoA H8cCAwEAAaOBujCBtzA1BglghkgBhvhCAQ0EKFB1cHBldCBSdWJ5L09wZW5TU0wg SW50ZXJuYWwgQ2VydGlmaWNhdGUwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQW MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBQV Bygt19XIerwYMgLimQ1LAQjwATAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7 nhV46zANBgkqhkiG9w0BAQsFAAOCAgEANw3dHIu3J71utQB8eiCwfhNKxBhM8Yj3 i+eeAvyo28ZN3knyCbXRQmaegVRbeRLHi3YC+9yG0MFTBeOasxVPK508WosaaQJA yOuP/++IYbPiqB/xZArAbe5lA/bmp7xekS/hB9PA0hp6eS2tj13GQBOfJ+tNDEFf su8H1w+OIA626LfR1PwaqkKCTQBHEtX+Cv8KyvDeYLt0cP2tMi4MQbqdqO093AHO 4o8ZdyBOJUwzjYzt+4Y3M5Lh7i/WXh4XxnH59F0RfhVmztf2cSrjRKUvop+IDP7b b1bx4vtnorFSFqcngb9jg6Om21+x1Akhew26ZsvlazSWwAxCrlPOSCPy/taW680K gA02sbAonkanLe44E3UE5puugWz8ImDp28gOGt1PWiCyNxtJcJ3j3OTw/L5jur8Q OEZNhTrZ5j7WdZrzEFMiu6K7C2c0RL2Gz1TWd4MocGTe/vs+0ebbh6D1w9EGci8V +FBIavrOc9ZmI03wGIHLnHD+QgxLeTvQOnFENr0k9ah320b8UHdpbbvr3pRf8an5 Q8GdVahd5gTY1CbkZGeaIlY8OWUG7s9mvGGDEswSsLIah0UjTNDpzexw1ibr+LDZ tSH7GPdtwSniix47ywtv/qzVa8erCpJQ6rucUThR7o4jn3BcZJm0ZDGzZhWhpxjt cCoGKqzr1xk= —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/ca/signed/activemq_pem.html0000644005276200011600000003170113265671735024644 0ustar jenkinsjenkins activemq.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFajCCA1KgAwIBAgIBAzANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjExNTYyNFoXDTIwMDMwMTExNTYy NFowEzERMA8GA1UEAwwIYWN0aXZlbXEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw ggIKAoICAQDu1l/bhODd+Rwgh1AfjVrauRrcEmmWX4Ma9Ai9tOzSEqHkDFc/aLeN 4I+mSGXFAuOyN2vuXFVlEv5R5yT8N1IRFQ5OR5oUmuEce8E2QMZWdmosDkTBGVMF RjkLe4orDwpHOAGGbn1iIJnBTiKalGB/1XPdaTGSCNQ7/u5H/AteJ4Bk/5w08IcY tah2zPOZ2pX/p5dT9SxtKNfuYXTbmf14PF6X43/+yxcI2QclTDLit1DhxXVF/h/U 7ffJMWFW2t/gmtfXOcRjMHy6umVKI67I1Q7FTnSbSDmw861mmQECGa9TG2lDb3OO SwH0bWHj+arbxS5B67ihEwBMDGRyGAaMcabiUll4BDd6HN42H6Q3BC5ohoUgPOOB xMHkztwNUl08I/stPw40nudJV/KRC6zc4waH5ivZAhQzyyafeDDhNrT1o2i/I+UL sDZ7R/q+aQ+lgyGV3cPYrorPn/aTWGW4Zo1uEFHumSVUfcpK141DwU+fI8idGbDi XELV7mRWOKSrJ1ywpbTxfhgStnMjLRyAd5g7taKQ8ImWnCyqYOQBXZRGGNyoG0dj zzio+2eWe1QQZOpKeMm5T00qyBJi6fQTuAPLLGIbZMS5WmWLvv00o312lw2a53Ic mbwH1eefBvyBDENklZpgKh0RBDLZ5iItmFtDPqEKVzjnFMb2sm92IwIDAQABo4G6 MIG3MDUGCWCGSAGG+EIBDQQoUHVwcGV0IFJ1YnkvT3BlblNTTCBJbnRlcm5hbCBD ZXJ0aWZpY2F0ZTAOBgNVHQ8BAf8EBAMCBaAwIAYDVR0lAQH/BBYwFAYIKwYBBQUH AwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFOgvbtpFuQfBRhXM PG9EvkScrrehMB8GA1UdIwQYMBaAFGqv9XNK2ov/0ddW3pDLk/ueFXjrMA0GCSqG SIb3DQEBCwUAA4ICAQCuyi/sp1xzTC/nIcwg2k9CiEJbY4I7lBX68Gh1uVVAJtTN TEJNGa+ot0pEWp5OQiD6tTZboCvJdIgq3SsZ/YHjjPEGWDbGwmTEWxAI/Oy6WOn7 fK6EyAsuoMFfIDoZo45AnIsCLjXAjwEOyRYcVYElJtFJYa806RsPMAeOkdQQDk1G Hk9QB5Z37ESfYZ11ULVPJ/akpgWMXk994SwE+B3Nqsrk1Gc3C/78ZXz+shHrJq6L 1Eiy4FzrYbOGD2+6B2SduD17YOyhuv8205eZnMyRKQ2RCyOA9ginUIJmE/xeAxgT ITpxEKI/dCAEt4QqUM2ffmvSTaaWeftEvSsrRHBmU82MRWGPCkockCL9cU9dAa+F cpJUYLDmjGs/xXQJpY+is5ZwdXNZE8Onv2zMPV3nAMWrw6l/N+SlWyHjyAgba9k4 ZJcSUQ20ZFX6R4zK7/FAQGBOpTpIYmWEMfJCJoV20Bf31HlYE7qToOnazw424csV OdjvXbusZdADw5TdVPRvl2Y3Ej6NA4AEqgOi4fC8C512GYQIMFOj8bcq+c3CGhfB X0fk9AKhxaen0HmSGR+CWx3EdUnjHRp64d5UKLaw/190Ny7bKNhLFFLVuMNn86dg oCabruNoOGS/ITCnQ8xYmY7KuhNFuh5/F3Z9PYp8Ej3lDMhO3ltZ7h4d7NP3uQ== —–END CERTIFICATE—–

mcollective-2.12.1/doc/acceptance/ssl/ca/signed/mcollective-server_pem.html0000644005276200011600000003174213265671735026652 0ustar jenkinsjenkins mcollective-server.pem - mcollective version 2.12.1

—–BEGIN CERTIFICATE—– MIIFdDCCA1ygAwIBAgIBBTANBgkqhkiG9w0BAQsFADAhMR8wHQYDVQQDDBZQdXBw ZXQgQ0E6IHNvY2tzLmxvY2FsMB4XDTE1MDMwMjEyMTgzNVoXDTIwMDMwMTEyMTgz NVowHTEbMBkGA1UEAwwSbWNvbGxlY3RpdmUtc2VydmVyMIICIjANBgkqhkiG9w0B AQEFAAOCAg8AMIICCgKCAgEAmb8vabpZIw3WHUXqV6HkOjmKXz5IgjhXBg5/XA6A TGrvFlo5mj6W2JpE7CxI+DhHYZ4+Z5NLkzIAYoSXuV/0umuPpXOAH5ma5JYudzKd 5RV7RiffSj5LfzBnGZwn3dVVhaZiR6vUM4cmP8CqUqfoxvpweh9nGDbkEKoHdC4p ENerwvuheAnPGflonDZFxaWEqXn5oXxSiuf88XZ0tgn1mMBn/JF0rXtCKYFfOAr7 Evw1eUak+4wEHym26q+BCogKpeW+lT0C/v/TH5XG63ycyhmqiWjTI2vPab6BC7t2 Bmgd5Grcr7cofvt4QYwIsIwL6ZZWahyLynHMBs85AAm1bfPdnMeia5WP+J98euAJ 7MK8TuX4sEjHt/yAiXT75aD0rsViP9BkKstBudMALokywnMHLa0/KdJCwXP5JH/Z D8v4LJvpfGxDodC+tyUCsr82Wn67AAculvMZDjY6SD9LtaNrtiNZeNj2PXYBYWWs CQ4aR2LzelDPDZ3TUu0BqMjNQ07zz/Daol6DASuF1TLUv6YW2tLZ5nggt1rkARx6 m2BTEpa1Jl6j8KkE2l+7KR6EaUCwz3bqlvAweqY/8mnHrwhXkeeQ6Bta53o5YjXD WFXTmZD/iSlT8hbnWmoww/EgRjsQyXZ9dS3OelsAPYBTKTwXsKIGdVgj4t8Xd9CK gicCAwEAAaOBujCBtzA1BglghkgBhvhCAQ0EKFB1cHBldCBSdWJ5L09wZW5TU0wg SW50ZXJuYWwgQ2VydGlmaWNhdGUwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQW MBQGCCsGAQUFBwMBBggrBgEFBQcDAjAMBgNVHRMBAf8EAjAAMB0GA1UdDgQWBBRm iqXTvKMtefbQHDRT3F9MVe9xtjAfBgNVHSMEGDAWgBRqr/VzStqL/9HXVt6Qy5P7 nhV46zANBgkqhkiG9w0BAQsFAAOCAgEAWSNWAshqJtEeV1vfdlU581ppNmodUs7Q 6Ypdx0eot6S9lvTl69GFYonuOvwf8U6chs5Hp051oMB8M1PWzD7CZHj+wMwRKqvG PWymZUsZQHFpb01tnABLL62fyqxEnaVPqUyRwNQMsXoD6OiW495kY+o17i2k6oYF pE2ofyREQd40i3wGk7u0MlpWvSfdpcUgzVTTlruOuHh+E2HTf53PH3AuFDgqd8Ee +2JCO2hcAT7JXfmlxzE6XtbalnaorBnJD09vRR/MshppN6l/11+yMDg3CpfkARBJ OqSVLd8PD3BZm4jUWd3w7tBMs1WUYrtMtUKVGc2Ct4QyxCpi1bKKZRcrnROo3lLH ZZGEYo+19KpCff/kOoBiyqkim8SN9cdy5nzEllGsIj+72mJuqRhkh58tlrTBUDl1 8Sc1rRLZ+T6k2A/UWybkPMVFw+e1DFOtK8QvjwXPiZyNTDmf05uesgp8sJ81iebv 1llZu24x5gVobMHEeXGmKGGs6vquwTrs0/mAy4ujHhkEXZPRkrdv1uBt0sG969/5 0Bnk+Lq0xxGDbgTt+8TOpV++cE3dU6K3Fb7JCJT8S6dzd/78+T+m13maW6WKdiZc QAzFNkiw4D0qvdCoL8bu45P58tPFGdJtRbIQ83Ik1Ie6M8nXxCcq0qIttw83Od+H qDxqCwAZL8E= —–END CERTIFICATE—–

mcollective-2.12.1/doc/MCollective/0000755005276200011600000000000013265671736016766 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/BackoffSuggestion.html0000644005276200011600000001031413265671732023252 0ustar jenkinsjenkins class MCollective::BackoffSuggestion - mcollective version 2.12.1

class MCollective::BackoffSuggestion

Attributes

backoff[R]

Public Class Methods

new(backoff = nil) click to toggle source
# File lib/mcollective/exceptions.rb, line 15
def initialize(backoff = nil)
  @backoff = backoff
end
mcollective-2.12.1/doc/MCollective/MsgDoesNotMatchRequestID.html0000644005276200011600000000464313265671733024445 0ustar jenkinsjenkins class MCollective::MsgDoesNotMatchRequestID - mcollective version 2.12.1

class MCollective::MsgDoesNotMatchRequestID

mcollective-2.12.1/doc/MCollective/InvalidRPCData.html0000644005276200011600000000464013265671732022401 0ustar jenkinsjenkins class MCollective::InvalidRPCData - mcollective version 2.12.1

class MCollective::InvalidRPCData

mcollective-2.12.1/doc/MCollective/MsgTTLExpired.html0000644005276200011600000000456713265671733022320 0ustar jenkinsjenkins class MCollective::MsgTTLExpired - mcollective version 2.12.1

class MCollective::MsgTTLExpired

mcollective-2.12.1/doc/MCollective/Generators/0000755005276200011600000000000013265671736021077 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Generators/Base.html0000644005276200011600000003300513265671732022634 0ustar jenkinsjenkins class MCollective::Generators::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Generators/DataGenerator.html0000644005276200011600000003077313265671732024513 0ustar jenkinsjenkins class MCollective::Generators::DataGenerator - mcollective version 2.12.1

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.12.1/doc/MCollective/Generators/AgentGenerator.html0000644005276200011600000003223513265671732024673 0ustar jenkinsjenkins class MCollective::Generators::AgentGenerator - mcollective version 2.12.1

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.12.1/doc/MCollective/MessageNotReceived.html0000644005276200011600000000470213265671733023370 0ustar jenkinsjenkins class MCollective::MessageNotReceived - mcollective version 2.12.1

class MCollective::MessageNotReceived

mcollective-2.12.1/doc/MCollective/Security.html0000644005276200011600000000651313265671733021465 0ustar jenkinsjenkins module MCollective::Security - mcollective version 2.12.1

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.12.1/doc/MCollective/NotTargettedAtUs.html0000644005276200011600000000460313265671733023055 0ustar jenkinsjenkins class MCollective::NotTargettedAtUs - mcollective version 2.12.1

class MCollective::NotTargettedAtUs

mcollective-2.12.1/doc/MCollective/Connector.html0000644005276200011600000000771013265671732021607 0ustar jenkinsjenkins module MCollective::Connector - mcollective version 2.12.1

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.12.1/doc/MCollective/Discovery.html0000644005276200011600000006511513265671732021627 0ustar jenkinsjenkins class MCollective::Discovery - mcollective version 2.12.1

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?(Integer)

  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.12.1/doc/MCollective/RPC.html0000644005276200011600000005656313265671733020314 0ustar jenkinsjenkins module MCollective::RPC - mcollective version 2.12.1

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] || Util.config_file_for_user
  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.12.1/doc/MCollective/UnknownRPCAction.html0000644005276200011600000000465013265671733023020 0ustar jenkinsjenkins class MCollective::UnknownRPCAction - mcollective version 2.12.1

class MCollective::UnknownRPCAction

mcollective-2.12.1/doc/MCollective/Aggregate/0000755005276200011600000000000013265671736020654 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Aggregate/Average.html0000644005276200011600000001556313265671731023121 0ustar jenkinsjenkins class MCollective::Aggregate::Average - mcollective version 2.12.1

class MCollective::Aggregate::Average

Public Instance Methods

process_result(value, reply) click to toggle source

Determines the average of a set of numerical values

# File lib/mcollective/aggregate/average.rb, line 16
def process_result(value, reply)
  @result[:value] += value
  @count += 1
end
startup_hook() click to toggle source

Before function is run processing

# File lib/mcollective/aggregate/average.rb, line 5
def startup_hook
  @result[:value] = 0
  @result[:type] = :numeric

  @count = 0

  # Set default aggregate_function if it is undefined
  @aggregate_format = "Average of #{@result[:output]}: %f" unless @aggregate_format
end
summarize() click to toggle source

Stops execution of the function and returns a ResultObject

# File lib/mcollective/aggregate/average.rb, line 22
def summarize
  @result[:value] /= @count

  result_class(:numeric).new(@result, @aggregate_format, @action)
end
mcollective-2.12.1/doc/MCollective/Aggregate/Base.html0000644005276200011600000002412013265671731022406 0ustar jenkinsjenkins class MCollective::Aggregate::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Aggregate/Result/0000755005276200011600000000000013265671736022132 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Aggregate/Result/Base.html0000644005276200011600000001714013265671731023670 0ustar jenkinsjenkins class MCollective::Aggregate::Result::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Aggregate/Result/NumericResult.html0000644005276200011600000001022713265671731025616 0ustar jenkinsjenkins class MCollective::Aggregate::Result::NumericResult - mcollective version 2.12.1

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.12.1/doc/MCollective/Aggregate/Result/CollectionResult.html0000644005276200011600000001256013265671731026311 0ustar jenkinsjenkins class MCollective::Aggregate::Result::CollectionResult - mcollective version 2.12.1

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.12.1/doc/MCollective/Aggregate/Result.html0000644005276200011600000000446513265671731023024 0ustar jenkinsjenkins module MCollective::Aggregate::Result - mcollective version 2.12.1

module MCollective::Aggregate::Result

mcollective-2.12.1/doc/MCollective/Aggregate/Summary.html0000644005276200011600000002445113265671731023200 0ustar jenkinsjenkins class MCollective::Aggregate::Summary - mcollective version 2.12.1

class MCollective::Aggregate::Summary

Public Instance Methods

add_value(value) click to toggle source
# File lib/mcollective/aggregate/summary.rb, line 25
def add_value(value)
  if @result[:value].keys.include?(value)
    @result[:value][value] += 1
  else
    @result[:value][value] = 1
  end
end
process_result(value, reply) click to toggle source

Increments the value field if value has been seen before Else create a new value field

# File lib/mcollective/aggregate/summary.rb, line 15
def process_result(value, reply)
  unless value.nil?
    if value.is_a? Array
      value.map{|val| add_value(val)}
    else
      add_value(value)
    end
  end
end
startup_hook() click to toggle source

Before function is run processing

# File lib/mcollective/aggregate/summary.rb, line 5
def startup_hook
  @result[:value] = {}
  @result[:type] = :collection

  # set default aggregate_format if it is undefined
  @aggregate_format = :calculate unless @aggregate_format
end
summarize() click to toggle source
Calls superclass method MCollective::Aggregate::Base#summarize
# File lib/mcollective/aggregate/summary.rb, line 33
def summarize
  if @aggregate_format == :calculate
    max_key_length = @result[:value].keys.map do |k|

      # Response values retain their types. Here we check
      # if the response is a string and turn it into a string
      # if it isn't one.
      if k.respond_to?(:length)
        k.length
      elsif k.respond_to?(:to_s)
        k.to_s.length
      end
    end.max
    @aggregate_format = "%#{max_key_length}s = %s"
  end

  super
end
mcollective-2.12.1/doc/MCollective/Aggregate/Sum.html0000644005276200011600000001253413265671731022306 0ustar jenkinsjenkins class MCollective::Aggregate::Sum - mcollective version 2.12.1

class MCollective::Aggregate::Sum

Public Instance Methods

process_result(value, reply) click to toggle source

Determines the average of a set of numerical values

# File lib/mcollective/aggregate/sum.rb, line 13
def process_result(value, reply)
  @result[:value] += value
end
startup_hook() click to toggle source
# File lib/mcollective/aggregate/sum.rb, line 4
def startup_hook
  @result[:value] = 0
  @result[:type] = :numeric

  # Set default aggregate_function if it is undefined
  @aggregate_format = "Sum of #{@result[:output]}: %f" unless @aggregate_format
end
mcollective-2.12.1/doc/MCollective/Registration/0000755005276200011600000000000013265671736021440 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Registration/Base.html0000644005276200011600000003466713265671733023215 0ustar jenkinsjenkins class MCollective::Registration::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Registration/Agentlist.html0000644005276200011600000000737013265671733024264 0ustar jenkinsjenkins class MCollective::Registration::Agentlist - mcollective version 2.12.1

class MCollective::Registration::Agentlist

A registration plugin that simply sends in the list of agents we have

Public Instance Methods

body() click to toggle source
# File lib/mcollective/registration/agentlist.rb, line 5
def body
  Agents.agentlist
end
mcollective-2.12.1/doc/MCollective/Data/0000755005276200011600000000000013265671736017637 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Data/Collective_data.html0000644005276200011600000000472513265671732023613 0ustar jenkinsjenkins class MCollective::Data::Collective_data - mcollective version 2.12.1

class MCollective::Data::Collective_data

mcollective-2.12.1/doc/MCollective/Data/Base.html0000644005276200011600000004122713265671732021401 0ustar jenkinsjenkins class MCollective::Data::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Data/Result.html0000644005276200011600000002607013265671732022004 0ustar jenkinsjenkins class MCollective::Data::Result - mcollective version 2.12.1

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)
  # checks using the string representation of the class name to avoid deprecations on Bignum and Fixnum
  raise "Can only store String, Integer, Float or Boolean data but got #{val.class} for key #{key}" unless ["String", "Integer", "Bignum", "Fixnum", "Float", "TrueClass", "FalseClass"].include?(val.class.to_s)

  @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 32
def keys
  @data.keys
end
method_missing(method, *args) click to toggle source
# File lib/mcollective/data/result.rb, line 36
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.12.1/doc/MCollective/Data/Agent_data.html0000644005276200011600000000462613265671732022560 0ustar jenkinsjenkins class MCollective::Data::Agent_data - mcollective version 2.12.1

class MCollective::Data::Agent_data

mcollective-2.12.1/doc/MCollective/Data/Fstat_data/0000755005276200011600000000000013265671736021711 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Data/Fstat_data/Digest.html0000644005276200011600000000455413265671732024022 0ustar jenkinsjenkins module MCollective::Data::Fstat_data::Digest - mcollective version 2.12.1

module MCollective::Data::Fstat_data::Digest

mcollective-2.12.1/doc/MCollective/Data/Fstat_data.html0000644005276200011600000000470113265671732022575 0ustar jenkinsjenkins class MCollective::Data::Fstat_data - mcollective version 2.12.1

class MCollective::Data::Fstat_data

mcollective-2.12.1/doc/MCollective/Data/Fact_data.html0000644005276200011600000000467513265671732022403 0ustar jenkinsjenkins class MCollective::Data::Fact_data - mcollective version 2.12.1

class MCollective::Data::Fact_data

mcollective-2.12.1/doc/MCollective/RPC/0000755005276200011600000000000013265671736017412 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/RPC/Request.html0000644005276200011600000005200313265671733021725 0ustar jenkinsjenkins class MCollective::RPC::Request - mcollective version 2.12.1

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] || msg[:body]["action"]
  @data = msg[:body][:data] || msg[:body]["data"]
  @sender = msg[:senderid]
  @agent = msg[:body][: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 58
def [](key)
  return nil unless @data.is_a?(Hash)
  return @data[compatible_key(key)]
end
compatible_key(key) click to toggle source

In a scenario where a request came from a JSON pure medium like a REST service or other language client MCollective::DDL::AgentDDL#validate_rpc_request will check “package†against the intput :package should the input “package†not also be known

Thus once the request is built it will also have “package†and not :package data, so we need to fetch the correct key out of the hash.

# File lib/mcollective/rpc/request.rb, line 25
def compatible_key(key)
  return key if data.include?(key)

  if ddl
    input = ddl.action_interface(action)[:input]

    # if :package is requested and the DDL also declares "package" we cant tell it to fetch
    # "package", hence the check against the input here
    return key.to_s if key.is_a?(Symbol) && !input.include?(key.to_s) && data.include?(key.to_s)
  end

  key
end
fetch(key, default) click to toggle source
# File lib/mcollective/rpc/request.rb, line 63
def fetch(key, default)
  return nil unless @data.is_a?(Hash)
  return @data.fetch(compatible_key(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 41
def include?(key)
  return false unless @data.is_a?(Hash)

  @data.include?(compatible_key(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 49
def should_respond?
  return false unless @data.is_a?(Hash)
  return @data[:process_results] if @data.include?(:process_results)
  return @data["process_results"] if @data.include?("process_results")

  true
end
to_hash() click to toggle source
# File lib/mcollective/rpc/request.rb, line 68
def to_hash
  {:agent => @agent,
   :action => @action,
   :data => @data}
end
to_json() click to toggle source
# File lib/mcollective/rpc/request.rb, line 79
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 75
def validate!
  @ddl.validate_rpc_request(@action, @data)
end
mcollective-2.12.1/doc/MCollective/RPC/Logfile.html0000644005276200011600000001312013265671733021653 0ustar jenkinsjenkins class MCollective::RPC::Logfile - mcollective version 2.12.1

class MCollective::RPC::Logfile

An audit plugin that just logs to a file

You can configure which file it logs to with the setting

plugin.rpcaudit.logfile

Public Instance Methods

audit_request(request, connection) click to toggle source
# File lib/mcollective/audit/logfile.rb, line 12
def audit_request(request, connection)
  logfile = Config.instance.pluginconf["rpcaudit.logfile"] || "/var/log/puppetlabs/mcollective/mcollective-audit.log"

  now = Time.now
  # Already told timezone to be in UTC so we don't look it up again
  # This avoids platform specific timezone representation issues
  now_iso8601 = now.utc.strftime("%Y-%m-%d %H:%M:%S UTC")

  File.open(logfile, "a") do |f|
    f.puts("[#{now_iso8601}] reqid=#{request.uniqid}: reqtime=#{request.time} caller=#{request.caller}@#{request.sender} agent=#{request.agent} action=#{request.action} data=#{request.data.pretty_print_inspect}")
  end
end
mcollective-2.12.1/doc/MCollective/RPC/Result.html0000644005276200011600000005000513265671733021553 0ustar jenkinsjenkins class MCollective::RPC::Result - mcollective version 2.12.1

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

  convert_data_based_on_ddl if ddl
end

Public Instance Methods

<=>(other) click to toggle source
# File lib/mcollective/rpc/result.rb, line 85
def <=>(other)
  self[:sender] <=> other[:sender]
end
[](key) click to toggle source
# File lib/mcollective/rpc/result.rb, line 60
def [](key)
  @results[compatible_key(key)]
end
[]=(key, item) click to toggle source
# File lib/mcollective/rpc/result.rb, line 64
def []=(key, item)
  @results[key] = item
end
compatible_key(key) click to toggle source
# File lib/mcollective/rpc/result.rb, line 52
def compatible_key(key)
  if key.is_a?(Symbol) && @results.include?(key.to_s)
    key.to_s
  else
    key
  end
end
convert_data_based_on_ddl() click to toggle source

Converts keys on the supplied data to those listed as outputs in the DDL. This is to facilitate JSON based transports without forcing everyone to rewrite DDLs and clients to convert symbols to strings, the data will be on symbol keys if the DDL has a symbol and not a string output defined

# File lib/mcollective/rpc/result.rb, line 38
def convert_data_based_on_ddl
  interface = ddl.action_interface(action)

  return if interface.fetch(:output, {}).empty?

  interface[:output].each do |output, properties|
    next if data.include?(output)

    if output.is_a?(Symbol) && data.include?(output.to_s)
      data[output] = data.delete(output.to_s)
    end
  end
end
data() click to toggle source
# File lib/mcollective/rpc/result.rb, line 27
def data
  @results[:data] = @results.delete("data") if @results.include?("data")

  self[:data]
end
ddl() click to toggle source
# File lib/mcollective/rpc/result.rb, line 21
def ddl
  @_ddl ||= DDL.new(agent)
rescue
  nil
end
each() { |k,v| ... } click to toggle source
# File lib/mcollective/rpc/result.rb, line 72
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 68
def fetch(key, default)
  @results.fetch(compatible_key(key), default)
end
to_json(*a) click to toggle source
# File lib/mcollective/rpc/result.rb, line 76
def to_json(*a)
  {:agent => @agent,
   :action => @action,
   :sender => self[:sender],
   :statuscode => self[:statuscode],
   :statusmsg => self[:statusmsg],
   :data => data}.to_json(*a)
end
mcollective-2.12.1/doc/MCollective/RPC/Audit.html0000644005276200011600000001467513265671733021360 0ustar jenkinsjenkins class MCollective::RPC::Audit - mcollective version 2.12.1

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.12.1/doc/MCollective/RPC/Agent.html0000644005276200011600000006161613265671733021345 0ustar jenkinsjenkins class MCollective::RPC::Agent - mcollective version 2.12.1

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 docs.puppetlabs.com/mcollective/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.12.1/doc/MCollective/RPC/Progress.html0000644005276200011600000002355113265671733022107 0ustar jenkinsjenkins class MCollective::RPC::Progress - mcollective version 2.12.1

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.12.1/doc/MCollective/RPC/Reply.html0000644005276200011600000004137513265671733021402 0ustar jenkinsjenkins class MCollective::RPC::Reply - mcollective version 2.12.1

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.12.1/doc/MCollective/RPC/ActionRunner.html0000644005276200011600000007225213265671733022714 0ustar jenkinsjenkins class MCollective::RPC::ActionRunner - mcollective version 2.12.1

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)
  if Util.absolute_path?(command)
    return command
  end

  Config.instance.libdir.each do |libdir|
    command_file_old = File.join(libdir, "agent", agent, command)
    command_file_new = File.join(libdir, "mcollective", "agent", agent, command)
    command_file_old_exists = File.exists?(command_file_old)
    command_file_new_exists = File.exists?(command_file_new)

    if command_file_new_exists && command_file_old_exists
      Log.debug("Found 'implemented_by' scripts found in two locations #{command_file_old} and #{command_file_new}")
      Log.debug("Running script: #{command_file_new}")
      return command_file_new
    elsif command_file_old_exists
      Log.debug("Running script: #{command_file_old}")
      return command_file_old
    elsif command_file_new_exists
      Log.debug("Running script: #{command_file_new}")
      return command_file_new
    end
  end

  Log.warn("No script found for: #{command}")
  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.12.1/doc/MCollective/RPC/Stats.html0000644005276200011600000014743213265671733021406 0ustar jenkinsjenkins class MCollective::RPC::Stats - mcollective version 2.12.1

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]
unexpectedresponsefrom[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]
  @unexpectedresponsefrom = stats[:unexpectedresponsefrom]
  @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 111
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 117
def finish_request
  @totaltime = @blocktime + @discoverytime

  # figure out who responded unexpectedly
  @unexpectedresponsefrom = @responsesfrom - @discovered_nodes

  # figure out who we had no responses from
  @noresponsefrom = @discovered_nodes - @responsesfrom
rescue
  @totaltime = 0
  @noresponsefrom = []
  @unexpectedresponsefrom = []
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 254
def no_response_report
  result_text = StringIO.new

  if @noresponsefrom.size > 0
    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
  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 132
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 190
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

  no_response_r = no_response_report
  unexpected_response_r = unexpected_response_report
  if no_response_r || unexpected_response_r
    result_text << ""
  end

  if no_response_r != ""
    result_text << "" << no_response_r
  end

  if unexpected_response_r != ""
    result_text << "" << unexpected_response_r
  end

  if no_response_r || unexpected_response_r
    result_text << ""
  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 = []
  @unexpectedresponsefrom = []
  @responsesfrom = []
  @responses = 0
  @starttime = Time.now.to_f
  @discoverytime = 0 unless @discoverytime
  @blocktime = 0
  @totaltime = 0
  @discovered = 0
  @discovered_nodes = []
  @okcount = 0
  @failcount = 0
  @requestid = nil
  @aggregate_summary = []
  @aggregate_failures = []
end
text_for_aggregates() click to toggle source
# File lib/mcollective/rpc/stats.rb, line 138
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 97
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 84
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,
   :unexpectedresponsefrom => @unexpectedresponsefrom,
   :starttime         => @starttime,
   :discoverytime     => @discoverytime,
   :blocktime         => @blocktime,
   :responses         => @responses,
   :totaltime         => @totaltime,
   :discovered        => @discovered,
   :discovered_nodes  => @discovered_nodes,
   :okcount           => @okcount,
   :requestid         => @requestid,
   :failcount         => @failcount,
   :aggregate_summary => @aggregate_summary,
   :aggregate_failures => @aggregate_failures}
end
unexpected_response_report() click to toggle source

Returns a blob of text indicating what nodes responded but weren’t discovered

# File lib/mcollective/rpc/stats.rb, line 274
def unexpected_response_report
  result_text = StringIO.new

  if @unexpectedresponsefrom.size > 0
    result_text.puts Util.colorize(:red, "Unexpected response from:")
    result_text.puts

    field_size = Util.field_size(@unexpectedresponsefrom, 30)
    fields_num = Util.field_number(field_size)
    format = "   " + ( " %-#{field_size}s" * fields_num )

    @unexpectedresponsefrom.sort.in_groups_of(fields_num) do |c|
      result_text.puts format % c
    end
  end

  result_text.string
end
mcollective-2.12.1/doc/MCollective/RPC/Client.html0000644005276200011600000044046713265671733021532 0ustar jenkinsjenkins class MCollective::RPC::Client - mcollective version 2.12.1

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(@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] || Config.instance.default_batch_size
  @batch_sleep_time = Float(initial_options[:batch_sleep_time] || Config.instance.default_batch_sleep_time)
  @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

  if initial_options[:stdin]
    @stdin = initial_options[:stdin]
  else
    @stdin = STDIN
  end
end

Public Instance Methods

agent_filter(agent) click to toggle source

Sets the agent filter

# File lib/mcollective/rpc/client.rb, line 437
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 731
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 648
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 659
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 913
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 808
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.unexpectedresponsefrom.concat @client.stats[:unexpectedresponsefrom]
      @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 413
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 607
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 451
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 311
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
detect_and_set_stdin_discovery() click to toggle source

Detects data on STDIN and sets the STDIN discovery method

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

Then we override discovery to try to grok the data on STDIN

# File lib/mcollective/rpc/client.rb, line 479
def detect_and_set_stdin_discovery
  if self.default_discovery_method && !@stdin.tty? && !@stdin.eof?
    self.discovery_method = 'stdin'
    self.discovery_options = 'auto'
  end
end
disconnect() click to toggle source

Disconnects cleanly from the middleware

# File lib/mcollective/rpc/client.rb, line 131
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 501
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
    filter = @filter.merge({'collective' => @collective})
    if @limit_method == :first and @limit_targets.is_a?(Integer)
      @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 392
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 407
def discovery_options=(options)
  @discovery_options = [options].flatten
  reset
end
discovery_timeout() click to toggle source
# File lib/mcollective/rpc/client.rb, line 359
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 364
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 420
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 759
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 136
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 444
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 790
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 639
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 618
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 720
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 241
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 159
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 590
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 679
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 1017
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 result[:statuscode] == 0
  @stats.fail if result[: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 997
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 result[:statuscode] == 0 || result[:statuscode] == 1
    @stats.ok if result[:statuscode] == 0
    @stats.fail if result[: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 458
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 463
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 741
def rpc_result_from_reply(agent, action, reply)
  senderid = reply.include?("senderid") ? reply["senderid"] : reply[:senderid]
  body = reply.include?("body") ? reply["body"] : reply[:body]
  s_code = body.include?("statuscode") ? body["statuscode"] : body[:statuscode]
  s_msg = body.include?("statusmsg") ? body["statusmsg"] : body[:statusmsg]
  data = body.include?("data") ? body["data"] : body[:data]

  Result.new(agent, action, {:sender => senderid, :statuscode => s_code, :statusmsg => s_msg, :data => 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 175
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.12.1/doc/MCollective/RPC/Helpers.html0000644005276200011600000013367413265671733021715 0ustar jenkinsjenkins class MCollective::RPC::Helpers - mcollective version 2.12.1

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 264
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 39
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 or puppet query and extract the “sender†/ “certname†of each entry

The simplist valid JSON based data would be:

[

{"sender" => "example.com"},
{"sender" => "another.com"}

]

or

[

{"certname" => "example.com"},
{"certname" => "another.com"}

]

# File lib/mcollective/rpc/helpers.rb, line 21
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)

    unless host.include?("sender") || host.include?("certname")
      raise "JSON host list does not have senders in it"
    end

    host["sender"] || host["certname"]
  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 213
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 64
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 200
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 130
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.12.1/doc/MCollective/Agents.html0000644005276200011600000006234213265671731021077 0ustar jenkinsjenkins class MCollective::Agents - mcollective version 2.12.1

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.12.1/doc/MCollective/Optionparser.html0000644005276200011600000007451213265671733022347 0ustar jenkinsjenkins class MCollective::Optionparser - mcollective version 2.12.1

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

  @parser.on("--connection-timeout TIMEOUT", Integer, "Set the timeout for establishing a connection to the middleware") do |v|
    @options[:connection_timeout] = Integer(v)
  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 configuration 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.12.1/doc/MCollective/Runner.html0000644005276200011600000004600613265671733021130 0ustar jenkinsjenkins class MCollective::Runner - mcollective version 2.12.1

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.12.1/doc/MCollective/Connector/0000755005276200011600000000000013265671736020720 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Connector/Base.html0000644005276200011600000001115013265671732022452 0ustar jenkinsjenkins class MCollective::Connector::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Connector/Activemq/0000755005276200011600000000000013265671736022471 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Connector/Activemq/Stomp.html0000644005276200011600000000511013265671732024452 0ustar jenkinsjenkins module MCollective::Connector::Activemq::Stomp - mcollective version 2.12.1

module MCollective::Connector::Activemq::Stomp

Constants

Error
mcollective-2.12.1/doc/MCollective/Connector/Activemq/Stomp/0000755005276200011600000000000013265671736023573 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Connector/Activemq/Stomp/Error.html0000644005276200011600000000537613265671732025561 0ustar jenkinsjenkins module MCollective::Connector::Activemq::Stomp::Error - mcollective version 2.12.1

module MCollective::Connector::Activemq::Stomp::Error

Constants

DuplicateSubscription
NoCurrentConnection
mcollective-2.12.1/doc/MCollective/Connector/Activemq/DummyError.html0000644005276200011600000000560613265671732025467 0ustar jenkinsjenkins class MCollective::Connector::Activemq::DummyError - mcollective version 2.12.1

class MCollective::Connector::Activemq::DummyError

Older stomp gems do not have these error classes, in order to be able to handle these exceptions if they are present and still support older gems we’re assigning the constants to a dummy exception that will never be thrown by us. End result is that the code catching these exceptions become noops on older gems but on newer ones they become usable and handle those new errors intelligently

mcollective-2.12.1/doc/MCollective/Connector/Activemq/EventLogger.html0000644005276200011600000005607113265671732025605 0ustar jenkinsjenkins class MCollective::Connector::Activemq::EventLogger - mcollective version 2.12.1

class MCollective::Connector::Activemq::EventLogger

Class for Stomp 1.1.9 callback based logging

Public Instance Methods

on_connected(params=nil) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 118
def on_connected(params=nil)
  Log.info("Connected to #{stomp_url(params)}")
rescue
end
on_connectfail(params=nil) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 128
def on_connectfail(params=nil)
  Log.info("TCP Connection to #{stomp_url(params)} failed on attempt #{params[:cur_conattempts]}")
rescue
end
on_connecting(params=nil) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 113
def on_connecting(params=nil)
  Log.info("TCP Connection attempt %d to %s" % [params[:cur_conattempts], stomp_url(params)])
rescue
end
on_disconnect(params=nil) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 123
def on_disconnect(params=nil)
  Log.info("Disconnected from #{stomp_url(params)}")
rescue
end
on_hbfire(params, srind, curt) click to toggle source

Log heart beat fires

# File lib/mcollective/connector/activemq.rb, line 185
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
on_hbread_fail(params, ticker_data) click to toggle source

Stomp 1.1+ - heart beat read (receive) failed.

# File lib/mcollective/connector/activemq.rb, line 153
def on_hbread_fail(params, ticker_data)
  if ticker_data["lock_fail"]
    if params[:max_hbrlck_fails] == 0
      # failure is disabled
      Log.debug("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect])
    elsif ticker_data['lock_fail_count'] >= params[:max_hbrlck_fails]
      # we're about to force a disconnect
      Log.error("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect])
    else
      Log.warn("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect])
    end
  else
    if params[:max_hbread_fails] == 0
      # failure is disabled
      Log.debug("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect])
    elsif ticker_data['read_fail_count'] >= params[:max_hbread_fails]
      # we're about to force a reconnect
      Log.error("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect])
    else
      Log.warn("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect])
    end
  end
rescue Exception => e
end
on_hbwrite_fail(params, ticker_data) click to toggle source

Stomp 1.1+ - heart beat send (transmit) failed.

# File lib/mcollective/connector/activemq.rb, line 179
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
on_miscerr(params, errstr) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 133
def on_miscerr(params, errstr)
  Log.error("Unexpected error on connection #{stomp_url(params)}: #{errstr}")
rescue
end
on_ssl_connected(params) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 143
def on_ssl_connected(params)
  Log.info("SSL session established with #{stomp_url(params)}")
rescue
end
on_ssl_connectfail(params) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 148
def on_ssl_connectfail(params)
  Log.error("SSL session creation with #{stomp_url(params)} failed: #{params[:ssl_exception]}")
end
on_ssl_connecting(params) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 138
def on_ssl_connecting(params)
  Log.info("Establishing SSL session with #{stomp_url(params)}")
rescue
end
stomp_url(params) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 195
def stomp_url(params)
  "%s://%s@%s:%d" % [ params[:cur_ssl] ? "stomp+ssl" : "stomp", params[:cur_login], params[:cur_host], params[:cur_port]]
end
mcollective-2.12.1/doc/MCollective/Connector/Rabbitmq/0000755005276200011600000000000013265671736022461 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Connector/Rabbitmq/EventLogger.html0000644005276200011600000005565413265671732025603 0ustar jenkinsjenkins class MCollective::Connector::Rabbitmq::EventLogger - mcollective version 2.12.1

class MCollective::Connector::Rabbitmq::EventLogger

Public Instance Methods

on_connected(params=nil) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 14
def on_connected(params=nil)
  Log.info("Connected to #{stomp_url(params)}")
rescue
end
on_connectfail(params=nil) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 24
def on_connectfail(params=nil)
  Log.info("TCP Connection to #{stomp_url(params)} failed on attempt #{params[:cur_conattempts]}")
rescue
end
on_connecting(params=nil) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 9
def on_connecting(params=nil)
  Log.info("TCP Connection attempt %d to %s" % [params[:cur_conattempts], stomp_url(params)])
rescue
end
on_disconnect(params=nil) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 19
def on_disconnect(params=nil)
  Log.info("Disconnected from #{stomp_url(params)}")
rescue
end
on_hbfire(params, srind, curt) click to toggle source

Log heart beat fires

# File lib/mcollective/connector/rabbitmq.rb, line 81
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
on_hbread_fail(params, ticker_data) click to toggle source

Stomp 1.1+ - heart beat read (receive) failed.

# File lib/mcollective/connector/rabbitmq.rb, line 49
def on_hbread_fail(params, ticker_data)
  if ticker_data["lock_fail"]
    if params[:max_hbrlck_fails] == 0
      # failure is disabled
      Log.debug("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect])
    elsif ticker_data['lock_fail_count'] >= params[:max_hbrlck_fails]
      # we're about to force a disconnect
      Log.error("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect])
    else
      Log.warn("Heartbeat failed to acquire readlock for '%s': %s" % [stomp_url(params), ticker_data.inspect])
    end
  else
    if params[:max_hbread_fails] == 0
      # failure is disabled
      Log.debug("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect])
    elsif ticker_data['read_fail_count'] >= params[:max_hbread_fails]
      # we're about to force a reconnect
      Log.error("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect])
    else
      Log.warn("Heartbeat read failed from '%s': %s" % [stomp_url(params), ticker_data.inspect])
    end
  end
rescue Exception => e
end
on_hbwrite_fail(params, ticker_data) click to toggle source

Stomp 1.1+ - heart beat send (transmit) failed.

# File lib/mcollective/connector/rabbitmq.rb, line 75
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
on_miscerr(params, errstr) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 29
def on_miscerr(params, errstr)
  Log.error("Unexpected error on connection #{stomp_url(params)}: #{errstr}")
rescue
end
on_ssl_connected(params) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 39
def on_ssl_connected(params)
  Log.info("SSL session established with #{stomp_url(params)}")
rescue
end
on_ssl_connectfail(params) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 44
def on_ssl_connectfail(params)
  Log.error("SSL session creation with #{stomp_url(params)} failed: #{params[:ssl_exception]}")
end
on_ssl_connecting(params) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 34
def on_ssl_connecting(params)
  Log.info("Establishing SSL session with #{stomp_url(params)}")
rescue
end
stomp_url(params) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 91
def stomp_url(params)
  "%s://%s@%s:%d" % [ params[:cur_ssl] ? "stomp+ssl" : "stomp", params[:cur_login], params[:cur_host], params[:cur_port]]
end
mcollective-2.12.1/doc/MCollective/Connector/Rabbitmq.html0000644005276200011600000021163113265671732023347 0ustar jenkinsjenkins class MCollective::Connector::Rabbitmq - mcollective version 2.12.1

class MCollective::Connector::Rabbitmq

Attributes

connection[R]

Public Class Methods

new() click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 96
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

  Log.info("RabbitMQ connector initialized.  Using stomp-gem #{stomp_version}")
end

Public Instance Methods

connect(connector = ::Stomp::Connection) click to toggle source

Connects to the RabbitMQ middleware

# File lib/mcollective/connector/rabbitmq.rb, line 110
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 = []
    middleware_user = ''
    middleware_password = ''
    prompt_for_username = get_bool_option("rabbitmq.prompt_user", "false")
    prompt_for_password = get_bool_option("rabbitmq.prompt_password", "false")
    if prompt_for_username
      Log.debug("No previous user exists and rabbitmq.prompt-user is set to true")
      print "Please enter user to connect to middleware: "
      middleware_user = STDIN.gets.chomp
    end

    if prompt_for_password
      Log.debug("No previous password exists and rabbitmq.prompt-password is set to true")
      middleware_password = MCollective::Util.get_hidden_input("Please enter password: ")
      print "\n"
    end
    
    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[:ssl] = get_bool_option("rabbitmq.pool.#{poolnum}.ssl", "false")
      
      # read user from config file
      host[:login] = get_env_or_option("STOMP_USER", "rabbitmq.pool.#{poolnum}.user", middleware_user)
      if prompt_for_username and host[:login] != middleware_user
        Log.info("Using #{host[:login]} from config file to connect to #{host[:host]}. "+
                  "plugin.rabbitmq.prompt_user should be set to false to remove the prompt.")
      end
      
      # read password from config file
      host[:passcode] = get_env_or_option("STOMP_PASSWORD", "rabbitmq.pool.#{poolnum}.password", middleware_password)
      if prompt_for_password and host[:passcode] != middleware_password
          Log.info("Using password from config file to connect to #{host[:host]}. "+
                  "plugin.rabbitmq.prompt_password should be set to false to remove the prompt.")
      end
     
      # 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", 0))
    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 ClientTimeoutError => e
    raise e
  rescue Exception => e
    raise("Could not connect to RabbitMQ Server: #{e}")
  end
end
connection_headers() click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 200
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
disconnect() click to toggle source

Disconnects from the RabbitMQ connection

# File lib/mcollective/connector/rabbitmq.rb, line 481
def disconnect
  Log.debug("Disconnecting from RabbitMQ")
  @connection.disconnect
  @connection = nil
end
exponential_back_off() click to toggle source

Calculate the exponential backoff needed

# File lib/mcollective/connector/rabbitmq.rb, line 294
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
get_bool_option(val, default) click to toggle source

looks up a boolean value in the config

# File lib/mcollective/connector/rabbitmq.rb, line 510
def get_bool_option(val, default)
  Util.str_to_bool(@config.pluginconf.fetch(val, default))
end
get_cert_file(poolnum) click to toggle source

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.

# File lib/mcollective/connector/rabbitmq.rb, line 289
def get_cert_file(poolnum)
  ENV["MCOLLECTIVE_RABBITMQ_POOL%s_SSL_CERT" % poolnum] || get_option("rabbitmq.pool.#{poolnum}.ssl.cert", false)
end
get_env_or_option(env, opt, default=nil) click to toggle source

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

# File lib/mcollective/connector/rabbitmq.rb, line 491
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
get_key_file(poolnum) click to toggle source

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.

# File lib/mcollective/connector/rabbitmq.rb, line 281
def get_key_file(poolnum)
  ENV["MCOLLECTIVE_RABBITMQ_POOL%s_SSL_KEY" % poolnum] || get_option("rabbitmq.pool.#{poolnum}.ssl.key", false)
end
get_option(opt, default=nil) click to toggle source

looks for a config option, accepts an optional default

raises an exception when it cant find a value anywhere

# File lib/mcollective/connector/rabbitmq.rb, line 502
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
make_target(agent, type, collective, reply_to=nil, node=nil) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 386
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)

  agents_multiplex = get_bool_option("rabbitmq.agents_multiplex", "false")
  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
      if agents_multiplex
        target[:name] = "/exchange/%s_broadcast" % collective
        target[:id] = "%s_broadcast" % collective
      else
        target[:name] = "/exchange/%s_broadcast/%s" % [collective, agent]
        target[:id] = "%s_broadcast_%s" % [collective, agent]
      end
      if reply_to
        target[:headers]["reply-to"] = reply_to
      else
        target[:headers]["reply-to"] = reply_path
      end

    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
publish(msg) click to toggle source

Sends a message to the RabbitMQ connection

# File lib/mcollective/connector/rabbitmq.rb, line 345
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
receive() click to toggle source

Receives a message from the RabbitMQ connection

# File lib/mcollective/connector/rabbitmq.rb, line 313
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
ssl_parameters(poolnum, fallback) click to toggle source

Sets the SSL paramaters for a specific connection

# File lib/mcollective/connector/rabbitmq.rb, line 244
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),
    :ciphers   => get_option("rabbitmq.pool.#{poolnum}.ssl.ciphers", 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
stomp_version() click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 235
def stomp_version
  ::Stomp::Version::STRING
end
stomp_version_supports_heartbeat?() click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 239
def stomp_version_supports_heartbeat?
  return Util.versioncmp(stomp_version, "1.2.10") >= 0
end
subscribe(agent, type, collective) click to toggle source

Subscribe to a topic or queue

# File lib/mcollective/connector/rabbitmq.rb, line 434
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
target_for(msg, node=nil) click to toggle source
# File lib/mcollective/connector/rabbitmq.rb, line 365
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

  target[:headers]["mc_sender"] = Config.instance.identity

  return target
end
unsubscribe(agent, type, collective) click to toggle source

Subscribe to a topic or queue

# File lib/mcollective/connector/rabbitmq.rb, line 464
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
mcollective-2.12.1/doc/MCollective/Connector/Activemq.html0000644005276200011600000022514113265671732023360 0ustar jenkinsjenkins class MCollective::Connector::Activemq - mcollective version 2.12.1

class MCollective::Connector::Activemq

Handles sending and receiving messages over the Stomp protocol for ActiveMQ servers specifically, we take advantages of ActiveMQ specific features and enhancements to the Stomp protocol. For best results in a clustered environment use ActiveMQ 5.5.0 at least.

This plugin takes an entirely different approach to dealing with ActiveMQ from the more generic stomp connector.

- Agents use /topic/<collective>.<agent>.agent
- Replies use temp-topics so they are private and transient.
- Point to Point messages using topics are supported by subscribing to
  /queue/<collective>.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.1.ssl.ciphers = TLSv1:!MD5:!LOW:!EXPORT

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

Attributes

connection[R]

Public Class Methods

new() click to toggle source
# File lib/mcollective/connector/activemq.rb, line 200
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

  Log.info("ActiveMQ connector initialized.  Using stomp-gem #{stomp_version}")
end

Public Instance Methods

connect(connector = ::Stomp::Connection) click to toggle source

Connects to the ActiveMQ middleware

# File lib/mcollective/connector/activemq.rb, line 215
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 = []
    middleware_user = ''
    middleware_password = ''
    prompt_for_username = get_bool_option("activemq.prompt_user", "false")
    prompt_for_password = get_bool_option("activemq.prompt_password", "false")
    
    if prompt_for_username
      Log.debug("No previous user exists and activemq.prompt-user is set to true")
      print "Please enter user to connect to middleware: "
      middleware_user = STDIN.gets.chomp
    end

    if prompt_for_password
      Log.debug("No previous password exists and activemq.prompt-password is set to true")
      middleware_password = MCollective::Util.get_hidden_input("Please enter password: ")
      print "\n"
    end

    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[:ssl] = get_bool_option("activemq.pool.#{poolnum}.ssl", "false")
      
      # read user from config file
      host[:login] = get_env_or_option("STOMP_USER", "activemq.pool.#{poolnum}.user", middleware_user)
      if prompt_for_username and host[:login] != middleware_user
          Log.info("Using #{host[:login]} from config file to connect to #{host[:host]}. "+
                  "plugin.activemq.prompt_user should be set to false to remove the prompt.")
      end
      
      # read user from config file
      host[:passcode] = get_env_or_option("STOMP_PASSWORD", "activemq.pool.#{poolnum}.password", middleware_password)
      if prompt_for_password and host[:passcode] != middleware_password
          Log.info("Using password from config file to connect to #{host[:host]}. "+
                  "plugin.activemq.prompt_password should be set to false to remove the prompt.")
      end

      # 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", 0))
    connection[:max_hbread_fails] = Integer(get_option("activemq.max_hbread_fails", 2))

    connection[:logger] = EventLogger.new

    @connection = connector.new(connection)

  rescue ClientTimeoutError => e
    raise e
  rescue Exception => e
    raise("Could not connect to ActiveMQ Server: #{e}")
  end
end
connection_headers() click to toggle source
# File lib/mcollective/connector/activemq.rb, line 313
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
disconnect() click to toggle source

Disconnects from the ActiveMQ connection

# File lib/mcollective/connector/activemq.rb, line 506
def disconnect
  Log.debug("Disconnecting from ActiveMQ")
  @connection.disconnect
  @connection = nil
end
exponential_back_off() click to toggle source

Calculate the exponential backoff needed

# File lib/mcollective/connector/activemq.rb, line 399
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
get_bool_option(val, default) click to toggle source

looks up a boolean value in the config

# File lib/mcollective/connector/activemq.rb, line 602
def get_bool_option(val, default)
  Util.str_to_bool(@config.pluginconf.fetch(val, default))
end
get_cert_file(poolnum) click to toggle source

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.

# File lib/mcollective/connector/activemq.rb, line 394
def get_cert_file(poolnum)
  ENV["MCOLLECTIVE_ACTIVEMQ_POOL%s_SSL_CERT" % poolnum] || get_option("activemq.pool.#{poolnum}.ssl.cert", false)
end
get_env_or_option(env, opt, default=nil) click to toggle source

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

# File lib/mcollective/connector/activemq.rb, line 583
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
get_key_file(poolnum) click to toggle source

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.

# File lib/mcollective/connector/activemq.rb, line 386
def get_key_file(poolnum)
  ENV["MCOLLECTIVE_ACTIVEMQ_POOL%s_SSL_KEY" % poolnum] || get_option("activemq.pool.#{poolnum}.ssl.key", false)
end
get_option(opt, default=nil) click to toggle source

looks for a config option, accepts an optional default

raises an exception when it cant find a value anywhere

# File lib/mcollective/connector/activemq.rb, line 594
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
headers_for(msg, identity=nil) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 512
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

  headers["mc_sender"] = Config.instance.identity

  return headers
end
make_target(agent, type, collective) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 540
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)

  agents_multiplex = get_bool_option("activemq.agents_multiplex", "false")
  target = {:name => nil, :headers => {}}

  case type
    when :reply
      target[:name] = ["/queue/" + collective, :reply, "#{Config.instance.identity}_#{$$}", Client.request_sequence].join(".")

    when :broadcast
      if agents_multiplex
        target[:name] = ["/topic/" + collective, :agents].join(".")
      else
        target[:name] = ["/topic/" + collective, agent, :agent].join(".")
      end

    when :request
      if agents_multiplex
        target[:name] = ["/topic/" + collective, :agents].join(".")
      else
        target[:name] = ["/topic/" + collective, agent, :agent].join(".")
      end

    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
publish(msg) click to toggle source

Sends a message to the ActiveMQ connection

# File lib/mcollective/connector/activemq.rb, line 449
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
receive() click to toggle source

Receives a message from the ActiveMQ connection

# File lib/mcollective/connector/activemq.rb, line 418
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.warn("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
ssl_parameters(poolnum, fallback) click to toggle source

Sets the SSL paramaters for a specific connection

# File lib/mcollective/connector/activemq.rb, line 349
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),
    :ciphers   => get_option("activemq.pool.#{poolnum}.ssl.ciphers", 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
stomp_version() click to toggle source
# File lib/mcollective/connector/activemq.rb, line 305
def stomp_version
  ::Stomp::Version::STRING
end
stomp_version_supports_heartbeat?() click to toggle source
# File lib/mcollective/connector/activemq.rb, line 309
def stomp_version_supports_heartbeat?
  return Util.versioncmp(stomp_version, "1.2.10") >= 0
end
subscribe(agent, type, collective) click to toggle source

Subscribe to a topic or queue

# File lib/mcollective/connector/activemq.rb, line 472
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
target_for(msg) click to toggle source
# File lib/mcollective/connector/activemq.rb, line 493
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
unsubscribe(agent, type, collective) click to toggle source

UnSubscribe to a topic or queue

# File lib/mcollective/connector/activemq.rb, line 485
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
mcollective-2.12.1/doc/MCollective/SSL.html0000644005276200011600000014447313265671733020327 0ustar jenkinsjenkins class MCollective::SSL - mcollective version 2.12.1

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 = [0x6b, 0xa7, 0xb8, 0x10, 0x9d, 0xad, 0x11, 0xd1, 0x80, 0xb4, 0x00, 0xc0, 0x4f, 0xd4, 0x30, 0xc8].map {|b| b.chr}.join

  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.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.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.12.1/doc/MCollective/Validator/0000755005276200011600000000000013265671736020713 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Validator/Ipv4addressValidator.html0000644005276200011600000001102613265671734025635 0ustar jenkinsjenkins class MCollective::Validator::Ipv4addressValidator - mcollective version 2.12.1

class MCollective::Validator::Ipv4addressValidator

Public Class Methods

validate(validator) click to toggle source
# File lib/mcollective/validator/ipv4address_validator.rb, line 6
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
mcollective-2.12.1/doc/MCollective/Validator/ArrayValidator.html0000644005276200011600000001045213265671734024525 0ustar jenkinsjenkins class MCollective::Validator::ArrayValidator - mcollective version 2.12.1

class MCollective::Validator::ArrayValidator

Public Class Methods

validate(validator, array) click to toggle source
# File lib/mcollective/validator/array_validator.rb, line 4
def self.validate(validator, array)
  raise ValidatorError, "value should be one of %s" % [ array.join(", ") ] unless array.include?(validator)
end
mcollective-2.12.1/doc/MCollective/Validator/LengthValidator.html0000644005276200011600000001061513265671734024671 0ustar jenkinsjenkins class MCollective::Validator::LengthValidator - mcollective version 2.12.1

class MCollective::Validator::LengthValidator

Public Class Methods

validate(validator, length) click to toggle source
# File lib/mcollective/validator/length_validator.rb, line 4
def self.validate(validator, length)
  if (validator.size > length) && (length > 0)
    raise ValidatorError, "Input string is longer than #{length} character(s)"
  end
end
mcollective-2.12.1/doc/MCollective/Validator/Ipv6addressValidator.html0000644005276200011600000001102613265671734025637 0ustar jenkinsjenkins class MCollective::Validator::Ipv6addressValidator - mcollective version 2.12.1

class MCollective::Validator::Ipv6addressValidator

Public Class Methods

validate(validator) click to toggle source
# File lib/mcollective/validator/ipv6address_validator.rb, line 6
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
mcollective-2.12.1/doc/MCollective/Validator/TypecheckValidator.html0000644005276200011600000001553013265671734025370 0ustar jenkinsjenkins class MCollective::Validator::TypecheckValidator - mcollective version 2.12.1

class MCollective::Validator::TypecheckValidator

Public Class Methods

check_type(validator, validation_type) click to toggle source
# File lib/mcollective/validator/typecheck_validator.rb, line 8
def self.check_type(validator, validation_type)
  case validation_type
    when Class
      validator.is_a?(validation_type)
    when :integer
      validator.is_a?(Integer)
    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
validate(validator, validation_type) click to toggle source
# File lib/mcollective/validator/typecheck_validator.rb, line 4
def self.validate(validator, validation_type)
  raise ValidatorError, "value should be a #{validation_type.to_s}" unless check_type(validator, validation_type)
end
mcollective-2.12.1/doc/MCollective/Validator/RegexValidator.html0000644005276200011600000001017013265671734024516 0ustar jenkinsjenkins class MCollective::Validator::RegexValidator - mcollective version 2.12.1

class MCollective::Validator::RegexValidator

Public Class Methods

validate(validator, regex) click to toggle source
# File lib/mcollective/validator/regex_validator.rb, line 4
def self.validate(validator, regex)
  raise ValidatorError, "value should match #{regex}" unless validator.match(regex)
end
mcollective-2.12.1/doc/MCollective/Validator/ShellsafeValidator.html0000644005276200011600000001201413265671734025351 0ustar jenkinsjenkins class MCollective::Validator::ShellsafeValidator - mcollective version 2.12.1

class MCollective::Validator::ShellsafeValidator

Public Class Methods

validate(validator) click to toggle source
# File lib/mcollective/validator/shellsafe_validator.rb, line 4
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
mcollective-2.12.1/doc/MCollective/ClientTimeoutError.html0000644005276200011600000000461313265671732023453 0ustar jenkinsjenkins class MCollective::ClientTimeoutError - mcollective version 2.12.1

class MCollective::ClientTimeoutError

mcollective-2.12.1/doc/MCollective/Application/0000755005276200011600000000000013265671736021231 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Application/Find.html0000644005276200011600000001356113265671732023001 0ustar jenkinsjenkins class MCollective::Application::Find - mcollective version 2.12.1

class MCollective::Application::Find

Public Instance Methods

main() click to toggle source
# File lib/mcollective/application/find.rb, line 4
def main
  mc = rpcclient("rpcutil")

  starttime = Time.now

  mc.detect_and_set_stdin_discovery

  nodes = mc.discover

  discoverytime = Time.now - starttime

  STDERR.puts if options[:verbose]

  nodes.each {|c| puts c}

  STDERR.puts "\nDiscovered %s nodes in %.2f seconds using the %s discovery plugin" % [nodes.size, discoverytime, mc.client.discoverer.discovery_method] if options[:verbose]

  nodes.size > 0 ? exit(0) : exit(1)
end
mcollective-2.12.1/doc/MCollective/Application/Completion.html0000644005276200011600000003732113265671731024231 0ustar jenkinsjenkins class MCollective::Application::Completion - mcollective version 2.12.1

class MCollective::Application::Completion

Public Instance Methods

list_actions() click to toggle source
# File lib/mcollective/application/completion.rb, line 55
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
list_agents() click to toggle source
# File lib/mcollective/application/completion.rb, line 41
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
list_applications() click to toggle source
# File lib/mcollective/application/completion.rb, line 85
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
list_inputs() click to toggle source
# File lib/mcollective/application/completion.rb, line 70
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
main() click to toggle source
# File lib/mcollective/application/completion.rb, line 95
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
mcollective-2.12.1/doc/MCollective/Application/Rpc.html0000644005276200011600000003734213265671732022650 0ustar jenkinsjenkins class MCollective::Application::Rpc - mcollective version 2.12.1

class MCollective::Application::Rpc

Public Instance Methods

main() click to toggle source
# File lib/mcollective/application/rpc.rb, line 80
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}

    mc.detect_and_set_stdin_discovery

    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
post_option_parser(configuration) click to toggle source
# File lib/mcollective/application/rpc.rb, line 28
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
string_to_ddl_type(arguments, ddl) click to toggle source
# File lib/mcollective/application/rpc.rb, line 64
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
mcollective-2.12.1/doc/MCollective/Application/Plugin.html0000644005276200011600000012612213265671732023355 0ustar jenkinsjenkins class MCollective::Application::Plugin - mcollective version 2.12.1

class MCollective::Application::Plugin

Public Instance Methods

doc_command() click to toggle source

Show application list and plugin help

# File lib/mcollective/application/plugin.rb, line 226
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
generate_command() click to toggle source

Generate a plugin skeleton

# File lib/mcollective/application/plugin.rb, line 170
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
identify_plugin() click to toggle source

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. Return the name of the type of plugin as a string

# File lib/mcollective/application/plugin.rb, line 334
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
info_command() click to toggle source

Display info about plugin

# File lib/mcollective/application/plugin.rb, line 163
def info_command
  plugin = prepare_plugin
  packager = PluginPackager["#{configuration[:format].capitalize}Packager"]
  packager.new(plugin).package_information
end
load_plugin_config_values() click to toggle source

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

# File lib/mcollective/application/plugin.rb, line 350
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
load_plugin_ddl(plugin, type) click to toggle source

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.

# File lib/mcollective/application/plugin.rb, line 213
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
main() click to toggle source
# File lib/mcollective/application/plugin.rb, line 357
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
package_command() click to toggle source

Package plugin

# File lib/mcollective/application/plugin.rb, line 196
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
plugin_directory_exists?(plugin_type) click to toggle source
# File lib/mcollective/application/plugin.rb, line 313
def plugin_directory_exists?(plugin_type)
  File.directory?(File.join(PluginPackager.get_plugin_path(configuration[:target]), plugin_type))
end
post_option_parser(configuration) click to toggle source

Handle alternative format that optparser can’t parse.

# File lib/mcollective/application/plugin.rb, line 136
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
prepare_plugin() click to toggle source

Creates the correct package plugin object.

# File lib/mcollective/application/plugin.rb, line 290
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
set_plugin_type() click to toggle source

Identify plugin type if not provided.

# File lib/mcollective/application/plugin.rb, line 318
def set_plugin_type
  if plugin_directory_exists?("agent") || plugin_directory_exists?("application")
    configuration[:plugintype] = "AgentDefinition"
    return "Agent"
  elsif plugin_directory_exists?(plugintype = identify_plugin)
    configuration[:plugintype] = "StandardDefinition"
    return plugintype
  else
    raise RuntimeError, "target directory is not a valid mcollective plugin"
  end
end
mcollective-2.12.1/doc/MCollective/Application/Inventory.html0000644005276200011600000014336113265671732024120 0ustar jenkinsjenkins class MCollective::Application::Inventory - mcollective version 2.12.1

class MCollective::Application::Inventory

Public Instance Methods

agents() click to toggle source
# File lib/mcollective/application/inventory.rb, line 219
def agents
  @node[:agents]
end
classes() click to toggle source
# File lib/mcollective/application/inventory.rb, line 215
def classes
  @node[:classes]
end
collectives_map(file) click to toggle source

Writes a crude DOT graph to a file

# File lib/mcollective/application/inventory.rb, line 55
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
collectives_report() click to toggle source

Prints a report of all known sub collectives

# File lib/mcollective/application/inventory.rb, line 79
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
facts() click to toggle source
# File lib/mcollective/application/inventory.rb, line 211
def facts
  @node[:facts]
end
fields(&blk) click to toggle source
# File lib/mcollective/application/inventory.rb, line 203
def fields(&blk)
  @flds = blk
end
format(fmt) click to toggle source

Helpers to create a simple DSL for scriptlets

# File lib/mcollective/application/inventory.rb, line 199
def format(fmt)
  @fmt = fmt
end
formatted_inventory(&blk) click to toggle source

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 <<TOP

            Node Report @<<<<<<<<<<<<<<<<<<<<<<<<<
                        time

Hostname:         Customer:     Distribution:
-------------------------------------------------------------------------
TOP

    page_body <<BODY

@<<<<<<<<<<<<<<<< @<<<<<<<<<<<< @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
identity,    facts["customer"], facts["lsbdistdescription"]
                                @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
                                facts["processor0"]
BODY
end
# File lib/mcollective/application/inventory.rb, line 291
def formatted_inventory(&blk)
  require 'formatr'

  raise "Need to give a block to formatted_inventory" unless block_given?

  blk.call if block_given?

  raise "Need to define page body format" if @page_body.nil?

  body_fmt = FormatR::Format.new(@page_heading, @page_body)
  body_fmt.setPageLength(@page_length)
  time = Time.now

  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]}

    body_fmt.printFormat(binding)
  end
rescue Exception => e
  STDERR.puts "Could not create report: #{e.class}: #{e}"
  exit 1
end
get_collectives() click to toggle source

Get all the known collectives and nodes that belong to them

# File lib/mcollective/application/inventory.rb, line 29
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
identity() click to toggle source
# File lib/mcollective/application/inventory.rb, line 207
def identity
  @node[:identity]
end
inventory(&blk) click to toggle source

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
# File lib/mcollective/application/inventory.rb, line 243
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
main() click to toggle source
# File lib/mcollective/application/inventory.rb, line 326
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
node_inventory() click to toggle source
# File lib/mcollective/application/inventory.rb, line 94
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
page_body(fmt) click to toggle source
# File lib/mcollective/application/inventory.rb, line 231
def page_body(fmt)
  @page_body = fmt
end
page_heading(fmt) click to toggle source
# File lib/mcollective/application/inventory.rb, line 227
def page_heading(fmt)
  @page_heading = fmt
end
page_length(len) click to toggle source
# File lib/mcollective/application/inventory.rb, line 223
def page_length(len)
  @page_length = len
end
post_option_parser(configuration) click to toggle source
# File lib/mcollective/application/inventory.rb, line 18
def post_option_parser(configuration)
  configuration[:node] = ARGV.shift if ARGV.size > 0
end
validate_configuration(configuration) click to toggle source
# File lib/mcollective/application/inventory.rb, line 22
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
mcollective-2.12.1/doc/MCollective/Application/Help.html0000644005276200011600000001474213265671732023013 0ustar jenkinsjenkins class MCollective::Application::Help - mcollective version 2.12.1

class MCollective::Application::Help

Public Instance Methods

main() click to toggle source
# File lib/mcollective/application/help.rb, line 10
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
post_option_parser(configuration) click to toggle source
# File lib/mcollective/application/help.rb, line 6
def post_option_parser(configuration)
  configuration[:application] = ARGV.shift if ARGV.size > 0
end
mcollective-2.12.1/doc/MCollective/Application/Ping.html0000644005276200011600000003370113265671732023014 0ustar jenkinsjenkins class MCollective::Application::Ping - mcollective version 2.12.1

class MCollective::Application::Ping

Public Instance Methods

main() click to toggle source
# File lib/mcollective/application/ping.rb, line 50
def main
  # If the user did not override the default timeout include the discovery timeout
  if options[:timeout] == 5
    discovery_timeout = options[:disctimeout] || Config.instance.discovery_timeout || 0
    options[:timeout] = options[:timeout] + discovery_timeout
  end
  client = MCollective::Client.new(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
spark(resp_times) click to toggle source

Convert the times structure into a array representing buckets of responses in 50 ms intervals. Return a small sparkline graph using UTF8 characters

# File lib/mcollective/application/ping.rb, line 14
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
mcollective-2.12.1/doc/MCollective/Application/Describe_filter.html0000644005276200011600000003551613265671731025211 0ustar jenkinsjenkins class MCollective::Application::Describe_filter - mcollective version 2.12.1

class MCollective::Application::Describe_filter

Public Instance Methods

describe_c_filter(classes) click to toggle source
# File lib/mcollective/application/describe_filter.rb, line 53
def describe_c_filter(classes)
  puts "-C filter expands to the following class checks:"
  puts
  classes.each do |c|
    puts "  " + get_class_string(c)
  end
end
describe_f_filter(facts) click to toggle source
# File lib/mcollective/application/describe_filter.rb, line 45
def describe_f_filter(facts)
  puts "-F filter expands to the following fact comparisons:"
  puts
  facts.each do |f|
    puts "  " + get_fact_string(f[:fact], f[:value], f[:operator])
  end
end
describe_s_filter(stack) click to toggle source
# File lib/mcollective/application/describe_filter.rb, line 9
def describe_s_filter(stack)
  indent = "  "
  old_indent = "  "
  puts "-S Query expands to the following instructions:"
  puts
  stack.each do |token|
    if token.keys[0] == "statement"
      if token.values[0] =~ /(<=|>=|=|=~|=)/
        op = $1
        k,v = token.values[0].split(op)
        puts indent + get_fact_string(k, v, op)
      else
        puts indent + get_class_string(token.values[0])
      end
    elsif token.keys[0] == "fstatement"
      v = token.values[0]
      result_string = indent + "Execute the Data Query '#{v["name"]}'"
      if v["params"]
        result_string += " with parameters (#{v["params"]})"
      end
      result_string += ". "
      result_string += "Check if the query's '#{v["value"]}' value #{v["operator"]} '#{v["r_compare"]}'  "
      puts result_string
    elsif token.keys[0] == "("
      puts indent + "("
      old_indent = indent
      indent *= 2
    elsif token.keys[0] == ")"
      indent = old_indent
      puts indent + ")"
    else
      puts indent + token.keys[0].upcase
    end
  end
end
main() click to toggle source
# File lib/mcollective/application/describe_filter.rb, line 61
def main
  if !(@options[:filter]["fact"].empty?)
    describe_f_filter(@options[:filter]["fact"])
    puts
  end

  if !(@options[:filter]["cf_class"].empty?)
    describe_c_filter(@options[:filter]["cf_class"])
    puts
  end

  if !(@options[:filter]["compound"].empty?)
    describe_s_filter(@options[:filter]["compound"][0])
    puts
  end
end
mcollective-2.12.1/doc/MCollective/Application/Facts.html0000644005276200011600000003013013265671731023147 0ustar jenkinsjenkins class MCollective::Application::Facts - mcollective version 2.12.1

class MCollective::Application::Facts

Public Instance Methods

main() click to toggle source
# File lib/mcollective/application/facts.rb, line 31
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
        if facts.include?(value)
          facts[value] << resp[:senderid]
        else
          facts[value] = [ resp[:senderid] ]
        end
      end
    rescue Exception => e
      STDERR.puts "Could not parse facts for #{resp[:senderid]}: #{e.class}: #{e}"
    end
  end

  if facts.empty?
    puts "No values found for fact #{configuration[:fact]}\n"
  else
    show_single_fact_report(configuration[:fact], facts, options[:verbose])
  end

  printrpcstats

  halt rpcutil.stats
end
post_option_parser(configuration) click to toggle source
# File lib/mcollective/application/facts.rb, line 4
def post_option_parser(configuration)
  configuration[:fact] = ARGV.shift if ARGV.size > 0
end
show_single_fact_report(fact, facts, verbose=false) click to toggle source
# File lib/mcollective/application/facts.rb, line 12
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
validate_configuration(configuration) click to toggle source
# File lib/mcollective/application/facts.rb, line 8
def validate_configuration(configuration)
  raise "Please specify a fact to report for" unless configuration.include?(:fact)
end
mcollective-2.12.1/doc/MCollective/Agent.html0000644005276200011600000000435213265671731020711 0ustar jenkinsjenkins module MCollective::Agent - mcollective version 2.12.1

module MCollective::Agent

mcollective-2.12.1/doc/MCollective/Log.html0000644005276200011600000005114413265671732020376 0ustar jenkinsjenkins class MCollective::Log - mcollective version 2.12.1

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.12.1/doc/MCollective/UnexpectedMessageType.html0000644005276200011600000000471613265671733024134 0ustar jenkinsjenkins class MCollective::UnexpectedMessageType - mcollective version 2.12.1

class MCollective::UnexpectedMessageType

mcollective-2.12.1/doc/MCollective/PluginPackager/0000755005276200011600000000000013265671736021662 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/PluginPackager/RpmpackagePackager.html0000644005276200011600000002744113265671733026265 0ustar jenkinsjenkins class MCollective::PluginPackager::RpmpackagePackager - mcollective version 2.12.1

class MCollective::PluginPackager::RpmpackagePackager

Public Class Methods

new(plugin, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = nil, module_template = nil) click to toggle source
# File lib/mcollective/pluginpackager/rpmpackage_packager.rb, line 6
def initialize(plugin, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = nil, module_template = nil)
  if @buildtool = select_command
    @plugin = plugin
    @package_name = "#{@plugin.mcname}-#{@plugin.metadata[:name]}"
    @package_name_and_version = "#{@package_name}-#{@plugin.metadata[:version]}"
    @verbose = verbose
    @libdir = pluginpath || '/usr/libexec/mcollective/mcollective/'
    @signature = signature
    @rpmdir = rpmdir
    @srpmdir = srpmdir
    @keep_artifacts = keep_artifacts
  else
    raise("Cannot build package. 'rpmbuild' or 'rpmbuild-md5' is not present on the system")
  end
end

Public Instance Methods

create_packages() click to toggle source

Build Process :

  • create temporary buildroot

  • create the spec file

  • create the tarball

  • run the build script

  • move pacakges to cwd

  • clean up

# File lib/mcollective/pluginpackager/rpmpackage_packager.rb, line 48
def create_packages
  begin
    puts "Building packages for #{@package_name} plugin."

    @tmpdir = Dir.mktmpdir('mcollective_packager')
    prepare_tmpdirs

    make_spec_file
    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
      cleanup_tmpdirs
    end
  end
end
rpmdir() click to toggle source
# File lib/mcollective/pluginpackager/rpmpackage_packager.rb, line 33
def rpmdir
  %x`rpm --eval '%_rpmdir'`.chomp
end
select_command() click to toggle source

Determine the build tool present on the system

# File lib/mcollective/pluginpackager/rpmpackage_packager.rb, line 23
def select_command
  if PluginPackager.command_available?('rpmbuild-md5')
    return 'rpmbuild-md5'
  elsif PluginPackager.command_available?('rpmbuild')
    return 'rpmbuild'
  else
    return nil
  end
end
srpmdir() click to toggle source
# File lib/mcollective/pluginpackager/rpmpackage_packager.rb, line 37
def srpmdir
  %x`rpm --eval '%_srcrpmdir'`.chomp
end
mcollective-2.12.1/doc/MCollective/PluginPackager/DebpackagePackager.html0000644005276200011600000002271713265671733026222 0ustar jenkinsjenkins class MCollective::PluginPackager::DebpackagePackager - mcollective version 2.12.1

class MCollective::PluginPackager::DebpackagePackager

Public Class Methods

new(plugin, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = nil, module_template = nil) click to toggle source
# File lib/mcollective/pluginpackager/debpackage_packager.rb, line 6
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

Public Instance Methods

create_packages() click to toggle source

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

# File lib/mcollective/pluginpackager/debpackage_packager.rb, line 30
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
mcollective-2.12.1/doc/MCollective/PluginPackager/StandardDefinition.html0000644005276200011600000004547113265671733026331 0ustar jenkinsjenkins class MCollective::PluginPackager::StandardDefinition - mcollective version 2.12.1

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 = PluginPackager.get_plugin_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.12.1/doc/MCollective/PluginPackager/AgentDefinition.html0000644005276200011600000006135713265671733025630 0ustar jenkinsjenkins class MCollective::PluginPackager::AgentDefinition - mcollective version 2.12.1

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 = PluginPackager.get_plugin_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.12.1/doc/MCollective/PluginPackager/ModulepackagePackager.html0000644005276200011600000001733213265671733026752 0ustar jenkinsjenkins class MCollective::PluginPackager::ModulepackagePackager - mcollective version 2.12.1

class MCollective::PluginPackager::ModulepackagePackager

Public Class Methods

new(plugin, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = nil, module_template = nil) click to toggle source
# File lib/mcollective/pluginpackager/modulepackage_packager.rb, line 6
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

Public Instance Methods

create_packages() click to toggle source

Build Process :

  • create module directory

  • run ‘puppet module build’

  • move generated package back to cwd

# File lib/mcollective/pluginpackager/modulepackage_packager.rb, line 19
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
mcollective-2.12.1/doc/MCollective/PluginPackager/OspackagePackager.html0000644005276200011600000003610613265671733026106 0ustar jenkinsjenkins class MCollective::PluginPackager::OspackagePackager - mcollective version 2.12.1

class MCollective::PluginPackager::OspackagePackager

MCollective plugin packager general OS implementation.

Attributes

package[RW]
package_type[RW]
packager[RW]
verbose[RW]

Public Class Methods

new(package, pluginpath = nil, signature = nil, verbose = false, keep_artifacts = false, module_template = nil) click to toggle source

Create packager object with package parameter containing list of files, dependencies and package metadata.

# File lib/mcollective/pluginpackager/ospackage_packager.rb, line 10
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

Public Instance Methods

create_packages() click to toggle source

Hands over package creation to the detected packager implementation based on operating system.

# File lib/mcollective/pluginpackager/ospackage_packager.rb, line 28
def create_packages
  @packager.create_packages
end
package_information() click to toggle source

Displays the package metadata and detected files

# File lib/mcollective/pluginpackager/ospackage_packager.rb, line 33
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
mcollective-2.12.1/doc/MCollective/Matcher.html0000644005276200011600000010016313265671732021234 0ustar jenkinsjenkins module MCollective::Matcher - mcollective version 2.12.1

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 214
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)
  r_compare = function_hash["r_compare"]
  operator = function_hash["operator"]

  # Break out early and return false if the function returns nil
  if l_compare.nil?
    return false
  end

  # 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

  # Do a regex comparison if right compare string is a regex
  if 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
      result = l_compare.match(r_compare)
      # Flip return value for != operator
      if function_hash["operator"] == "!=~"
        return !result
      else
        return !!result
      end
    end
    # Otherwise do a normal comparison while taking the type into account
  else
    if l_compare.is_a? String
      r_compare = r_compare.to_s
    elsif r_compare.is_a? String
      if l_compare.is_a? Numeric
        r_compare = r_compare.strip
        begin
          r_compare = Integer(r_compare)
        rescue ArgumentError
          begin
            r_compare = Float(r_compare)
          rescue ArgumentError
            raise ArgumentError, "invalid numeric value: #{r_compare}"
          end
        end
      elsif l_compare.is_a? TrueClass or l_compare.is_a? FalseClass
        r_compare = r_compare.strip
        if r_compare == true.to_s
          r_compare = true
        elsif r_compare == false.to_s
          r_compare = false
        else
          raise ArgumentError, "invalid boolean value: #{r_compare}"
        end
      end
    end
    operator = operator.strip
    if operator =~ /(?:(!=|<=|>=|<|>)|==?)/
      operator = $1 ? $1.to_sym : :==
    else
      raise ArgumentError, "invalid operator: #{operator}"
    end
    result = l_compare.send(operator, r_compare)
    return 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.12.1/doc/MCollective/Logger.html0000644005276200011600000000435613265671732021077 0ustar jenkinsjenkins module MCollective::Logger - mcollective version 2.12.1

module MCollective::Logger

mcollective-2.12.1/doc/MCollective/Registration.html0000644005276200011600000000570713265671733022334 0ustar jenkinsjenkins module MCollective::Registration - mcollective version 2.12.1

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.12.1/doc/MCollective/Util.html0000644005276200011600000026435513265671734020606 0ustar jenkinsjenkins module MCollective::Util - mcollective version 2.12.1

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
  # the set of acceptable config files
  config_paths = []

  # user dotfile
  begin
    # File.expand_path will raise if HOME isn't set, catch it
    user_path = File.expand_path("~/.mcollective")
    config_paths << user_path
  rescue Exception
  end

  # standard locations
  if self.windows?
    config_paths << File.join(self.windows_prefix, 'etc', 'client.cfg')
  else
    config_paths << '/etc/puppetlabs/mcollective/client.cfg'
    config_paths << '/etc/mcollective/client.cfg'
  end

  # use the first readable config file, or if none are the first listed
  found = config_paths.find_index { |file| File.readable?(file) } || 0
  return config_paths[found]
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
get_hidden_input(message='Please enter data: ') click to toggle source
# File lib/mcollective/util.rb, line 560
def self.get_hidden_input(message='Please enter data: ')
  unless message.nil?
    print message
  end
  if versioncmp(ruby_version, '1.9.3') >= 0
    require 'io/console'
    input = $stdin.noecho(&:gets)
  else
    # Use hacks to get hidden input on Ruby <1.9.3
    if self.windows?
      input = self.get_hidden_input_on_windows()
    else
      input = self.get_hidden_input_on_unix()
    end
  end
  input.chomp! if input
  input
end
get_hidden_input_on_unix() click to toggle source
# File lib/mcollective/util.rb, line 545
def self.get_hidden_input_on_unix()
  unless $stdin.tty?
    raise 'Could not hook to stdin to hide input. If using SSH, try using -t flag while connecting to server.'
  end
  unless system 'stty -echo -icanon'
    raise 'Could not hide input using stty command.'
  end
  input = $stdin.gets
  ensure
    unless system 'stty echo icanon'
      raise 'Could not enable echoing of input. Try executing `stty echo icanon` to debug.'
    end
  input
end
get_hidden_input_on_windows() click to toggle source
# File lib/mcollective/util.rb, line 524
def self.get_hidden_input_on_windows()
  require 'Win32API'
  # Hook into getch from crtdll. Keep reading all keys till return
  # or newline is hit.
  # If key is backspace or delete, then delete the character and update
  # the buffer.
  input = ''
  while char = Win32API.new("crtdll", "_getch", [ ], "I").Call do
    break if char == 10 || char == 13 # return or newline
    if char == 127 || char == 8 # backspace and delete
      if input.length > 0
        input.slice!(-1, 1)
      end
    else
      input << char.chr
    end
  end
  char = ''
  input
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])/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)$/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
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 [%x`tput cols`.to_i, %x`tput lines`.to_i]

  elsif command_in_path?('stty')
    return %x`stty 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/i)
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.12.1/doc/MCollective/SecurityValidationFailed.html0000644005276200011600000000464313265671733024607 0ustar jenkinsjenkins class MCollective::SecurityValidationFailed - mcollective version 2.12.1

class MCollective::SecurityValidationFailed

mcollective-2.12.1/doc/MCollective/Aggregate.html0000644005276200011600000004512113265671731021540 0ustar jenkinsjenkins class MCollective::Aggregate - mcollective version 2.12.1

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.12.1/doc/MCollective/Message.html0000644005276200011600000013251213265671732021240 0ustar jenkinsjenkins class MCollective::Message - mcollective version 2.12.1

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 241
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 183
def decode!
  raise "Cannot decode message type #{type}" unless [:request, :reply].include?(type)

  begin
    @payload = PluginManager["security_plugin"].decodemsg(self)
  rescue Exception => e
    if type == :request
      # If we're a server receiving a request, reraise
      raise(e)
    else
      # We're in the client, log and carry on as best we can

      # Note: mc_sender is unverified.  The verified identity is in the
      # payload we just failed to decode
      Log.warn("Failed to decode a message from '#{headers["mc_sender"]}': #{e}")
      return
    end
  end

  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
description() click to toggle source
# File lib/mcollective/message.rb, line 133
def description
  cid = ""
  cid += payload[:callerid] + "@" if payload.include?(:callerid)
  cid += payload[:senderid]

  "#{requestid} for agent '#{agent}' in collective '#{collective}' from #{cid}"
end
encode!() click to toggle source
# File lib/mcollective/message.rb, line 141
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 228
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 212
def validate
  raise "Can only validate request messages" unless type == :request

  msg_age = Time.now.utc.to_i - msgtime

  if msg_age > ttl
    PluginManager["global_stats"].ttlexpired
    raise(MsgTTLExpired, "Message #{description} created at #{msgtime} is #{msg_age} seconds old, TTL is #{ttl}. Rejecting message.")
  end

  raise(NotTargettedAtUs, "Message #{description} does not pass filters. Ignoring message.") 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 159
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.12.1/doc/MCollective/UnknownRPCError.html0000644005276200011600000000464413265671733022677 0ustar jenkinsjenkins class MCollective::UnknownRPCError - mcollective version 2.12.1

class MCollective::UnknownRPCError

mcollective-2.12.1/doc/MCollective/DDLValidationError.html0000644005276200011600000000471013265671732023302 0ustar jenkinsjenkins class MCollective::DDLValidationError - mcollective version 2.12.1

class MCollective::DDLValidationError

Exceptions for the RPC system

mcollective-2.12.1/doc/MCollective/Logger/0000755005276200011600000000000013265671736020205 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Logger/Base.html0000644005276200011600000002562313265671732021751 0ustar jenkinsjenkins class MCollective::Logger::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Logger/Console_logger.html0000644005276200011600000003252413265671732024036 0ustar jenkinsjenkins class MCollective::Logger::Console_logger - mcollective version 2.12.1

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.12.1/doc/MCollective/Logger/File_logger.html0000644005276200011600000002723113265671732023312 0ustar jenkinsjenkins class MCollective::Logger::File_logger - mcollective version 2.12.1

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 39
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 47
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 24
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
  # ISO-8601 with sub-second precision and offset from UTC.
  @logger.formatter.datetime_format = "%Y-%m-%dT%H:%M:%S.%6N%:z "

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

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.12.1/doc/MCollective/Matcher/0000755005276200011600000000000013265671736020351 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Matcher/Parser.html0000644005276200011600000006307013265671732022475 0ustar jenkinsjenkins class MCollective::Matcher::Parser - mcollective version 2.12.1

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.12.1/doc/MCollective/Matcher/Scanner.html0000644005276200011600000003003213265671732022622 0ustar jenkinsjenkins class MCollective::Matcher::Scanner - mcollective version 2.12.1

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.12.1/doc/MCollective/Applications.html0000644005276200011600000004712713265671732022311 0ustar jenkinsjenkins class MCollective::Applications - mcollective version 2.12.1

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.12.1/doc/MCollective/UnixDaemon.html0000644005276200011600000002617513265671733021733 0ustar jenkinsjenkins class MCollective::UnixDaemon - mcollective version 2.12.1

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
      # Clean up stale pidfile if needed
      if File.exist?(pid)
        lock_pid = File.read(pid)
        begin
          lock_pid = Integer(lock_pid)
        rescue ArgumentError, TypeError
          lock_pid = nil
        end

        # If there's no pid in the pidfile, remove it
        if lock_pid.nil?
          File.unlink(pid)
        else
          begin
            # This will raise an error if the process doesn't
            # exist, and do nothing otherwise
            Process.kill(0, lock_pid)
            # If we reach this point then the process is running.
            # We should raise an error rather than continuing on
            # trying to create the PID
            raise "Process is already running with PID #{lock_pid}"
          rescue Errno::ESRCH
            # Errno::ESRCH = no such process
            # PID in pidfile doesn't exist, remove pidfile
            File.unlink(pid)
          end
        end

      end

      # Use exclusive create on the PID to avoid race condition
      # when two mcollectived processes start at the same time
      opt =  File::CREAT | File::EXCL | File::WRONLY
      File.open(pid, opt) {|f| f.print(Process.pid) }
    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.12.1/doc/MCollective/Cache.html0000644005276200011600000005105713265671732020663 0ustar jenkinsjenkins module MCollective::Cache - mcollective version 2.12.1

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.12.1/doc/MCollective/DDL/0000755005276200011600000000000013265671736017371 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/DDL/AgentDDL.html0000644005276200011600000012014513265671732021640 0ustar jenkinsjenkins class MCollective::DDL::AgentDDL - mcollective version 2.12.1

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         => "https://docs.puppetlabs.com/mcollective/",
: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 243
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 248
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 key.is_a?(Symbol) && arguments.include?(key.to_s) && !input.include?(key.to_s)
      compat_arg = key.to_s
    else
      compat_arg = key
    end

    if !arguments.include?(compat_arg) && !input[key][:default].nil? && !input[key][:optional]
      Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]])
      arguments[compat_arg] = 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
symbolize_basic_input_arguments(input, arguments) click to toggle source

Creates a new set of arguments with string arguments mapped to symbol ones

This is to assist with moving to a JSON pure world where requests might come in from REST or other languages, those languages and indeed JSON itself does not support symbols.

It ensures a backward compatible mode where for rpcutil both of these requests are equivelant

c.get_fact(:fact => "cluster")
c.get_fact("fact" => "cluster")

The case where both :fact and “fact†is in the DDL cannot be handled correctly and this code will assume the caller means “fact†in that case. There’s no way to represent such a request in JSON and just in general sounds like a bad idea, so a warning is logged which would in default client configuration appear on the clients display

# File lib/mcollective/ddl/agentddl.rb, line 199
def symbolize_basic_input_arguments(input, arguments)
  warned = false

  Hash[arguments.map do |key, value|
    if input.include?(key.intern) && input.include?(key.to_s) && !warned
      Log.warn("String and Symbol versions of input %s found in the DDL for %s, ensure your DDL keys are unique." % [key, @pluginname])
      warned = true
    end

    if key.is_a?(String) && input.include?(key.intern) && !input.include?(key)
      [key.intern, value]
    else
      [key, value]
    end
  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 218
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] || {}
  compatible_args = symbolize_basic_input_arguments(input, arguments)

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

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

  true
end
mcollective-2.12.1/doc/MCollective/DDL/Base.html0000644005276200011600000010710113265671732021125 0ustar jenkinsjenkins class MCollective::DDL::Base - mcollective version 2.12.1

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 146
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 208
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 179
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 191
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 120
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.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.12.1/doc/MCollective/DDL/DataDDL.html0000644005276200011600000002412513265671732021454 0ustar jenkinsjenkins class MCollective::DDL::DataDDL - mcollective version 2.12.1

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         => "https://docs.puppetlabs.com/mcollective/",
: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.12.1/doc/MCollective/DDL/DiscoveryDDL.html0000644005276200011600000002244713265671732022557 0ustar jenkinsjenkins class MCollective::DDL::DiscoveryDDL - mcollective version 2.12.1

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         => "https://docs.puppetlabs.com/mcollective/",
: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.12.1/doc/MCollective/DDL/ValidatorDDL.html0000644005276200011600000000470413265671732022531 0ustar jenkinsjenkins class MCollective::DDL::ValidatorDDL - mcollective version 2.12.1

class MCollective::DDL::ValidatorDDL

mcollective-2.12.1/doc/MCollective/Shell.html0000644005276200011600000004745013265671733020732 0ustar jenkinsjenkins class MCollective::Shell - mcollective version 2.12.1

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?(Integer) && 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?(Integer)
        # 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.12.1/doc/MCollective/ValidatorError.html0000644005276200011600000000457313265671734022622 0ustar jenkinsjenkins class MCollective::ValidatorError - mcollective version 2.12.1

class MCollective::ValidatorError

mcollective-2.12.1/doc/MCollective/Agent/0000755005276200011600000000000013265671736020024 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Agent/Discovery.html0000644005276200011600000001735313265671731022665 0ustar jenkinsjenkins class MCollective::Agent::Discovery - mcollective version 2.12.1

class MCollective::Agent::Discovery

Discovery agent for The Marionette Collective

Released under the Apache License, Version 2

Attributes

meta[R]
timeout[R]

Public Class Methods

new() click to toggle source
# File lib/mcollective/agent/discovery.rb, line 9
def initialize
  config = Config.instance.pluginconf

  @timeout = 5
  @meta = {:license => "Apache License, Version 2",
           :author => "R.I.Pienaar <rip@devco.net>",
           :timeout => @timeout,
           :name => "Discovery Agent",
           :version => MCollective.version,
           :url => "https://docs.puppetlabs.com/mcollective/",
           :description => "MCollective Discovery Agent"}
end

Public Instance Methods

handlemsg(msg, stomp) click to toggle source
# File lib/mcollective/agent/discovery.rb, line 22
def handlemsg(msg, stomp)
  reply = "unknown request"

  case msg[:body]
    when "ping"
      reply = "pong"

    else
      reply = "Unknown Request: #{msg[:body]}"
  end

  reply
end
mcollective-2.12.1/doc/MCollective/Agent/Rpcutil.html0000644005276200011600000000462413265671731022335 0ustar jenkinsjenkins class MCollective::Agent::Rpcutil - mcollective version 2.12.1

class MCollective::Agent::Rpcutil

mcollective-2.12.1/doc/MCollective/Facts/0000755005276200011600000000000013265671736020026 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Facts/Base.html0000644005276200011600000003375313265671732021575 0ustar jenkinsjenkins class MCollective::Facts::Base - mcollective version 2.12.1

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 18
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
  @mutex = Mutex.new
  @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 78
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 23
def get_fact(fact=nil)
  config = Config.instance

  cache_time = config.fact_cache_time || 300

  @mutex.synchronize 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 68
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 73
def has_fact?(fact)
  get_fact(nil).include?(fact)
end
mcollective-2.12.1/doc/MCollective/Facts/Yaml_facts.html0000644005276200011600000002547013265671732023002 0ustar jenkinsjenkins class MCollective::Facts::Yaml_facts - mcollective version 2.12.1

class MCollective::Facts::Yaml_facts

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.

Public Class Methods

new() click to toggle source
Calls superclass method MCollective::Facts::Base.new
# File lib/mcollective/facts/yaml_facts.rb, line 11
def initialize
  @yaml_file_mtimes = {}

  super
end

Public Instance Methods

force_reload?() click to toggle source

force fact reloads when the mtime on the yaml file change

# File lib/mcollective/facts/yaml_facts.rb, line 43
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
load_facts_from_source() click to toggle source
# File lib/mcollective/facts/yaml_facts.rb, line 17
def load_facts_from_source
  config = Config.instance

  fact_files = config.pluginconf["yaml"].split(File::PATH_SEPARATOR)
  facts = {}

  fact_files.each do |file|
    begin
      if File.exist?(file)
        if YAML.respond_to? :safe_load
          facts.merge!(YAML.safe_load(File.read(file), [Symbol], [], true))
        else
          facts.merge!(YAML.load(File.read(file)))  # rubocop:disable Security/YAMLLoad
        end
      else
        raise("Can't find YAML file to load: #{file}")
      end
    rescue Exception => e
      Log.error("Failed to load yaml facts from #{file}: #{e.class}: #{e}")
    end
  end

  facts
end
mcollective-2.12.1/doc/MCollective/Validator.html0000644005276200011600000003635213265671734021610 0ustar jenkinsjenkins module MCollective::Validator - mcollective version 2.12.1

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.12.1/doc/MCollective/RPCError.html0000644005276200011600000000454413265671733021316 0ustar jenkinsjenkins class MCollective::RPCError - mcollective version 2.12.1

class MCollective::RPCError

mcollective-2.12.1/doc/MCollective/Config.html0000644005276200011600000014211013265671732021054 0ustar jenkinsjenkins class MCollective::Config - mcollective version 2.12.1

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]
connection_timeout[R]
connector[R]
daemonize[R]
default_batch_size[R]
default_batch_sleep_time[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]
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 22
def initialize
  @configured = false
end

Public Instance Methods

libdir() click to toggle source
# File lib/mcollective/config.rb, line 223
def libdir
  $LOAD_PATH
end
loadconfig(configfile) click to toggle source
# File lib/mcollective/config.rb, line 26
def loadconfig(configfile)
  set_config_defaults(configfile)

  if File.exists?(configfile)
    libdirs = []
    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)

                libdirs << path
              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 "connection_timeout"
              @connection_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 "default_batch_size"
              @default_batch_size = Integer(val)
            when "default_batch_sleep_time"
              @default_batch_sleep_time = Float(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

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

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

    @configured = true

    libdirs.each do |dir|
      unless File.directory?(dir)
        Log.debug("Cannot find libdir: #{dir}")
      end

      # remove the old one if it exists, we're moving it to the front
      $LOAD_PATH.reject! { |elem| elem == dir }
      $LOAD_PATH.unshift dir
    end

    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 227
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 177
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
  @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
  @connection_timeout = nil
  @default_batch_size = 0
  @default_batch_sleep_time = 1
end
mcollective-2.12.1/doc/MCollective/PluginManager.html0000644005276200011600000006774413265671733022424 0ustar jenkinsjenkins module MCollective::PluginManager - mcollective version 2.12.1

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.12.1/doc/MCollective/Security/0000755005276200011600000000000013265671736020575 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Security/Base.html0000644005276200011600000011156613265671733022344 0ustar jenkinsjenkins class MCollective::Security::Base - mcollective version 2.12.1

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.12.1/doc/MCollective/Security/Aes_security.html0000644005276200011600000016770113265671733024133 0ustar jenkinsjenkins class MCollective::Security::Aes_security - mcollective version 2.12.1

class MCollective::Security::Aes_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

Public Instance Methods

callerid() click to toggle source

sets the caller id to the md5 of the public key

# File lib/mcollective/security/aes_security.rb, line 239
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
certname_from_callerid(id) click to toggle source

Takes our cert=foo callerids and return the foo bit else nil

# File lib/mcollective/security/aes_security.rb, line 380
def certname_from_callerid(id)
  if id =~ /^cert=([\w\.\-]+)/
    return $1
  else
    raise("Received a callerid in an unexpected format: '#{id}', ignoring")
  end
end
certname_from_certificate(cert) click to toggle source
# File lib/mcollective/security/aes_security.rb, line 388
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
client_cert_dir() click to toggle source

Figures out where to get client public certs from the plugin.aes.client_cert_dir config option

# File lib/mcollective/security/aes_security.rb, line 370
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
client_private_key() click to toggle source

Figures out the client private key either from MCOLLECTIVE_AES_PRIVATE or the plugin.aes.client_private config option

# File lib/mcollective/security/aes_security.rb, line 339
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
client_public_key() click to toggle source

Figures out the client public key either from MCOLLECTIVE_AES_PUBLIC or the plugin.aes.client_public config option

# File lib/mcollective/security/aes_security.rb, line 349
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
decodemsg(msg) click to toggle source
# File lib/mcollective/security/aes_security.rb, line 68
def decodemsg(msg)
  body = deserialize(msg.payload)

  should_process_msg?(msg, body[:requestid])
  # if we get a message that has a pubkey attached and we're set to learn
  # then add it to the client_cert_dir this should only happen on servers
  # since clients will get replies using their own pubkeys
  if Util.str_to_bool(@config.pluginconf.fetch("aes.learn_pubkeys", false)) && body.include?(:sslpubkey)
    certname = certname_from_callerid(body[:callerid])
    certfile = "#{client_cert_dir}/#{certname}.pem"
    if !File.exist?(certfile)
      if !Util.str_to_bool(@config.pluginconf.fetch("aes.insecure_learning", false))
        if !@config.pluginconf.fetch("aes.ca_cert", nil)
          raise "Cannot verify certificate for '#{certname}'. No CA certificate specified."
        end

        if !validate_certificate(body[:sslpubkey], certname)
          raise "Unable to validate certificate '#{certname}' against CA"
        end

        Log.debug("Verified certificate '#{certname}' against CA")
      else
        Log.warn("Insecure key learning is not a secure method of key distribution. Do NOT use this mode in sensitive environments.")
      end

      Log.debug("Caching client cert in #{certfile}")
      File.open(certfile, "w") {|f| f.print body[:sslpubkey]}
    else
      Log.debug("Not caching client cert. File #{certfile} already exists.")
    end
  end

  cryptdata = {:key => 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
decrypt(string, certid) click to toggle source
# File lib/mcollective/security/aes_security.rb, line 282
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
deserialize(msg) click to toggle source

De-Serializes a message using the configured encoder

# File lib/mcollective/security/aes_security.rb, line 221
def deserialize(msg)
  serializer = @config.pluginconf["aes.serializer"] || "marshal"

  Log.debug("De-Serializing using #{serializer}")

  case serializer
  when "yaml"
    if YAML.respond_to? :safe_load
      return YAML.safe_load(msg, [Symbol])
    else
      raise "YAML.safe_load not supported by Ruby #{RUBY_VERSION}. Please update to Ruby 2.1+."
    end
  else
    return Marshal.load(msg)
  end
end
encodereply(sender, msg, requestid, requestcallerid) click to toggle source

Encodes a reply

# File lib/mcollective/security/aes_security.rb, line 170
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
encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) click to toggle source

Encodes a request msg

# File lib/mcollective/security/aes_security.rb, line 180
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
encrypt(string, certid) click to toggle source
# File lib/mcollective/security/aes_security.rb, line 259
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
public_key_path_for_client(clientid) click to toggle source

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

# File lib/mcollective/security/aes_security.rb, line 329
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
request_description(msg) click to toggle source
# File lib/mcollective/security/aes_security.rb, line 375
def request_description(msg)
  "%s from %s@%s" % [msg[:requestid], msg[:callerid], msg[:senderid]]
end
serialize(msg) click to toggle source

Serializes a message using the configured encoder

# File lib/mcollective/security/aes_security.rb, line 207
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
server_private_key() click to toggle source

Figures out the server private key from the plugin.aes.server_private config option

# File lib/mcollective/security/aes_security.rb, line 364
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
server_public_key() click to toggle source

Figures out the server public key from the plugin.aes.server_public config option

# File lib/mcollective/security/aes_security.rb, line 358
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
update_secure_property(msg, secure_property, property, description) click to toggle source

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

# File lib/mcollective/security/aes_security.rb, line 151
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
validate_certificate(client_cert, certid) click to toggle source
# File lib/mcollective/security/aes_security.rb, line 295
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
mcollective-2.12.1/doc/MCollective/Security/Psk.html0000644005276200011600000003542113265671733022222 0ustar jenkinsjenkins class MCollective::Security::Psk - mcollective version 2.12.1

class MCollective::Security::Psk

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.

Public Instance Methods

callerid() click to toggle source
# File lib/mcollective/security/psk.rb, line 72
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
decodemsg(msg) click to toggle source

Decodes a message by unserializing all the bits etc, it also validates it as valid using the psk etc

# File lib/mcollective/security/psk.rb, line 21
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
encodereply(sender, msg, requestid, requestcallerid=nil) click to toggle source

Encodes a reply

# File lib/mcollective/security/psk.rb, line 35
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
encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) click to toggle source

Encodes a request msg

# File lib/mcollective/security/psk.rb, line 46
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
validrequest?(req) click to toggle source

Checks the md5 hash in the request body against our psk, the request sent for validation should not have been deserialized already

# File lib/mcollective/security/psk.rb, line 58
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
mcollective-2.12.1/doc/MCollective/Security/Ssl.html0000644005276200011600000006026513265671733022232 0ustar jenkinsjenkins class MCollective::Security::Ssl - mcollective version 2.12.1

class MCollective::Security::Ssl

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

Public Instance Methods

callerid() click to toggle source

sets the caller id to the md5 of the public key

# File lib/mcollective/security/ssl.rb, line 188
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
decodemsg(msg) click to toggle source

Decodes a message by unserializing all the bits etc, it also validates it as valid using the psk etc

# File lib/mcollective/security/ssl.rb, line 90
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
encodereply(sender, msg, requestid, requestcallerid=nil) click to toggle source

Encodes a reply

# File lib/mcollective/security/ssl.rb, line 142
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
encoderequest(sender, msg, requestid, filter, target_agent, target_collective, ttl=60) click to toggle source

Encodes a request msg

# File lib/mcollective/security/ssl.rb, line 154
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
update_secure_property(msg, secure_property, property, description) click to toggle source

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

# File lib/mcollective/security/ssl.rb, line 123
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
validrequest?(req) click to toggle source

Checks the SSL signature in the request body

# File lib/mcollective/security/ssl.rb, line 171
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
mcollective-2.12.1/doc/MCollective/Generators.html0000644005276200011600000000437613265671732021773 0ustar jenkinsjenkins module MCollective::Generators - mcollective version 2.12.1

module MCollective::Generators

mcollective-2.12.1/doc/MCollective/Application.html0000644005276200011600000016217113265671731022122 0ustar jenkinsjenkins class MCollective::Application - mcollective version 2.12.1

class MCollective::Application

Public Class Methods

[](option) click to toggle source

retrieves a specific option

# File lib/mcollective/application.rb, line 22
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 16
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 10
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 30
def description(descr)
  self[:description] = descr
end
exclude_argument_sections(*sections) click to toggle source
# File lib/mcollective/application.rb, line 40
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 81
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 67
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 36
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 249
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 236
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 255
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 231
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 137
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 241
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 113
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 90
def configuration
  @application_configuration ||= {}
  @application_configuration
end
disconnect() click to toggle source
# File lib/mcollective/application.rb, line 301
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 351
def halt(stats)
  exit(halt_code(stats))
end
halt_code(stats) click to toggle source
# File lib/mcollective/application.rb, line 313
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 279
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 308
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 96
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 358
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 286
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 209
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 102
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.12.1/doc/MCollective/MissingRPCData.html0000644005276200011600000000464013265671733022425 0ustar jenkinsjenkins class MCollective::MissingRPCData - mcollective version 2.12.1

class MCollective::MissingRPCData

mcollective-2.12.1/doc/MCollective/DDL.html0000644005276200011600000004030013265671732020250 0ustar jenkinsjenkins module MCollective::DDL - mcollective version 2.12.1

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.12.1/doc/MCollective/WindowsDaemon.html0000644005276200011600000002416213265671734022435 0ustar jenkinsjenkins class MCollective::WindowsDaemon - mcollective version 2.12.1

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.12.1/doc/MCollective/PluginPackager.html0000644005276200011600000004615313265671733022556 0ustar jenkinsjenkins module MCollective::PluginPackager - mcollective version 2.12.1

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
get_plugin_path(target) click to toggle source

Return the path to a plugin’s core directories

# File lib/mcollective/pluginpackager.rb, line 90
def self.get_plugin_path(target)
  if (File.exists?(File.join(target, "lib", "mcollective")))
    return File.join(target, "lib", "mcollective")
  end

  return target
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.12.1/doc/MCollective/Client.html0000644005276200011600000016204513265671732021076 0ustar jenkinsjenkins class MCollective::Client - mcollective version 2.12.1

class MCollective::Client

Helpers for writing clients that can talk to agents, do discovery and so forth

Attributes

connection_timeout[RW]
discoverer[RW]
options[RW]
stats[RW]

Public Class Methods

new(options) click to toggle source
# File lib/mcollective/client.rb, line 6
def initialize(options)
  @config = Config.instance
  @options = nil

  if options.is_a?(String)
    # String is the path to a config file
    @config.loadconfig(options) unless @config.configured
  elsif options.is_a?(Hash)
    @config.loadconfig(options[:config]) unless @config.configured
    @options = options
    @connection_timeout = options[:connection_timeout]
  else
    raise "Invalid parameter passed to Client constructor. Valid types are Hash or String"
  end

  @connection_timeout ||= @config.connection_timeout

  @connection = PluginManager["connector_plugin"]
  @security = PluginManager["security_plugin"]

  @security.initiated_by = :client
  @subscriptions = {}

  @discoverer = Discovery.new(self)

  # Time box the connection if a timeout has been specified
  # connection_timeout defaults to nil which means it will try forever if
  # not specified
  begin
    Timeout::timeout(@connection_timeout, ClientTimeoutError) do
      @connection.connect
    end
  rescue ClientTimeoutError => e
    Log.error("Timeout occured while trying to connect to middleware")
    raise e
  end
end
request_sequence() click to toggle source
# File lib/mcollective/client.rb, line 45
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 51
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 73
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 60
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 147
def discover(filter, timeout, limit=0)
  @discoverer.discover(filter.merge({'collective' => collective}), timeout, limit)
end
discovered_req(body, agent, options=false) click to toggle source
# File lib/mcollective/client.rb, line 300
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 305
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

  if stats[:unexpectedresponsefrom].size > 0
    puts("\nUnexpected response from:\n")

    stats[:unexpectedresponsefrom].each do |c|
      puts if c % 4 == 1
      printf("%30s", c)
    end

    puts
  end
end
publish(request) click to toggle source
# File lib/mcollective/client.rb, line 231
def publish(request)
  Log.info("Sending request #{request.requestid} for agent '#{request.agent}' with ttl #{request.ttl} in collective '#{request.collective}'")
  request.publish
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 115
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

    Log.debug("Received reply to #{reply.requestid} from #{reply.payload[:senderid]}")
  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=[], &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 159
def req(body, agent=nil, options=false, waitfor=[], &block)
  if body.is_a?(Message)
    agent = body.agent
    waitfor = body.discovered_hosts || []
    @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] || @config.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 67
def sendreq(msg, agent, filter = {})
  request = createreq(msg, agent, filter)
  publish(request)
  request.requestid
end
start_publisher(request, publish_timeout) click to toggle source

Starts the request publishing routine

# File lib/mcollective/client.rb, line 220
def start_publisher(request, publish_timeout)
  Log.debug("Starting publishing with publish timeout of #{publish_timeout}")
  begin
    Timeout.timeout(publish_timeout) do
      publish(request)
    end
  rescue Timeout::Error => e
    Log.warn("Could not publish all messages. Publishing timed out.")
  end
end
start_receiver(requestid, waitfor, timeout) { |payload, resp| ... } click to toggle source

Starts the response receiver routine Expected to return the amount of received responses.

# File lib/mcollective/client.rb, line 238
def start_receiver(requestid, waitfor, timeout, &block)
  Log.debug("Starting response receiver with timeout of #{timeout}")
  hosts_responded = 0

  if (waitfor.is_a?(Array))
    unfinished = Hash.new(0)
    waitfor.each {|w| unfinished[w] += 1}
  else
    unfinished = []
  end

  begin
    Timeout.timeout(timeout) do
      loop do
        resp = receive(requestid)

        if block.arity == 2
          yield resp.payload, resp
        else
          yield resp.payload
        end

        hosts_responded += 1

        if (waitfor.is_a?(Array))
          sender = resp.payload[:senderid]
          if unfinished[sender] <= 1
            unfinished.delete(sender)
          else
            unfinished[sender] -= 1
          end

          break if !waitfor.empty? && unfinished.empty?
        else
          break unless waitfor == 0 || hosts_responded < waitfor
        end
      end
    end
  rescue Timeout::Error => e
    if waitfor.is_a?(Array)
      if !unfinished.empty?
        Log.warn("Could not receive all responses. Did not receive responses from #{unfinished.keys.join(', ')}")
      end
    elsif (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 90
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 199
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 100
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 191
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 289
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[:unexpectedresponsefrom] = []
  stat[:requestid] = requestid

  @stats = stat
end
mcollective-2.12.1/doc/MCollective/Data.html0000644005276200011600000004447013265671732020532 0ustar jenkinsjenkins module MCollective::Data - mcollective version 2.12.1

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$/i ? plugin.to_s.downcase : "%s_data" % plugin.to_s.downcase
end
mcollective-2.12.1/doc/MCollective/RPCAborted.html0000644005276200011600000000462013265671733021600 0ustar jenkinsjenkins class MCollective::RPCAborted - mcollective version 2.12.1

class MCollective::RPCAborted

mcollective-2.12.1/doc/MCollective/RunnerStats.html0000644005276200011600000004105413265671733022145 0ustar jenkinsjenkins class MCollective::RunnerStats - mcollective version 2.12.1

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.12.1/doc/MCollective/Discovery/0000755005276200011600000000000013265671736020735 5ustar jenkinsjenkinsmcollective-2.12.1/doc/MCollective/Discovery/Mc.html0000644005276200011600000001503713265671732022164 0ustar jenkinsjenkins class MCollective::Discovery::Mc - mcollective version 2.12.1

class MCollective::Discovery::Mc

Public Class Methods

discover(filter, timeout, limit, client) click to toggle source
# File lib/mcollective/discovery/mc.rb, line 4
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
mcollective-2.12.1/doc/MCollective/Discovery/Flatfile.html0000644005276200011600000002107713265671732023354 0ustar jenkinsjenkins class MCollective::Discovery::Flatfile - mcollective version 2.12.1

class MCollective::Discovery::Flatfile

Public Class Methods

discover(filter, timeout, limit=0, client=nil) click to toggle source
# File lib/mcollective/discovery/flatfile.rb, line 7
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
mcollective-2.12.1/doc/MCollective/Discovery/Stdin.html0000644005276200011600000002332013265671732022700 0ustar jenkinsjenkins class MCollective::Discovery::Stdin - mcollective version 2.12.1

class MCollective::Discovery::Stdin

Public Class Methods

discover(filter, timeout, limit=0, client=nil) click to toggle source
# File lib/mcollective/discovery/stdin.rb, line 9
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

  Log.debug("Parsing STDIN input as type %s" % type)

  if type == 'json'
    hosts = 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
mcollective-2.12.1/doc/MCollective/Facts.html0000644005276200011600000001634313265671732020717 0ustar jenkinsjenkins module MCollective::Facts - mcollective version 2.12.1

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.12.1/doc/table_of_contents.html0000644005276200011600000046477113265671736021161 0ustar jenkinsjenkins Table of Contents - mcollective version 2.12.1

Table of Contents - mcollective version 2.12.1

Pages

Classes and Modules

Methods

mcollective-2.12.1/doc/Array.html0000644005276200011600000001612713265671731016526 0ustar jenkinsjenkins class Array - mcollective version 2.12.1

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.12.1/doc/created.rid0000644005276200011600000004312613265671736016675 0ustar jenkinsjenkinsWed, 18 Apr 2018 09:35:33 -0700 ./CONTRIBUTING.md Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/monkey_patches.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/connector.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginmanager.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/client.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/helpers.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/client.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/progress.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/reply.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/audit.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/result.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/agent.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/request.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/stats.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/rpc/actionrunner.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/facts/yaml_facts.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/facts/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/security.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/log.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/matcher.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/unix_daemon.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/rpc.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/plugin.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/ping.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/completion.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/describe_filter.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/facts.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/help.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/inventory.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application/find.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/regex_validator.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/typecheck_validator.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/shellsafe_validator.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/ipv6address_validator.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/length_validator.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/shellsafe_validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/ipv4address_validator.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/ipv4address_validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/array_validator.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/length_validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/regex_validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/ipv6address_validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/array_validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator/typecheck_validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/windows_daemon.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/sum.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/average.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/summary.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/result/collection_result.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/result/numeric_result.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/result/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/sum.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/result.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/average.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/summary.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/runner.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/optionparser.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/ssl.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/runnerstats.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/shell.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/validator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/matcher/parser.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/matcher/scanner.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/audit/logfile.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/config.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/message.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/application.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/facts.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/aggregate.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/cache.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/exceptions.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/registration/agentlist.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/registration/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/discovery/mc.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/discovery/mc.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/discovery/flatfile.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/discovery/stdin.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/discovery/stdin.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/discovery/flatfile.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/applications.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/fact_data.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/agent_data.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/collective_data.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/agent_data.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/result.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/collective_data.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/fact_data.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/fstat_data.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/fstat_data.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/ddl/dataddl.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/ddl/discoveryddl.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/ddl/validatorddl.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/ddl/agentddl.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/ddl/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/data.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/logger/file_logger.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/logger/console_logger.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/logger/syslog_logger.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/logger/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/registration.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/discovery.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/util.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/logger.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/agent_definition.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/debian/compat.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/debian/control.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/debian/rules.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/debian/changelog.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/debian/Makefile.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/debian/copyright.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/module/README.md.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/module/_manifest.pp.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/templates/module/Modulefile.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/standard_definition.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/rpmpackage_packager.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/debpackage_packager.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/ospackage_packager.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/pluginpackager/modulepackage_packager.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/agent.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/agents.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/agent/discovery.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/agent/rpcutil.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/agent/rpcutil.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/ddl.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/connector/activemq.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/connector/rabbitmq.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/connector/rabbitmq.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/connector/activemq.ddl Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/connector/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators/agent_generator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators/templates/action_snippet.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators/templates/ddl.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators/templates/data_input_snippet.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators/templates/plugin.erb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators/data_generator.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/generators/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/security/ssl.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/security/aes_security.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/security/psk.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective/security/base.rb Wed, 18 Apr 2018 09:35:32 -0700 ./lib/mcollective.rb Wed, 18 Apr 2018 09:35:32 -0700 ./install.rb Wed, 18 Apr 2018 09:35:32 -0700 ./bin/mco Wed, 18 Apr 2018 09:35:32 -0700 ./bin/mcollectived Wed, 18 Apr 2018 09:35:32 -0700 ./MAINTAINERS Wed, 18 Apr 2018 09:35:32 -0700 ./Rakefile Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/lib/puppet/acceptance/git_utils.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/lib/puppet/acceptance/common_utils.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/lib/puppet/acceptance/install_utils.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/lib/helper.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/common/pre-suite/110_SetPEPuppetService.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/common/pre-suite/070_InstallCACerts.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/common/pre-suite/040_ValidateSignCert.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/common/pre-suite/025_StopFirewall.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/aio/pre-suite/015_PackageHostsPresets.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/aio/pre-suite/060_Install-mcollective-daemon.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/aio/pre-suite/050_Install-activemq.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/aio/pre-suite/070_Install_puppet-agent-plugin.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/aio/pre-suite/045_EnsureMasterStartedOnPassenger.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/setup/aio/pre-suite/010_Install.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/debian-7-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2012-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/redhat-5-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2003x86-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/debian-8-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2003r2x64-rubyx64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2003r2x64-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/ubuntu-1404-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2012-rubyx64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2003r2x86-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/fedora-20-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2008r2-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2008r2-rubyx64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2012r2-rubyx64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/centos-5-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2003x64-rubyx64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/fedora-21-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/centos-6-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/ubuntu-1204-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2008-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/ubuntu-1410-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/redhat-6-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/redhat-7-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2003x64-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2012r2-rubyx86.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/win2008-rubyx64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/nodes/debian-6-x86_64.yaml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/config/aio/options.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/tests/mco_reconnect.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/tests/mco_puppet_exec.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/tests/mco_puppet_runonce.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/tests/mco_puppet_batch_count.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/tests/mco_ping.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/tests/mco_puppet_powershell.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/tests/mco_puppet_count.rb Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/Rakefile Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/ca_crt.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/server.crt Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/server.key Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/client.key Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/activemq.keystore Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/activemq.truststore Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/client.cfg Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/client.crt Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/windows-client.cfg Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/files/activemq.xml Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/Gemfile Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/private_keys/socks.local.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/private_keys/mcollective-server.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/private_keys/activemq.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/private_keys/mcollective-client.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/public_keys/socks.local.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/public_keys/mcollective-server.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/public_keys/activemq.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/public_keys/mcollective-client.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/certs/socks.local.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/certs/ca.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/certs/mcollective-server.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/certs/activemq.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/certs/mcollective-client.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/crl.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/ca_key.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/ca_crt.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/ca_crl.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/ca_pub.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/inventory.txt Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/serial Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/private/ca.pass Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/signed/socks.local.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/signed/mcollective-server.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/signed/activemq.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/ssl/ca/signed/mcollective-client.pem Wed, 18 Apr 2018 09:35:32 -0700 ./acceptance/README.md Wed, 18 Apr 2018 09:35:32 -0700 ./mcollective.init Wed, 18 Apr 2018 09:35:32 -0700 ./Gemfile Wed, 18 Apr 2018 09:35:32 -0700 ./etc/metadata-help.erb Wed, 18 Apr 2018 09:35:32 -0700 ./etc/data-help.erb Wed, 18 Apr 2018 09:35:32 -0700 ./etc/facts.yaml.dist Wed, 18 Apr 2018 09:35:32 -0700 ./etc/rpc-help.erb Wed, 18 Apr 2018 09:35:32 -0700 ./etc/client.cfg.dist Wed, 18 Apr 2018 09:35:32 -0700 ./etc/discovery-help.erb Wed, 18 Apr 2018 09:35:32 -0700 ./etc/ssl/PLACEHOLDER Wed, 18 Apr 2018 09:35:32 -0700 ./etc/ssl/clients/PLACEHOLDER Wed, 18 Apr 2018 09:35:32 -0700 ./etc/server.cfg.dist Wed, 18 Apr 2018 09:35:32 -0700 ./COPYING Wed, 18 Apr 2018 09:35:32 -0700 ./README.md Wed, 18 Apr 2018 09:35:32 -0700 mcollective-2.12.1/doc/Puppet/0000755005276200011600000000000013265671736016035 5ustar jenkinsjenkinsmcollective-2.12.1/doc/Puppet/Acceptance/0000755005276200011600000000000013265671736020063 5ustar jenkinsjenkinsmcollective-2.12.1/doc/Puppet/Acceptance/CommandUtils.html0000644005276200011600000001211313265671734023344 0ustar jenkinsjenkins module Puppet::Acceptance::CommandUtils - mcollective version 2.12.1

module Puppet::Acceptance::CommandUtils

Public Class Methods

gem_command(host) click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 134
def gem_command(host)
  if host['platform'] =~ /windows/
    "env PATH=\"#{host['privatebindir']}:${PATH}\" cmd /c gem"
  else
    "env PATH=\"#{host['privatebindir']}:${PATH}\" gem"
  end
end
ruby_command(host) click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 129
def ruby_command(host)
  "env PATH=\"#{host['privatebindir']}:${PATH}\" ruby"
end
mcollective-2.12.1/doc/Puppet/Acceptance/InstallUtils.html0000644005276200011600000010365013265671734023403 0ustar jenkinsjenkins module Puppet::Acceptance::InstallUtils - mcollective version 2.12.1

module Puppet::Acceptance::InstallUtils

Constants

PLATFORM_PATTERNS

Public Instance Methods

configure_gem_mirror(hosts) click to toggle source

Configures gem sources on hosts to use a mirror, if specified This is a duplicate of the Gemfile logic.

# File acceptance/lib/puppet/acceptance/install_utils.rb, line 165
      def configure_gem_mirror(hosts)
        hosts = [hosts] unless hosts.kind_of?(Array)
        gem_source = ENV['GEM_SOURCE'] || 'https://rubygems.org'

        hosts.each do |host|
          gem = Puppet::Acceptance::CommandUtils.gem_command(host)
          gem_version = on(host, "#{gem} --version").stdout.chomp
          if host['platform'] =~ /win/ && gem_version < '2.6.8' then
            # The vendored gem command does not have an updated
            # TLS cert on Windows.
            # http://guides.rubygems.org/ssl-certificate-update
            geotrust_ca = <<-EOS
-----BEGIN CERTIFICATE-----
MIIDdTCCAl2gAwIBAgILBAAAAAABFUtaw5QwDQYJKoZIhvcNAQEFBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
b3QgQ0ExGzAZBgNVBAMTEkdsb2JhbFNpZ24gUm9vdCBDQTAeFw05ODA5MDExMjAw
MDBaFw0yODAxMjgxMjAwMDBaMFcxCzAJBgNVBAYTAkJFMRkwFwYDVQQKExBHbG9i
YWxTaWduIG52LXNhMRAwDgYDVQQLEwdSb290IENBMRswGQYDVQQDExJHbG9iYWxT
aWduIFJvb3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDaDuaZ
jc6j40+Kfvvxi4Mla+pIH/EqsLmVEQS98GPR4mdmzxzdzxtIK+6NiY6arymAZavp
xy0Sy6scTHAHoT0KMM0VjU/43dSMUBUc71DuxC73/OlS8pF94G3VNTCOXkNz8kHp
1Wrjsok6Vjk4bwY8iGlbKk3Fp1S4bInMm/k8yuX9ifUSPJJ4ltbcdG6TRGHRjcdG
snUOhugZitVtbNV4FpWi6cgKOOvyJBNPc1STE4U6G7weNLWLBYy5d4ux2x8gkasJ
U26Qzns3dLlwR5EiUWMWea6xrkEmCMgZK9FGqkjWZCrXgzT/LCrBbBlDSgeF59N8
9iFo7+ryUp9/k5DPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBRge2YaRQ2XyolQL30EzTSo//z9SzANBgkqhkiG9w0B
AQUFAAOCAQEA1nPnfE920I2/7LqivjTFKDK1fPxsnCwrvQmeU79rXqoRSLblCKOz
yj1hTdNGCbM+w6DjY1Ub8rrvrTnhQ7k4o+YviiY776BQVvnGCv04zcQLcFGUl5gE
38NflNUVyRRBnMRddWQVDf9VMOyGj/8N7yy5Y0b2qvzfvGn9LhJIZJrglfCm7ymP
AbEVtQwdpf5pLGkkeB6zpxxxYu7KyJesF12KwvhHhm4qxFYxldBniYUr+WymXUad
DKqC5JlR3XC321Y9YeRq4VzW9v493kHMB65jUr9TU/Qr6cf9tveCX4XSQRjbgbME
HMUfpIBvFSDJ3gyICh3WZlXi/EjJKSZp4A==
-----END CERTIFICATE-----
EOS
            p = on(host, "#{gem} which rubygems").stdout.chomp.sub('rubygems.rb', 'rubygems/ssl_certs')
            create_remote_file(host, "#{p}/geotrustglobal.pem", geotrust_ca)
          end
          on host, "#{gem} source --clear-all"
          on host, "#{gem} source --add #{gem_source}"
        end
      end
fetch(base_url, file_name, dst_dir) click to toggle source
# File acceptance/lib/puppet/acceptance/install_utils.rb, line 58
def fetch(base_url, file_name, dst_dir)
  FileUtils.makedirs(dst_dir)
  src = "#{base_url}/#{file_name}"
  dst = File.join(dst_dir, file_name)
  if File.exists?(dst)
    logger.notify "Already fetched #{dst}"
  else
    logger.notify "Fetching: #{src}"
    logger.notify "  and saving to #{dst}"
    open(src) do |remote|
      File.open(dst, "w") do |file|
        FileUtils.copy_stream(remote, file)
      end
    end
  end
  return dst
end
fetch_remote_dir(url, dst_dir) click to toggle source
# File acceptance/lib/puppet/acceptance/install_utils.rb, line 76
def fetch_remote_dir(url, dst_dir)
  logger.notify "fetch_remote_dir (url: #{url}, dst_dir #{dst_dir})"
  if url[-1, 1] !~ /\//
    url += '/'
  end
  url = URI.parse(url)
  chunks = url.path.split('/')
  dst = File.join(dst_dir, chunks.last)
  #determine directory structure to cut
  #only want to keep the last directory, thus cut total number of dirs - 2 (hostname + last dir name)
  cut = chunks.length - 2
  wget_command = "wget -nv -P #{dst_dir} --reject \"index.html*\",\"*.gif\" --cut-dirs=#{cut} -np -nH --no-check-certificate -r #{url}"

  logger.notify "Fetching remote directory: #{url}"
  logger.notify "  and saving to #{dst}"
  logger.notify "  using command: #{wget_command}"

  #in ruby 1.9+ we can upgrade this to popen3 to gain access to the subprocess pid
  result = %x`#{wget_command} 2>&1`
  result.each_line do |line|
    logger.debug(line)
  end
  if $?.to_i != 0
    raise "Failed to fetch_remote_dir '#{url}' (exit code #{$?}"
  end
  dst
end
install_packages_on(hosts, package_hash, options = {}) click to toggle source

Installs packages on the hosts.

@param hosts [Array<Host>] Array of hosts to install packages to. @param package_hash [Hash{Symbol=>Array<String,Array<String,String>>}]

Keys should be a symbol for a platform in PLATFORM_PATTERNS.  Values
should be an array of package names to install, or of two element
arrays where a[0] is the command we expect to find on the platform
and a[1] is the package name (when they are different).

@param options [Hash{Symbol=>Boolean}] @option options [Boolean] :check_if_exists First check to see if

command is present before installing package.  (Default false)

@return true

# File acceptance/lib/puppet/acceptance/install_utils.rb, line 30
def install_packages_on(hosts, package_hash, options = {})
  check_if_exists = options[:check_if_exists]
  hosts = [hosts] unless hosts.kind_of?(Array)
  hosts.each do |host|
    package_hash.each do |platform_key,package_list|
      if pattern = PLATFORM_PATTERNS[platform_key]
        if pattern.match(host['platform'])
          package_list.each do |cmd_pkg|
            if cmd_pkg.kind_of?(Array)
              command, package = cmd_pkg
            else
              command = package = cmd_pkg
            end
            if !check_if_exists || !host.check_for_package(command)
              host.logger.notify("Installing #{package}")
              additional_switches = '--allow-unauthenticated' if platform_key == :debian
              host.install_package(package, additional_switches)
            end
          end
        end
      else
        raise("Unknown platform '#{platform_key}' in package_hash")
      end
    end
  end
  return true
end
install_puppet_from_msi( host, opts ) click to toggle source
# File acceptance/lib/puppet/acceptance/install_utils.rb, line 207
def install_puppet_from_msi( host, opts )
  if not link_exists?(opts[:url])
    raise "Puppet does not exist at #{opts[:url]}!"
  end

  # `start /w` blocks until installation is complete, but needs to be wrapped in `cmd.exe /c`
  on host, "cmd.exe /c start /w msiexec /qn /i #{opts[:url]} /L*V C:\\\\Windows\\\\Temp\\\\Puppet-Install.log"

  # make sure the background service isn't running while the test executes
  on host, "net stop puppet"

  # make sure install is sane, beaker has already added puppet and ruby
  # to PATH in ~/.ssh/environment
  on host, puppet('--version')
  ruby = Puppet::Acceptance::CommandUtils.ruby_command(host)
  on host, "#{ruby} --version"
end
install_repos_on(host, project, sha, repo_configs_dir) click to toggle source
# File acceptance/lib/puppet/acceptance/install_utils.rb, line 119
def install_repos_on(host, project, sha, repo_configs_dir)
  platform = host['platform'].with_version_codename
  platform_configs_dir = File.join(repo_configs_dir,platform)
  tld     = sha == 'nightly' ? 'ravi.puppetlabs.com' : 'builds.puppetlabs.lan'
  project = sha == 'nightly' ? project + '-latest'        :  project
  sha     = sha == 'nightly' ? nil                        :  sha

  case platform
  when /^(fedora|el|centos)-(\d+)-(.+)$/
    variant = (($1 == 'centos') ? 'el' : $1)
    fedora_prefix = ((variant == 'fedora') ? 'f' : '')
    version = $2
    arch = $3

    repo_filename = "pl-%s%s-%s-%s%s-%s.repo" % [
      project,
      sha ? '-' + sha : '',
      variant,
      fedora_prefix,
      version,
      arch
    ]
    repo_url = "http://%s/%s/%s/repo_configs/rpm/%s" % [tld, project, sha, repo_filename]

    on host, "curl -o /etc/yum.repos.d/#{repo_filename} #{repo_url}"
  when /^(debian|ubuntu)-([^-]+)-(.+)$/
    variant = $1
    version = $2
    arch = $3

    list_filename = "pl-%s%s-%s.list" % [
      project,
      sha ? '-' + sha : '',
      version
    ]
    list_url = "http://%s/%s/%s/repo_configs/deb/%s" % [tld, project, sha, list_filename]

    on host, "curl -o /etc/apt/sources.list.d/#{list_filename} #{list_url}"
    on host, "apt-get update"
  else
    host.logger.notify("No repository installation step for #{platform} yet...")
  end
end
stop_firewall_on(host) click to toggle source
# File acceptance/lib/puppet/acceptance/install_utils.rb, line 104
def stop_firewall_on(host)
  case host['platform']
  when /debian/
    on host, 'iptables -F'
  when /fedora|el-7/
    on host, puppet('resource', 'service', 'firewalld', 'ensure=stopped')
  when /el|centos/
    on host, puppet('resource', 'service', 'iptables', 'ensure=stopped')
  when /ubuntu/
    on host, puppet('resource', 'service', 'ufw', 'ensure=stopped')
  else
    logger.notify("Not sure how to clear firewall on #{host['platform']}")
  end
end
mcollective-2.12.1/doc/Puppet/Acceptance/GitUtils.html0000644005276200011600000001453213265671734022520 0ustar jenkinsjenkins module Puppet::Acceptance::GitUtils - mcollective version 2.12.1

module Puppet::Acceptance::GitUtils

Public Instance Methods

build_giturl(project_name, git_fork = nil, git_server = nil) click to toggle source
# File acceptance/lib/puppet/acceptance/git_utils.rb, line 9
def build_giturl(project_name, git_fork = nil, git_server = nil)
  git_fork ||= lookup_in_env('FORK', project_name, 'puppetlabs')
  git_server ||= lookup_in_env('GIT_SERVER', project_name, 'github.com')
  repo = (git_server == 'github.com') ?
    "#{git_fork}/#{project_name}.git" :
    "#{git_fork}-#{project_name}.git"
  "git://#{git_server}/#{repo}"
end
lookup_in_env(env_variable_name, project_name, default) click to toggle source
# File acceptance/lib/puppet/acceptance/git_utils.rb, line 4
def lookup_in_env(env_variable_name, project_name, default)
  project_specific_name = "#{project_name.upcase.gsub("-","_")}_#{env_variable_name}"
  ENV[project_specific_name] || ENV[env_variable_name] || default
end
mcollective-2.12.1/doc/Puppet/Acceptance/CAUtils.html0000644005276200011600000004705213265671734022263 0ustar jenkinsjenkins module Puppet::Acceptance::CAUtils - mcollective version 2.12.1

module Puppet::Acceptance::CAUtils

Public Instance Methods

clean_cert(host, cn, check = true) click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 70
def clean_cert(host, cn, check = true)
  if host == master && master[:is_puppetserver]
      on master, puppet_resource("service", master['puppetservice'], "ensure=stopped")
  end

  on(host, puppet('cert', 'clean', cn), :acceptable_exit_codes => check ? [0] : [0, 24])
  if check
    assert_match(/remov.*Certificate.*#{cn}/i, stdout, "Should see a log message that certificate request was removed.")
    on(host, puppet('cert', 'list', '--all'))
    assert_no_match(/#{cn}/, stdout, "Should not see certificate in list anymore.")
  end
end
clear_agent_ssl() click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 83
def clear_agent_ssl
  return if master.is_pe?
  step "All: Clear agent only ssl settings (do not clear master)"
  hosts.each do |host|
    next if host == master
    ssldir = on(host, puppet('agent --configprint ssldir')).stdout.chomp
    on( host, host_command("rm -rf '#{ssldir}'") )
  end
end
initialize_ssl() click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 23
def initialize_ssl
  hostname = on(master, 'facter hostname').stdout.strip
  fqdn = on(master, 'facter fqdn').stdout.strip

  if master.use_service_scripts?
    step "Ensure puppet is stopped"
    # Passenger, in particular, must be shutdown for the cert setup steps to work,
    # but any running puppet master will interfere with webrick starting up and
    # potentially ignore the puppet.conf changes.
    on(master, puppet('resource', 'service', master['puppetservice'], "ensure=stopped"))
  end

  step "Clear SSL on all hosts"
  hosts.each do |host|
    ssldir = on(host, puppet('agent --configprint ssldir')).stdout.chomp
    on(host, "rm -rf '#{ssldir}'")
  end

  step "Master: Start Puppet Master" do
    master_opts = {
      :main => {
        :dns_alt_names => "puppet,#{hostname},#{fqdn}",
      },
      :__service_args__ => {
        # apache2 service scripts can't restart if we've removed the ssl dir
        :bypass_service_script => true,
      },
    }
    with_puppet_running_on(master, master_opts) do

      hosts.each do |host|
        next if host['roles'].include? 'master'

        step "Agents: Run agent --test first time to gen CSR"
        on host, puppet("agent --test --server #{master}"), :acceptable_exit_codes => [1]
      end

      # Sign all waiting certs
      step "Master: sign all certs"
      on master, puppet("cert --sign --all"), :acceptable_exit_codes => [0,24]

      step "Agents: Run agent --test second time to obtain signed cert"
      on agents, puppet("agent --test --server #{master}"), :acceptable_exit_codes => [0,2]
    end
  end
end
reset_agent_ssl(resign = true) click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 93
def reset_agent_ssl(resign = true)
  return if master.is_pe?
  clear_agent_ssl

  hostname = master.execute('facter hostname')
  fqdn = master.execute('facter fqdn')

  step "Clear old agent certificates from master" do
    agents.each do |agent|
      next if agent == master && agent.is_using_passenger?
      agent_cn = on(agent, puppet('agent --configprint certname')).stdout.chomp
      clean_cert(master, agent_cn, false) if agent_cn
    end
  end

  if resign
    step "Master: Ensure the master is listening and autosigning"
    with_puppet_running_on(master,
                            :master => {
                              :dns_alt_names => "puppet,#{hostname},#{fqdn}",
                              :autosign => true,
                            }
                          ) do

      agents.each do |agent|
        next if agent == master && agent.is_using_passenger?
        step "Agents: Run agent --test once to obtain auto-signed cert" do
          on agent, puppet('agent', "--test --server #{master}"), :acceptable_exit_codes => [0,2]
        end
      end
    end
  end
end
mcollective-2.12.1/doc/Puppet/Acceptance/CronUtils.html0000644005276200011600000001434313265671734022676 0ustar jenkinsjenkins module Puppet::Acceptance::CronUtils - mcollective version 2.12.1

module Puppet::Acceptance::CronUtils

Public Instance Methods

clean(agent, o={}) click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 4
def clean(agent, o={})
  o = {:user => 'tstuser'}.merge(o)
  run_cron_on(agent, :remove, o[:user])
  apply_manifest_on(agent, %Q[user { '%s': ensure => absent, managehome => false }] % o[:user])
end
setup(agent, o={}) click to toggle source
# File acceptance/lib/puppet/acceptance/common_utils.rb, line 10
def setup(agent, o={})
  o = {:user => 'tstuser'}.merge(o)
  apply_manifest_on(agent, %Q[user { '%s': ensure => present, managehome => false }] % o[:user])
  apply_manifest_on(agent, %Q[case $operatingsystem {
                               centos, redhat: {$cron = 'cronie'}
                               solaris: { $cron = 'core-os' }
                               default: {$cron ='cron'} }
                               package {'cron': name=> $cron, ensure=>present, }])
end
mcollective-2.12.1/doc/Puppet/Acceptance.html0000644005276200011600000000435213265671734020753 0ustar jenkinsjenkins module Puppet::Acceptance - mcollective version 2.12.1

module Puppet::Acceptance

mcollective-2.12.1/doc/Gemfile.html0000644005276200011600000003133413265671734017020 0ustar jenkinsjenkins Gemfile - mcollective version 2.12.1

source ENV || ‘rubygems.org’

gem ‘stomp’, ‘>= 1.4.4’

if RUBY_VERSION =~ /^1.8/

gem 'systemu', '2.6.4'
gem 'json', '~> 1.8.3'

else

gem 'systemu'

end

if RUBY_VERSION =~ /^2.4/

gem 'json', '~> 2.1.0'

end

group :dev do

gem 'rake', '~> 10.4'
if RUBY_VERSION =~ /^2\.[0-9]\.[0-9]/
  gem 'rubocop', '~> 0.48.1', :platforms => [:ruby]
end

end

group :test do

if RUBY_VERSION =~ /^1\.8/
  gem 'rdoc', '~> 4.2.2'
else
  gem 'rdoc'
end
gem 'yarjuf', "~> 1.0"
gem 'rspec', '~> 2.11.0'
gem 'mocha', '~> 0.10.0'
gem 'mcollective-test'

end

if File.exists? “#{__FILE__}.localâ€

eval(File.read("#{__FILE__}.local"), binding)

end

mcollective-2.12.1/doc/OpenSSL/0000755005276200011600000000000013265671736016043 5ustar jenkinsjenkinsmcollective-2.12.1/doc/OpenSSL/SSL.html0000644005276200011600000000432213265671734017371 0ustar jenkinsjenkins module OpenSSL::SSL - mcollective version 2.12.1

module OpenSSL::SSL

mcollective-2.12.1/doc/OpenSSL/SSL/0000755005276200011600000000000013265671736016504 5ustar jenkinsjenkinsmcollective-2.12.1/doc/OpenSSL/SSL/SSLContext.html0000644005276200011600000001250313265671734021377 0ustar jenkinsjenkins class OpenSSL::SSL::SSLContext - mcollective version 2.12.1

class OpenSSL::SSL::SSLContext

Public Class Methods

new(*args) click to toggle source
# File lib/mcollective/monkey_patches.rb, line 144
def initialize(*args)
  __mcollective_original_initialize(*args)
  params = {
    :options => DEFAULT_PARAMS[:options],
    :ciphers => DEFAULT_PARAMS[:ciphers],
  }
  set_params(params)
end

Public Instance Methods

__mcollective_original_initialize(*args)
Alias for: new
mcollective-2.12.1/doc/COPYING.html0000644005276200011600000005154613265671734016567 0ustar jenkinsjenkins COPYING - mcollective version 2.12.1
         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.12.1/doc/MCollective.html0000644005276200011600000001164013265671731017651 0ustar jenkinsjenkins module MCollective - mcollective version 2.12.1

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

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

Constants

VERSION

Public Class Methods

version() click to toggle source
# File lib/mcollective.rb, line 64
def self.version
  VERSION
end
mcollective-2.12.1/doc/etc/0000755005276200011600000000000013265671736015333 5ustar jenkinsjenkinsmcollective-2.12.1/doc/etc/facts_yaml_dist.html0000644005276200011600000002420713265671735021372 0ustar jenkinsjenkins facts.yaml.dist - mcollective version 2.12.1

mcollective: 1

mcollective-2.12.1/doc/etc/client_cfg_dist.html0000644005276200011600000002512513265671735021345 0ustar jenkinsjenkins client.cfg.dist - mcollective version 2.12.1

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.12.1/doc/etc/server_cfg_dist.html0000644005276200011600000002516013265671735021374 0ustar jenkinsjenkins server.cfg.dist - mcollective version 2.12.1

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.12.1/doc/etc/ssl/0000755005276200011600000000000013265671736016134 5ustar jenkinsjenkinsmcollective-2.12.1/doc/etc/ssl/clients/0000755005276200011600000000000013265671736017575 5ustar jenkinsjenkinsmcollective-2.12.1/doc/etc/ssl/clients/PLACEHOLDER.html0000644005276200011600000002533713265671735022216 0ustar jenkinsjenkins PLACEHOLDER - mcollective version 2.12.1
mcollective-2.12.1/doc/etc/ssl/PLACEHOLDER.html0000644005276200011600000002463713265671735020557 0ustar jenkinsjenkins PLACEHOLDER - mcollective version 2.12.1
mcollective-2.12.1/doc/OpenSSL.html0000644005276200011600000000426513265671734016736 0ustar jenkinsjenkins module OpenSSL - mcollective version 2.12.1

module OpenSSL

mcollective-2.12.1/doc/MAINTAINERS.html0000644005276200011600000002573013265671734017225 0ustar jenkinsjenkins MAINTAINERS - mcollective version 2.12.1

{

"version": 1,
"file_format": "This MAINTAINERS file format is described at http://pup.pt/maintainers",
"issues": "https://tickets.puppet.com/browse/MCO",
"internal_list": "https://groups.google.com/a/puppet.com/forum/?hl=en#!forum/discuss-orchestrator-maintainers",
"people": [
  {
    "github": "MikaelSmith",
    "email": "michael.smith@puppet.com",
    "name": "Michael Smith"
  },
  {
    "github": "mruzicka",
    "name": "Michal RůžiÄka"
  },
  {
    "github": "parisiale",
    "email": "alessandro@puppet.com",
    "name": "Alessandro Parisi"
  },
  {
    "github": "johnduarte",
    "email": "john.duarte@puppet.com",
    "name": "John Duarte"
  }
]

}

mcollective-2.12.1/doc/Object.html0000644005276200011600000013312713265671734016661 0ustar jenkinsjenkins class Object - mcollective version 2.12.1

class Object

Constants

AGENT_PACKAGES
EQUIFAX_CA
GEOTRUST_GLOBAL_CA
GitHubSig
GitURI
InstallOptions
MASTER_PACKAGES
PREREQS
RbConfig
SourcePath

On Windows, ‘/’ leads to a different path for Cygwin utils vs Git. Explicitly use the root drive.

USERTRUST_NETWORK_CA
Version
WINDOWS

Public Instance Methods

build_rdoc(files) click to toggle source

Build the rdoc documentation.

# File install.rb, line 260
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 66
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 87
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 77
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 95
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 58
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 275
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}" unless WINDOWS
      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
last_run_report(h) click to toggle source
# File acceptance/tests/mco_puppet_exec.rb, line 42
def last_run_report(h)
  if h['platform'] =~ /windows/
    'C:/ProgramData/PuppetLabs/puppet/cache'
  else
    '/opt/puppetlabs/puppet/cache'
  end + '/state/last_run_report.yaml'
end
prepare_installation() click to toggle source

Prepare the file installation.

# File install.rb, line 111
def prepare_installation
  InstallOptions.configs = true
  InstallOptions.batch_files = 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('--no-batch-files', 'Prevents installation of batch files for windows', 'Default off') do |batch_files|
      InstallOptions.batch_files = false
    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
    if WINDOWS
      InstallOptions.service_files = true
      opts.on('--[no-]service-files', 'Installation of windows service files', 'Default is to install the windows service files') do |service_files|
        InstallOptions.service_files = service_files
      end
    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

  unless destdir.empty?
    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)
  end

  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
testfile(h) click to toggle source
# File acceptance/tests/mco_puppet_runonce.rb, line 6
def testfile(h)
  if /windows/ =~ h[:platform]
    "C:/#{@testfilename}"
  else
    "/tmp/#{@testfilename}"
  end
end
mcollective-2.12.1/doc/Symbol.html0000644005276200011600000000770013265671734016715 0ustar jenkinsjenkins class Symbol - mcollective version 2.12.1

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.12.1/doc/Dir.html0000644005276200011600000002265613265671731016172 0ustar jenkinsjenkins class Dir - mcollective version 2.12.1

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.12.1/doc/fonts/0000755005276200011600000000000013265671736015711 5ustar jenkinsjenkinsmcollective-2.12.1/doc/fonts/Lato-LightItalic.ttf0000644005276200011600000026776413265671731021543 0ustar jenkinsjenkinsGPOS'ÂòHPGSUBV.TIlOS/2Ù8§J|`cmapRÔŸ×JÜæcvt &‚6e¤8fpgm‹ zAeÜ ‘gaspeœglyf©^ÚÎOÄ‘Ìheadþ’dÊá6hhea»1áÈ$hmtx?kðáìTkernOÞQ˜æ@gòlocaÉ)N4,maxp> ¨P` nameìBbdP€}post:Ó\Þbšprepœx9½op 0JDFLTlatnÿÿÿÿkernkernGrîè☒Tî°v  ò  ® ¨ Ú ˆ V è¦8’,^$^ Â0æÐ DŽÔJÖJÔŽÔJºL F à!z" ##P#š$4$~%x&&ü'æ(Ð)º*¤+Ž+¸,J,Ü-n..’/$/V/ˆ/º/ì1¢242n2¨2â33V3œ3Ö44J4„4¾4ø5²5ì6¦77º7ô8~8Ä8þ:´;";;þ<˜=2>,?&?è@âAÜBžC8CúD”E.F(> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[-#ÿØ&ÿØ*ÿØ2ÿØ4ÿØDÿçFÿçGÿçHÿçRÿçTÿçkÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿç£ÿç¤ÿç¥ÿç¦ÿç§ÿç¨ÿç©ÿçªÿç«ÿç¬ÿç­ÿç²ÿç´ÿçµÿç¶ÿç·ÿç¸ÿçºÿçÃÿçÄÿØÅÿçÇÿçÍÿØÎÿç> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[0ÿ= ÿ= ÿ=ÿI#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿA9ÿ=:ÿm<ÿd?ÿ=Yÿ~ZÿÉ\ÿkÿÆlÿ=mÿIoÿIpÿÆrÿ=yÿI|ÿ=}ÿI‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿd¿ÿ~Áÿ~ÄÿÆÍÿÆÓÿdåÿIæÿIçÿ=èÿ=êÿ=ëÿ=ïÿIòÿIóÿI&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ0ÿ= ÿ= ÿ=ÿI#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿA9ÿ=:ÿm<ÿd?ÿ=Yÿ~ZÿÉ\ÿkÿÆlÿ=mÿIoÿIpÿÆrÿ=yÿI|ÿ=}ÿI‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿd¿ÿ~Áÿ~ÄÿÆÍÿÆÓÿdåÿIæÿIçÿ=èÿ=êÿ=ëÿ=ïÿIòÿIóÿIq> ÿ« > >ÿGÿ’ÿGÿ«ÿ­ÿ­"F#ÿØ$ÿ«&ÿØ*ÿØ-ÿo2ÿØ4ÿØDÿ™Fÿ™Gÿ™Hÿ™Pÿ­Qÿ­Rÿ™Sÿ­Tÿ™Uÿ­Vÿ¡Xÿ­YÿäZÿä\ÿä]ÿÉkÿØl>mÿ’oÿ’pÿØr>tPuPwÿ­yÿ’{P|>}ÿ’‚ÿ«ƒÿ«„ÿ«…ÿ«†ÿ«‡ÿ«ˆÿ«‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿ™£ÿ™¤ÿ™¥ÿ™¦ÿ™§ÿ™¨ÿ™©ÿ™ªÿ™«ÿ™¬ÿ™­ÿ™²ÿ™³ÿ­´ÿ™µÿ™¶ÿ™·ÿ™¸ÿ™ºÿ™»ÿ­¼ÿ­½ÿ­¾ÿ­¿ÿäÁÿäÂÿ«Ãÿ™ÄÿØÅÿ™Çÿ™Ìÿ­ÍÿØÎÿ™Ðÿ¡Òÿ¡ÕÿÉ×ÿÉÙÿÉåÿ’æÿ’ç>è>éÿGê>ë>ìÿGïÿ’ðÿGòÿ’óÿ’ùÿ«$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ ÿomÿooÿoyÿo}ÿoåÿoæÿoïÿoòÿoóÿo$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä> ÿÿLÿLÿÿÄÿÄ"$ÿ-ÿ.DÿµFÿµGÿµHÿµPÿÄQÿÄRÿµSÿÄTÿµUÿÄXÿÄwÿÄ‚ÿƒÿ„ÿ…ÿ†ÿ‡ÿˆÿ¢ÿµ£ÿµ¤ÿµ¥ÿµ¦ÿµ§ÿµ¨ÿµ©ÿµªÿµ«ÿµ¬ÿµ­ÿµ²ÿµ³ÿÄ´ÿµµÿµ¶ÿµ·ÿµ¸ÿµºÿµ»ÿļÿĽÿľÿÄÂÿÃÿµÅÿµÇÿµÌÿÄÎÿµéÿLìÿLðÿLùÿ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ+  ÿº#ÿ¦&ÿ¦*ÿ¦2ÿ¦4ÿ¦IÿÓWÿ¡YÿÎZÿÕ\ÿÎkÿ¦lmÿºoÿºpÿ¦ryÿº|}ÿº‰ÿ¦”ÿ¦•ÿ¦–ÿ¦—ÿ¦˜ÿ¦šÿ¦¿ÿÎÁÿÎÄÿ¦Íÿ¦åÿºæÿºçèêëïÿºòÿºóÿº3þè þè þèÿ$#ÿ«&ÿ«*ÿ«2ÿ«4ÿ«7ÿ89ÿV:ÿy<ÿ=?ÿVYÿzZÿ­\ÿzkÿ«lþèmÿ$oÿ$pÿ«rþètÿ=uÿ=yÿ${ÿ=|þè}ÿ$‰ÿ«”ÿ«•ÿ«–ÿ«—ÿ«˜ÿ«šÿ«Ÿÿ=¿ÿzÁÿzÄÿ«Íÿ«Óÿ=åÿ$æÿ$çþèèþèêþèëþèïÿ$òÿ$óÿ$$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä/ ÿŠÿ=ÿ=ÿŠ$ÿŠ-ÿVDÿâFÿâGÿâHÿâRÿâTÿâ‚ÿŠƒÿŠ„ÿŠ…ÿІÿЇÿŠˆÿŠ¢ÿâ£ÿâ¤ÿâ¥ÿâ¦ÿâ§ÿâ¨ÿâ©ÿâªÿâ«ÿâ¬ÿâ­ÿâ²ÿâ´ÿâµÿâ¶ÿâ·ÿâ¸ÿâºÿâÂÿŠÃÿâÅÿâÇÿâÎÿâéÿ=ìÿ=ðÿ=ùÿŠ$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä#ÿØ&ÿØ*ÿØ2ÿØ4ÿØ7ÿÓ8ÿâkÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ›ÿâœÿâÿâžÿâÄÿØÍÿØf ÿÿLÿLÿLÿÿ]ÿ]"-#ÿˆ$ÿ&ÿˆ*ÿˆ-ÿ82ÿˆ4ÿˆDÿ)Fÿ)Gÿ)Hÿ)JÿAPÿ]Qÿ]Rÿ)Sÿ]Tÿ)Uÿ]VÿFXÿ]YÿQZÿy[ÿb\ÿL]ÿekÿˆmÿLoÿLpÿˆwÿ]yÿL}ÿL‚ÿƒÿ„ÿ…ÿ†ÿ‡ÿˆÿ‰ÿˆ”ÿˆ•ÿˆ–ÿˆ—ÿˆ˜ÿˆšÿˆ¢ÿ)£ÿ)¤ÿ)¥ÿ)¦ÿ)§ÿ)¨ÿ)©ÿ)ªÿ)«ÿ)¬ÿ)­ÿ)²ÿ)³ÿ]´ÿ)µÿ)¶ÿ)·ÿ)¸ÿ)ºÿ)»ÿ]¼ÿ]½ÿ]¾ÿ]¿ÿQÁÿQÂÿÃÿ)ÄÿˆÅÿ)Çÿ)Ìÿ]ÍÿˆÎÿ)ÐÿFÒÿFÕÿe×ÿeÙÿeåÿLæÿLéÿLìÿLïÿLðÿLòÿLóÿLùÿ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØq> ÿ« > >ÿGÿ’ÿGÿ«ÿ­ÿ­"F#ÿØ$ÿ«&ÿØ*ÿØ-ÿo2ÿØ4ÿØDÿ™Fÿ™Gÿ™Hÿ™Pÿ­Qÿ­Rÿ™Sÿ­Tÿ™Uÿ­Vÿ¡Xÿ­YÿäZÿä\ÿä]ÿÉkÿØl>mÿ’oÿ’pÿØr>tPuPwÿ­yÿ’{P|>}ÿ’‚ÿ«ƒÿ«„ÿ«…ÿ«†ÿ«‡ÿ«ˆÿ«‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿ™£ÿ™¤ÿ™¥ÿ™¦ÿ™§ÿ™¨ÿ™©ÿ™ªÿ™«ÿ™¬ÿ™­ÿ™²ÿ™³ÿ­´ÿ™µÿ™¶ÿ™·ÿ™¸ÿ™ºÿ™»ÿ­¼ÿ­½ÿ­¾ÿ­¿ÿäÁÿäÂÿ«Ãÿ™ÄÿØÅÿ™Çÿ™Ìÿ­ÍÿØÎÿ™Ðÿ¡Òÿ¡ÕÿÉ×ÿÉÙÿÉåÿ’æÿ’ç>è>éÿGê>ë>ìÿGïÿ’ðÿGòÿ’óÿ’ùÿ«NF ÿÉ F FÿœÿœÿÉÿÆÿÆ$ÿÉ-ÿ¦DÿªFÿªGÿªHÿªJÿ´PÿÆQÿÆRÿªSÿÆTÿªUÿÆVÿ²XÿÆlFrFt<u<wÿÆ{<|F‚ÿɃÿÉ„ÿÉ…ÿɆÿɇÿɈÿÉ¢ÿª£ÿª¤ÿª¥ÿª¦ÿª§ÿª¨ÿª©ÿªªÿª«ÿª¬ÿª­ÿª²ÿª³ÿÆ´ÿªµÿª¶ÿª·ÿª¸ÿªºÿª»ÿƼÿƽÿƾÿÆÂÿÉÃÿªÅÿªÇÿªÌÿÆÎÿªÐÿ²Òÿ²çFèFéÿœêFëFìÿœðÿœùÿÉ+  ÿº#ÿ¦&ÿ¦*ÿ¦2ÿ¦4ÿ¦IÿÓWÿ¡YÿÎZÿÕ\ÿÎkÿ¦lmÿºoÿºpÿ¦ryÿº|}ÿº‰ÿ¦”ÿ¦•ÿ¦–ÿ¦—ÿ¦˜ÿ¦šÿ¦¿ÿÎÁÿÎÄÿ¦Íÿ¦åÿºæÿºçèêëïÿºòÿºóÿºm4 ÿ~ 4 4ÿ[ÿVÿ[ÿ~ÿŠÿŠ"2#ÿµ$ÿ~&ÿµ*ÿµ-ÿ82ÿµ4ÿµDÿGFÿGGÿGHÿGJÿ_PÿŠQÿŠRÿGSÿŠTÿGUÿŠVÿGXÿŠ]ÿ¯kÿµl4mÿVoÿVpÿµr4t2u2wÿŠyÿV{2|4}ÿV‚ÿ~ƒÿ~„ÿ~…ÿ~†ÿ~‡ÿ~ˆÿ~‰ÿµ”ÿµ•ÿµ–ÿµ—ÿµ˜ÿµšÿµ¢ÿG£ÿG¤ÿG¥ÿG¦ÿG§ÿG¨ÿG©ÿGªÿG«ÿG¬ÿG­ÿG²ÿG³ÿŠ´ÿGµÿG¶ÿG·ÿG¸ÿGºÿG»ÿмÿнÿоÿŠÂÿ~ÃÿGÄÿµÅÿGÇÿGÌÿŠÍÿµÎÿGÐÿGÒÿGÕÿ¯×ÿ¯Ùÿ¯åÿVæÿVç4è4éÿ[ê4ë4ìÿ[ïÿVðÿ[òÿVóÿVùÿ~ÿ¨" #ÿº&ÿº*ÿº2ÿº4ÿºkÿºmÿ¨oÿ¨pÿºyÿ¨}ÿ¨‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿºÄÿºÍÿºåÿ¨æÿ¨ïÿ¨òÿ¨óÿ¨-#ÿØ&ÿØ*ÿØ2ÿØ4ÿØDÿçFÿçGÿçHÿçRÿçTÿçkÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿç£ÿç¤ÿç¥ÿç¦ÿç§ÿç¨ÿç©ÿçªÿç«ÿç¬ÿç­ÿç²ÿç´ÿçµÿç¶ÿç·ÿç¸ÿçºÿçÃÿçÄÿØÅÿçÇÿçÍÿØÎÿç:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°K K KÿyÿylKrKtdud{d|KçKèKéÿyêKëKìÿyðÿyÿÄ ÿÄ ÿÄYÿç\ÿâlÿÄrÿÄtÿ°uÿ°{ÿ°|ÿÄ¿ÿçÁÿççÿÄèÿÄêÿÄëÿÄDÿÓFÿÓGÿÓHÿÓRÿÓTÿÓ¢ÿÓ£ÿÓ¤ÿÓ¥ÿÓ¦ÿÓ§ÿÓ¨ÿÓ©ÿÓªÿÓ«ÿÓ¬ÿÓ­ÿÓ²ÿÓ´ÿÓµÿÓ¶ÿÓ·ÿÓ¸ÿÓºÿÓÃÿÓÅÿÓÇÿÓÎÿÓÿÄ ÿÄ ÿÄYÿç\ÿâlÿÄrÿÄtÿ°uÿ°{ÿ°|ÿÄ¿ÿçÁÿççÿÄèÿÄêÿÄëÿÄÿÄ ÿÄ ÿÄYÿç\ÿâlÿÄrÿÄtÿ°uÿ°{ÿ°|ÿÄ¿ÿçÁÿççÿÄèÿÄêÿÄëÿÄÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°"ÿyÿyDÿØFÿØGÿØHÿØRÿØTÿØ¢ÿØ£ÿؤÿØ¥ÿئÿاÿبÿØ©ÿتÿØ«ÿجÿØ­ÿزÿØ´ÿصÿضÿØ·ÿظÿغÿØÃÿØÅÿØÇÿØÎÿØéÿyìÿyðÿy. ÿßÿ~ÿ~ÿß$ÿßDÿîFÿîGÿîHÿîRÿîTÿî‚ÿ߃ÿß„ÿß…ÿ߆ÿ߇ÿ߈ÿߢÿî£ÿî¤ÿî¥ÿî¦ÿî§ÿî¨ÿî©ÿîªÿî«ÿî¬ÿî­ÿî²ÿî´ÿîµÿî¶ÿî·ÿî¸ÿîºÿîÂÿßÃÿîÅÿîÇÿîÎÿîéÿ~ìÿ~ðÿ~ùÿß ÿñÿÄÿÄÿñ$ÿñ‚ÿñƒÿñ„ÿñ…ÿñ†ÿñ‡ÿñˆÿñÂÿñéÿÄìÿÄðÿÄùÿñDÿÓFÿÓGÿÓHÿÓRÿÓTÿÓ¢ÿÓ£ÿÓ¤ÿÓ¥ÿÓ¦ÿÓ§ÿÓ¨ÿÓ©ÿÓªÿÓ«ÿÓ¬ÿÓ­ÿÓ²ÿÓ´ÿÓµÿÓ¶ÿÓ·ÿÓ¸ÿÓºÿÓÃÿÓÅÿÓÇÿÓÎÿÓ. ÿßÿyÿyÿß$ÿßDÿîFÿîGÿîHÿîRÿîTÿî‚ÿ߃ÿß„ÿß…ÿ߆ÿ߇ÿ߈ÿߢÿî£ÿî¤ÿî¥ÿî¦ÿî§ÿî¨ÿî©ÿîªÿî«ÿî¬ÿî­ÿî²ÿî´ÿîµÿî¶ÿî·ÿî¸ÿîºÿîÂÿßÃÿîÅÿîÇÿîÎÿîéÿyìÿyðÿyùÿß-#ÿØ&ÿØ*ÿØ2ÿØ4ÿØDÿçFÿçGÿçHÿçRÿçTÿçkÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿç£ÿç¤ÿç¥ÿç¦ÿç§ÿç¨ÿç©ÿçªÿç«ÿç¬ÿç­ÿç²ÿç´ÿçµÿç¶ÿç·ÿç¸ÿçºÿçÃÿçÄÿØÅÿçÇÿçÍÿØÎÿç$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[ ÿeÿe$ÿe9F:F<(?F‚ÿeƒÿe„ÿe…ÿe†ÿe‡ÿeˆÿeŸ(ÂÿeÓ(ùÿe ÿeÿe$ÿe9F:F<(?F‚ÿeƒÿe„ÿe…ÿe†ÿe‡ÿeˆÿeŸ(ÂÿeÓ(ùÿe&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ ÿeÿe$ÿe9F:F<(?F‚ÿeƒÿe„ÿe…ÿe†ÿe‡ÿeˆÿeŸ(ÂÿeÓ(ùÿe> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ ÿomÿooÿoyÿo}ÿoåÿoæÿoïÿoòÿoóÿo$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿä ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØm4 ÿ~ 4 4ÿ[ÿVÿ[ÿ~ÿŠÿŠ"2#ÿµ$ÿ~&ÿµ*ÿµ-ÿ82ÿµ4ÿµDÿGFÿGGÿGHÿGJÿ_PÿŠQÿŠRÿGSÿŠTÿGUÿŠVÿGXÿŠ]ÿ¯kÿµl4mÿVoÿVpÿµr4t2u2wÿŠyÿV{2|4}ÿV‚ÿ~ƒÿ~„ÿ~…ÿ~†ÿ~‡ÿ~ˆÿ~‰ÿµ”ÿµ•ÿµ–ÿµ—ÿµ˜ÿµšÿµ¢ÿG£ÿG¤ÿG¥ÿG¦ÿG§ÿG¨ÿG©ÿGªÿG«ÿG¬ÿG­ÿG²ÿG³ÿŠ´ÿGµÿG¶ÿG·ÿG¸ÿGºÿG»ÿмÿнÿоÿŠÂÿ~ÃÿGÄÿµÅÿGÇÿGÌÿŠÍÿµÎÿGÐÿGÒÿGÕÿ¯×ÿ¯Ùÿ¯åÿVæÿVç4è4éÿ[ê4ë4ìÿ[ïÿVðÿ[òÿVóÿVùÿ~$ÿÆ ÿä ÿÆ ÿØ ÿÆÿä$ÿä7ÿˆ9ÿÓ;ÿÐ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÆrÿÆ|ÿÆ‚ÿäƒÿä„ÿä…ÿä†ÿä‡ÿäˆÿäŸÿ°ÂÿäÓÿ°ÔÿµÖÿµØÿµçÿÆèÿÆêÿÆëÿÆùÿäÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿÄ ÿÄ ÿÄYÿç\ÿâlÿÄrÿÄtÿ°uÿ°{ÿ°|ÿÄ¿ÿçÁÿççÿÄèÿÄêÿÄëÿÄÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°. ÿßÿ~ÿ~ÿß$ÿßDÿîFÿîGÿîHÿîRÿîTÿî‚ÿ߃ÿß„ÿß…ÿ߆ÿ߇ÿ߈ÿߢÿî£ÿî¤ÿî¥ÿî¦ÿî§ÿî¨ÿî©ÿîªÿî«ÿî¬ÿî­ÿî²ÿî´ÿîµÿî¶ÿî·ÿî¸ÿîºÿîÂÿßÃÿîÅÿîÇÿîÎÿîéÿ~ìÿ~ðÿ~ùÿßÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°. ÿßÿ~ÿ~ÿß$ÿßDÿîFÿîGÿîHÿîRÿîTÿî‚ÿ߃ÿß„ÿß…ÿ߆ÿ߇ÿ߈ÿߢÿî£ÿî¤ÿî¥ÿî¦ÿî§ÿî¨ÿî©ÿîªÿî«ÿî¬ÿî­ÿî²ÿî´ÿîµÿî¶ÿî·ÿî¸ÿîºÿîÂÿßÃÿîÅÿîÇÿîÎÿîéÿ~ìÿ~ðÿ~ùÿß:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝ ÿomÿooÿoyÿo}ÿoåÿoæÿoïÿoòÿoóÿoÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°"ÿo ÿo ÿoÿt9ÿj:ÿ’<ÿy?ÿjYÿ°ZÿÓ\ÿ°lÿomÿtoÿtrÿotÿƒuÿƒyÿt{ÿƒ|ÿo}ÿtŸÿy¿ÿ°Áÿ°ÓÿyåÿtæÿtçÿoèÿoêÿoëÿoïÿtòÿtóÿtÿÄ ÿÄ ÿÄYÿç\ÿâlÿÄrÿÄtÿ°uÿ°{ÿ°|ÿÄ¿ÿçÁÿççÿÄèÿÄêÿÄëÿÄÿ° ÿ° ÿç ÿ°@ÿç[ÿÓ`ÿçlÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°m4 ÿ~ 4 4ÿ[ÿVÿ[ÿ~ÿŠÿŠ"2#ÿµ$ÿ~&ÿµ*ÿµ-ÿ82ÿµ4ÿµDÿGFÿGGÿGHÿGJÿ_PÿŠQÿŠRÿGSÿŠTÿGUÿŠVÿGXÿŠ]ÿ¯kÿµl4mÿVoÿVpÿµr4t2u2wÿŠyÿV{2|4}ÿV‚ÿ~ƒÿ~„ÿ~…ÿ~†ÿ~‡ÿ~ˆÿ~‰ÿµ”ÿµ•ÿµ–ÿµ—ÿµ˜ÿµšÿµ¢ÿG£ÿG¤ÿG¥ÿG¦ÿG§ÿG¨ÿG©ÿGªÿG«ÿG¬ÿG­ÿG²ÿG³ÿŠ´ÿGµÿG¶ÿG·ÿG¸ÿGºÿG»ÿмÿнÿоÿŠÂÿ~ÃÿGÄÿµÅÿGÇÿGÌÿŠÍÿµÎÿGÐÿGÒÿGÕÿ¯×ÿ¯Ùÿ¯åÿVæÿVç4è4éÿ[ê4ë4ìÿ[ïÿVðÿ[òÿVóÿVùÿ~ÿ¨" #ÿº&ÿº*ÿº2ÿº4ÿºkÿºmÿ¨oÿ¨pÿºyÿ¨}ÿ¨‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿºÄÿºÍÿºåÿ¨æÿ¨ïÿ¨òÿ¨óÿ¨ÿ¨" #ÿº&ÿº*ÿº2ÿº4ÿºkÿºmÿ¨oÿ¨pÿºyÿ¨}ÿ¨‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿºÄÿºÍÿºåÿ¨æÿ¨ïÿ¨òÿ¨óÿ¨ÿ¨" #ÿº&ÿº*ÿº2ÿº4ÿºkÿºmÿ¨oÿ¨pÿºyÿ¨}ÿ¨‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿºÄÿºÍÿºåÿ¨æÿ¨ïÿ¨òÿ¨óÿ¨&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[0ÿ= ÿ= ÿ=ÿI#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿA9ÿ=:ÿm<ÿd?ÿ=Yÿ~ZÿÉ\ÿkÿÆlÿ=mÿIoÿIpÿÆrÿ=yÿI|ÿ=}ÿI‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿd¿ÿ~Áÿ~ÄÿÆÍÿÆÓÿdåÿIæÿIçÿ=èÿ=êÿ=ëÿ=ïÿIòÿIóÿI> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[0ÿ= ÿ= ÿ=ÿI#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿA9ÿ=:ÿm<ÿd?ÿ=Yÿ~ZÿÉ\ÿkÿÆlÿ=mÿIoÿIpÿÆrÿ=yÿI|ÿ=}ÿI‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿd¿ÿ~Áÿ~ÄÿÆÍÿÆÓÿdåÿIæÿIçÿ=èÿ=êÿ=ëÿ=ïÿIòÿIóÿI&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ0ÿ= ÿ= ÿ=ÿI#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿA9ÿ=:ÿm<ÿd?ÿ=Yÿ~ZÿÉ\ÿkÿÆlÿ=mÿIoÿIpÿÆrÿ=yÿI|ÿ=}ÿI‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿd¿ÿ~Áÿ~ÄÿÆÍÿÆÓÿdåÿIæÿIçÿ=èÿ=êÿ=ëÿ=ïÿIòÿIóÿI&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ&ÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝ> ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[:ÿV ÿV ÿVÿÝ#ÿß&ÿß*ÿß-22ÿß4ÿß7ÿ8ÿØ9ÿ«:ÿÄ<ÿy?ÿ«WÿÕYÿßZÿñ\ÿßkÿßlÿVmÿÝoÿÝpÿßrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØœÿØÿØžÿØŸÿy¿ÿßÁÿßÄÿßÍÿßÓÿyåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝr #$&')-./2345789:;<=>?EHIKNPQRSUYZ[\^klmoprtuy{|}‚ƒ„…†‡‰’”•–—˜›œžŸ ¨ª«¬­³´µ¶·¸º¿ÀÁÂÄÇÉÌÎÓÔÖØåæçèéêëìïðòóöù 8‚DFLTlatnÿÿÿÿcase&case,liga2liga8sups>supsD,>B      @ LO,{tu CjqvÛÜÞßàâãI¾,xxºAô ¯P`KtyPLûJþz¶ª “㇠†&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a†‡‰‹“˜ž£¢¤¦¥§©«ª¬­¯®°±³µ´¶¸·¼»½¾írdeiïx¡pkövjˆšþsgwøûúäÿl|÷¨ºcnýÚùm}ðb‚…—ÍÎåæêëçè¹ÁÓôõòóîyéìñ„ŒƒŠ‘Ž•–”œ›ÈÛâqÞßàzãáÜ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ/Ò‡,8<@µBK° PX@3 `f[[ Q CQ D@4hf[[ Q CQ DY@@?>=<;:9751/+)&$$ +>32#'.<54>54.#"#"'4632#"&!!7!!374>32#"&ÌF  >FÎ!""'5‡ýÄ-PS\88\SP-<úÆ""!5ç§t‡ ,@) Bk D   $+#"&'7!#"&'7e$  &]$  &‡þÜšš$þÜšš$Pr‡:>yK°)PX@(  Y  C Q C D@&  Z  Y  C DY@>=<;::5320-,'%#"!#!+#"&547!+#"546?3#76;>;!323+32%!!è”' †þÔ†)”·Û‚ý )Ö‰(•+–) ‰â »‚â ý“,ƒþÔ»þE  þl»‡%–þD¼  þm$þyE‡rþþB[9DO‘@! *BK° PX@2j  h f_ SC SD@1j  h fk SC SDY@LKA@#$#$ +.'7632.54>?>;#".'+4.'>ေ>  "/AU8oc­6M9*,6Tl}ÿï8–'0DXuK°PX@+[ [SC C SD@/[ [ CSC C SDY@ US((%"&((($ +#".54>324.#"32>>;+#".54>324.#"32>Â7Zt>3W?#4XvA3V?$J.>$0XC'-?$0WC(XAû«B«7Zt>3W>#4XuA3V?$J.=$0XC(.?$0WB(qgœh4&ImGgœi5&JmI7>;#"&/#".54>7.54>32>7Î>hJ)40K6=eI(>;a2A <TDíMš/lz†GG|\6:c†M106bˆþ0.J_2>si^(þŒGrP,—)Kh?  E:%,Mi>EŽIþNHšG ^Ç[þÞ ½3V>#-U|NR‘y\F…FPˆd8û±BeD"!:P/ÌVizç§e‡ @ Bk D $+#"&'7e$  &‡þÜšš$œþøHõ ³ (+.54>7ï!2! ++<' 4UxR& S~T+ÂO¨§¡H  X¶³­PV¥¦«¸Ér wäðþ÷þø¯õ ³ (+4.'&546?'&547>\!1! ++<' 4UxR& S~T++O¨§¡H  Yµ³­PV¥¦«¸Ér wä𠨂ÃÕ;*@'40/+'&" Bja;;+7>7'7>7./7.5<?3>?'.'x  ¹º    2 ¹¹    ‚Ön+n  n,n  ×Ö n,n  n+n   Ö€¶3… .@+jkMRF +!!#!7!º7°þO7L8þQ ®8…þ=Gþ;ÅGÃ?ÿðª@B ?S D"+74632'.54>7#"&?0'+/(8"'$ %/U#2;0)URL  %9J,2c*x@MQE+!!n¬ þRxN:ÿñôª@SD&$+74>32#"&:!#7''5M""'55ÿ¶ÿ¬¥@kD""++6; !&ï''/Õ$Zÿñ`—',@)SCSD'' +2#".54>2>54.#"¶\œr@b¦Ûy\œr@b§Û.c³‰Q5\}Gc´‰Q5]}—HÙ‘Ýþº×jHÙ‘ÜG×jú¨_Ã)Ê‚¾z;_Ãþ×Ƀ½{;úÚ)@&Bh CR D$+%!7#"/3!!FþŸ ÃI¥5ý(H‰Eþä 'iú»HF5—1<@9- BhSCQ D+)#! 11+2>3!2!7>7>54.#"#"/>ËKƒc9;e‡Kþ G#hüq !K}Z2-Mf9Du_F.\~›—*S}R[—‡€Dþ8!5! êC{|…NCcA 'Ig?ZŽb4qÿð<—HU@RD Bhh[SCSDB@:80/.-%#HH +2#".'763232>54.#7>54.#"#"/>ÙK`73XwCEgF#QЏha‘f< ) 2OpNa—g6$Vm ašk8,Kd9Dv`F.[~œ—)OvLP~^? 7Oe:`¤xD.\‡Y GhF"Ci‚@7aG)B2X|L@_?'Hg@ZŽb4>^‰+@( B\ C D!#+!+#!"&/367!Zé1T2ýg;Tk ý5bá2 þi— -­¾ 'üÏgÿð8‡-@@=+*Bh[Q CSD(#&(""+#!632#".'763232>54.#"'!2ýþ‘‚kg›g3[™Çm:hXI  -HgF[¡yF(TXn‘8»{_þ$6a†P}ÇŠJ#-'%=r¥iBmN,%rÿð‡32@/B[ CSD0.&$ +2#".54>7>;>32>54.#"žT‰b6QÀoY‘f7:Z>Ä Qþ&>B®þš*PsJ^œp=,QtG_›o=M5`‡Rl¶ƒJ7h“\:qwH ýØ*K#?Gþ-DtU0=l•WFrP+DoŽå¡‡$@!BQ C D$'++>7!"&57¡ üÏH. üÎ ‡# úë   >fÿð&—%;OD@A B[SCSD=<'&GE7.54>32'2>54.#"2>54.#"^™m<6`„MmpBx§fWˆ_1)MpH6]E'2XwE2^H+èZ—22@/B[SC D/-%# +".54>32+>74.#"32>MP„^3OЏiU‹c5:V7þH Tê >B°V+NnBV“l=)MnD]•i8Z3\‚Of¯I8fŽVDrosEýÞI&L)BH¾CqQ-9gVClK(Ai†:ÿñZÇ;K°#PX@SCSD@[SDYµ&&&$+74>32#"&4>32#"&:!#7''5f!#7''5M""'55D""'55<ÿeÇ)K@ B ?K°#PX@SCS D@[S DY@ (& "+74632'.54>7#"&4>32#"&<0'+/(8"'$ %/o!#7''5U#2;0)URL  %9J,2<""'55­ Ï5³(+²ý´%(ýB´Aþè  þç  ‚ ä c!@YMQE+!!!!¨; üÆ/:üÅ+GGŒ ®5³(+ 767>7&'&5<>7©üãL%(ýù¾ŠþA    þ~ÿñÛ—'95@2BhfSCSD(&#,$+>32#7>54.#"#"'4>32#"&EQ\4=gK*1LYQ< @9NZL2 9M,>\@( "!""'5 3'&Fa;SvXA<>(¤ª,E>?PhH/K5#*# ûg""!5aÿ 2Wlf@c ^ ?Bh[  [  [OSGYXb`XlYlOMCA=;86,*"  WW +%"&5<7#".54>3232>54.#"3267632#".54>32%2>7.#"IG?ŒL-B*!>ZqˆN1T!';sZ7L„´g\±Ÿ…a7V™Ïz‰é_ oþÿ˜Šè§^JP[­ùŸéÇ¡q=IÒ‰Õ™TD?lT7%AXck4!9*ÿÌX‡ $@!BZ C D# +!#"&'!+3!.'XLeý†ÊLÃbþ>E¯   žþb ‡üzÐ*ƒ5‡*=@: B[S CS D*(" !+3!2# !2654.#%!2>54&#!ƒ®yf”b/,QtIŠ’C¶sòJDºÆ)OsJþÄaŽ\,•›þê‡*OrHD|eI—ybŸp<§ý¨µ¦:]B$H:_zAw~ƒÿðó—/D@A BhfSCSD'%" //+%2>32#".546$32#".#"§ItZA. #.ix‹PzÅ‹KpÅ ›ItaQ&  )N}`„ä¨aBv¥E%+%*/K5X¢âй3Üy,B*( ,5,hÀþîª|Ç‹Jƒ-‡@S CS D!(!&+#!!24.#!!2>-1[ ºgþ$®Ü}ÊLg?u§hþˆ›x„Þ¢Z/}ÚµŽb3‡UœÞ‹y†Hûc¼ƒ?‡ (@%YQ CQ D+!!!!!!4ýWG7 ýÊI¬ üñ®5ýÀPý­R‡ƒ?‡ "@YQ C D+!!!#!4ýWJO ý³Pd®5ý©Sýu‡ƒÿð—9G@D$ Bh[SCSD1/(& 99+%2>7#"504>7!#".546$32#"'.#"»CobZ-0÷k?5o}ŒRÍMnƤM~hV&  '7Kb?ê©]Bz¬A* þ %:'Y¢ãŠ»3Úw,@)'!hÀþí«|ÇŒKƒ!‡ @Z C D+!#!#3!3sdSüØSd®dR(Rd§ýY‡ýk•¥¶‡@ C D+!#3 d­d‡ÿðõ‡'@$Bh CSD#%$+#"&'7>3232>73}Hn‘W6Y+  #1#;lW= xd¹u¬q76%WiÐží‰ &@# B\ C D'(% +3267>;#"&'.+#3^M"(PQý~ÿP þ* TTc­døL ýƒýX f  ýU‰ƒX‡@ CR D+7!!3ðh ý6®cUU‡ƒ<‡!'@$Bh C D!6(+67>;#>7+"'#32- ~E®W•ýŠ þn”W®D”  ä úy¹ ü#àûF‡üƒ!‡@B C D!+2>73#"'#3b ®”W®. ýQ”W®1‡ û0 ±úyÐ-ûG‡ƒÿñq—'@SCSD((($+#".546$324.#"32>qmÂþøš|ÈLnÁ™}ÉLg?v§g‚Þ£\@v¦g„Þ¢[0»þÎÚxY¢â‰º3ÛxY¢ã‹{Å‹KhÂþì¬{ÅŠKgž?‡ /@,[S C D   !+#!2# 32>54&#GFc­TÐÐJˆÀv’Tð^—k:££;ýŇ»©l³HýýSq5a‰U#N î||ÈLnÁ™}ÉLg?v§g‚Þ£\@v¦g„Þ¢[0⼓1þ{ C2Y¢â‰º3ÛxY¢ã‹{Å‹KhÂþì¬{ÅŠKgž3‡!7@4 B[S C D! *!+#!2#"'.# 32>54&#POc­PÍË54.54>32”  ";]FLwS,2QgmgQ2B{°m€°S:((0GgKQ–sDKH‡†‡ @Q C D+!#!7† þ8£c£þ6 ‡TúÍ3T³ÿï‡#@  CSD +%2>73#".5473o]œuL kdl]“Âsg¡o:kdl-W€FH~¬clü”uË–VG|«e,-lü•(Th; ‡@B C D, +32>7>;#O&   GNý:Y‡ ûc00 úy~‡( @#B C D+; +32>7>;2>7>;#.'#N»ÿÞ ÛMý¸Yç ýêY‡ûh))˜ ûh))˜ úyÑ  û/ÿ¾‡@ B C D)"'!+ 32676; #"&'+þ¢Y -°cþŒY þ¦þ  bô“ ýµ 4ýrý§ ýp ‡®‡@ B C D,"+#32>7>;gIcIþƒX+ ÐSGý¹H? ýd! œ ,¡‡$@!Q CQ D+!!7>7!7¡ü28 üA ÐüÙ ‡! û R! ÷RDþùDì'@$[OQE!#+!+32DÚ& »Ì¼ þùó# ùŠ  qÿ¬H¥@kD" +32#"&'q'' €%¥$ú+þùì '@$ B[OQE!#+!!7>;#"5ß(Ûþٻ˻ìù # v ì‡@ Bk D+!+3#"&'&'+\;UC ñ  ðF‡ý™ ´þLÿ¢þð€ÿ1@MQE+!7€ ý+ÏAA‰~—@kD+2#"/Øs5 ©—îþ<ÿósð+^@ BK°%PX@SCS D@SC CSDY@ #!++*' +!#"&5#".54>322>7&#"þ.!&[gr=?^? ,Qp‡šR32#"&'#"32>54&f°]a'`ju32#".#"32>325_[^5VT*!>XnI1OB6 1N;T‘k=AbC5TA1& ¶;L,;j’VO›ŒvV1#2! % Z˜ÇlEvV0")" @ÿóϯ.p@ "BK°%PX@CSCS D@!CSC CSDY@&$.. +!"&5#".54>323%2>7.#"ï!'alv=AbB" ;Ul€IQˆ,I\²þI9rk]$#+€E?mYE/f =Q…_42\‚QLŸ•ƒb9GGKúQ@?t£c N@3Xt‚Š@†–FÿóFñ,=7@42BhSCSD.--=.=*&%,+32>32#".54>32%">54.F8`–ÐŒ‡ˆ5UC3( 1]_f:SX.;WpˆOIe?þòPcC¸€O,,G%)JB;1')•¨$($ 4K07e‘ZK˜ŒzZ4)=GgDq•Q%)0361(Bÿ ™%^µBK°!PX@kSCQD@kYSDY@%%!$%$ ++'&546737>32#"&#"!2j*#Œ~˜ :Wn?8  (-Q?-  ~ü—æw ~UV+ 0 AhK{FÿÅþŽ–ò>Td”@<& CBBK°PX@+ [[CSCSD@.h [[SCSDY@VU^\UdVdQOGD7642*( +#".54>32.5467#".54>32!4&'.#"32>2>54&#"ú(/(#*#@t£dOƒ_4K«b"D#")[69bG(1]ˆW3Z# ¢l 1X)c’a1'Hf>N€[2ø@eF$n]@dE$là7ZKA>@$3016@'?uZ6 =X8HoL(4 #H3$GgDE…h?  Lü¿1F%?T.+C-*FZ 6Wo8hi3Tm9jnf”¯,@)BCSC D%%+33>32#654&#"f°^\(akp9po ^KMS6pi^$A¯ýKuQ*‡2]ë§|.)fj7g’Zýçx—“&@#SCC D +##".54>32Ny]y¦ãüãW !ÿmþ•“(4@1 BSCCSD%#!%%+#"&'7>323267#".54>32JŠ(?W5)JR Чãûž4XA% XQbW !c‚¯0@-B\CC D%(%!+3267>;#"&'.+#rm%œ Rþ9   lP þ¶$=]²¯ü‰ { þ_  þ À þ ¯u‚¯@C D+33u°]±¯úQ^žò5Z@  BK°)PX@SC D@CSC DY@55%&($! +332>32>32#>54&#"#654&#"^x+#NÌqdU']gl5k^ ]K>M1eaW#C]K :JrÁI?æ$þꛫ~v"'PwO'‡} 6_ í©|7Z]-Zˆ\ýÉ|E6TWоýì^Žò!P@ BK°)PX@SC D@CSC DY@ !!%&!+332>32#654&#"^x+")cnt;op ^KMS8rk^$=æ$þàO}W-’ˆ 2[›é§|.)ej9i•]ýòGÿó•ó%,@)SCSD%% +%2>54&#"".54>32²Wf7Ž~:fVE/wPƒ]3Hƒ´lPƒ]3I‚´>[—Ãg ®)Jex†FŸ¯K8j—`yߪe9i˜^yÞ«f2þ ³ò-m@ !BK°)PX@SCSCD@!CSCSCDY@%#--*&!+32>32#"&'"32>54&2¤+# 'amv=AbB" ;UlHQ‰+;ÿ9sj^$#,€E?mYE/fþ F$þÇQ…_42[‚PMŸ•„b9HFþ?t£dþâO@3Xu‚‰A†•<þ sð.6@3#BSCSCD&$..*) +#"&547#".54>322>?&#"Ò6O&Yeo32&#"^x+#D¯c)+.(k«=<æ$þØ£±YÓÇþ!ÿðÞò<=@:<BhfSCSD:8'%" #!+#".#"#"&'7>3232>54.54>32Ç 2J73[C'&?OTO?&3^…Ra‚/ 5R>;aE&&>PSP>&2Z|JT|0i"9K*)8)%4K5@x]8C6" ' 'DY2,<+#2H4:kS144lÿð‰@+g@ &BK°!PX@#jhQCSD@!jh\SDY@ %#(&&+74>7#"5?>;!!32>32#"&¯DƒB ./ þàE=0!3&(r9R]§$9,5) d þ„GýÎ)4!A:**3Zrÿó¡ã"P@ BK°%PX@CT D@C CTDY@ ""'!%+32>73#"&5#"&54>7!KMS7pj^$@^x,)cmt:oo ãý‡/(fk8i“\üO|U-“ˆ 3Z›è§Sªã@BC D+ +3267>;#SIË  GþNã üï,*0 üVxè) @#BC D(!+: +32>76;2>7>;#"'&5+VC† V$“ F BþVD™ þ Bãüï))üè** ü(üØÿÕVã@BC D("(!+3267>; #"&'+sûP Ñ   Tþ“P ë þ² PÏþr q þ0ýí È þQ Vþ ®ã@ BCD,"!++32>7>;ûCÒþõJ Ò ‘ Iþ¼…¾ üú  <ã@QCQ D+!!7>7!7!6ýa/ ý^ ¡ýÖ ›»ü¶K& MKGþùFìD7@4"5B[[OSG<:303)+4𑯎&54>;+";20#".54>ÄD9J\+NoE3:P1 '?.*")"'8#38U:#*#Ó9I;tw<‹-TF3 )5 =vw{A)B0  %BY4D{utÊþ í@QD+3#ÊKKíø³þùìD7@45"B[[OSG<:303)+3"+7>;2>54&54>7.54>54.+"&504>732…D9J\+NoE3:P1 '>/*")"'9"37V:#*#9I;tw<‹-TF3 (6 =vw{A(C0  %BY4D{utP²Àë9@6jk[OSG +2>53#".#"#4>32Ï%=*N 734632#"&«D ) C 5'#7''5þ &-PS\88\SP-ýÚõ'7#'55›ÿà/:K@H%BjhfkSCSD##'# +.54>?>;#".'>32+VŠb4LŽË€# #)V‚.  4N8–KlI* J]l:$ #ò)LmE–i¥r= @o›`לXÏ ð;- ü“"' 1%Ó ¦Q]7lJ…»i—=C@@+ Bh[SCS D%&%##(" +#!>3!#!7>7#7>;>32#"'.#"!aþ%'#/)ü5$=/*°’$ Lz¦fPuQ4(  &;XAN‚`= $ù• þ¾,F9/'@ #4I5W% )] uC%?V0 <.4_ƒOþÖ$ÆúD#79@6! B @ ?WSD42*((+467'7>327'#"&''7.732>54.#"(#š2™-n?>n-š1š%))#™1š-n?>m,›1š$)H+Ib89cJ++Jc98bI+Ÿ>m-š2›%*)$š2™-n?>m-š2š$)($š2š-m?8bI++Ib88cJ++Jc”™‡!8@5 B Z Y C D! +! +!32>7>;!!!!#!7!7!°[þ½M   ®Ný÷[þ‘oþ‘/]/þ‘nþ’hýd##œ üá9v8þ8vÊþ í@YQD+3#3#ÊKKKKíüöþÆü÷Xÿ}b–EYA@>EWM:#BhfWSDCA,*'%!#!+#".#"#"&'763232>54.5467.54>32>54.'K  1I76[B%(BUXUB(^](22]‡Ta…0  4P>>bD$Im€mIdq*42Z}LS{0ýŠ!8IPR$NC2CJL#`M #:N*)>3,-3AR7^Š&"U;Dy\5B6" % 'CY2=S@:GaGUŠ)!V>;lR145ý™&<2+(+&kE(?3*&&+gvÊqm%3K°'PX@ S D@OSGYµ&(($+#".54>32#".54632 V 0"   "1 kÿò–/KaÌ@ BK°PX@4hf[[ SCSDK°PX@4hf[[ SCSD@4hf[[ SCSDYY@ ^\*,*(#&(%" +>32#".54>32#".#"32>4>32#".732>54.#"Y"; t` s@Cw¦c4WJA !;[DSˆa55_Ld•0:L@I!Bh[ WSD21651:2:%#00 +"&=#".54>7>454#"#"&/>32'26? 4cB2&)[‘go'8)   1i?*>)  ö=[+ ’9G D.11!*H7! w/-1A$ 4Y‡a*5)‹KB.)’†·‡%µ%(+77’  Ê ƒ =  Ê ƒ  z þÙþÙ  { z þÙþÙ  ¸TüÂ=K° PX@_MQE@kMQEY´+!#!À<.O$ýÂþ’'c*x@MQE+!!n¬ þRxNqÿò–1GP¾µ<BK°PX@/h  [ [SCSDK°PX@/h  [ [SCSD@/h  [ [SCSDYY@22PNJH2G2F)!**,& +4>32#".732>54.#"#32#"'.#'32654&+q4^„ ¸dd¸ „^44^„ ¹cc¹ „^432#".732>54.#"¡0Qm>?nQ//Qn?>mQ0E$>U00T=$$=T00U>$n>mQ..Qm>>lQ//Ql>0T>$$>T00T?$$?T<P8 <@9jhZMQE  +!!#!7!!!¹2± þO1L1þS®2ýתüVþjGþl”G–üG„˜V/g@ - BK°!PX@h[SD@!h[OQEY@+)%# //+2>3!2!767%>54.#"#"/>Ü(E33C%ë(þ' "=,#/9Z $‰V,?*-LD?!Ôí;>@$-B= `e°|¨V>‘@:BK°PX@,hh[[SD@1hh[[OSGY@8631+*)( >> +2#".'763232>54.#7>54.#"#"/>î(D2UE@A+H^3!3C$  ('0;-!2RH+B;0I2‰T—@kD"++7>3Tè7­ —þîþ Žã$7@4#BCS CD$$&(!&+32673#"50>7#"&'#"&5RcjX¤H][z3K¥]Xn(.¢ãýcmybXïü ;3X[JD*R#þ·yÿB4‡*@'hiS D+##!#".54>34 â»U»þ¸»Vm\“h8E€´n‡Rú óú y-SwKVi;À슷@OSG($+4>32#".À$&&$P&&%%þTV@  BK° PX@^jTD@jjTDY@+232654.'73#"&'764#3:(8"8:'JN2E(&D þî 2* †a73!5% ñ„lQN¶ BK°PX@jjQD@jjMRFY¶$+37#"/733!øžC  é9Rþ‹¹%·ýh5›;²•#)@&WSD## +2#".54>2>54&#"Ì7U;+OpE8V;+Oq7T8RR:U8R•$C]9O[2$B]9O\2ýÞ*MlBYk+NlAYj†¢‡%µ#(+7'&54767&'&54?'&54767&'&54?¨ Í  „¯ Í  „¯† ' ' þ† þ… ' ' þ† Õ`Š(.Q@N" Bh  h   Z \  C D.-('&%$#%"#!" +%3+#7!"5'3+6;37#"/733!>7!án=þºšAü‰ &‹$(û«žC•  é9Rþ‹ùþ°û'ÄÄ"ÔýU d#ýk%w ·ýh5t þ|ÕHŠ9Jb@_D=7Bhh   Z [  CT D JIHGFEB@;:52/- 9 9"" +%+6;2>3!2!7>7%>54.#"#*/>%37#"/733!w &‹$(y(E33C%ë(þ' "=,#/>X ‰ü­žC•  é9Rþ‹ d#ýK,?*-LD?!Ô í;>@$-F9`e %w ·ýh5¤rY_y@vU ! . B  h h  h [  [\ S C D_^SPMKEDCB:820*(YY"#!#+%3+#7!"5'3+6;%2#".'763232>54.#7>54.#"#*/>>7!ó n=þºšAü‘ &‹$(ü—(D2UE@A+H^3!3C$ -%0;-!2RH+E80I2üº þ|ÿáþ”;ó(:5@2BhfSCSD('#,$+#".54>?332>324>32#"&;DP\4;gL+1KZP; >6MXK1!8K)=]B'Ð!""'5à3'%Db>RtS=68%¤«+?99KeG0L5$*$P""!5ÿÿÿÌXê&$ @ÿÿÿÌXê&$ HÿÿÿÌXÍ&$ @ÿÿÿÌX´&$@ÿÿÿÌXÎ&$ @ÿÿÿÌXý&$@ÿ°¡‡:@7BYYQ CS D# +!!!!!!+!!—ý6 ýÕW ýXýßþþMwzú¶ô  5ý¿Pý®R¸þb ‡üz5+ƒþó—Lh@e9? J Bhf hSCSC S  DHF>=750.&$!LL +232654.'7.546$32#".#"32>32#"&'76#3:(8"/r¸FpÅ ›ItaQ&  )N}`„ä¨aBv¥bItZA. #-ds„LJN2E(&D þî 2* r^ Ü…¹3Üy,B*( ,5,hÀþîª|Ç‹J%+%*-I5L73!5% ÿÿƒ?ê&( Gÿÿƒ?ê&( Gÿÿƒ?Í&( Gÿÿƒ?Î&( GÿÿÛê&, ÿÿ¥Åê&, ÿÿˆÍ&, ÿÿ ‰Î&, E`‡!,@)YS CS D!%(!+3!2#!#%4.#!!!!2>MÆRÜ}ÉLlÀþùšþ$TÆ´?u§gþ‡HsþJx„Ý¡Zì›UœÞ‰»þÒÔr®y†Hý¶>ý£c¼ÿÿƒ!´&1ªÿÿƒÿñqê&2 Ìÿÿƒÿñqê&2 ÌÿÿƒÿñqÍ&2 Ìÿÿƒÿñq´&2ÌÿÿƒÿñqÎ&2 Ì‚õ)C ³ (+  ' 7 )þcF6þ¸þZ,¥þ¼9C þ‘þŒ3uþ‰3wp4þŽpOÿ—›Ç!-9b@ 21&%BK°PX@kCSCSD@jkSCSDY·**%(%$+#"&'+7.546$327>;.#"%4&'32>qmÂþøšh­C")ÌJNnÁ™mµEz 4¶DHûw:6%:œ`‚Þ£\"40üÝ9•Z„Þ¢[0»þÎÚx>9²ýQåº3ÛxE?— âQÞþ¤v¾Eç<@hÂþì(oµEü67gÂÿÿ³ÿïê&8 –ÿÿ³ÿïê&8 –ÿÿ³ÿïÍ&8 –ÿÿ³ÿïÎ&8 –ÿÿ‡®ê&< &ÚY‡,@)\[ C D"& +32+#3 32>54&#ÇòÏÑJ‰Àuñ#c­d-Tñ^—k:¢¥iº©l³Hþâ‡þ“ýT54.54>54.#"+'&573>µInJ%4MZM4.EQE.6_ƒM]~0  3L::^B$/HRH/5O]O54T=A{cE p*#Œƒ¡W|š˜-I\.FdL<:A+)3'&7TBL\3C6! % (E]58F0#-B58RC=GZ?D9&Bv¡^üzæv (_«€Kÿÿ<ÿós—&DCTÿÿ<ÿó—&Dv-ÿÿ<ÿós‡&DÛÿÿ<ÿóŒh&Dâÿÿ<ÿóm&Djÿÿ<ÿósº&Dà22ÿðjôGWd¼@?E!BK°)PX@5hh  [ S C SD@?hh  [ S CSC SDY@&YX^]XdYdSQIHCA=;860/'% GG+232>32#"&'#".54>?>54&#"#"/>32>32>7">54&Z7]D&>y5UD3( 0\`g:t[jo08]C%F–ì¦ \[BaF. Q£crt 9µþ¶ŠÆ=0B'=oY< ÆGtY; —Ê{4bó ;Q0:hN0))¬¹$($ 3K1…MjA8W32#".#"32>32#"&'76#3:(8"1OtM&!>XnI1OB6 1N;T‘k=AbC5TA1& 2XVV/ JN2E(&D þî 2* u?iŒSO›ŒvV1#2! % Z˜ÇlEvV0")" 7I,P73!5% ÿÿFÿóF—&HCîÿÿFÿóF—&HvàÿÿFÿóF‡&HÛàÿÿFÿóQm&Hjàÿÿa`—&ÈCâÿÿx'—&ÈvÓÿÿB%‡&ÈÛÓÿÿIDm&ÈjÓIÿô¤y7I5@20B76 @[SD98A?8I9I,*" +&54?.'&54?7#".54>324654&'267.#"ëŒ3vD Q›C¡ €+H4@€½~L^5Ay«i1`TE\U«I¢Ì! +GeDZ`2*Hc. y#0 =3ˆ o(f~–YŸþóÄn9h”[mÁ’U9X=¡æK’ü,ýÿ3dO1H|¥^M{V.ÿÿ^Žj&Qâ±°°++ÿÿGÿó•™&RCë±°°++ÿÿGÿó•™&Rvܱ°°++ÿÿGÿó•‰&RÛܱ°°++ÿÿGÿó•j&Râܱ°°++ÿÿGÿó•o&Rjܱ°°++€Þ3^#+@([YOSG&&&%+!!4>32#"&4>32#"&‰ªüU©%$.&$,X%$.%#-ÂG{&1#%/ýY&1#%/ ÿ¾µ *5u@ 43$#BK°PX@!kCSCSD@!jkSCSDY@,++5,5'%  +"'+7.54>327>;&#"2>54&'ª†YF !&+-Hƒ´lDq-= 2u),I‚´þ‹7GnWh9W‘g9ýúG K`¬5’[yߪe(&SŸ5XyÞ«f™‰WºGY–ÆþAZ—ÅkBn*ýGBÿÿrÿó¡™&XCç±°°++ÿÿrÿó¡™&Xvر°°++ÿÿrÿó¡‰&XÛØ±°°++ÿÿrÿó¡o&Xjر°°++ÿÿVþ ®™&\v±±°°++;þ ¼¯0?@<"BCSCSCD(&00*%+3>32#"&'#"32>54&;Ý]b'`ktC#?mYF/gþ üåN\32[‚PMŸ•„b9HGþE@u¤dþä'6"3Xu‚‰A†•ÿÿVþ ®o&\j±±°°++ÿÌþ®™‡'0L@I-BAhZ C CSD)($"''+2#"&54>7.'!+332>!.'ƒ  Q+BK*6eý†ÊLÃbg7.3*$ü±E¯  ô!B:">6. žþb ‡úy $0<")0 õÐ+,<þ®sð4F³K°%PX@&;B@&;BYK°%PX@*hSC SCSD@.hSC C SCSDY@65><5F6F1/('$"44 +2#"&54>7.5#".54>32#32>2>7&#",  Q+BK-8!&[gr=?^? ,Qp‡šR7!!!!!!#32>X  Q+BK*6ý¤® ýWG7 ýÊI¬ p3+3*$ô!B:">6.‡RýÀPý­R $0<")0 Fþ®FñL]e@bR9@?Bh h SCSCSDNMM]N]IG>=750.LL +2#"&54>7"#".54>3232>32732>">54.C  Q+BK$/ SX.;WpˆOIe?8`–ÐŒ‡ˆ5UC3( /XZ_6)-3+3*$PcC¸€O,,Gô!B:93-7e‘ZK˜ŒzZ4)=G)JB;1')•¨$($ 1H0 $0<")0 ŸDq•Q%)0361(xNã@C D+#Ny]yãüã0‡!@ B CR D+%!!7>?3g” þ~Dh ý6HÄ  ²[cÒ×= Ðý×UMf@ ^åS8¯@BC D+46?37#S ²L]HÀ ·_\ZºÈ Tvý±W2 VüðêVÿÿƒ!ê&1 ªÿÿ^Ž™&Qv±°°++ƒÿó— 46@  BK°PX@*YSCQ  C S DK°PX@4YSCQ  CS C S DK°#PX@*YSCQ  C S DK°%PX@4YSCQ  CS C S D@2YSCQ  CQ C SDYYYY@1/'% (% +!!!!!#".54>324.#"32> ýZG2 ýÍH¥ ý'+qˆVo±}Ce±ï‹W‘rQ,]7g”]wÌ•U8h”\xÌ”T‡Rý¿Pý®R>N{U-WžÞ‡½6Ýz5a‹Vgý±x‰KkÆþè­x‰IjÄ6ÿóÎó8N]S@PT6$Bh S C SDPO:9O]P]FD9N:N42*(" 88 +232>32#"&'#".54>32>2>54.#"">54.É=aD#8^ƃ‹y5UD3( 0\`g:yŸUi~GOtK$M‡¶ix‘:Äý8GrW>(:W;Z’h88Z~H{_? ®Û{-0Jó 9O0%F@80'  ¬¹$($ 3K1™EnL):aE¡ú¬Z‰uvˆüI2Vs€‡?CoO+TšÛˆ6fP0q:k™_:EN'6)ÿÿ'ÿðÆê&6 ÿÿ!ÿðÞ™&Vv†±°°++ÿÿ'ÿð±Í&6 ÿÿ!ÿð÷‰&VÜ…±°°++ÿÿ‡®Î&< &ÿÿ,¡ê&= Fÿÿ <™&]v˜±°°++ÿÿ,¡ä&=Fÿÿ <¡&]ߘ±°°++ÿÿ,¡Í&=Fÿÿ <‰&]Ü—±°°++þ—U˜%SK°PX@ YSCSDK°PX@ YSCSDK°PX@ YSCSDK°PX@ YSCSDK°PX@ YSCSDK°PX@ YSCSDK°PX@ YSCSD@ YSCSDYYYYYYY@%%"" +#763>7'&546737>3#"!Ÿ¿Vw–W%3eWE¿Àä//ì¶"5dWE1f üîbŒ\+/FqS  ÀÀµ1 FqR¾Eo‘R‡ @Bk D' +#"/+73R= { ¤AçS‘ ¤ ¤ö‘r‡ @Bk D' +32?6;#A { £ ?çS‡¥ ¥ öÿÿräq$q¢a‡ @W D+".547332>73e5K.C 3&,?+C#=Y5H*4%3B&2ZD'2ïäŸ@SD($+#".54>32ä    F!!Æsº=K°'PX@WSD@[OSGYµ$&($+4>32#".732654&#"Æ/?##?//?##?/:@32@@23@$<++<$$;,,;$2@@22@@7þ®\ +@(B@jSD+2#"&54>732>F  Q+BK1>-3+3*$ô!B:%C:/ $0<")0 |»nhQK°PX@WS D@[OSGY@ +2673#".#"#>32Ø(.9(6 1-+&0:)5 2,*ÿ7/$>.!(!:-$?-!'!É㗠#@ SD   #++7>3!+7>3çÕ )© =ô-Å —þ îþîÿðÿöYã\µBK°1PX@SCS D@SC CSDY@!$## ++#!#"&'7632327#7>3Yq]qþVVsb0  ‚V·ã$üdœý;ns & Ä! ld@MQE+!!—Õý+dEBd@MQE+!!—« ûVdEÌ#ϳ(+&5467ã\H):#7;X B 1v?/*š\ó(+'&5467>54&'&547E\H*8 Ã7;X¡A 1v?, ÿÏ®³(+7'&5467>54&'&547¸\H*8 ®7;X¡A 1v?,Ì#‡Ï)µ(+&5467&5467ã\H):¼\H):#7;X B 1v?/*7;X B 1v?/*©eÃ+µ(+'&5467>54&'&547%'&5467>54&'&547T\H*8 8\H*8 Ã7;X¡A 1v?,7;X¡A 1v?,ÿÑÿ®+µ(+7'&5467>54&'&547%'&5467>54&'&547|\H*8 8\H*8 ®7;X¡A 1v?,7;X¡A 1v?,Ãþ–ͤ"YK°PX@#CSCSCD@OCSCDY@ $!#"+>3>32>72! #"'!ÈFK/  GL¢Fþ =O  h=þ¢§Úþ& þý éüfþ–ͤ6•µ* BK°PX@6   `   [CSCSC  D@1   `O   [CSC  DY@661.,+)'$##!##+%!7>3>32>72!!#.'#"'"&5467ÊXþ¡FK/  GL¢Fþ¡Z_FŸK,  G&OOM$²Õ Úþ& ý+þ' Ù 91ƒ|@OSG($+4>32#".9.Pi<=lP..Pl=32#"&%4>32#"&%4>32#"&:!#7''5ë!""'5ê!#7''5M""'55'""!5'""'55}ÿï–'0DXl€…K°PX@/[   [SC C  S D@3[   [ CSC C  S DY@}{sqig_]US((%"&((($+#".54>324.#"32>>;+#".54>324.#"32>%#".54>324.#"32>Â7Zt>3W?#4XvA3V?$J.>$0XC'-?$0WC(XAû«B«7Zt>3W>#4XuA3V?$J.=$0XC(.?$0WB(ä6Zt?3W>#4XuA3V?$J.=$0WC'->$0WC'qgœh4&ImGgœi5&JmI32#".#"!#!!#!32>32#".5#73>7#LžqžÆr@jWI %,@W:_¤„`eý¶ þ4aŒY>cM:)  )^l|Fn«u<¢˜^…Ò“N,A*$$@}¶u *X0 z¼€C%+% !.K5Q˜Ù‡9/Y*¬H‡&9@6BhiS  D&&!4) +>7>;#7+"'#32'###7½î 6E<6 õ  754.#"!"504>7!7.54>32!à,d¨yDCv ]pË›[0Y|L+þ3¢N‚]4l¹ô‡rÃPKƒ±g¢hY‰»yj¢n7I׎V‰hFþ˜ ßRwš\šó¨YF„½v}Í›hß3Wÿôÿ™/AC@@5"Bh[SCSD10;90A1A#)**$+>32#".54>32>54&#"#"'2>7.#"Ï%BBD&EjH& k¡Ô‚ClM)&EawŠK1WG4ol-G6% NKˆv_!7V?f£r=t=# 5d’^AIÃþ²ö‹,SvKRœŠtT/7T:N¥Vœ£û&F…¾x4dN0`š¿`y…ÿÓi‡ @ B CR D+)3!.'iûjÍYýjŠþâ‡úÉ%&¶þ w‡ $@!Q CD +##!##7w ÓÏ_Îý¬Ï_ÏÒ ‡Pùi—ùi—Pÿòþ ‡*@' BQ CQD+!!!767 &5467 üIîýRµ û¹Ìýü‡RüìüëR$2- ¸{üÂ@MQE+!!À< üÅÂGES¸"@Bj[ D+%!+!##"&5467!267>;RM¶è9 ¤ 7³ ýß)#(é L¶ƒ';OL@IK-B[  O  SG=<)(GE32>32%2>7.#"!2>54.#"3O?3"HPX30T>$3Un<3O?3"GPX31U?$2Upý€(IDA -4@(-P=$-<m0Q;"-<$(ICA!,5?'BV..VB'$C`32#"&#"#"&'7>3232>7ëEZl;$4  `Ž"üKas@;  6[J7>W‚U+ , ƒûÚdZ) ' GrQÊ¥êœ7d@a!0"/B[[[ O SG42+)&$77  +2>7#".#"'>322>7#".#"'>3243-% #i83a]\.3,% #h<4a][2-& #h84a]\/3,$ $h<5a]Z( 8+.$,$ 6-/$,$þÅ 8*/$,$ 6-0%+% Ô pkK° PX@)^_ ZMQE@'jk ZMQEY@  +!3!!!!#!7!7!Ïá–F–þ͇œ þF˜E—þÆZ†þ>c þóGñGþðGñgPÓO@@MQE+!!¸ý¹&)$ý@Aî ýÙv> þñ  þð xýèIuP³Q@ @MQE+%!7!7>767.'&5<>7cý îAüåG&)$ýüÀPIþŠ>    þˆÿfæ"@ BMQE+3 # >7 .'’B’þnBþÁL  Mþ³  ¦@üÀüÀ@ýS­­ÿŠþ ~Ü@ja+3vòòþ Ðø0@ÿT¢%k@ BK°!PX@"kSCQC D@ k[SC DY@ !&%$ +!#!+'&5737>32#"&#"8w^nþFl*"Œ~› Dl‘X%$  ,"’²Äü<}ü—æv *U\’e6. Ÿ¦@ÿºŸ$Ö@ BK°!PX@$kSC QC DK°%PX@"k [SC DK°)PX@&k [CSC D@-hk [CSC DYYY@$$!!%$ ++'&5737>32;#.#"!3l*"Œ~› Bj‘YE€94¯]¥/h0EqU7 ~ü–æv *BX–m> únL /X~NBFSþŒüÿ¥-K°1PX@ jD@ jaY@ +#ün;N[þç{Õê @ja  +2#"&/ç ·I  îê ÎÕš6ƒÎ @OSG$$$"+#"&54632#"&546324/../O.--.,, -- ,, --Œ6¢p@MQE+!!“ ýóp:,¿ê @ja #++7>3¿þÝ  IêêÕÎ ‚ŠÍ@Bja+ +#"&/.'+73ŠG†§GñWƒƒÇŸ§Í@Bja+ +326?>;#ŸG†§GñW̓ƒÇÂø‡Í(@%jOSG +"&547332673Œgc>DNXU? {øXT9AQHcr@8íä@OSG($+#".54>32í  ãÀ1ý!@[OSG$&($+4>32#".732654&#"ã-<"">-->""<-5?32@@23?^";**;"#:**:#2@@22@@¨–´1@.[OSG +2673#".#"#>32&/4&4 3/-&/7'4 4.-O7+"<, & 9*"<, % âå +@(OSG   #++7>3!+7>3Ï -£Zï3¾åϿϿ„¸± @kD  +2+¤ $U± !;XB - ¯M _<õ ÐÊ“^pÍÓ¡ãÿ þ ý ¶þV ÿ þÖÐö/‚T£¦çˆPˆr†}úM™ç(œ(ਈ€‹?dc‹:”ÿ¶ˆZˆúˆFˆqˆ>ˆgˆˆåˆfˆè‹:‹<+­ˆ +Œ­àaœÿÌ›ƒóƒvƒ?ƒƒ`ƒpƒ&¥BÀž­ƒ‰ƒnƒ¹ƒFž¸ƒŠžÂ'5‡E³§Tÿ¾S‡,(D“q(ˆÖÿ¢Ô<ûf]Hý@F8BqÿÅfÇx¸ÿmxcÀu^^ÜGñ2Ô< ^!’lþr‡SSVVÿÕ‡VF (GXÊ(ˆP‚T«ˆ›ˆˆÆˆ”XÊšXvMkk›2’ˆ¸dcMqr÷¡ˆ<––°åðyÀ–ñ±›2ÃÕÃÕä­ÿáœÿÌœÿÌœÿÌœÿÌœÿÌœÿÌ¢ÿ°óƒ?ƒ?ƒ?ƒ?ƒ&&¥&ˆ& ©Enƒ¹ƒ¹ƒ¹ƒ¹ƒ¹ƒˆ‚¸OE³E³E³E³S‡FÚ;Ô<Ô<Ô<Ô<Ô<Ô<–2]HFFFFÇaÇxÇBÇIÞI^ÜGÜGÜGÜGÜGˆ€Ü þrþrþrþr‡Vú;‡VœÿÌÏ< ƒ]H?ƒFÇxÖ02Snƒ^ƒú6Â'!Â'!S‡,F ,F ,F ˆor¢2Æ7|Ã<ÿðå»nÌnš… gÌg©gÿÑ,Ã,fˆ9`: }E’EÏÿ ˆ)!¬l(+WµÿÓT¶èÿòˆ¸bEçLÇÿeˆÊˆ +g+uˆÿŠÛ@@S{šŒ,‚ŸÂ@ã¨âgîP` à ÿ[ÿjÿyÿjÿ[$ÿ[9>:A</?>Dÿ«Fÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿyoÿyyÿy}ÿy‚ÿ[ƒÿ[„ÿ[…ÿ[†ÿ[‡ÿ[ˆÿ[Ÿ/¢ÿ«£ÿ«¤ÿ«¥ÿ«¦ÿ«§ÿ«¨ÿ«©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«Âÿ[Ãÿ«Åÿ«Çÿ«Îÿ«Ó/åÿyæÿyéÿjìÿjïÿyðÿjòÿyóÿyùÿ[ ÿ[ ÿj ÿy ÿj ÿ[ $ÿ[ 9> :A </ ?> Dÿ« Fÿ« Gÿ« Hÿ« Rÿ« Tÿ« mÿy oÿy yÿy }ÿy ‚ÿ[ ƒÿ[ „ÿ[ …ÿ[ †ÿ[ ‡ÿ[ ˆÿ[ Ÿ/ ¢ÿ« £ÿ« ¤ÿ« ¥ÿ« ¦ÿ« §ÿ« ¨ÿ« ©ÿ« ªÿ« «ÿ« ¬ÿ« ­ÿ« ²ÿ« ´ÿ« µÿ« ¶ÿ« ·ÿ« ¸ÿ« ºÿ« Âÿ[ Ãÿ« Åÿ« Çÿ« Îÿ« Ó/ åÿy æÿy éÿj ìÿj ïÿy ðÿj òÿy óÿy ùÿ[ #ÿØ &ÿØ *ÿØ 2ÿØ 4ÿØ Dÿç Fÿç Gÿç Hÿç Rÿç Tÿç kÿØ pÿØ ‰ÿØ ”ÿØ •ÿØ –ÿØ —ÿØ ˜ÿØ šÿØ ¢ÿç £ÿç ¤ÿç ¥ÿç ¦ÿç §ÿç ¨ÿç ©ÿç ªÿç «ÿç ¬ÿç ­ÿç ²ÿç ´ÿç µÿç ¶ÿç ·ÿç ¸ÿç ºÿç Ãÿç ÄÿØ Åÿç Çÿç ÍÿØ Îÿç ÿ[ ÿj ÿy ÿj ÿ[ $ÿ[ 9> :A </ ?> Dÿ« Fÿ« Gÿ« Hÿ« Rÿ« Tÿ« mÿy oÿy yÿy }ÿy ‚ÿ[ ƒÿ[ „ÿ[ …ÿ[ †ÿ[ ‡ÿ[ ˆÿ[ Ÿ/ ¢ÿ« £ÿ« ¤ÿ« ¥ÿ« ¦ÿ« §ÿ« ¨ÿ« ©ÿ« ªÿ« «ÿ« ¬ÿ« ­ÿ« ²ÿ« ´ÿ« µÿ« ¶ÿ« ·ÿ« ¸ÿ« ºÿ« Âÿ[ Ãÿ« Åÿ« Çÿ« Îÿ« Ó/ åÿy æÿy éÿj ìÿj ïÿy ðÿj òÿy óÿy ùÿ[ÿ= ÿ= ÿ=ÿI#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿA9ÿ=:ÿm<ÿd?ÿ=Yÿ~ZÿÉ\ÿkÿÆlÿ=mÿIoÿIpÿÆrÿ=yÿI|ÿ=}ÿI‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿd¿ÿ~Áÿ~ÄÿÆÍÿÆÓÿdåÿIæÿIçÿ=èÿ=êÿ=ëÿ=ïÿIòÿIóÿIÿy ÿÝ ÿy ÿyÿjÿjÿÝ$ÿÝ7ÿG9ÿ’;ÿº<ÿQ=ÿË?ÿ’lÿyrÿy|ÿy‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿQÂÿÝÓÿQÔÿËÖÿËØÿËçÿyèÿyéÿjêÿyëÿyìÿjðÿjùÿÝÿ= ÿ= ÿ=ÿI#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿA9ÿ=:ÿm<ÿd?ÿ=Yÿ~ZÿÉ\ÿkÿÆlÿ=mÿIoÿIpÿÆrÿ=yÿI|ÿ=}ÿI‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿd¿ÿ~Áÿ~ÄÿÆÍÿÆÓÿdåÿIæÿIçÿ=èÿ=êÿ=ëÿ=ïÿIòÿIóÿI> ÿ« > >ÿGÿ’ÿGÿ«ÿ­ÿ­"F#ÿØ$ÿ«&ÿØ*ÿØ-ÿo2ÿØ4ÿØDÿ™Fÿ™Gÿ™Hÿ™Pÿ­Qÿ­Rÿ™Sÿ­Tÿ™Uÿ­Vÿ¡Xÿ­YÿäZÿä\ÿä]ÿÉkÿØl>mÿ’oÿ’pÿØr>tPuPwÿ­yÿ’{P|>}ÿ’‚ÿ«ƒÿ«„ÿ«…ÿ«†ÿ«‡ÿ«ˆÿ«‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿ™£ÿ™¤ÿ™¥ÿ™¦ÿ™§ÿ™¨ÿ™©ÿ™ªÿ™«ÿ™¬ÿ™­ÿ™²ÿ™³ÿ­´ÿ™µÿ™¶ÿ™·ÿ™¸ÿ™ºÿ™»ÿ­¼ÿ­½ÿ­¾ÿ­¿ÿäÁÿäÂÿ«Ãÿ™ÄÿØÅÿ™Çÿ™Ìÿ­ÍÿØÎÿ™Ðÿ¡Òÿ¡ÕÿÉ×ÿÉÙÿÉåÿ’æÿ’ç>è>éÿGê>ë>ìÿGïÿ’ðÿGòÿ’óÿ’ùÿ«#ÿÆ# ÿä# ÿÆ# ÿØ# ÿÆ#ÿä#$ÿä#7ÿˆ#9ÿÓ#;ÿÐ#<ÿ°#=ÿµ#?ÿÓ#@ÿØ#`ÿØ#lÿÆ#rÿÆ#|ÿÆ#‚ÿä#ƒÿä#„ÿä#…ÿä#†ÿä#‡ÿä#ˆÿä#Ÿÿ°#Âÿä#Óÿ°#Ôÿµ#Öÿµ#Øÿµ#çÿÆ#èÿÆ#êÿÆ#ëÿÆ#ùÿä$ÿV$ ÿV$ ÿV$ÿÝ$#ÿß$&ÿß$*ÿß$-2$2ÿß$4ÿß$7ÿ$8ÿØ$9ÿ«$:ÿÄ$<ÿy$?ÿ«$WÿÕ$Yÿß$Zÿñ$\ÿß$kÿß$lÿV$mÿÝ$oÿÝ$pÿß$rÿV$tÿ`$uÿ`$yÿÝ${ÿ`$|ÿV$}ÿÝ$‰ÿß$”ÿß$•ÿß$–ÿß$—ÿß$˜ÿß$šÿß$›ÿØ$œÿØ$ÿØ$žÿØ$Ÿÿy$¿ÿß$Áÿß$Äÿß$Íÿß$Óÿy$åÿÝ$æÿÝ$çÿV$èÿV$êÿV$ëÿV$ïÿÝ$òÿÝ$óÿÝ&ÿo&mÿo&oÿo&yÿo&}ÿo&åÿo&æÿo&ïÿo&òÿo&óÿo'ÿÆ' ÿä' ÿÆ' ÿØ' ÿÆ'ÿä'$ÿä'7ÿˆ'9ÿÓ';ÿÐ'<ÿ°'=ÿµ'?ÿÓ'@ÿØ'`ÿØ'lÿÆ'rÿÆ'|ÿÆ'‚ÿä'ƒÿä'„ÿä'…ÿä'†ÿä'‡ÿä'ˆÿä'Ÿÿ°'Âÿä'Óÿ°'Ôÿµ'Öÿµ'Øÿµ'çÿÆ'èÿÆ'êÿÆ'ëÿÆ'ùÿä) ÿ)ÿL)ÿL)ÿ)ÿÄ)ÿÄ)")$ÿ)-ÿ.)Dÿµ)Fÿµ)Gÿµ)Hÿµ)PÿÄ)QÿÄ)Rÿµ)SÿÄ)Tÿµ)UÿÄ)XÿÄ)wÿÄ)‚ÿ)ƒÿ)„ÿ)…ÿ)†ÿ)‡ÿ)ˆÿ)¢ÿµ)£ÿµ)¤ÿµ)¥ÿµ)¦ÿµ)§ÿµ)¨ÿµ)©ÿµ)ªÿµ)«ÿµ)¬ÿµ)­ÿµ)²ÿµ)³ÿÄ)´ÿµ)µÿµ)¶ÿµ)·ÿµ)¸ÿµ)ºÿµ)»ÿÄ)¼ÿÄ)½ÿÄ)¾ÿÄ)Âÿ)Ãÿµ)Åÿµ)Çÿµ)ÌÿÄ)Îÿµ)éÿL)ìÿL)ðÿL)ùÿ- ÿØ-ÿØ-$ÿØ-‚ÿØ-ƒÿØ-„ÿØ-…ÿØ-†ÿØ-‡ÿØ-ˆÿØ-ÂÿØ-ùÿØ.. . .ÿº.#ÿ¦.&ÿ¦.*ÿ¦.2ÿ¦.4ÿ¦.IÿÓ.Wÿ¡.YÿÎ.ZÿÕ.\ÿÎ.kÿ¦.l.mÿº.oÿº.pÿ¦.r.yÿº.|.}ÿº.‰ÿ¦.”ÿ¦.•ÿ¦.–ÿ¦.—ÿ¦.˜ÿ¦.šÿ¦.¿ÿÎ.ÁÿÎ.Äÿ¦.Íÿ¦.åÿº.æÿº.ç.è.ê.ë.ïÿº.òÿº.óÿº/þè/ þè/ þè/ÿ$/#ÿ«/&ÿ«/*ÿ«/2ÿ«/4ÿ«/7ÿ8/9ÿV/:ÿy/<ÿ=/?ÿV/Yÿz/Zÿ­/\ÿz/kÿ«/lþè/mÿ$/oÿ$/pÿ«/rþè/tÿ=/uÿ=/yÿ$/{ÿ=/|þè/}ÿ$/‰ÿ«/”ÿ«/•ÿ«/–ÿ«/—ÿ«/˜ÿ«/šÿ«/Ÿÿ=/¿ÿz/Áÿz/Äÿ«/Íÿ«/Óÿ=/åÿ$/æÿ$/çþè/èþè/êþè/ëþè/ïÿ$/òÿ$/óÿ$2ÿÆ2 ÿä2 ÿÆ2 ÿØ2 ÿÆ2ÿä2$ÿä27ÿˆ29ÿÓ2;ÿÐ2<ÿ°2=ÿµ2?ÿÓ2@ÿØ2`ÿØ2lÿÆ2rÿÆ2|ÿÆ2‚ÿä2ƒÿä2„ÿä2…ÿä2†ÿä2‡ÿä2ˆÿä2Ÿÿ°2Âÿä2Óÿ°2Ôÿµ2Öÿµ2Øÿµ2çÿÆ2èÿÆ2êÿÆ2ëÿÆ2ùÿä3 ÿŠ3ÿ=3ÿ=3ÿŠ3$ÿŠ3-ÿV3Dÿâ3Fÿâ3Gÿâ3Hÿâ3Rÿâ3Tÿâ3‚ÿŠ3ƒÿŠ3„ÿŠ3…ÿŠ3†ÿŠ3‡ÿŠ3ˆÿŠ3¢ÿâ3£ÿâ3¤ÿâ3¥ÿâ3¦ÿâ3§ÿâ3¨ÿâ3©ÿâ3ªÿâ3«ÿâ3¬ÿâ3­ÿâ3²ÿâ3´ÿâ3µÿâ3¶ÿâ3·ÿâ3¸ÿâ3ºÿâ3ÂÿŠ3Ãÿâ3Åÿâ3Çÿâ3Îÿâ3éÿ=3ìÿ=3ðÿ=3ùÿŠ4ÿÆ4 ÿä4 ÿÆ4 ÿØ4 ÿÆ4ÿä4$ÿä47ÿˆ49ÿÓ4;ÿÐ4<ÿ°4=ÿµ4?ÿÓ4@ÿØ4`ÿØ4lÿÆ4rÿÆ4|ÿÆ4‚ÿä4ƒÿä4„ÿä4…ÿä4†ÿä4‡ÿä4ˆÿä4Ÿÿ°4Âÿä4Óÿ°4Ôÿµ4Öÿµ4Øÿµ4çÿÆ4èÿÆ4êÿÆ4ëÿÆ4ùÿä5#ÿØ5&ÿØ5*ÿØ52ÿØ54ÿØ57ÿÓ58ÿâ5kÿØ5pÿØ5‰ÿØ5”ÿØ5•ÿØ5–ÿØ5—ÿØ5˜ÿØ5šÿØ5›ÿâ5œÿâ5ÿâ5žÿâ5ÄÿØ5ÍÿØ7 ÿ7ÿL7ÿL7ÿL7ÿ7ÿ]7ÿ]7"-7#ÿˆ7$ÿ7&ÿˆ7*ÿˆ7-ÿ872ÿˆ74ÿˆ7Dÿ)7Fÿ)7Gÿ)7Hÿ)7JÿA7Pÿ]7Qÿ]7Rÿ)7Sÿ]7Tÿ)7Uÿ]7VÿF7Xÿ]7YÿQ7Zÿy7[ÿb7\ÿL7]ÿe7kÿˆ7mÿL7oÿL7pÿˆ7wÿ]7yÿL7}ÿL7‚ÿ7ƒÿ7„ÿ7…ÿ7†ÿ7‡ÿ7ˆÿ7‰ÿˆ7”ÿˆ7•ÿˆ7–ÿˆ7—ÿˆ7˜ÿˆ7šÿˆ7¢ÿ)7£ÿ)7¤ÿ)7¥ÿ)7¦ÿ)7§ÿ)7¨ÿ)7©ÿ)7ªÿ)7«ÿ)7¬ÿ)7­ÿ)7²ÿ)7³ÿ]7´ÿ)7µÿ)7¶ÿ)7·ÿ)7¸ÿ)7ºÿ)7»ÿ]7¼ÿ]7½ÿ]7¾ÿ]7¿ÿQ7ÁÿQ7Âÿ7Ãÿ)7Äÿˆ7Åÿ)7Çÿ)7Ìÿ]7Íÿˆ7Îÿ)7ÐÿF7ÒÿF7Õÿe7×ÿe7Ùÿe7åÿL7æÿL7éÿL7ìÿL7ïÿL7ðÿL7òÿL7óÿL7ùÿ8 ÿØ8ÿØ8$ÿØ8‚ÿØ8ƒÿØ8„ÿØ8…ÿØ8†ÿØ8‡ÿØ8ˆÿØ8ÂÿØ8ùÿØ9>9 ÿ«9 >9 >9ÿG9ÿ’9ÿG9ÿ«9ÿ­9ÿ­9"F9#ÿØ9$ÿ«9&ÿØ9*ÿØ9-ÿo92ÿØ94ÿØ9Dÿ™9Fÿ™9Gÿ™9Hÿ™9Pÿ­9Qÿ­9Rÿ™9Sÿ­9Tÿ™9Uÿ­9Vÿ¡9Xÿ­9Yÿä9Zÿä9\ÿä9]ÿÉ9kÿØ9l>9mÿ’9oÿ’9pÿØ9r>9tP9uP9wÿ­9yÿ’9{P9|>9}ÿ’9‚ÿ«9ƒÿ«9„ÿ«9…ÿ«9†ÿ«9‡ÿ«9ˆÿ«9‰ÿØ9”ÿØ9•ÿØ9–ÿØ9—ÿØ9˜ÿØ9šÿØ9¢ÿ™9£ÿ™9¤ÿ™9¥ÿ™9¦ÿ™9§ÿ™9¨ÿ™9©ÿ™9ªÿ™9«ÿ™9¬ÿ™9­ÿ™9²ÿ™9³ÿ­9´ÿ™9µÿ™9¶ÿ™9·ÿ™9¸ÿ™9ºÿ™9»ÿ­9¼ÿ­9½ÿ­9¾ÿ­9¿ÿä9Áÿä9Âÿ«9Ãÿ™9ÄÿØ9Åÿ™9Çÿ™9Ìÿ­9ÍÿØ9Îÿ™9Ðÿ¡9Òÿ¡9ÕÿÉ9×ÿÉ9ÙÿÉ9åÿ’9æÿ’9ç>9è>9éÿG9ê>9ë>9ìÿG9ïÿ’9ðÿG9òÿ’9óÿ’9ùÿ«:F: ÿÉ: F: F:ÿœ:ÿœ:ÿÉ:ÿÆ:ÿÆ:$ÿÉ:-ÿ¦:Dÿª:Fÿª:Gÿª:Hÿª:Jÿ´:PÿÆ:QÿÆ:Rÿª:SÿÆ:Tÿª:UÿÆ:Vÿ²:XÿÆ:lF:rF:t<:u<:wÿÆ:{<:|F:‚ÿÉ:ƒÿÉ:„ÿÉ:…ÿÉ:†ÿÉ:‡ÿÉ:ˆÿÉ:¢ÿª:£ÿª:¤ÿª:¥ÿª:¦ÿª:§ÿª:¨ÿª:©ÿª:ªÿª:«ÿª:¬ÿª:­ÿª:²ÿª:³ÿÆ:´ÿª:µÿª:¶ÿª:·ÿª:¸ÿª:ºÿª:»ÿÆ:¼ÿÆ:½ÿÆ:¾ÿÆ:ÂÿÉ:Ãÿª:Åÿª:Çÿª:ÌÿÆ:Îÿª:Ðÿ²:Òÿ²:çF:èF:éÿœ:êF:ëF:ìÿœ:ðÿœ:ùÿÉ;; ; ;ÿº;#ÿ¦;&ÿ¦;*ÿ¦;2ÿ¦;4ÿ¦;IÿÓ;Wÿ¡;YÿÎ;ZÿÕ;\ÿÎ;kÿ¦;l;mÿº;oÿº;pÿ¦;r;yÿº;|;}ÿº;‰ÿ¦;”ÿ¦;•ÿ¦;–ÿ¦;—ÿ¦;˜ÿ¦;šÿ¦;¿ÿÎ;ÁÿÎ;Äÿ¦;Íÿ¦;åÿº;æÿº;ç;è;ê;ë;ïÿº;òÿº;óÿº<4< ÿ~< 4< 4<ÿ[<ÿV<ÿ[<ÿ~<ÿŠ<ÿŠ<"2<#ÿµ<$ÿ~<&ÿµ<*ÿµ<-ÿ8<2ÿµ<4ÿµ<DÿG<FÿG<GÿG<HÿG<Jÿ_<PÿŠ<QÿŠ<RÿG<SÿŠ<TÿG<UÿŠ<VÿG<XÿŠ<]ÿ¯<kÿµ<l4<mÿV<oÿV<pÿµ<r4<t2<u2<wÿŠ<yÿV<{2<|4<}ÿV<‚ÿ~<ƒÿ~<„ÿ~<…ÿ~<†ÿ~<‡ÿ~<ˆÿ~<‰ÿµ<”ÿµ<•ÿµ<–ÿµ<—ÿµ<˜ÿµ<šÿµ<¢ÿG<£ÿG<¤ÿG<¥ÿG<¦ÿG<§ÿG<¨ÿG<©ÿG<ªÿG<«ÿG<¬ÿG<­ÿG<²ÿG<³ÿŠ<´ÿG<µÿG<¶ÿG<·ÿG<¸ÿG<ºÿG<»ÿŠ<¼ÿŠ<½ÿŠ<¾ÿŠ<Âÿ~<ÃÿG<Äÿµ<ÅÿG<ÇÿG<ÌÿŠ<Íÿµ<ÎÿG<ÐÿG<ÒÿG<Õÿ¯<×ÿ¯<Ùÿ¯<åÿV<æÿV<ç4<è4<éÿ[<ê4<ë4<ìÿ[<ïÿV<ðÿ[<òÿV<óÿV<ùÿ~=ÿ¨=" =#ÿº=&ÿº=*ÿº=2ÿº=4ÿº=kÿº=mÿ¨=oÿ¨=pÿº=yÿ¨=}ÿ¨=‰ÿº=”ÿº=•ÿº=–ÿº=—ÿº=˜ÿº=šÿº=Äÿº=Íÿº=åÿ¨=æÿ¨=ïÿ¨=òÿ¨=óÿ¨>#ÿØ>&ÿØ>*ÿØ>2ÿØ>4ÿØ>Dÿç>Fÿç>Gÿç>Hÿç>Rÿç>Tÿç>kÿØ>pÿØ>‰ÿØ>”ÿØ>•ÿØ>–ÿØ>—ÿØ>˜ÿØ>šÿØ>¢ÿç>£ÿç>¤ÿç>¥ÿç>¦ÿç>§ÿç>¨ÿç>©ÿç>ªÿç>«ÿç>¬ÿç>­ÿç>²ÿç>´ÿç>µÿç>¶ÿç>·ÿç>¸ÿç>ºÿç>Ãÿç>ÄÿØ>Åÿç>Çÿç>ÍÿØ>Îÿç?ÿV? ÿV? ÿV?ÿÝ?#ÿß?&ÿß?*ÿß?-2?2ÿß?4ÿß?7ÿ?8ÿØ?9ÿ«?:ÿÄ?<ÿy??ÿ«?WÿÕ?Yÿß?Zÿñ?\ÿß?kÿß?lÿV?mÿÝ?oÿÝ?pÿß?rÿV?tÿ`?uÿ`?yÿÝ?{ÿ`?|ÿV?}ÿÝ?‰ÿß?”ÿß?•ÿß?–ÿß?—ÿß?˜ÿß?šÿß?›ÿØ?œÿØ?ÿØ?žÿØ?Ÿÿy?¿ÿß?Áÿß?Äÿß?Íÿß?Óÿy?åÿÝ?æÿÝ?çÿV?èÿV?êÿV?ëÿV?ïÿÝ?òÿÝ?óÿÝEÿ°E ÿ°E ÿçE ÿ°E@ÿçE[ÿÓE`ÿçElÿ°Erÿ°E|ÿ°Eçÿ°Eèÿ°Eêÿ°Eëÿ°Hÿ°H ÿ°H ÿçH ÿ°H@ÿçH[ÿÓH`ÿçHlÿ°Hrÿ°H|ÿ°Hçÿ°Hèÿ°Hêÿ°Hëÿ°IKI KI KIÿyIÿyIlKIrKItdIudI{dI|KIçKIèKIéÿyIêKIëKIìÿyIðÿyKÿÄK ÿÄK ÿÄKYÿçK\ÿâKlÿÄKrÿÄKtÿ°Kuÿ°K{ÿ°K|ÿÄK¿ÿçKÁÿçKçÿÄKèÿÄKêÿÄKëÿÄNDÿÓNFÿÓNGÿÓNHÿÓNRÿÓNTÿÓN¢ÿÓN£ÿÓN¤ÿÓN¥ÿÓN¦ÿÓN§ÿÓN¨ÿÓN©ÿÓNªÿÓN«ÿÓN¬ÿÓN­ÿÓN²ÿÓN´ÿÓNµÿÓN¶ÿÓN·ÿÓN¸ÿÓNºÿÓNÃÿÓNÅÿÓNÇÿÓNÎÿÓPÿÄP ÿÄP ÿÄPYÿçP\ÿâPlÿÄPrÿÄPtÿ°Puÿ°P{ÿ°P|ÿÄP¿ÿçPÁÿçPçÿÄPèÿÄPêÿÄPëÿÄQÿÄQ ÿÄQ ÿÄQYÿçQ\ÿâQlÿÄQrÿÄQtÿ°Quÿ°Q{ÿ°Q|ÿÄQ¿ÿçQÁÿçQçÿÄQèÿÄQêÿÄQëÿÄRÿ°R ÿ°R ÿçR ÿ°R@ÿçR[ÿÓR`ÿçRlÿ°Rrÿ°R|ÿ°Rçÿ°Rèÿ°Rêÿ°Rëÿ°Sÿ°S ÿ°S ÿçS ÿ°S@ÿçS[ÿÓS`ÿçSlÿ°Srÿ°S|ÿ°Sçÿ°Sèÿ°Sêÿ°Sëÿ°UÿyUÿyUDÿØUFÿØUGÿØUHÿØURÿØUTÿØU¢ÿØU£ÿØU¤ÿØU¥ÿØU¦ÿØU§ÿØU¨ÿØU©ÿØUªÿØU«ÿØU¬ÿØU­ÿØU²ÿØU´ÿØUµÿØU¶ÿØU·ÿØU¸ÿØUºÿØUÃÿØUÅÿØUÇÿØUÎÿØUéÿyUìÿyUðÿyY ÿßYÿ~Yÿ~YÿßY$ÿßYDÿîYFÿîYGÿîYHÿîYRÿîYTÿîY‚ÿßYƒÿßY„ÿßY…ÿßY†ÿßY‡ÿßYˆÿßY¢ÿîY£ÿîY¤ÿîY¥ÿîY¦ÿîY§ÿîY¨ÿîY©ÿîYªÿîY«ÿîY¬ÿîY­ÿîY²ÿîY´ÿîYµÿîY¶ÿîY·ÿîY¸ÿîYºÿîYÂÿßYÃÿîYÅÿîYÇÿîYÎÿîYéÿ~Yìÿ~Yðÿ~YùÿßZ ÿñZÿÄZÿÄZÿñZ$ÿñZ‚ÿñZƒÿñZ„ÿñZ…ÿñZ†ÿñZ‡ÿñZˆÿñZÂÿñZéÿÄZìÿÄZðÿÄZùÿñ[DÿÓ[FÿÓ[GÿÓ[HÿÓ[RÿÓ[TÿÓ[¢ÿÓ[£ÿÓ[¤ÿÓ[¥ÿÓ[¦ÿÓ[§ÿÓ[¨ÿÓ[©ÿÓ[ªÿÓ[«ÿÓ[¬ÿÓ[­ÿÓ[²ÿÓ[´ÿÓ[µÿÓ[¶ÿÓ[·ÿÓ[¸ÿÓ[ºÿÓ[ÃÿÓ[ÅÿÓ[ÇÿÓ[ÎÿÓ\ ÿß\ÿy\ÿy\ÿß\$ÿß\Dÿî\Fÿî\Gÿî\Hÿî\Rÿî\Tÿî\‚ÿß\ƒÿß\„ÿß\…ÿß\†ÿß\‡ÿß\ˆÿß\¢ÿî\£ÿî\¤ÿî\¥ÿî\¦ÿî\§ÿî\¨ÿî\©ÿî\ªÿî\«ÿî\¬ÿî\­ÿî\²ÿî\´ÿî\µÿî\¶ÿî\·ÿî\¸ÿî\ºÿî\Âÿß\Ãÿî\Åÿî\Çÿî\Îÿî\éÿy\ìÿy\ðÿy\ùÿß^#ÿØ^&ÿØ^*ÿØ^2ÿØ^4ÿØ^Dÿç^Fÿç^Gÿç^Hÿç^Rÿç^Tÿç^kÿØ^pÿØ^‰ÿØ^”ÿØ^•ÿØ^–ÿØ^—ÿØ^˜ÿØ^šÿØ^¢ÿç^£ÿç^¤ÿç^¥ÿç^¦ÿç^§ÿç^¨ÿç^©ÿç^ªÿç^«ÿç^¬ÿç^­ÿç^²ÿç^´ÿç^µÿç^¶ÿç^·ÿç^¸ÿç^ºÿç^Ãÿç^ÄÿØ^Åÿç^Çÿç^ÍÿØ^ÎÿçkÿÆk ÿäk ÿÆk ÿØk ÿÆkÿäk$ÿäk7ÿˆk9ÿÓk;ÿÐk<ÿ°k=ÿµk?ÿÓk@ÿØk`ÿØklÿÆkrÿÆk|ÿÆk‚ÿäkƒÿäk„ÿäk…ÿäk†ÿäk‡ÿäkˆÿäkŸÿ°kÂÿäkÓÿ°kÔÿµkÖÿµkØÿµkçÿÆkèÿÆkêÿÆkëÿÆkùÿäl ÿ[lÿjlÿylÿjlÿ[l$ÿ[l9>l:Al</l?>lDÿ«lFÿ«lGÿ«lHÿ«lRÿ«lTÿ«lmÿyloÿylyÿyl}ÿyl‚ÿ[lƒÿ[l„ÿ[l…ÿ[l†ÿ[l‡ÿ[lˆÿ[lŸ/l¢ÿ«l£ÿ«l¤ÿ«l¥ÿ«l¦ÿ«l§ÿ«l¨ÿ«l©ÿ«lªÿ«l«ÿ«l¬ÿ«l­ÿ«l²ÿ«l´ÿ«lµÿ«l¶ÿ«l·ÿ«l¸ÿ«lºÿ«lÂÿ[lÃÿ«lÅÿ«lÇÿ«lÎÿ«lÓ/låÿylæÿyléÿjlìÿjlïÿylðÿjlòÿylóÿylùÿ[mÿym ÿÝm ÿym ÿymÿjmÿjmÿÝm$ÿÝm7ÿGm9ÿ’m;ÿºm<ÿQm=ÿËm?ÿ’mlÿymrÿym|ÿym‚ÿÝmƒÿÝm„ÿÝm…ÿÝm†ÿÝm‡ÿÝmˆÿÝmŸÿQmÂÿÝmÓÿQmÔÿËmÖÿËmØÿËmçÿymèÿyméÿjmêÿymëÿymìÿjmðÿjmùÿÝoÿyo ÿÝo ÿyo ÿyoÿjoÿjoÿÝo$ÿÝo7ÿGo9ÿ’o;ÿºo<ÿQo=ÿËo?ÿ’olÿyorÿyo|ÿyo‚ÿÝoƒÿÝo„ÿÝo…ÿÝo†ÿÝo‡ÿÝoˆÿÝoŸÿQoÂÿÝoÓÿQoÔÿËoÖÿËoØÿËoçÿyoèÿyoéÿjoêÿyoëÿyoìÿjoðÿjoùÿÝpÿÆp ÿäp ÿÆp ÿØp ÿÆpÿäp$ÿäp7ÿˆp9ÿÓp;ÿÐp<ÿ°p=ÿµp?ÿÓp@ÿØp`ÿØplÿÆprÿÆp|ÿÆp‚ÿäpƒÿäp„ÿäp…ÿäp†ÿäp‡ÿäpˆÿäpŸÿ°pÂÿäpÓÿ°pÔÿµpÖÿµpØÿµpçÿÆpèÿÆpêÿÆpëÿÆpùÿär ÿ[rÿjrÿyrÿjrÿ[r$ÿ[r9>r:Ar</r?>rDÿ«rFÿ«rGÿ«rHÿ«rRÿ«rTÿ«rmÿyroÿyryÿyr}ÿyr‚ÿ[rƒÿ[r„ÿ[r…ÿ[r†ÿ[r‡ÿ[rˆÿ[rŸ/r¢ÿ«r£ÿ«r¤ÿ«r¥ÿ«r¦ÿ«r§ÿ«r¨ÿ«r©ÿ«rªÿ«r«ÿ«r¬ÿ«r­ÿ«r²ÿ«r´ÿ«rµÿ«r¶ÿ«r·ÿ«r¸ÿ«rºÿ«rÂÿ[rÃÿ«rÅÿ«rÇÿ«rÎÿ«rÓ/råÿyræÿyréÿjrìÿjrïÿyrðÿjròÿyróÿyrùÿ[t ÿetÿet$ÿet9Ft:Ft<(t?Ft‚ÿetƒÿet„ÿet…ÿet†ÿet‡ÿetˆÿetŸ(tÂÿetÓ(tùÿeu ÿeuÿeu$ÿeu9Fu:Fu<(u?Fu‚ÿeuƒÿeu„ÿeu…ÿeu†ÿeu‡ÿeuˆÿeuŸ(uÂÿeuÓ(uùÿeyÿyy ÿÝy ÿyy ÿyyÿjyÿjyÿÝy$ÿÝy7ÿGy9ÿ’y;ÿºy<ÿQy=ÿËy?ÿ’ylÿyyrÿyy|ÿyy‚ÿÝyƒÿÝy„ÿÝy…ÿÝy†ÿÝy‡ÿÝyˆÿÝyŸÿQyÂÿÝyÓÿQyÔÿËyÖÿËyØÿËyçÿyyèÿyyéÿjyêÿyyëÿyyìÿjyðÿjyùÿÝ{ ÿe{ÿe{$ÿe{9F{:F{<({?F{‚ÿe{ƒÿe{„ÿe{…ÿe{†ÿe{‡ÿe{ˆÿe{Ÿ({Âÿe{Ó({ùÿe| ÿ[|ÿj|ÿy|ÿj|ÿ[|$ÿ[|9>|:A|</|?>|Dÿ«|Fÿ«|Gÿ«|Hÿ«|Rÿ«|Tÿ«|mÿy|oÿy|yÿy|}ÿy|‚ÿ[|ƒÿ[|„ÿ[|…ÿ[|†ÿ[|‡ÿ[|ˆÿ[|Ÿ/|¢ÿ«|£ÿ«|¤ÿ«|¥ÿ«|¦ÿ«|§ÿ«|¨ÿ«|©ÿ«|ªÿ«|«ÿ«|¬ÿ«|­ÿ«|²ÿ«|´ÿ«|µÿ«|¶ÿ«|·ÿ«|¸ÿ«|ºÿ«|Âÿ[|Ãÿ«|Åÿ«|Çÿ«|Îÿ«|Ó/|åÿy|æÿy|éÿj|ìÿj|ïÿy|ðÿj|òÿy|óÿy|ùÿ[}ÿy} ÿÝ} ÿy} ÿy}ÿj}ÿj}ÿÝ}$ÿÝ}7ÿG}9ÿ’};ÿº}<ÿQ}=ÿË}?ÿ’}lÿy}rÿy}|ÿy}‚ÿÝ}ƒÿÝ}„ÿÝ}…ÿÝ}†ÿÝ}‡ÿÝ}ˆÿÝ}ŸÿQ}ÂÿÝ}ÓÿQ}ÔÿË}ÖÿË}ØÿË}çÿy}èÿy}éÿj}êÿy}ëÿy}ìÿj}ðÿj}ùÿÝ‚ÿV‚ ÿV‚ ÿV‚ÿÝ‚#ÿß‚&ÿß‚*ÿß‚-2‚2ÿß‚4ÿß‚7ÿ‚8ÿØ‚9ÿ«‚:ÿÄ‚<ÿy‚?ÿ«‚WÿÕ‚Yÿß‚Zÿñ‚\ÿß‚kÿß‚lÿV‚mÿÝ‚oÿÝ‚pÿß‚rÿV‚tÿ`‚uÿ`‚yÿÝ‚{ÿ`‚|ÿV‚}ÿÝ‚‰ÿß‚”ÿß‚•ÿß‚–ÿß‚—ÿß‚˜ÿß‚šÿß‚›ÿØ‚œÿØ‚ÿØ‚žÿØ‚Ÿÿy‚¿ÿß‚Áÿß‚Äÿß‚Íÿß‚Óÿy‚åÿÝ‚æÿÝ‚çÿV‚èÿV‚êÿV‚ëÿV‚ïÿÝ‚òÿÝ‚óÿ݃ÿVƒ ÿVƒ ÿVƒÿ݃#ÿ߃&ÿ߃*ÿ߃-2ƒ2ÿ߃4ÿ߃7ÿƒ8ÿ؃9ÿ«ƒ:ÿă<ÿyƒ?ÿ«ƒWÿÕƒYÿ߃Zÿñƒ\ÿ߃kÿ߃lÿVƒmÿ݃oÿ݃pÿ߃rÿVƒtÿ`ƒuÿ`ƒyÿ݃{ÿ`ƒ|ÿVƒ}ÿ݃‰ÿ߃”ÿ߃•ÿ߃–ÿ߃—ÿ߃˜ÿ߃šÿ߃›ÿ؃œÿ؃ÿ؃žÿ؃Ÿÿyƒ¿ÿ߃Áÿ߃Äÿ߃Íÿ߃Óÿyƒåÿ݃æÿ݃çÿVƒèÿVƒêÿVƒëÿVƒïÿ݃òÿ݃óÿÝ„ÿV„ ÿV„ ÿV„ÿÝ„#ÿß„&ÿß„*ÿß„-2„2ÿß„4ÿß„7ÿ„8ÿØ„9ÿ«„:ÿÄ„<ÿy„?ÿ«„WÿÕ„Yÿß„Zÿñ„\ÿß„kÿß„lÿV„mÿÝ„oÿÝ„pÿß„rÿV„tÿ`„uÿ`„yÿÝ„{ÿ`„|ÿV„}ÿÝ„‰ÿß„”ÿß„•ÿß„–ÿß„—ÿß„˜ÿß„šÿß„›ÿØ„œÿØ„ÿØ„žÿØ„Ÿÿy„¿ÿß„Áÿß„Äÿß„Íÿß„Óÿy„åÿÝ„æÿÝ„çÿV„èÿV„êÿV„ëÿV„ïÿÝ„òÿÝ„óÿÝ…ÿV… ÿV… ÿV…ÿÝ…#ÿß…&ÿß…*ÿß…-2…2ÿß…4ÿß…7ÿ…8ÿØ…9ÿ«…:ÿÄ…<ÿy…?ÿ«…WÿÕ…Yÿß…Zÿñ…\ÿß…kÿß…lÿV…mÿÝ…oÿÝ…pÿß…rÿV…tÿ`…uÿ`…yÿÝ…{ÿ`…|ÿV…}ÿÝ…‰ÿß…”ÿß…•ÿß…–ÿß…—ÿß…˜ÿß…šÿß…›ÿØ…œÿØ…ÿØ…žÿØ…Ÿÿy…¿ÿß…Áÿß…Äÿß…Íÿß…Óÿy…åÿÝ…æÿÝ…çÿV…èÿV…êÿV…ëÿV…ïÿÝ…òÿÝ…óÿ݆ÿV† ÿV† ÿV†ÿ݆#ÿ߆&ÿ߆*ÿ߆-2†2ÿ߆4ÿ߆7ÿ†8ÿ؆9ÿ«†:ÿĆ<ÿy†?ÿ«†WÿÕ†Yÿ߆Zÿñ†\ÿ߆kÿ߆lÿV†mÿ݆oÿ݆pÿ߆rÿV†tÿ`†uÿ`†yÿ݆{ÿ`†|ÿV†}ÿ݆‰ÿ߆”ÿ߆•ÿ߆–ÿ߆—ÿ߆˜ÿ߆šÿ߆›ÿ؆œÿ؆ÿ؆žÿ؆Ÿÿy†¿ÿ߆Áÿ߆Äÿ߆Íÿ߆Óÿy†åÿ݆æÿ݆çÿV†èÿV†êÿV†ëÿV†ïÿ݆òÿ݆óÿ݇ÿV‡ ÿV‡ ÿV‡ÿ݇#ÿ߇&ÿ߇*ÿ߇-2‡2ÿ߇4ÿ߇7ÿ‡8ÿ؇9ÿ«‡:ÿć<ÿy‡?ÿ«‡WÿÕ‡Yÿ߇Zÿñ‡\ÿ߇kÿ߇lÿV‡mÿ݇oÿ݇pÿ߇rÿV‡tÿ`‡uÿ`‡yÿ݇{ÿ`‡|ÿV‡}ÿ݇‰ÿ߇”ÿ߇•ÿ߇–ÿ߇—ÿ߇˜ÿ߇šÿ߇›ÿ؇œÿ؇ÿ؇žÿ؇Ÿÿy‡¿ÿ߇Áÿ߇Äÿ߇Íÿ߇Óÿy‡åÿ݇æÿ݇çÿV‡èÿV‡êÿV‡ëÿV‡ïÿ݇òÿ݇óÿ݉ÿo‰mÿo‰oÿo‰yÿo‰}ÿo‰åÿo‰æÿo‰ïÿo‰òÿo‰óÿo’ÿÆ’ ÿä’ ÿÆ’ ÿØ’ ÿÆ’ÿä’$ÿä’7ÿˆ’9ÿÓ’;ÿÐ’<ÿ°’=ÿµ’?ÿÓ’@ÿØ’`ÿØ’lÿÆ’rÿÆ’|ÿÆ’‚ÿä’ƒÿä’„ÿä’…ÿä’†ÿä’‡ÿä’ˆÿä’Ÿÿ°’Âÿä’Óÿ°’Ôÿµ’Öÿµ’Øÿµ’çÿÆ’èÿÆ’êÿÆ’ëÿÆ’ùÿä”ÿÆ” ÿä” ÿÆ” ÿØ” ÿÆ”ÿä”$ÿä”7ÿˆ”9ÿÓ”;ÿД<ÿ°”=ÿµ”?ÿÓ”@ÿØ”`ÿØ”lÿÆ”rÿÆ”|ÿÆ”‚ÿ䔃ÿ䔄ÿä”…ÿ䔆ÿ䔇ÿ䔈ÿ䔟ÿ°”Âÿä”Óÿ°”Ôÿµ”Öÿµ”Øÿµ”çÿÆ”èÿÆ”êÿÆ”ëÿÆ”ùÿä•ÿÆ• ÿä• ÿÆ• ÿØ• ÿÆ•ÿä•$ÿä•7ÿˆ•9ÿÓ•;ÿЕ<ÿ°•=ÿµ•?ÿÓ•@ÿØ•`ÿØ•lÿÆ•rÿÆ•|ÿÆ•‚ÿ䕃ÿä•„ÿä•…ÿ䕆ÿ䕇ÿ䕈ÿ䕟ÿ°•Âÿä•Óÿ°•Ôÿµ•Öÿµ•Øÿµ•çÿÆ•èÿÆ•êÿÆ•ëÿÆ•ùÿä–ÿÆ– ÿä– ÿÆ– ÿØ– ÿÆ–ÿä–$ÿä–7ÿˆ–9ÿÓ–;ÿЖ<ÿ°–=ÿµ–?ÿÓ–@ÿØ–`ÿØ–lÿÆ–rÿÆ–|ÿÆ–‚ÿä–ƒÿä–„ÿä–…ÿä–†ÿä–‡ÿä–ˆÿä–Ÿÿ°–Âÿä–Óÿ°–Ôÿµ–Öÿµ–Øÿµ–çÿÆ–èÿÆ–êÿÆ–ëÿÆ–ùÿä—ÿÆ— ÿä— ÿÆ— ÿØ— ÿÆ—ÿä—$ÿä—7ÿˆ—9ÿÓ—;ÿЗ<ÿ°—=ÿµ—?ÿÓ—@ÿØ—`ÿØ—lÿÆ—rÿÆ—|ÿÆ—‚ÿä—ƒÿä—„ÿä—…ÿä—†ÿä—‡ÿä—ˆÿä—Ÿÿ°—Âÿä—Óÿ°—Ôÿµ—Öÿµ—Øÿµ—çÿÆ—èÿÆ—êÿÆ—ëÿÆ—ùÿä˜ÿƘ ÿä˜ ÿƘ ÿؘ ÿƘÿä˜$ÿä˜7ÿˆ˜9ÿÓ˜;ÿИ<ÿ°˜=ÿµ˜?ÿÓ˜@ÿؘ`ÿؘlÿƘrÿƘ|ÿƘ‚ÿ䘃ÿ䘄ÿ䘅ÿ䘆ÿ䘇ÿ䘈ÿ䘟ÿ°˜Âÿä˜Óÿ°˜Ôÿµ˜Öÿµ˜Øÿµ˜çÿƘèÿƘêÿƘëÿƘùÿä› ÿØ›ÿØ›$ÿØ›‚ÿØ›ƒÿØ›„ÿØ›…ÿØ›†ÿØ›‡ÿØ›ˆÿØ›ÂÿØ›ùÿØœ ÿØœÿØœ$ÿØœ‚ÿØœƒÿØœ„ÿØœ…ÿØœ†ÿØœ‡ÿØœˆÿØœÂÿØœùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØž ÿØžÿØž$ÿØž‚ÿØžƒÿØž„ÿØž…ÿØž†ÿØž‡ÿØžˆÿØžÂÿØžùÿØŸ4Ÿ ÿ~Ÿ 4Ÿ 4Ÿÿ[ŸÿVŸÿ[Ÿÿ~ŸÿŠŸÿŠŸ"2Ÿ#ÿµŸ$ÿ~Ÿ&ÿµŸ*ÿµŸ-ÿ8Ÿ2ÿµŸ4ÿµŸDÿGŸFÿGŸGÿGŸHÿGŸJÿ_ŸPÿŠŸQÿŠŸRÿGŸSÿŠŸTÿGŸUÿŠŸVÿGŸXÿŠŸ]ÿ¯ŸkÿµŸl4ŸmÿVŸoÿVŸpÿµŸr4Ÿt2Ÿu2ŸwÿŠŸyÿVŸ{2Ÿ|4Ÿ}ÿVŸ‚ÿ~Ÿƒÿ~Ÿ„ÿ~Ÿ…ÿ~Ÿ†ÿ~Ÿ‡ÿ~Ÿˆÿ~Ÿ‰ÿµŸ”ÿµŸ•ÿµŸ–ÿµŸ—ÿµŸ˜ÿµŸšÿµŸ¢ÿGŸ£ÿGŸ¤ÿGŸ¥ÿGŸ¦ÿGŸ§ÿGŸ¨ÿGŸ©ÿGŸªÿGŸ«ÿGŸ¬ÿGŸ­ÿGŸ²ÿGŸ³ÿŠŸ´ÿGŸµÿGŸ¶ÿGŸ·ÿGŸ¸ÿGŸºÿGŸ»ÿŠŸ¼ÿŠŸ½ÿŠŸ¾ÿŠŸÂÿ~ŸÃÿGŸÄÿµŸÅÿGŸÇÿGŸÌÿŠŸÍÿµŸÎÿGŸÐÿGŸÒÿGŸÕÿ¯Ÿ×ÿ¯ŸÙÿ¯ŸåÿVŸæÿVŸç4Ÿè4Ÿéÿ[Ÿê4Ÿë4Ÿìÿ[ŸïÿVŸðÿ[ŸòÿVŸóÿVŸùÿ~ ÿÆ  ÿä  ÿÆ  ÿØ  ÿÆ ÿä $ÿä 7ÿˆ 9ÿÓ ;ÿР<ÿ° =ÿµ ?ÿÓ @ÿØ `ÿØ lÿÆ rÿÆ |ÿÆ ‚ÿä ƒÿä „ÿä …ÿä †ÿä ‡ÿä ˆÿä Ÿÿ° Âÿä Óÿ° Ôÿµ Öÿµ Øÿµ çÿÆ èÿÆ êÿÆ ëÿÆ ùÿä¨ÿ°¨ ÿ°¨ ÿç¨ ÿ°¨@ÿç¨[ÿÓ¨`ÿç¨lÿ°¨rÿ°¨|ÿ°¨çÿ°¨èÿ°¨êÿ°¨ëÿ°ªÿ°ª ÿ°ª ÿçª ÿ°ª@ÿçª[ÿÓª`ÿçªlÿ°ªrÿ°ª|ÿ°ªçÿ°ªèÿ°ªêÿ°ªëÿ°«ÿ°« ÿ°« ÿç« ÿ°«@ÿç«[ÿÓ«`ÿç«lÿ°«rÿ°«|ÿ°«çÿ°«èÿ°«êÿ°«ëÿ°¬ÿ°¬ ÿ°¬ ÿç¬ ÿ°¬@ÿç¬[ÿÓ¬`ÿç¬lÿ°¬rÿ°¬|ÿ°¬çÿ°¬èÿ°¬êÿ°¬ëÿ°­ÿ°­ ÿ°­ ÿç­ ÿ°­@ÿç­[ÿÓ­`ÿç­lÿ°­rÿ°­|ÿ°­çÿ°­èÿ°­êÿ°­ëÿ°³ÿij ÿij ÿijYÿç³\ÿâ³lÿijrÿijtÿ°³uÿ°³{ÿ°³|ÿij¿ÿç³Áÿç³çÿijèÿijêÿijëÿÄ´ÿ°´ ÿ°´ ÿç´ ÿ°´@ÿç´[ÿÓ´`ÿç´lÿ°´rÿ°´|ÿ°´çÿ°´èÿ°´êÿ°´ëÿ°µÿ°µ ÿ°µ ÿçµ ÿ°µ@ÿçµ[ÿÓµ`ÿçµlÿ°µrÿ°µ|ÿ°µçÿ°µèÿ°µêÿ°µëÿ°¶ÿ°¶ ÿ°¶ ÿç¶ ÿ°¶@ÿç¶[ÿÓ¶`ÿç¶lÿ°¶rÿ°¶|ÿ°¶çÿ°¶èÿ°¶êÿ°¶ëÿ°·ÿ°· ÿ°· ÿç· ÿ°·@ÿç·[ÿÓ·`ÿç·lÿ°·rÿ°·|ÿ°·çÿ°·èÿ°·êÿ°·ëÿ°¸ÿ°¸ ÿ°¸ ÿç¸ ÿ°¸@ÿç¸[ÿÓ¸`ÿç¸lÿ°¸rÿ°¸|ÿ°¸çÿ°¸èÿ°¸êÿ°¸ëÿ°ºÿ°º ÿ°º ÿçº ÿ°º@ÿçº[ÿÓº`ÿçºlÿ°ºrÿ°º|ÿ°ºçÿ°ºèÿ°ºêÿ°ºëÿ°¿ ÿß¿ÿ~¿ÿ~¿ÿß¿$ÿß¿Dÿî¿Fÿî¿Gÿî¿Hÿî¿Rÿî¿Tÿî¿‚ÿß¿ƒÿß¿„ÿß¿…ÿß¿†ÿß¿‡ÿß¿ˆÿß¿¢ÿî¿£ÿÿî¿¥ÿÿî¿§ÿÿî¿©ÿÿî¿«ÿÿî¿­ÿÿî¿´ÿÿî¿¶ÿî¿·ÿÿÿî¿Âÿß¿Ãÿî¿Åÿî¿Çÿî¿Îÿî¿éÿ~¿ìÿ~¿ðÿ~¿ùÿßÀÿ°À ÿ°À ÿçÀ ÿ°À@ÿçÀ[ÿÓÀ`ÿçÀlÿ°Àrÿ°À|ÿ°Àçÿ°Àèÿ°Àêÿ°Àëÿ°Á ÿßÁÿ~Áÿ~ÁÿßÁ$ÿßÁDÿîÁFÿîÁGÿîÁHÿîÁRÿîÁTÿîÁ‚ÿßÁƒÿßÁ„ÿßÁ…ÿßÁ†ÿßÁ‡ÿßÁˆÿßÁ¢ÿîÁ£ÿîÁ¤ÿîÁ¥ÿîÁ¦ÿîÁ§ÿîÁ¨ÿîÁ©ÿîÁªÿîÁ«ÿîÁ¬ÿîÁ­ÿîÁ²ÿîÁ´ÿîÁµÿîÁ¶ÿîÁ·ÿîÁ¸ÿîÁºÿîÁÂÿßÁÃÿîÁÅÿîÁÇÿîÁÎÿîÁéÿ~Áìÿ~Áðÿ~ÁùÿßÂÿV ÿV ÿVÂÿÝÂ#ÿßÂ&ÿßÂ*ÿßÂ-2Â2ÿßÂ4ÿßÂ7ÿÂ8ÿØÂ9ÿ«Â:ÿÄÂ<ÿyÂ?ÿ«ÂWÿÕÂYÿßÂZÿñÂ\ÿßÂkÿßÂlÿVÂmÿÝÂoÿÝÂpÿßÂrÿVÂtÿ`Âuÿ`ÂyÿÝÂ{ÿ`Â|ÿVÂ}ÿ݉ÿß”ÿß•ÿß–ÿß—ÿߘÿßšÿß›ÿØÂœÿØÂÿØÂžÿØÂŸÿy¿ÿßÂÁÿßÂÄÿßÂÍÿßÂÓÿyÂåÿÝÂæÿÝÂçÿVÂèÿVÂêÿVÂëÿVÂïÿÝÂòÿÝÂóÿÝÄÿoÄmÿoÄoÿoÄyÿoÄ}ÿoÄåÿoÄæÿoÄïÿoÄòÿoÄóÿoÇÿ°Ç ÿ°Ç ÿçÇ ÿ°Ç@ÿçÇ[ÿÓÇ`ÿçÇlÿ°Çrÿ°Ç|ÿ°Ççÿ°Çèÿ°Çêÿ°Çëÿ°ÉÿoÉ ÿoÉ ÿoÉÿtÉ9ÿjÉ:ÿ’É<ÿyÉ?ÿjÉYÿ°ÉZÿÓÉ\ÿ°ÉlÿoÉmÿtÉoÿtÉrÿoÉtÿƒÉuÿƒÉyÿtÉ{ÿƒÉ|ÿoÉ}ÿtÉŸÿyÉ¿ÿ°ÉÁÿ°ÉÓÿyÉåÿtÉæÿtÉçÿoÉèÿoÉêÿoÉëÿoÉïÿtÉòÿtÉóÿtÌÿÄÌ ÿÄÌ ÿÄÌYÿçÌ\ÿâÌlÿÄÌrÿÄÌtÿ°Ìuÿ°Ì{ÿ°Ì|ÿÄÌ¿ÿçÌÁÿçÌçÿÄÌèÿÄÌêÿÄÌëÿÄÎÿ°Î ÿ°Î ÿçÎ ÿ°Î@ÿçÎ[ÿÓÎ`ÿçÎlÿ°Îrÿ°Î|ÿ°Îçÿ°Îèÿ°Îêÿ°Îëÿ°Ó4Ó ÿ~Ó 4Ó 4Óÿ[ÓÿVÓÿ[Óÿ~ÓÿŠÓÿŠÓ"2Ó#ÿµÓ$ÿ~Ó&ÿµÓ*ÿµÓ-ÿ8Ó2ÿµÓ4ÿµÓDÿGÓFÿGÓGÿGÓHÿGÓJÿ_ÓPÿŠÓQÿŠÓRÿGÓSÿŠÓTÿGÓUÿŠÓVÿGÓXÿŠÓ]ÿ¯ÓkÿµÓl4ÓmÿVÓoÿVÓpÿµÓr4Ót2Óu2ÓwÿŠÓyÿVÓ{2Ó|4Ó}ÿVÓ‚ÿ~Óƒÿ~Ó„ÿ~Ó…ÿ~Ó†ÿ~Ó‡ÿ~Óˆÿ~Ó‰ÿµÓ”ÿµÓ•ÿµÓ–ÿµÓ—ÿµÓ˜ÿµÓšÿµÓ¢ÿGÓ£ÿGÓ¤ÿGÓ¥ÿGÓ¦ÿGÓ§ÿGÓ¨ÿGÓ©ÿGÓªÿGÓ«ÿGÓ¬ÿGÓ­ÿGÓ²ÿGÓ³ÿŠÓ´ÿGÓµÿGÓ¶ÿGÓ·ÿGÓ¸ÿGÓºÿGÓ»ÿŠÓ¼ÿŠÓ½ÿŠÓ¾ÿŠÓÂÿ~ÓÃÿGÓÄÿµÓÅÿGÓÇÿGÓÌÿŠÓÍÿµÓÎÿGÓÐÿGÓÒÿGÓÕÿ¯Ó×ÿ¯ÓÙÿ¯ÓåÿVÓæÿVÓç4Óè4Óéÿ[Óê4Óë4Óìÿ[ÓïÿVÓðÿ[ÓòÿVÓóÿVÓùÿ~Ôÿ¨Ô" Ô#ÿºÔ&ÿºÔ*ÿºÔ2ÿºÔ4ÿºÔkÿºÔmÿ¨Ôoÿ¨ÔpÿºÔyÿ¨Ô}ÿ¨Ô‰ÿºÔ”ÿºÔ•ÿºÔ–ÿºÔ—ÿºÔ˜ÿºÔšÿºÔÄÿºÔÍÿºÔåÿ¨Ôæÿ¨Ôïÿ¨Ôòÿ¨Ôóÿ¨Öÿ¨Ö" Ö#ÿºÖ&ÿºÖ*ÿºÖ2ÿºÖ4ÿºÖkÿºÖmÿ¨Öoÿ¨ÖpÿºÖyÿ¨Ö}ÿ¨Ö‰ÿºÖ”ÿºÖ•ÿºÖ–ÿºÖ—ÿºÖ˜ÿºÖšÿºÖÄÿºÖÍÿºÖåÿ¨Öæÿ¨Öïÿ¨Öòÿ¨Öóÿ¨Øÿ¨Ø" Ø#ÿºØ&ÿºØ*ÿºØ2ÿºØ4ÿºØkÿºØmÿ¨Øoÿ¨ØpÿºØyÿ¨Ø}ÿ¨Ø‰ÿºØ”ÿºØ•ÿºØ–ÿºØ—ÿºØ˜ÿºØšÿºØÄÿºØÍÿºØåÿ¨Øæÿ¨Øïÿ¨Øòÿ¨Øóÿ¨åÿyå ÿÝå ÿyå ÿyåÿjåÿjåÿÝå$ÿÝå7ÿGå9ÿ’å;ÿºå<ÿQå=ÿËå?ÿ’ålÿyårÿyå|ÿyå‚ÿÝåƒÿÝå„ÿÝå…ÿÝå†ÿÝå‡ÿÝåˆÿÝåŸÿQåÂÿÝåÓÿQåÔÿËåÖÿËåØÿËåçÿyåèÿyåéÿjåêÿyåëÿyåìÿjåðÿjåùÿÝæÿyæ ÿÝæ ÿyæ ÿyæÿjæÿjæÿÝæ$ÿÝæ7ÿGæ9ÿ’æ;ÿºæ<ÿQæ=ÿËæ?ÿ’ælÿyærÿyæ|ÿyæ‚ÿÝæƒÿÝæ„ÿÝæ…ÿÝæ†ÿÝæ‡ÿÝæˆÿÝæŸÿQæÂÿÝæÓÿQæÔÿËæÖÿËæØÿËæçÿyæèÿyæéÿjæêÿyæëÿyæìÿjæðÿjæùÿÝç ÿ[çÿjçÿyçÿjçÿ[ç$ÿ[ç9>ç:Aç</ç?>çDÿ«çFÿ«çGÿ«çHÿ«çRÿ«çTÿ«çmÿyçoÿyçyÿyç}ÿyç‚ÿ[çƒÿ[ç„ÿ[ç…ÿ[ç†ÿ[ç‡ÿ[çˆÿ[çŸ/ç¢ÿ«ç£ÿ«ç¤ÿ«ç¥ÿ«ç¦ÿ«ç§ÿ«ç¨ÿ«ç©ÿ«çªÿ«ç«ÿ«ç¬ÿ«ç­ÿ«ç²ÿ«ç´ÿ«çµÿ«ç¶ÿ«ç·ÿ«ç¸ÿ«çºÿ«çÂÿ[çÃÿ«çÅÿ«çÇÿ«çÎÿ«çÓ/çåÿyçæÿyçéÿjçìÿjçïÿyçðÿjçòÿyçóÿyçùÿ[è ÿ[èÿjèÿyèÿjèÿ[è$ÿ[è9>è:Aè</è?>èDÿ«èFÿ«èGÿ«èHÿ«èRÿ«èTÿ«èmÿyèoÿyèyÿyè}ÿyè‚ÿ[èƒÿ[è„ÿ[è…ÿ[è†ÿ[è‡ÿ[èˆÿ[èŸ/è¢ÿ«è£ÿ«è¤ÿ«è¥ÿ«è¦ÿ«è§ÿ«è¨ÿ«è©ÿ«èªÿ«è«ÿ«è¬ÿ«è­ÿ«è²ÿ«è´ÿ«èµÿ«è¶ÿ«è·ÿ«è¸ÿ«èºÿ«èÂÿ[èÃÿ«èÅÿ«èÇÿ«èÎÿ«èÓ/èåÿyèæÿyèéÿjèìÿjèïÿyèðÿjèòÿyèóÿyèùÿ[éÿ=é ÿ=é ÿ=éÿIé#ÿÆé&ÿÆé*ÿÆé2ÿÆé4ÿÆé7ÿAé9ÿ=é:ÿmé<ÿdé?ÿ=éYÿ~éZÿÉé\ÿékÿÆélÿ=émÿIéoÿIépÿÆérÿ=éyÿIé|ÿ=é}ÿIé‰ÿÆé”ÿÆé•ÿÆé–ÿÆé—ÿÆé˜ÿÆéšÿÆéŸÿdé¿ÿ~éÁÿ~éÄÿÆéÍÿÆéÓÿdéåÿIéæÿIéçÿ=éèÿ=éêÿ=éëÿ=éïÿIéòÿIéóÿIê ÿ[êÿjêÿyêÿjêÿ[ê$ÿ[ê9>ê:Aê</ê?>êDÿ«êFÿ«êGÿ«êHÿ«êRÿ«êTÿ«êmÿyêoÿyêyÿyê}ÿyê‚ÿ[êƒÿ[ê„ÿ[ê…ÿ[ê†ÿ[ê‡ÿ[êˆÿ[êŸ/ê¢ÿ«ê£ÿ«ê¤ÿ«ê¥ÿ«ê¦ÿ«ê§ÿ«ê¨ÿ«ê©ÿ«êªÿ«ê«ÿ«ê¬ÿ«ê­ÿ«ê²ÿ«ê´ÿ«êµÿ«ê¶ÿ«ê·ÿ«ê¸ÿ«êºÿ«êÂÿ[êÃÿ«êÅÿ«êÇÿ«êÎÿ«êÓ/êåÿyêæÿyêéÿjêìÿjêïÿyêðÿjêòÿyêóÿyêùÿ[ë ÿ[ëÿjëÿyëÿjëÿ[ë$ÿ[ë9>ë:Aë</ë?>ëDÿ«ëFÿ«ëGÿ«ëHÿ«ëRÿ«ëTÿ«ëmÿyëoÿyëyÿyë}ÿyë‚ÿ[ëƒÿ[ë„ÿ[ë…ÿ[ë†ÿ[ë‡ÿ[ëˆÿ[ëŸ/ë¢ÿ«ë£ÿ«ë¤ÿ«ë¥ÿ«ë¦ÿ«ë§ÿ«ë¨ÿ«ë©ÿ«ëªÿ«ë«ÿ«ë¬ÿ«ë­ÿ«ë²ÿ«ë´ÿ«ëµÿ«ë¶ÿ«ë·ÿ«ë¸ÿ«ëºÿ«ëÂÿ[ëÃÿ«ëÅÿ«ëÇÿ«ëÎÿ«ëÓ/ëåÿyëæÿyëéÿjëìÿjëïÿyëðÿjëòÿyëóÿyëùÿ[ìÿ=ì ÿ=ì ÿ=ìÿIì#ÿÆì&ÿÆì*ÿÆì2ÿÆì4ÿÆì7ÿAì9ÿ=ì:ÿmì<ÿdì?ÿ=ìYÿ~ìZÿÉì\ÿìkÿÆìlÿ=ìmÿIìoÿIìpÿÆìrÿ=ìyÿIì|ÿ=ì}ÿIì‰ÿÆì”ÿÆì•ÿÆì–ÿÆì—ÿÆì˜ÿÆìšÿÆìŸÿdì¿ÿ~ìÁÿ~ìÄÿÆìÍÿÆìÓÿdìåÿIìæÿIìçÿ=ìèÿ=ìêÿ=ìëÿ=ìïÿIìòÿIìóÿIïÿyï ÿÝï ÿyï ÿyïÿjïÿjïÿÝï$ÿÝï7ÿGï9ÿ’ï;ÿºï<ÿQï=ÿËï?ÿ’ïlÿyïrÿyï|ÿyï‚ÿÝïƒÿÝï„ÿÝï…ÿÝï†ÿÝï‡ÿÝïˆÿÝïŸÿQïÂÿÝïÓÿQïÔÿËïÖÿËïØÿËïçÿyïèÿyïéÿjïêÿyïëÿyïìÿjïðÿjïùÿÝðÿ=ð ÿ=ð ÿ=ðÿIð#ÿÆð&ÿÆð*ÿÆð2ÿÆð4ÿÆð7ÿAð9ÿ=ð:ÿmð<ÿdð?ÿ=ðYÿ~ðZÿÉð\ÿðkÿÆðlÿ=ðmÿIðoÿIðpÿÆðrÿ=ðyÿIð|ÿ=ð}ÿIð‰ÿÆð”ÿÆð•ÿÆð–ÿÆð—ÿÆð˜ÿÆðšÿÆðŸÿdð¿ÿ~ðÁÿ~ðÄÿÆðÍÿÆðÓÿdðåÿIðæÿIðçÿ=ðèÿ=ðêÿ=ðëÿ=ðïÿIðòÿIðóÿIòÿyò ÿÝò ÿyò ÿyòÿjòÿjòÿÝò$ÿÝò7ÿGò9ÿ’ò;ÿºò<ÿQò=ÿËò?ÿ’òlÿyòrÿyò|ÿyò‚ÿÝòƒÿÝò„ÿÝò…ÿÝò†ÿÝò‡ÿÝòˆÿÝòŸÿQòÂÿÝòÓÿQòÔÿËòÖÿËòØÿËòçÿyòèÿyòéÿjòêÿyòëÿyòìÿjòðÿjòùÿÝóÿyó ÿÝó ÿyó ÿyóÿjóÿjóÿÝó$ÿÝó7ÿGó9ÿ’ó;ÿºó<ÿQó=ÿËó?ÿ’ólÿyórÿyó|ÿyó‚ÿÝóƒÿÝó„ÿÝó…ÿÝó†ÿÝó‡ÿÝóˆÿÝóŸÿQóÂÿÝóÓÿQóÔÿËóÖÿËóØÿËóçÿyóèÿyóéÿjóêÿyóëÿyóìÿjóðÿjóùÿÝö ÿ[öÿjöÿyöÿjöÿ[ö$ÿ[ö9>ö:Aö</ö?>öDÿ«öFÿ«öGÿ«öHÿ«öRÿ«öTÿ«ömÿyöoÿyöyÿyö}ÿyö‚ÿ[öƒÿ[ö„ÿ[ö…ÿ[ö†ÿ[ö‡ÿ[öˆÿ[öŸ/ö¢ÿ«ö£ÿ«ö¤ÿ«ö¥ÿ«ö¦ÿ«ö§ÿ«ö¨ÿ«ö©ÿ«öªÿ«ö«ÿ«ö¬ÿ«ö­ÿ«ö²ÿ«ö´ÿ«öµÿ«ö¶ÿ«ö·ÿ«ö¸ÿ«öºÿ«öÂÿ[öÃÿ«öÅÿ«öÇÿ«öÎÿ«öÓ/öåÿyöæÿyöéÿjöìÿjöïÿyöðÿjöòÿyöóÿyöùÿ[ùÿVù ÿVù ÿVùÿÝù#ÿßù&ÿßù*ÿßù-2ù2ÿßù4ÿßù7ÿù8ÿØù9ÿ«ù:ÿÄù<ÿyù?ÿ«ùWÿÕùYÿßùZÿñù\ÿßùkÿßùlÿVùmÿÝùoÿÝùpÿßùrÿVùtÿ`ùuÿ`ùyÿÝù{ÿ`ù|ÿVù}ÿÝù‰ÿßù”ÿßù•ÿßù–ÿßù—ÿßù˜ÿßùšÿßù›ÿØùœÿØùÿØùžÿØùŸÿyù¿ÿßùÁÿßùÄÿßùÍÿßùÓÿyùåÿÝùæÿÝùçÿVùèÿVùêÿVùëÿVùïÿÝùòÿÝùóÿݦ¦¦¦î.ÊŠBà>tèTn’°<¦4tØ@x  n ¼ L r ž  Ì l Ô  B l â "^ªÆNœÞR¨@„¶ Pˆ¼î :jˆ¨”ôräN"j¦Pjä@Ž j †äj¬âŠ dd¦$¢  n !*!|"f"à#&#V#p$@$Z$¢$ä%`&& &v&°&Ü'6'|'Æ( („)$)ì*X*d*p*|*ˆ*”* *î+Ž+š+¦+²+¾+Ê+Ö+â+î,>,J,V,b,n,z,†,¬-<-H-T-`-l-x-º.j.v.‚.Ž.š.¦.²/œ0<0H0T0`0l0x0„00œ1 121D1V1h1z1Œ1Ú2j2|2Ž2 2²2Ä3,3>3´4r4~44ø5¨5Ä5þ6.6:6L7:7ä7ð888 8,888J8V8h8t8†9l9”9¼9Ä9ü:&:t:¶; ;>; ;º;Ô;ú<"(>T>¨?œ?Ä?ê@@ž@üAjAèBBBB€BšBÖCjC¼DBDžDÞEE`EzEìF’F¶FØGG(GJGxG¤GØHHDHˆHÀHæd"/n‹ ‘ †Š Š”/šÉ(Ú0B B ÜQ - 2E Âw9 S g  { ^‡  å P  å `U µ µ ¸Ó 0 ‹ d » „ 4£ × ßCopyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.Lato LightItalictyPolandLukaszDziedzic: Lato Light Italic: 2013Lato Light ItalicVersion 1.105; Western+Polish opensourceLato-LightItalicLato is a trademark of tyPoland Lukasz Dziedzic.Lukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2013-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLCopyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.Lato LightItalictyPolandLukaszDziedzic: Lato Light Italic: 2013Lato-LightItalicVersion 1.105; Western+Polish opensourceLato is a trademark of tyPoland Lukasz Dziedzic.Lukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2013-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLLatoLight ItalicÿùÿXA  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a£„…½–膎‹©¤Šƒ“òó—ˆÞñžªõôö¢­ÉÇ®bcdËeÈÊÏÌÍÎéfÓÐѯgð‘ÖÔÕhëí‰jikmln oqprsutvwêxzy{}|¸¡~€ìîºýþ  ×âã  °± äå»æç¦ØáÛÜÝàÙß›²³¶·Ä´µÅ‚‡«Æ¾¿¼ŒŸ˜š™ï¥’œ§”•¹ÀÁ !"#NULLuni00A0uni00ADmacronperiodcenteredAogonekaogonekEogonekeogonekNacutenacuteSacutesacuteZacutezacute Zdotaccent zdotaccentuni02C9EuroDeltauni2669undercommaaccent grave.case dieresis.case macron.case acute.casecircumflex.case caron.case breve.casedotaccent.case ring.case tilde.casehungarumlaut.case caron.saltÿÿ_K_K‡¡ãþ ¶þV—ÿð¡òÿóþ¶þV°,° `f-°, d °ÀP°&Z°E[X!#!ŠX °PPX!°@Y °8PX!°8YY ° Ead°(PX!° E °0PX!°0Y °ÀPX f ŠŠa ° PX` ° PX!° ` °6PX!°6``YYY°+YY#°PXeYY-°, E °%ad °CPX°#B°#B!!Y°`-°,#!#! d±bB °#B² *! °C Ра+±0%ŠQX`PaRYX#Y! °@SX°+!°@Y#°PXeY-°,°C+²C`B-°,°#B# °#Ba°€b°`°*-°, E °Ec°Eb`D°`-°, E °+#±%` EŠ#a d ° PX!°°0PX° °@YY#°PXeY°%#aDD°`-°,±E°aD-° ,°` ° CJ°PX ° #BY° CJ°RX ° #BY-° , ¸b ¸cŠ#a° C` Š` ° #B#-° ,KTX±DY$° e#x-° ,KQXKSX±DY!Y$°e#x-° ,± CUX± C°aB° +Y°C°%B± %B± %B°# °%PX±C`°%BŠŠ Š#a° *!#°a Š#a° *!±C`°%B°%a° *!Y° CG° CG`°€b °Ec°Eb`±#D°C°>²C`B-°,±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,± +-°,°+±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-° ,±+-°!,±+-°",±+-°#,± +-°$, <°`-°%, `° ` C#°`C°%a°`°$*!-°&,°%+°%*-°', G °Ec°Eb`#a8# ŠUX G °Ec°Eb`#a8!Y-°(,±ETX°°'*°0"Y-°),°+±ETX°°'*°0"Y-°*, 5°`-°+,°Ec°Eb°+°Ec°Eb°+°´D>#8±**-°,, < G °Ec°Eb`°Ca8-°-,.<-°., < G °Ec°Eb`°Ca°Cc8-°/,±% . G°#B°%IŠŠG#G#a Xb!Y°#B².*-°0,°°%°%G#G#a°E+eŠ.# <Š8-°1,°°%°% .G#G#a °#B°E+ °`PX °@QX³  ³&YBB# °C Š#G#G#a#F`°C°€b` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°%°€ba# °&#Fa8#°CF°%°CG#G#a` °C°€b`# °+#°C`°+°%a°%°€b°&a °%`d#°%`dPX!#!Y# °&#Fa8Y-°2,° °& .G#G#a#<8-°3,° °#B F#G°+#a8-°4,°°%°%G#G#a°TX. <#!°%°%G#G#a °%°%G#G#a°%°%I°%a°Ec# Xb!Yc°Eb`#.# <Š8#!Y-°5,° °C .G#G#a `° `f°€b# <Š8-°6,# .F°%FRX ,°1+!# <°#B#8±&+°C.°&+-°?,° G°#B².°,*-°@,° G°#B².°,*-°A,±°-*-°B,°/*-°C,°E# . FŠ#a8±&+-°D,°#B°C+-°E,²<+-°F,²<+-°G,²<+-°H,²<+-°I,²=+-°J,²=+-°K,²=+-°L,²=+-°M,²9+-°N,²9+-°O,²9+-°P,²9+-°Q,²;+-°R,²;+-°S,²;+-°T,²;+-°U,²>+-°V,²>+-°W,²>+-°X,²>+-°Y,²:+-°Z,²:+-°[,²:+-°\,²:+-°],°2+.±&+-°^,°2+°6+-°_,°2+°7+-°`,°°2+°8+-°a,°3+.±&+-°b,°3+°6+-°c,°3+°7+-°d,°3+°8+-°e,°4+.±&+-°f,°4+°6+-°g,°4+°7+-°h,°4+°8+-°i,°5+.±&+-°j,°5+°6+-°k,°5+°7+-°l,°5+°8+-°m,+°e°$Px°0-K¸KRX±ŽY¹c °#D°#p°E °(`f ŠUX°%a°Ec#b°#D² *² *²*Y²( ERD² *±D±$ˆQX°@ˆX±D±&ˆQX¸ˆX±DYYYY¸ÿ…°±Dmcollective-2.12.1/doc/fonts/Lato-Light.ttf0000644005276200011600000027071413265671731020402 0ustar jenkinsjenkinsGPOSÔjN¨K´GSUBV.TLÐOS/2Ù8¨Mà`cmapRÔŸ×N@æcvt &ˆ7g|8fpgm‹ zAg´ ‘gaspgtglyfñ®i S(‹’headÿDeJÞ¼6hheaixÞô$hmtxESvJßTkern“2’•ãllälocaKºp±PP,maxp> ¤R| nameœ UÿRœ:post:Ü\ácØšprepœx9½qH 0JDFLTlatnÿÿÿÿkernkernJnvö$RÜ À^ h B l  ä & ì¸rjØÈî´Z¦€Æj´úL’Ø*|²DV¨:Äò !.!Ô##L#–$4$~%¬&J'$'þ(Ø)²*Œ+f+,6,Ü-‚.(.Î/t/¶/ø0:0|2"2È33T3š3à4&4l4¾55b5´66L6ž6ð7B7”7æ888Š9d9ª9Ô:&:¨:î;@<æ=¨>j?,?Ê@hA–BÄCzD¨EÖFŒG*GÈHfI”K ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿVK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV"#ÿØ&ÿØ*ÿØ2ÿØ4ÿØFÿçGÿçHÿçRÿçTÿç‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ©ÿçªÿç«ÿç¬ÿç­ÿç²ÿç´ÿçµÿç¶ÿç·ÿç¸ÿçºÿçÄÿØÅÿçÇÿçÍÿØÎÿçK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV-ÿ ÿ ÿ ÿv#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿL9ÿL:ÿ’<ÿy?ÿLYÿƒZÿÎ\ÿƒlÿ mÿvoÿvrÿ yÿv|ÿ }ÿv‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿyÄÿÆÍÿÆÓÿyåÿvæÿvçÿ èÿ êÿ ëÿ ïÿvòÿvóÿvöÿ 'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ-ÿ ÿ ÿ ÿv#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿL9ÿL:ÿ’<ÿy?ÿLYÿƒZÿÎ\ÿƒlÿ mÿvoÿvrÿ yÿv|ÿ }ÿv‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿyÄÿÆÍÿÆÓÿyåÿvæÿvçÿ èÿ êÿ ëÿ ïÿvòÿvóÿvöÿ k7 ÿ¡ 7 7ÿGÿ—ÿGÿ¡ÿ·ÿ·"<#ÿÓ$ÿ¡&ÿÓ*ÿÓ-ÿo2ÿÓ4ÿÓDÿ”Fÿ”Gÿ”Hÿ”IÿâJÿ{Pÿ·Qÿ·Rÿ”Sÿ·Tÿ”Uÿ·Vÿ™WÿÝXÿ·Yÿß[ÿÝ\ÿß]ÿºl7mÿ—oÿ—r7tFuFwÿ·yÿ—{F|7}ÿ—‚ÿ¡ƒÿ¡„ÿ¡…ÿ¡†ÿ¡‡ÿ¡ˆÿ¡‰ÿÓ”ÿÓ•ÿÓ–ÿÓ—ÿÓ˜ÿÓšÿÓ¢ÿ”£ÿ”¤ÿ”¥ÿ”¦ÿ”§ÿ”¨ÿ”©ÿ”ªÿ”«ÿ”¬ÿ”­ÿ”²ÿ”³ÿ·´ÿ”µÿ”¶ÿ”·ÿ”¸ÿ”ºÿ”»ÿ·¼ÿ·½ÿ·¾ÿ·Âÿ¡Ãÿ”ÄÿÓÅÿ”Çÿ”Ìÿ·ÍÿÓÎÿ”åÿ—æÿ—ç7è7éÿGê7ë7ìÿGïÿ—òÿ—óÿ—ö7ùÿ¡)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿV ÿemÿeoÿeyÿe}ÿeåÿeæÿeïÿeòÿeóÿe)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ4 ÿÿLÿLÿÿÄÿÄ"$ÿ-ÿ.FÿµGÿµHÿµPÿÄQÿÄRÿµSÿÄTÿµUÿÄXÿÄwÿÄ‚ÿƒÿ„ÿ…ÿ†ÿ‡ÿˆÿ©ÿµªÿµ«ÿµ¬ÿµ­ÿµ²ÿµ³ÿÄ´ÿµµÿµ¶ÿµ·ÿµ¸ÿµºÿµ»ÿļÿĽÿľÿÄÂÿÅÿµÇÿµÌÿÄÎÿµéÿLìÿLùÿ ÿÆÿÎÿÎÿÆ$ÿÆ‚ÿƃÿÆ„ÿÆ…ÿƆÿƇÿƈÿÆÂÿÆéÿÎìÿÎùÿÆ1ÿÉ#ÿâ&ÿâ*ÿâ2ÿâ4ÿâFÿäGÿäHÿäIÿÓRÿäTÿäWÿºYÿÆZÿÆ\ÿÆmÿÉoÿÉyÿÉ}ÿɉÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ©ÿäªÿä«ÿä¬ÿä­ÿä²ÿä´ÿäµÿä¶ÿä·ÿä¸ÿäºÿäÄÿâÅÿäÇÿäÍÿâÎÿäåÿÉæÿÉïÿÉòÿÉóÿÉIþÙ þÙ þÙ4ÿ$4"ÿÎ#ÿ°&ÿ°*ÿ°2ÿ°4ÿ°7ÿ[9ÿV:ÿy<ÿ=?ÿVFÿßGÿßHÿßRÿßTÿßYÿ£Zÿ°\ÿ£lþÙmÿ$oÿ$rþÙtÿBuÿByÿ${ÿB|þÙ}ÿ$‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°Ÿÿ=©ÿߪÿß«ÿ߬ÿß­ÿß²ÿß´ÿßµÿß¶ÿß·ÿ߸ÿߺÿßÄÿ°ÅÿßÇÿßÍÿ°ÎÿßÓÿ=åÿ$æÿ$çþÙèþÙé4êþÙëþÙì4ïÿ$òÿ$óÿ$öþÙ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ. ÿŠÿÿÿŠ$ÿŠ-ÿVDÿÎFÿâGÿâHÿâRÿâTÿâ‚ÿŠƒÿŠ„ÿŠ…ÿІÿЇÿŠˆÿŠ¢ÿΣÿΤÿÎ¥ÿΦÿΧÿΨÿΩÿâªÿâ«ÿâ¬ÿâ­ÿâ²ÿâ´ÿâµÿâ¶ÿâ·ÿâ¸ÿâºÿâÂÿŠÃÿÎÅÿâÇÿâÎÿâéÿìÿùÿŠ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ#ÿÕ&ÿÕ*ÿÕ2ÿÕ4ÿÕ7ÿÓ8ÿâ‰ÿÕ”ÿÕ•ÿÕ–ÿÕ—ÿÕ˜ÿÕšÿÕ›ÿâœÿâÿâžÿâÄÿÕÍÿÕ[ ÿÿLÿLÿLÿÿ`ÿ`#ÿˆ$ÿ&ÿˆ*ÿˆ-ÿ82ÿˆ4ÿˆDÿFÿ)Gÿ)Hÿ)Jÿ>Pÿ`Qÿ`Rÿ)Sÿ`Tÿ)Uÿ`VÿDXÿ`YÿLZÿt[ÿg\ÿL]ÿˆmÿLoÿLwÿ`yÿL}ÿL‚ÿƒÿ„ÿ…ÿ†ÿ‡ÿˆÿ‰ÿˆ”ÿˆ•ÿˆ–ÿˆ—ÿˆ˜ÿˆšÿˆ¢ÿ£ÿ¤ÿ¥ÿ¦ÿ§ÿ¨ÿ©ÿ)ªÿ)«ÿ)¬ÿ)­ÿ)²ÿ)³ÿ`´ÿ)µÿ)¶ÿ)·ÿ)¸ÿ)ºÿ)»ÿ`¼ÿ`½ÿ`¾ÿ`ÂÿÃÿÄÿˆÅÿ)Çÿ)Ìÿ`ÍÿˆÎÿ)åÿLæÿLéÿLìÿLïÿLòÿLóÿLùÿ ÿÆÿÎÿÎÿÆ$ÿÆ‚ÿƃÿÆ„ÿÆ…ÿƆÿƇÿƈÿÆÂÿÆéÿÎìÿÎùÿÆk7 ÿ¡ 7 7ÿGÿ—ÿGÿ¡ÿ·ÿ·"<#ÿÓ$ÿ¡&ÿÓ*ÿÓ-ÿo2ÿÓ4ÿÓDÿ”Fÿ”Gÿ”Hÿ”IÿâJÿ{Pÿ·Qÿ·Rÿ”Sÿ·Tÿ”Uÿ·Vÿ™WÿÝXÿ·Yÿß[ÿÝ\ÿß]ÿºl7mÿ—oÿ—r7tFuFwÿ·yÿ—{F|7}ÿ—‚ÿ¡ƒÿ¡„ÿ¡…ÿ¡†ÿ¡‡ÿ¡ˆÿ¡‰ÿÓ”ÿÓ•ÿÓ–ÿÓ—ÿÓ˜ÿÓšÿÓ¢ÿ”£ÿ”¤ÿ”¥ÿ”¦ÿ”§ÿ”¨ÿ”©ÿ”ªÿ”«ÿ”¬ÿ”­ÿ”²ÿ”³ÿ·´ÿ”µÿ”¶ÿ”·ÿ”¸ÿ”ºÿ”»ÿ·¼ÿ·½ÿ·¾ÿ·Âÿ¡Ãÿ”ÄÿÓÅÿ”Çÿ”Ìÿ·ÍÿÓÎÿ”åÿ—æÿ—ç7è7éÿGê7ë7ìÿGïÿ—òÿ—óÿ—ö7ùÿ¡I7 ÿ¿ 7 7ÿ’ÿçÿ’ÿ¿"%$ÿ¿-ÿ¦Dÿ·FÿìGÿìHÿìJÿRÿìTÿìVÿÚl7mÿçoÿçr7t7u7yÿç{7|7}ÿç‚ÿ¿ƒÿ¿„ÿ¿…ÿ¿†ÿ¿‡ÿ¿ˆÿ¿¢ÿ·£ÿ·¤ÿ·¥ÿ·¦ÿ·§ÿ·¨ÿ·©ÿìªÿì«ÿì¬ÿì­ÿì²ÿì´ÿìµÿì¶ÿì·ÿì¸ÿìºÿìÂÿ¿Ãÿ·ÅÿìÇÿìÎÿìåÿçæÿçç7è7éÿ’ê7ë7ìÿ’ïÿçòÿçóÿçö7ùÿ¿1ÿÉ#ÿâ&ÿâ*ÿâ2ÿâ4ÿâFÿäGÿäHÿäIÿÓRÿäTÿäWÿºYÿÆZÿÆ\ÿÆmÿÉoÿÉyÿÉ}ÿɉÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ©ÿäªÿä«ÿä¬ÿä­ÿä²ÿä´ÿäµÿä¶ÿä·ÿä¸ÿäºÿäÄÿâÅÿäÇÿäÍÿâÎÿäåÿÉæÿÉïÿÉòÿÉóÿÉi ÿt  ÿyÿ`ÿyÿtÿ’ÿ’"%#ÿ°$ÿt&ÿ°*ÿ°-ÿ82ÿ°4ÿ°Dÿ”Fÿ`Gÿ`Hÿ`Jÿ[Pÿ’Qÿ’Rÿ`Sÿ’Tÿ`Uÿ’VÿXÿ’YÿœZÿ«[ÿƒ\ÿœlmÿ`oÿ`rt-u-wÿ’yÿ`{-|}ÿ`‚ÿtƒÿt„ÿt…ÿt†ÿt‡ÿtˆÿt‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°¢ÿ”£ÿ”¤ÿ”¥ÿ”¦ÿ”§ÿ”¨ÿ”©ÿ`ªÿ`«ÿ`¬ÿ`­ÿ`²ÿ`³ÿ’´ÿ`µÿ`¶ÿ`·ÿ`¸ÿ`ºÿ`»ÿ’¼ÿ’½ÿ’¾ÿ’ÂÿtÃÿ”Äÿ°Åÿ`Çÿ`Ìÿ’Íÿ°Îÿ`åÿ`æÿ`çèéÿyêëìÿyïÿ`òÿ`óÿ`öùÿt0ÿµ"%#ÿº&ÿº*ÿº2ÿº4ÿºFÿÎGÿÎHÿÎRÿÎTÿÎVÿØYÿÓ\ÿÓmÿµoÿµyÿµ}ÿµ‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿº©ÿΪÿΫÿάÿέÿβÿδÿεÿζÿηÿθÿκÿÎÄÿºÅÿÎÇÿÎÍÿºÎÿÎåÿµæÿµïÿµòÿµóÿµ"#ÿØ&ÿØ*ÿØ2ÿØ4ÿØFÿçGÿçHÿçRÿçTÿç‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ©ÿçªÿç«ÿç¬ÿç­ÿç²ÿç´ÿçµÿç¶ÿç·ÿç¸ÿçºÿçÄÿØÅÿçÇÿçÍÿØÎÿç6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿVÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«K K KÿyÿylKrKtdud{d|KçKèKéÿyêKëKìÿyöKÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿FÿÄGÿÄHÿÄRÿÄTÿÄ©ÿĪÿÄ«ÿĬÿÄ­ÿIJÿÄ´ÿĵÿĶÿÄ·ÿĸÿĺÿÄÅÿÄÇÿÄÎÿÄÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ« ÿƒÿƒDÿ΢ÿΣÿΤÿÎ¥ÿΦÿΧÿΨÿÎÃÿÎéÿƒìÿƒ$ ÿºÿƒÿƒÿº$ÿºFÿîGÿîHÿîRÿîTÿî‚ÿºƒÿº„ÿº…ÿº†ÿº‡ÿºˆÿº©ÿîªÿî«ÿî¬ÿî­ÿî²ÿî´ÿîµÿî¶ÿî·ÿî¸ÿîºÿîÂÿºÅÿîÇÿîÎÿîéÿƒìÿƒùÿºÿÎÿÎéÿÎìÿÎFÿÄGÿÄHÿÄRÿÄTÿÄ©ÿĪÿÄ«ÿĬÿÄ­ÿIJÿÄ´ÿĵÿĶÿÄ·ÿĸÿĺÿÄÅÿÄÇÿÄÎÿÄ$ ÿºÿƒÿƒÿº$ÿºFÿîGÿîHÿîRÿîTÿî‚ÿºƒÿº„ÿº…ÿº†ÿº‡ÿºˆÿº©ÿîªÿî«ÿî¬ÿî­ÿî²ÿî´ÿîµÿî¶ÿî·ÿî¸ÿîºÿîÂÿºÅÿîÇÿîÎÿîéÿƒìÿƒùÿº"#ÿØ&ÿØ*ÿØ2ÿØ4ÿØFÿçGÿçHÿçRÿçTÿç‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ©ÿçªÿç«ÿç¬ÿç­ÿç²ÿç´ÿçµÿç¶ÿç·ÿç¸ÿçºÿçÄÿØÅÿçÇÿçÍÿØÎÿçK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV ÿ`ÿ`$ÿ`9F:F<(?F‚ÿ`ƒÿ`„ÿ`…ÿ`†ÿ`‡ÿ`ˆÿ`Ÿ(Âÿ`Ó(ùÿ` ÿ`ÿ`$ÿ`9F:F<(?F‚ÿ`ƒÿ`„ÿ`…ÿ`†ÿ`‡ÿ`ˆÿ`Ÿ(Âÿ`Ó(ùÿ`'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ ÿ`ÿ`$ÿ`9F:F<(?F‚ÿ`ƒÿ`„ÿ`…ÿ`†ÿ`‡ÿ`ˆÿ`Ÿ(Âÿ`Ó(ùÿ`K ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿV6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿV6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿV6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿV6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿV6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿV ÿemÿeoÿeyÿe}ÿeåÿeæÿeïÿeòÿeóÿe)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâ ÿÆÿÎÿÎÿÆ$ÿÆ‚ÿƃÿÆ„ÿÆ…ÿƆÿƇÿƈÿÆÂÿÆéÿÎìÿÎùÿÆ ÿÆÿÎÿÎÿÆ$ÿÆ‚ÿƃÿÆ„ÿÆ…ÿƆÿƇÿƈÿÆÂÿÆéÿÎìÿÎùÿÆ ÿÆÿÎÿÎÿÆ$ÿÆ‚ÿƃÿÆ„ÿÆ…ÿƆÿƇÿƈÿÆÂÿÆéÿÎìÿÎùÿÆ ÿÆÿÎÿÎÿÆ$ÿÆ‚ÿƃÿÆ„ÿÆ…ÿƆÿƇÿƈÿÆÂÿÆéÿÎìÿÎùÿÆi ÿt  ÿyÿ`ÿyÿtÿ’ÿ’"%#ÿ°$ÿt&ÿ°*ÿ°-ÿ82ÿ°4ÿ°Dÿ”Fÿ`Gÿ`Hÿ`Jÿ[Pÿ’Qÿ’Rÿ`Sÿ’Tÿ`Uÿ’VÿXÿ’YÿœZÿ«[ÿƒ\ÿœlmÿ`oÿ`rt-u-wÿ’yÿ`{-|}ÿ`‚ÿtƒÿt„ÿt…ÿt†ÿt‡ÿtˆÿt‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°¢ÿ”£ÿ”¤ÿ”¥ÿ”¦ÿ”§ÿ”¨ÿ”©ÿ`ªÿ`«ÿ`¬ÿ`­ÿ`²ÿ`³ÿ’´ÿ`µÿ`¶ÿ`·ÿ`¸ÿ`ºÿ`»ÿ’¼ÿ’½ÿ’¾ÿ’ÂÿtÃÿ”Äÿ°Åÿ`Çÿ`Ìÿ’Íÿ°Îÿ`åÿ`æÿ`çèéÿyêëìÿyïÿ`òÿ`óÿ`öùÿt)ÿÐ ÿâ ÿÐ ÿØ ÿÐÿÆÿÆÿâ$ÿâ7ÿˆ9ÿÓ;ÿâ<ÿ°=ÿµ?ÿÓ@ÿØ`ÿØlÿÐrÿÐ|ÿЂÿâƒÿâ„ÿâ…ÿâ†ÿâ‡ÿâˆÿâŸÿ°ÂÿâÓÿ°ÔÿµÖÿµØÿµçÿÐèÿÐéÿÆêÿÐëÿÐìÿÆöÿÐùÿâÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿVÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ ÿemÿeoÿeyÿe}ÿeåÿeæÿeïÿeòÿeóÿeÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ« ÿt ÿt ÿtÿo9ÿj:ÿ’<ÿy?ÿjYÿË\ÿËlÿtmÿooÿorÿttÿƒuÿƒyÿo{ÿƒ|ÿt}ÿoŸÿyÓÿyåÿoæÿoçÿtèÿtêÿtëÿtïÿoòÿoóÿoöÿtÿ¿ ÿ¿ ÿ¿YÿçZÿó\ÿçlÿ¿rÿ¿tÿ¿uÿ¿{ÿ¿|ÿ¿çÿ¿èÿ¿êÿ¿ëÿ¿öÿ¿ÿ« ÿ« ÿç ÿ«9ÿ”:ÿì?ÿ”@ÿçYÿî[ÿÄ\ÿî`ÿçlÿ«rÿ«|ÿ«çÿ«èÿ«êÿ«ëÿ«öÿ«i ÿt  ÿyÿ`ÿyÿtÿ’ÿ’"%#ÿ°$ÿt&ÿ°*ÿ°-ÿ82ÿ°4ÿ°Dÿ”Fÿ`Gÿ`Hÿ`Jÿ[Pÿ’Qÿ’Rÿ`Sÿ’Tÿ`Uÿ’VÿXÿ’YÿœZÿ«[ÿƒ\ÿœlmÿ`oÿ`rt-u-wÿ’yÿ`{-|}ÿ`‚ÿtƒÿt„ÿt…ÿt†ÿt‡ÿtˆÿt‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°¢ÿ”£ÿ”¤ÿ”¥ÿ”¦ÿ”§ÿ”¨ÿ”©ÿ`ªÿ`«ÿ`¬ÿ`­ÿ`²ÿ`³ÿ’´ÿ`µÿ`¶ÿ`·ÿ`¸ÿ`ºÿ`»ÿ’¼ÿ’½ÿ’¾ÿ’ÂÿtÃÿ”Äÿ°Åÿ`Çÿ`Ìÿ’Íÿ°Îÿ`åÿ`æÿ`çèéÿyêëìÿyïÿ`òÿ`óÿ`öùÿt0ÿµ"%#ÿº&ÿº*ÿº2ÿº4ÿºFÿÎGÿÎHÿÎRÿÎTÿÎVÿØYÿÓ\ÿÓmÿµoÿµyÿµ}ÿµ‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿº©ÿΪÿΫÿάÿέÿβÿδÿεÿζÿηÿθÿκÿÎÄÿºÅÿÎÇÿÎÍÿºÎÿÎåÿµæÿµïÿµòÿµóÿµ0ÿµ"%#ÿº&ÿº*ÿº2ÿº4ÿºFÿÎGÿÎHÿÎRÿÎTÿÎVÿØYÿÓ\ÿÓmÿµoÿµyÿµ}ÿµ‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿº©ÿΪÿΫÿάÿέÿβÿδÿεÿζÿηÿθÿκÿÎÄÿºÅÿÎÇÿÎÍÿºÎÿÎåÿµæÿµïÿµòÿµóÿµ0ÿµ"%#ÿº&ÿº*ÿº2ÿº4ÿºFÿÎGÿÎHÿÎRÿÎTÿÎVÿØYÿÓ\ÿÓmÿµoÿµyÿµ}ÿµ‰ÿº”ÿº•ÿº–ÿº—ÿº˜ÿºšÿº©ÿΪÿΫÿάÿέÿβÿδÿεÿζÿηÿθÿκÿÎÄÿºÅÿÎÇÿÎÍÿºÎÿÎåÿµæÿµïÿµòÿµóÿµ'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿVK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV-ÿ ÿ ÿ ÿv#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿL9ÿL:ÿ’<ÿy?ÿLYÿƒZÿÎ\ÿƒlÿ mÿvoÿvrÿ yÿv|ÿ }ÿv‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿyÄÿÆÍÿÆÓÿyåÿvæÿvçÿ èÿ êÿ ëÿ ïÿvòÿvóÿvöÿ K ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿVK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV-ÿ ÿ ÿ ÿv#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿL9ÿL:ÿ’<ÿy?ÿLYÿƒZÿÎ\ÿƒlÿ mÿvoÿvrÿ yÿv|ÿ }ÿv‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿyÄÿÆÍÿÆÓÿyåÿvæÿvçÿ èÿ êÿ ëÿ ïÿvòÿvóÿvöÿ 'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝ'ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝK ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV6ÿV ÿV ÿVÿÝ"ÿÐ#ÿâ&ÿâ*ÿâ--2ÿâ4ÿâ7ÿ8ÿÆ9ÿ¡:ÿÄ<ÿt?ÿ¡Yÿº\ÿºlÿVmÿÝoÿÝrÿVtÿ`uÿ`yÿÝ{ÿ`|ÿV}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆœÿÆÿÆžÿÆŸÿtÄÿâÍÿâÓÿtåÿÝæÿÝçÿVèÿVêÿVëÿVïÿÝòÿÝóÿÝöÿVv #$&')-./2345789:;<=>?DEHIKNPQRSUYZ[\^lmoprtuy{|}‚ƒ„…†‡‰’”•–—˜›œžŸ ¢£¤¥¦§¨ª«¬­³´µ¶·¸ºÀÂÃÄÇÉÌÎÓÔÖØåæçèéêëìïòóöù 8‚DFLTlatnÿÿÿÿcase&case,liga2liga8sups>supsD,>B      @ LO,{tu CjqvÛÜÞßàâãIý,xxºDô ¯P`KtyPL@ûJþz¶ª “㇠†&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a†‡‰‹“˜ž£¢¤¦¥§©«ª¬­¯®°±³µ´¶¸·¼»½¾írdeiïx¡pkövjˆšþsgwøûúäÿl|÷¨ºcnýÚùm}ðb‚…—ÍÎåæêëçè¹ÁÓôõòóîyéìñ„ŒƒŠ‘Ž•–”œ›ÈÛâqÞßàzãáÜ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ/Ò‡'37;‰µBK° PX@3 `f[[ Q CQ D@4hf[[ Q CQ DY@ ;:$$#,$ +>32#'&>54.#"#"'4632#"&!!7!!332#"&tA1!""'5‡ýÄ-PS\88\SP-<úÆ""!5´¦)‡ ,@) BQ D   $+#"&/!#"&/  u  ‡þÜ™™$þÜ™™$V<‡6:€µ5BK°-PX@(  Y  C Q C D@&  Z  Y  C DY@:98766421/-,(&#"!#!+#"&547!+#"&5<?3#76;>;!323+32%!!õ^)VþÖV*^·ÚRý(ÕW*_*_*Wá(¸Ráý‹*RþÖ»þEþm»  …&!”þFºþp&!þ{,F…˜þÿ \;FQ¡@) ! LA*7BK° PX@2j  h f_ SC TD@1j  h fk SC TDY@NMCB;965$#$ +.'7632.54>?>;#".'+4.'>€¾H  &4EX8 De>4e”_ $ e˜?  '?]A/_WJ7:mŸf  $ž2Tm<P}U,ýƒ,Lc8LqL&bP'!'#|4QwZEe>¨ÉK?"!(%ýÄ (4G[X?-(0K`Wÿ聯'0DX?@<[ [ CSC C SDUS((%"&((($ +#".54>324.#"32>>;+#".54>324.#"32>ª0Rl<>mP..Pm>>lQ/L#=Q--P<##mP..Pm>>lQ.L#7>;#"&/#".54>7.54>32>7&'“CxY5  ":W;*YÍ-l~OK’sG4Z|HGG3^ƒþ‰;^s8F}m[&þ5BkJ(—.Ng8 D<*%C\7R–VþHHœI ^Ç[þÜÉ4X?$2aZHƒmVP—VFwX2ûÙMrM& 9N/ÃN_n´¦‡ @BQ D $+#"&/  ‡þÜ™™$ªþ÷õö³(+.54>7ýyx*Mi@@iM* wyvàþe¿ vÝÛÜuuÝÛÝv Àþebþ÷­ö³(+4'&54?'&5476Zyw *Lj@@jL*xyvá›À vÝÛÝuuÜÛÝv ¿›{¢Õ51@.1-,($# BMQE55+5467'7>7./7.=3>?'.'t ¼¼¾½4 ¼¼½¾ Õ m,n n-n ÖÕ m-m n-n Ömµ† ,@)MYQE +!!#!5!i¯þQNþR®†þ=Iþ;ÅIÃnÿª@B ?S D"+74632'&5467>7#"&n0'+/(8"  %/U#2;0)URL    '5@$2d)5z@MQE+!!dÑþ/zQiÿñ"ª@SD($+74>32#"&i!""'5M""!5ÿûÿ¬È¯@kD""++6;[ 'n')-Þ%Iÿñ?—'@SCSD((($+#".54>324.#"32>?Pйii¸ŠOOЏii¹ŠPdBo•RR”pBBp”RR•oBĺþð²WW²ºº²WW²þðº¬ô›HH›ô¬­ó›GG›óö*@' Bh CR D%+%!47#"/3!!5EþÈ  ™J5ý'J|"&þë (iú½J—2>@;. BhSCQ D-,+)$" 22+2>3!2!5467>54.#"#"&#'>ZQ“pB1To=þE E#cüo ãCqW9 0 Kr–—/_`P‰~w>þ<8" é=rtzENsJ$)Ie<#[Žb3ÿð—HU@RD Bhh[SCSDB@;910/.&$HH +2#".'763232>54.#5>54.#"#"/>eQm?+Kf<œ Fz¦ay¡h:& #4KeBZ‡[-)`s\Œ^02Tp=CpV:1 Jr–—-Z†XEoT9·‰W•l=Ah@ 599-<]s6ApQ/E-NlAKnH"(He>#[Žb3?^‰ @[ C D!#+!+#!"&/3467![ èVýe ÇUVý^â4 þj– /¬Â&üДÿðì‡/@@=-,Bh[Q CSD(#&(#"+#!>32#".'763232>54.#"'!ÆþUCx7n¨q:N…²d;j^N  1MiFRŽi;-[‹^9€H>o|^þ(?qœ\p±|B#.'$5d“]L€[3sˆÿð‡02@/B[ CSD-+#! +2#".54676;>32>54.#"t[šo?E{¬gc¤u@T`‚"TþZ4;¢þÑ1Z‚QVŠa43]€NX‹`2N7.54>32'2>54.#"2>54.#"Df©yD-RqD>`A"9j—_^—k9"B`>DqR.Dy©fQ…`4Dj€<<€jD4_†QQwN%*QuKKvQ*%Nw6fZNzZ<>Vi32+>74.#"32>=W“k32#"&4>32#"&‘!""'5!""'5M""!5D""!5‘ÿJÇ-H@ (B?K°#PX@SCS D@[S DY¶,*$($+4>32#"&4632'&5467>7#"&‘!""'50'+/(8"  %/j""!5ý#2;0)URL    '5@$2  Ž5³(+ î ý×!'*) ý´A þè þç A‚¦âáe!@YMQE+!!!!¦;üÅ;üÅ,JƒJû é5³(+546767.'&=û *("ýÖî A   Aþ)!ÿñ¿—(:9@6BhfSCSD(&#-$+>32#'54>54.#"#"'4>32#"&!DQ`8F|]7/HTI3 A0GSG0*H^3BaB&±!""'5 2'*NqFLnS?78"¤«)>:3232>54.#"3267632#"$&546$32%2>7.#"XY8R8R6A}¶t5]%[ %09hP/a§Þ}‰õ¹mqÂ’”õd pþó¤¥þâÔzvÌ›†ö½q:eˆýû$MG>P3"Z“g9(;¼]W]S'D\5U«‰Vþ¥.I,7 54&#!Ò—y±t9'JkE§´@z²qþ›c»¾3`ŒYþœ3b‹Y*µ½þχ0]†U8hXA¬Zd5¦ý¬¡’CkK(K2Rh7“|ÿð—2D@ABhfSCSD,*"  22+%2#".54>32#".#"32>7>Ö (,h{“X“õ¯be·ÿšM€ob/ +=TnFØœWV˜ÏyLwdV* î+/K5g½ ¤¡ ¿i+@*- %Y¥ë’–ì£W(:'Òy‡ @S CS D!(!$+#!!24.#!!2>yb²ø—ýü—ø²bjR•Ò€þc€Ò•RĤþú¸b‡b¸þú£”è TûT èÒ"‡ .@+YQ CQ D +!!!!!"ýlý”éü°‡UýÄSý²U‡Ò"‡ (@%YQ C D +!!!#"ý…ý{g‡Uý­Uýv‡|ÿðC—4H@E! Bh[SCSD,*%# 44+%2>7!"&=!#".546$32#"'.#"2Ixg]/þð ‡5r‚–Y™ýµdd¸£Q‰vf/  9a’hŠÞUV›ÙC+} 8þ %:'g½ ¤¤ ½g+?),,0&X¤ì“–î¥XÒ‡ @Y C D+!#!#3!3güŠggvg¦ýZ‡ýl”÷^‡@ C D+!#3^gg‡Yÿðº‡'@$Bh CSD#'$+#"&'>7>3232>53º;mb-\1  #-LzV-gØw¶|? 0c—f±ð‰"&@#B[ C D'(' +32>7>;#"&'.+#3VT ;Sý–!ŒS ý©  [ffú  K ý…ýX d  ýV‰Òׇ@ CR D+%!!38ŸüûfWW‡ÒH‡ %@"Bh C D!5(+>7>;#47+"'#32z  ,GZýÓýÄYF;œ × úy¶ü#ÞûJ‡ ü(Ò‡@ C D!+2&53#"'#3 ”Z1ühY2‡ û6£úyÌûY‡{ÿñÀ—'@SCSD((($+#".54>324.#"32>Àb²ø—•ø²cc²ø•—ø²bjR•Ò€~Ñ—SS—Ñ~€Ò•RĤþõ½gg½ ¤£ ½hg¾þõ£”ì¤WW¤ì”•ë¤VV¤ëðS‡ *@'[S C D !+#!2#%!2>54&#!VfmùýE‚ºuþù^”g7ÉÇþù:ýƇÚÊ]œq?R4\}J£°{þÈú—0T@  BK°PX@SCSCD@kSCSDY¶(((%&+#"&'#".54>324.#"32>À+PsHpV þÒ?ŒN•ø²cc²ø•—ø²bjR•Ò€~Ñ—SS—Ñ~€Ò•RÄk½Ÿ~-þv Fg½ ¤£ ½hg¾þõ£”ì¤WW¤ì”•ë¤VV¤ëðÀ‡"2@/ B[S C D" *!+#!2#"&'.#'32>54&#!Vfiõò8i”]çY þ;')¹ö^•g6ƽþý€ý€‡½·Q†eA ýœ >L-TvJ—’Wÿð¾—==@:=BhfSCSD;9(&!#!+#".#"#"&'763232>54.54>32~  )FiMMuO)=d†d==t¨lŒÊL  (8J`=T„[0=d†d=6h™bn¯HÛ%-&,Ld7H^B-,5PuWYtCcV,#)#3XyEKaA,*4Qy[G„e=FH#n‡ @Q C D+!#!5nþfþ ‡WúÐ0WÅÿïõ‡#@  CSD +%2>53#".53Ýf¡p;fJ‹Ç||Ç‹Jg;o¡IG~«ckü•uË—VV—Ëukü–c«~Hú‡@B C D* +3267>;#Qâ áPý¸[‡ ûj.46,– úy¤‡' @#B C D,; +32>7>;2>7>;#&'#Ue   eNþC\þ^þ][‡ûu..‹  ûu,-‹ úyÌû4‡@B C D(")!+ 32>7>; #"&'+þe ®Ÿ bþýeþB þJ ]Ö± ý”L ýTý%Œý‘ –‡@ B C D,"+#32>7>;‹gýõY•”ZHý¸H? ýv++Š s‹‡ $@!Q CQ D +!!547!5‹ü~ˆûð „üЇ#ûU%éU¯þøíí '@$[OQE !#+!+32¯>ÎÎþøõ$ ù $ÿøÿ¬Å¯@kD" +32#"&')'n' ¯%ú"kþø¨í !@[OQE!"+46;#"&=!!kÎÎ=þÃä s $ù Ë ±‡@ Bk D,!+3#"&'.'+ <UD ñ  ð G‡ý™ ´þLþïÿ3@MQE+!5üìÍDDV‰Š— @kD  +2#"&/² 6 Ù—î ýkÿðCô'7T@Q-Bh[SC C SD)(/.(7)7"  '' +!"/#".54>754&#"#"/>32%2>7)QXf=3`J-H™ñ©wuHeF, N¬mPxO(þJ:aSH#ŽÍ…?!6H(@,>aD?lP0hŠ•(0(NP3_‡Sýx5->$#32#"&'#"32>54&¬`B®m·Í9o¡ih–6hc ?9ŠZX„Y-¤¯ý†Xfýþm½‹PQOz§f[ýóVE@u£bÞÑXÿóXó,9@6,BhfSCSD(&#(#"+#".#"32>32#".54>321  #:W>V…[01Z€NHa?% FXg8_q?5Wÿó˜¯#q@ BK°%PX@CSCS D@!CSC CSDY@## +!"/#"&54>323%267.#"c B±n·Í9o¡ie”6`þ^c ?:‹XX„Y-¤Zjþþm½‹PKHNúQ@f[ UF@u£bÞÑXÿó ó%0H@EBh[SCSD'&,+&0'0 %% +2#!32>32#".54>"!4.Ti; ý14`ˆTKnK, Oao8f¨xA=s§kMzY7 ,Ppó:o¢ipªs:!)! 4%GˆÅkµƒJG0Z~NPX/n™_¶BK°!PX@SCQC D@[SC DY@!$%! +3'&=354>32#"&#"!!ÇŒ©,PqD? )1S7.54>4.'32>2>54.#"êAo,ÿ—4]‚OVF*0u¦gižk6h[2; -GO3]„Á1Rkuv3&A.-W~RLƒa7þ@dD#$Ec??dE$$Edô ! &_6HuS.B+.-QC>rX5+I`6Or@9.-()ˆ[HuT.ûÉ/9  '1;#,K7 ">V.%B\88]B$$B]88\B%¢°¯-@*BCSC D##+33>32#4&#"¢_E±nR}R*_€„b¨B¯ýŠUe4bŠWý„|ŒŸeXý–G“&@#SCC D +##".54>32_‰    ãüãW!!ÿÎþG“'4@1 BSCCSD$"!$%+#"&'76323265#".54>32;Y<. QN‰    ãûž4XA% 0 XQbW!!¬¨¯0@-B[CC D%(%!+3267>;#"&'.+# . TþI   ÍS þ^-`¯ü‰{ þ_  þ À þ ¯¾¯@C D+#_¯úQ¯¢®ó,8@5+ BCSC D,,##&&! +332>32>32#4&#"#4&#"¢4 BKU/m‚ATc4JvS,`~u4_I+_toQŠ6ã)D1q>Z;1`ŒZý„|’™&KpJý„|‘šaVý¢°ó1@.BCSC D#$!+332>32#4&#"¢4 DµoR}R*_€„b¨Bã–Vi4bŠWý„|ŒŸeXýWÿóâó',@)SCSD'' +2#".54>2>54.#"k©t==t©kk©u==u©kY…Y,,Y…YY…Y--Y…óI†½uu¼†HH†¼uu½†IüK=r¡dc¢s>>s¢cd¡r=¢þ âô$D@ABCSCSCD$$&%!+32>32#"&'"32>54&¢4  B±n¸Ì9n¡if”6Bc¡>9‹YX„Y-£þ C šZjþþm½‹PJIþg\ýöVF@u£bÞÑWþ ˜ô#D@ABCSCSCD##&#+##"&54>32763267.#"˜`B¯l·Í9o¡ig—6 þ“c ?6ŽYX„Y-¤ãú½Xfþþm½‹PNLpü]f[RH@u£bÞÑ¢Ýô8@5 BhCSC D#$%!+332>32#".#"¢20 u-L" !0q‘-ãÒy‰B Œ„ýyYÿðóó<=@:<BhfSCSD:8'%" #!+#".#"#"&'7>3232>54.54>32Æ #9S<6Y?#0NchcN0/Y‚Si˜< &=[C?`@!0NcicN0.UzL[;h4E&/>, ':T5bJ,464ÿðŒ@#x@ !BK°PX@%jhQCSD@#jh[SDY@ ## +"&5#"&=7>;!!32>32²eq ª -9þÇ%2$4&&upu¨ % d þ„Gý]);& **3‰ÿð˜ã-@*BC CSD$!#+32673#"/#".5逃a¨C`5 EµoS|R*ãý„ŒŸcXìü”Vh4bŠW|µã@ BC D,!+!#32>7>;Tþ\KH  J Hã üï*+ ¬è* @#BC D*!); +32>76;2>76;#"'.'+Iû  '  üFþ³FþïþìCã üï**üè-*+ü(üØ#vã@BC D("(!+ 3267>; #"&'+‹þ¦[ $  Xþ¤j[þÒ þÞ Uýæ þZ þýþ¸þh þ ·ã@BCD,""++32>7>;{D¯þ[OO  K IþÀ…¾ üú J:ã @QCQ D+!!547!5!:ý‘dý)qý¡Ï»ü¶K&MK?þøïíH3@0*B[[OSG@>;83-+4.#52>54.54>;+";2#".54>Û):##:)&HiB7!+G3)55)3G+!7BiH&½!7)=)7!7hhi8=hM+* 8P19lii5%>./=%5ijl81P8 *+Mh=8ihhþ Qí@QD+3#KKíø³iþøíH5@2B[[OSGEDCB530-3)++546;2>54.54>7.54>54.+"&=323"}&HiB7 !+G3)55)3G+! 7BiH&)9##9)½7hhi8=hM+* 8P18lji5%=/.>%5iil91P8 *+Mh=8ihh7!7)=)7Œ²üë9@6jk[OSG +2>53#".#"#4>32 %=*N 734632#"&AŽ5'""'5þ &-PS\88\SP-ýÚ÷'5""7Ÿÿãà0;U@R 6&,BjhfkSCTD##'# +.54>?>;#".'>32+M`žr>@{³s $ W‡5  !7P8+KfC' EXh: $þ¼/Y}N,\a3 J‚·tp»‡MÍ ð:.!ü–"' 1%Ò ïb›o@i?q CQ–:@@=3)Bh[SCS D#&#%&%" +46;4>32#"'.#"!#!>3!#!5>5#C¥4g›fLtW>&  ->V327'#"&''7.732>54.#"í($š1š-n?>m-š1™$*)$š2š-n>>m-›1š$)H+Ic88cJ++Jc88cI+Ÿ>m-š2š$*)$š2™-n?>m-š2š$)($š2™-n?8bI++Ib88cJ++JcU(‡ 8@5 B Z Y C D *! +!3267>;!!!!#!5!5!¡ZþZO Z  YPþY[þ’nþ’_þ’nþ’hýg#!'™ üá:v:þ‚~:vþ Qí@YQD+3#3#KKKKíüöþÆü÷‹ÿ}k—H\A@>HZP=#BhfWSDFD-+(&!#!+#".#"#"&'7>3232>54.5467.54>32>54.'( #9S<8[@#2RhmhR2TR6C/Y‚Si˜< &>\D>`B"4VmqmV4[g7E.UzL[;ý£'CX`c-G>$=QZ]+YJ 5F'.D7.07F]>S{#&bEApR0C6" % "32#".54>32Ë W     Uÿòø–,H\Ì@ BK°PX@4hf[[ SCSDK°PX@4hf[[ SCSD@4hf[[ SCSDYY@ YW*,((#&(%! +632#".54>32#".#"3264>32#".732>54.#"8 ; t`¡s@Cx¦c4WJ@ !:[DSˆa55^MU{üS4]„ ¸dd¸¡„]44]„¡¸dc¹ „]4;g´ñŠŠó´hh´óŠŠñ´gž ;FAv¦fd¦wB)! 5bŒWZŒa3)Pc¹ „^44^„ ¹cd¸ „^44^„ ¸d‹óµhhµó‹‹ó¶ii¶ój>5–&4B@? ,Bh[WSD('.-'4(4$##' +#"&/#".54>754&#"#"/>322>755  15=$!<.*]”iDI+;(  0jCehþï!81+¨—!*G D$ %9'$A13LO 0-ubþ´"ŠD8' ›†¹ˆ%µ%(+55›ñ· ·ñ· · z  þÛ þÚ  { z  þÛ þÚ  ¤SáÃ=K° PX@_MQE@kMQEY´+!#!¤=RýÃþ'd)5z@MQE+!!dÑþ/zQUÿòø–/FO¾µ:BK°PX@/h  [ [SCSDK°PX@/h  [ [SCSD@/h  [ [SCSDYY@00OMIG0F0E)!(*,& +4>32#".732>54.#"#32#"&'.#'32654&+U4]„ ¸dd¸¡„]44]„¡¸dc¹ „]4;g´ñŠŠó´hh´óŠŠñ´gàU÷™u  P þòn”ywlv¢Äc¹ „^44^„ ¹cd¸ „^44^„ ¸d‹óµhhµó‹‹ó¶ii¶ó¾þyusvd€þ _ @a[\Tä5%@MQE+!!!ýß%A[Dµ˜'@WSD((($+4>32#".732>54.#"[/Qn??nQ//Qn??nQ/G$>T00T=$$=T00T>$n>mQ..Qm>>lQ//Ql>0T>$$>T00T?$$?TmP 7@4YYMQE  +!!#!5!!!i¯þQNþR®þR«üUþjIþn’I–üIa„;W-g@ + BK°#PX@h[SD@!h[OQEY@(&#! --+2>3!2!546?>54.#"#"&/>V,N9!*8Ï'þ&ë1%'4>H |W1I0(E?;Ò*í89< #4"G7`fb|=W?‘@=BK°PX@,hh[[SD@1hh[[OSGY@;964.-,+#!?? +2#".'7>3232>54.#5>54.#"#"/>\,L7 H).J5[Y'3?I{W/C+BWTB0M73C$  (%*4!4%1K?"2!A;`fω— @kD #++7>3Ø 9 —ý î‰þ ˜ã0@-BCS CD&$!#+32673#"/#"&'#"&5逃a¨C`5 Fªfb„%0ãý„ŒŸcXìü”X\MH+X$þ¸6ÿBć*@'hiS D+##!#".54>3ÄáUþ¸Vg¤r==r¤g‡Rú óú y8b…MP‚\2¥ìo·@OSG($+4>32#".¥$&&$P&&%%„þÍV@  BK° PX@^jTD@jjTDY@+232654.'73#"&'76ž(390A'*=Y]1D()N þï ,&! †`<;0! •„5RO· BK°PX@jjQD@jjMRFY¶$+37#"/733!»ž Ó:“þ†µ%~ ¸ýc1W;”•)@&WSD +2#".54>2654&#"wBjJ''JjBDjK''KjDhiihkii•*OoEEoO**OoEEoO*ýàrq‚‚qr°†Ïˆ)µ'(+7'&547>7.'&54?'&547>7.'&54?ß·  ·ñ·  ·ñ†  &  %  þ† þ…  &  %  þ† †q‹#-O@L! Bh  Z [  CS  D-+(&#"$!" +%3+#5!"/3%37#"/733!47!+>;ó~m?þ»`Cû¹ž Ó:“þ†þßüä ( *÷$Ãà Ø#%~ ¸ýc1wþ}Ø d†K‹-=Ge@b710+ Bhh  Z [  CS  DGEB@=<;:9853/.(&#! -- +2>3!2!546?>54.#"#"&/>%37#"/733!+>;f,N9!*8Ï'þ&ë1%'4>I|ü£ž Ó:“þ†½ ( *Ó1I0(E?;Ò*í89< #4"@>`f%~ ¸ýc1ýb dSqPU_]@N  $SBK° PX@B h  h  [[ \ S CS DK° PX@B h  h  [[ \ SCS DK° PX@B h  h  [[ \ S CS D@B h  h  [[ \ SCS DYYY@!_]ZXUTKHEC=<;:20)' PP!"+%3+#5!"/32#".'7>3232>54.#5>54.#"#*.'>47!+>;ó~m?þ»`CüZ,L7 H).J5[Y'3>J {Âþßüì ( *÷$Ãà ØÅ/C+BWTB0M73C$  (%*4!4%1K?"2!G5`fü¶þ}Ø d*þ•Èó);5@2BhfSCTD('#-$+#".54>?332>324>32#"&ÈDQ`8E}]70GTJ3 A0GTG0*H^3E_@$ þ–!""'5à2')MoGLkM924#£ª*<45E]B6U;#+#O""!5ÿÿûê&$ [ÿÿûê&$ [ÿÿûÍ&$ `ÿÿûµ&$`ÿÿûÓ&$ `ÿÿûý&$`ÿí»‡4@1YYQ CS D" +!!!!!!!+!ûÀüÑHjý¡K‘ý8ý¬ã O† i ‡UýÄSý²U¶þc7.|þ—Oh@e<B M Bhf hSCSC S  DKIA@:820(&!OO +232654.'7.54>32#".#"32>7>32#"&'76(390A'$‰ã¢Ze·ÿšM€ob/ +=TnFØœWV˜ÏyLwdV*  (,eyUY]1D()N þï ,&! sn¼ž¡ ¿i+@*- %Y¥ë’–ì£W(:'+.J5K<;0! ÿÿÒ"ê&( ZÿÿÒ"ê&( ZÿÿÒ"Í&( _ÿÿÒ"Ó&( _ÿÿ™ê&, ÿÿ½Nê&, ÿÿEÍ&, ÿÿ NÓ&, 1°‡!,@)YS CS D!%(!+3!2#!#%4.#!!!!2>1Ø—ø²bb²ø—ýüØR•Ò€þc”þl€Ò•Ríšb¸þú£¤þú¸b­”è Tý¹@ý¦T èÿÿÒµ&1ôÿÿ{ÿñÀê&2 öÿÿ{ÿñÀê&2 öÿÿ{ÿñÀÍ&2 ûÿÿ{ÿñÀµ&2ûÿÿ{ÿñÀÓ&2 û™ôëD ³ (+  ' 7 æþ‘t3þ‹þŠ4uþ5pn þ’þŒ4tþŠ4vp6þo{ÿ—ÀÈ!-9b@ 21&%BK°PX@kCSCSD@jkSCSDY·**%(%$+#"&'+7&54>327>;.#"4&'32>Àb²ø—sÇQƒ ,¹lwc²ø•yÐSo 7¥eoû$^UêG³j~Ñ—SrVNýE«d€Ò•RĤþõ½g=9°ù`·£ ½hC?•Þ`þê° õRí954&#!VffùýE‚ºuþù^”g7ÉÇþùþâ‡þäÚÊ]œq?R4\}J£¯Çÿð'™J>@;BhSC CSDED?='%" JJ+2#"&'7>3232>54.54>54.#"#4>U„Z./GRG/!7EIE7!2Z~Me‘< %\G87:%$1$!):DS8I?*+X‰^üée¡o;ÿÿkÿðC—&DCÖÿÿkÿðC—&DvÖÿÿkÿðC‡&DÛÖÿÿkÿðCi&DâÖÿÿkÿðCW&DjÖÿÿkÿðCº&DàÖkÿðëôBR]¼@:@BK°1PX@5hh  [ S C SD@?hh  [ S CSC SDY@&TSYXS]T]NLDC><7520-,$" BB+2#!32>32#"&'#".54>754&#"#"&/>32>32>5"!4.‡K‚`7 ýl0X|LGdD& JZe4ƒ½/Uly7?lP.H™ñ©wuHeF, M§k~/´þâŽÍ…?!32#".#"32>32#"&'76‰(390A'%Wg8V…[01Z€NHa?% -§jY]1D()N þï ,&! vK‚·sr¼ˆK>5" =r¢ei¢o:"(" :NO<;0! ÿÿXÿó —&HCõÿÿXÿó —&HvõÿÿXÿó ‡&HÛõÿÿXÿó W&Hjõÿÿ"V—&ÈCÌÿÿ›Ñ—&ÈvÌÿÿÿìô‡&ÈÛÌÿÿÿóîW&ÈjÌYÿôØ}6J6@3<2B65@[SD87B@7J8J.,$"+&54?.'.546?7#".54>32.'2>7.#"Æ ~<…J Z¯N™ r?iM*9r¬s[ŸwD:o¢g5kcV Œn¤9X†\2;XvLX…Y-7_}. m!/<0‡ d/zžÂuŠá¡WAy­l`ªI9X<µòK‘ü-Eºt3fP2327>;.#"2>54'h<>=t©k¡lQ !&‡DF=u©kX7W 2ü¿/0ø-vKY‡[.iY‡Z.Pþ WiC¾vu¼†HOmµCÆu½†I1-týÌh¤9§*,?s£ýç>r¢d¿sý]Eÿÿ‰ÿð˜—&XCóÿÿ‰ÿð˜—&Xvóÿÿ‰ÿð˜‡&XÛóÿÿ‰ÿð˜W&Xjóÿÿþ ·—&\vÜ¢þ â¯!@@=BCSCSCD!!&#+3>32#"&'"32>54&¢_B®m¸Ì9n¡if”6Bc¡>9‹YX„Y-£þ ý…Xgýþm½‹PNLþg\ýöVF@u£bÞÑÿÿþ ·W&\jÜþ®c‡&/O@L,BAhZ C CSD('#!&& +2#"&54>7&'!+3"32>!.'F T/L^'1§ýI¦NCfC 23&@2&üzþßó"KB92*›þe ‡úy/A'05 õË++kþ®½ôBRn@k-H 6Bh h  [SC SCSDDCJICRDR?=1/*(%# BB +2#"&54>7&/#".54>754&#"#"&/>3232>2>7  T/L^)4 )QXf=3`J-H™ñ©wuHeF, N¬mPxO(.(@2&ýò:aSH#ŽÍ…?!6Hó"KB ;3+ (@,>aD?lP0hŠ•(0( NP3_‡Sýx "-705 (->$#7!!!!!!#32>$ T/L^&1ýRPýlý”é[.(@2&ó"KB82+‡UýÄSý²U "-705 Xþ® óCNg@d2Bh h  [  SCSCSDEDJIDNEN@>0.+)$"CC +2#"&54>7"#".54>32#!32>3232>"!4.é T/L^!+ f¨xA=s§jTi; ý14`ˆTKnK, 7EP*,&@2&ÊMzY7 ,Ppó"KB4/)GˆÅkµƒJ:o¢ipªs:!)! *" ",505 Ÿ0Z~NPX/¾ã@C D+#_ãüã:‡#@  B CR D+%!!54?3c¡ þrŸüûñfÒÙ? ÔýÞWKeB ]å9¯#@  BC D+7#54?[Ä ±_ñ¯ý¯Y2 TüîîZ4TvÿÿÒê&1 ïÿÿ¢°—&Qv{ÿó7— 4ñ@  BK°PX@+YSCQ C S DK°%PX@)YSCQ C S DK°)PX@3YSCQ C Q C SD@1YSCQ CQ C SDYYY@ 1/%(% +!!!!!#".54>32!4.#"32>7ýlý”éü»%kˆ£]‡ß YY ß‡]£ˆk%Eü°J‡½sr¾‡KK‡¾rs½‡J2ýÄSý²UNRY/f½ ¤£ ½h0ZRMý=”ì¥XX¥ì”•ì£WW£ìWÿóWó2FQa@^. Bh  [ S C SDHG43MLGQHQ><3F4F,*"  22+2#!32>32#"&'#".54>32>2>54.#""!4.òKƒ`7 ýl0Y|LCcE) JZe4‡Â,*Ä”bœm::mžcÂ*E^xýZR{R))R{RS|S))S|?HsR1W(Ieó>s¢cd¡r=n4^†RU‡]1ÿÿWÿð¾ê&6  ÿÿYÿðó—&Vv¼ÿÿWÿð¾Í&6ÿÿYÿðó‡&VÜ£ÿÿ–Ó&< 4ÿÿs‹ê&= hÿÿJ:—&]vµÿÿs‹ä&=kÿÿJ:Ÿ&]ßµÿÿs‹Í&=kÿÿJ:‡&]ܵvþ˜å™#2@/YSCSD##"" +#543>7'&=37>3#"!{` Dl‘X$3`N7 _Àãض$5`N7 eüðbŒ\+0 FpR '¿À¶3FpR½G ‘(‡@Bk D* +#"/&'+73(A œCÚU‘ ££ö ‘(‡ @Bk D' +32?>;# C œ œAÙU‡ £ £öÿÿä5%q5‡ @W D +".5332>53$B[9E(A00A)E9['DZ2%B22B%2ZD'Ìï}Ÿ@SD($+#".54>32}    F!!xsѺ=K°'PX@WSD@[OSGYµ$&($+4>32#".732654&#"x/?##?//?##?/:@32@@23@$<++<$$;,,;$2@@22@@Šþ®Ó +@(B@jSD+2#"&54>732>¶ T/L^-81.(@2&ó"KB"=6, "-705 &»*iQK°PX@WS D@[OSGY@ +2673#".#"#4>32—+,;%6"!;63*,=&6"";537/$?.!'!9-$?.!'!|‰¡— #@ SD   #++7>3!+7>3‘Æ *˜ Rç /¶ —ý îý î.ÿösã\µBK°1PX@SCS D@SC CSDY@!$## ++#!#"&'7632325#5463s¬_þ,mi4 Êã$üe›ý=nt 'œÂ" “¦f@MQE+!!“üífG“¤f@MQE+!!“úïfG‚!.ѳ(+.5467«L=&2#!&K&N;3d6;=nų(+'&547>54'&547ðL=&2#Å&K&N;2e6:=nÿ°³(+7'&547>54'&547ðL=&2#°&K&N;2e6:=‚!;Ñ)µ(+.5467.5467«L=&2#ÏL=&2#!&K&N;3d6;=&K&N;3d6;=n'Å)µ(+'&547>54'&547%'&547>54'&547ðL=&2#JL=&2#Å&K&N;2e6:=&K&N;2e6:=nÿ'°)µ(+7'&547>54'&547%'&547>54'&547ðL=&2#JL=&2#°&K&N;2e6:=&K&N;2e6:= þ–䥄@ BK°PX@CCSCDK°%PX@ZCCD@`ZCDYY@ #!""+463632>72!#"'! M®S  R®Nþƒ  þ„œã þ þý êï þ–ä¥-Å@  &" BK°PX@/  `  [CCSC  DK°%PX@-  `Z  [CC  D@/`  `Z  [C  DYY@+)('%#! !"#+7!!5463632>72!!#.'#"'"&5 |þ„M®S  R®Nþƒ}N®R  S®M¾¾ ã þ ýB þ ã1i|@OSG($+4>32#"..Pj<=lP..Pl=32#"&%4>32#"&%4>32#"&i!""'5Õ!""'5þ!""'5M""!5'""!5'""!5Wÿïz—'0DXl€K@H[   [ CSC C  S D}{sqig_]US((%"&((($+#".54>324.#"32>>;+#".54>324.#"32>%#".54>324.#"32>ª0Rl<>mP..Pm>>lQ/L#=Q--P<##mP..Pm>>lQ.L#mP..Pm>>lQ.L#7.'&54?ß·  ·ñ†  &  %  þ† ÿ6·‡ @ C D#"+'+>;k ( * dÿño•N[@X ; Bh  h [   [SC S  DNMHGFEA?97$##%'$+3>32#".#"!#!!#!32>32#".'#53.547#¤Zжn@m]Q% "1CY:Z–qJZýÀþ Hp–Z>_H4$ %$TfxFq¸‡T¢ž_„Ò’N,A+#%?z´v /2' |¼~@%+% #.K5N–Ù‹;'10HH‡#C@@ BhS  CS  D##!4% +67>;#7+"'#32###5ÓÕ 9>Û ß?9 ÙþWÇGÉò lýÁ»,þ„z*þE?þ”x;ýü;h}—71@.1BSCS D76***+!>54.#"!"&=!5.54>32!#c`žr>V”ÅooÅ”V>qž`þ Éb¦yDe°ë‡‡ë¯eDx¦bÈkOw¡g~¼~??~¼~g¡wOþ•4Þ\ˆ´m‰Ø–OO–؉m´ˆ\Þ4{ÿô™.BC@@4Bh[SCSD0/:8/B0B#+(($+>32#".54>32>54&#"#"&'2>7.#"‘$CCG(W‰_2GŽÔŒQ…`5I„¹q5cVD–Ž1K8%  MRoO +GeEbšk9(If=# HˆÅ~Ãþ²ö‹6f‘[rÉ–V9W;;5-Úê û&G…¿y3cN/I­dKvR+‡ @B CR D+3!7!&'^\Mû ‹àþ*  ‡úySz!+&Mþ ‡ $@!Q CD +##!##5Òbý¯bÒ‡Rùk•ùk•RWþ ü‡&@# BQ CQD+!!!!547 &5W¥ûð…ý{û[ ¡ý` ‡RüìüëR$1. ¤záÃ@MQE+!!¤=üÃÃI(½·"@ Bj[ D*# +#"&=!267>;#5æ:Ï è :ýÒO² ýå.)%'å ùIJ ƒ';OL@IK-B[  O  SG=<)(GE32>32%2>7.#"!2>54.#"î7\NCCN[79hO//Oh97[NCCN\79hO//OhýF,KB;;BK,,M:!!:Mª+M9!!9M+-KB;;BK(BW//VC(+PsGGrP+(CW..WC(+PrGGsP+N&@S--T@&:V78V::V87V:&@T--S@&ÿþ™K—)*@'BSCSD!%)U$+>32#"&#"#"&'7>3232>7ˆ :Vo@'9  4XC- „ ?]xE!A "<`G/ >W‚U+ +  DgHûÚcZ* '  GqQƤÁž/d@a('B[[[ O SG,*%# //  +267#".#"'>32267#".#"'>32 4Xd84ec`.7Wc<4fc_.4Xd84ec`.7Wc<4fc_*-:+/$,$-8-0$,$þÄ-:+.$,$-8-/$,$¦ÓápkK° PX@)^_ ZMQE@'jk ZMQEY@  +!3!!!!#!5!7!¦ÜuGtþÈh þ@vHvþÍThþDe þõJïJþñJï PŽO!@ @MQE+!! î ýÚ''%ýîýÙv> þñ  þð  ?xýèIúPèO!@ @MQE+5467>7.'.=!5!èý &&&ýÚ îýîÙ(þˆ?    >üI‘ÿf÷æ"@ BMQE+3 # >7 .'‘’B’þnBþÀL  Nþ²  ¦@üÀüÀ@ýS­­ÿþþ ¯@CD+3#¯øñk¦aµBK°!PX@SCQC  D@[SC  DY@!$%! +3'&=354>32#"&#"!#!ÇŒ©6g”_!G +!ž¬G`þ{ 'U\“g7 0 ¢¦Rü<}üƒ "„K°!PX@SCQC  DK°1PX@[SC  D@![CSC  DYY@""A15! +3'&=354>32;#.#"!!ÇŒ©4d•`"IE<H_6w0KvQ+ þâ{ 'AX—n>únO/Y~NAGüƒÓþŒRÿ¥ @jD  +2+:  0[ "6M6 •ê @ja  +2#"&'%täG  þÛê ÎØ2EÓ%@OSG&(($+#".54>32#".54632¥    0!    !1 U5ôq@MQE+!!UŸþaq<¹Jê @ja #++7>3JþÜ  FãêØÎ  <Í@Bja, +#"&/.'+73?>;# J œIëZÍ{{Ç2÷Í (@%jOSG   +"&5332653$|v@R``R@v÷pfGQQGbtÎ8{ä@OSG($+#".54>32{  }ÀÌý!@[OSG$&($+4>32#".732654&#"}-="">-->""=-5@32@@23@^";**;"#:**:#2@@22@@*)µ1@.[OSG +2673#".#"#4>32œ*,6"5"!<85*+8$4""<84P7+#;- % 8*"<- % j´å +@(OSG   #++7>3!+7>3„Æ /™ qæ  4µåÎ ¿Î ¿æƒm± @kD  +2+U  3± #;U< . ÄÐU_<õÐÊ“^pÍÓ¡ãÿ6þŒzý ¶þVÒÿ6ÿ:z/‚…æÞ´ˆVˆ˜þWgj»´XªXb {ˆm‹n™d‹iÉÿûˆIˆöˆˆˆ?ˆ”ˆˆˆ‚ˆxˆ¬Û‘Û‘ˆ ˆ¦ˆûå!kl Ò|ðÒžÒxÒÙ|èÒV÷ŒY(ðÿÒÒèÒ;{£ð;{îðW‘#»Å ¶¯¯òsX¯ËÿøXkˆËIVÍkD¬ŽXDWþX„ñK:¢Û–×ÿÎجÛ¾7¢:¢:W.¢DW¢\YÉ4:‰Î™#ΈJX?XXiˆŒ‚…戟ˆCˆŸˆUXê‹I'MUŸjj›ˆ¤™dMUI[ˆm–a–bIÏ:‰ð6¥I„–•ëWj°††”Så*      7ÿí|žÒžÒžÒžÒVV½VV '1èÒ;{;{;{;{;{ˆ™;{»Å»Å»Å»Å¯£ð†ÇÍkÍkÍkÍkÍkÍkIkŽXþXþXþXþXÛ"Û›ÛÿìÛÿó0Y:¢:W:W:W:W:Wˆm:W:‰:‰:‰:‰Î.¢Î Ík|ŽXÈÒþXÛ¾*:X9èÒ:¢³{µW:W\YW\Y¯òsˆJòsˆJòsˆJˆvI I II5IÌIxIŠI&I|–.:“7“‹‚‹n‹n™‚™n™nˆ ˆ ˆ`iÒWk›k°ñÿ6ˆ“Hæhˆ{TMTWˆ¤c(TJÿˆÆˆ¦ˆ ˆúˆ‘ÿþ*lIÓIIIUI¹I I I2IÎI}I*IjIælà#` Ò ÿVÿ ÿBÿ ÿV#ÿÐ$ÿV&ÿÐ*ÿÐ2ÿÐ4ÿÐ97:7<?7DÿÂFÿ«Gÿ«Hÿ«Rÿ«Tÿ«mÿBoÿByÿB}ÿB‚ÿVƒÿV„ÿV…ÿV†ÿV‡ÿVˆÿV‰ÿДÿЕÿЖÿЗÿИÿКÿП¢ÿ£ÿ¤ÿÂ¥ÿ¦ÿ§ÿ¨ÿ©ÿ«ªÿ««ÿ«¬ÿ«­ÿ«²ÿ«´ÿ«µÿ«¶ÿ«·ÿ«¸ÿ«ºÿ«ÂÿVÃÿÂÄÿÐÅÿ«Çÿ«ÍÿÐÎÿ«ÓåÿBæÿBéÿ ìÿ ïÿBòÿBóÿBùÿV ÿV ÿ ÿB ÿ ÿV #ÿÐ $ÿV &ÿÐ *ÿÐ 2ÿÐ 4ÿÐ 97 :7 < ?7 Dÿ Fÿ« Gÿ« Hÿ« Rÿ« Tÿ« mÿB oÿB yÿB }ÿB ‚ÿV ƒÿV „ÿV …ÿV †ÿV ‡ÿV ˆÿV ‰ÿÐ ”ÿÐ •ÿÐ –ÿÐ —ÿÐ ˜ÿÐ šÿÐ Ÿ ¢ÿ £ÿ ¤ÿ ¥ÿ ¦ÿ §ÿ ¨ÿ ©ÿ« ªÿ« «ÿ« ¬ÿ« ­ÿ« ²ÿ« ´ÿ« µÿ« ¶ÿ« ·ÿ« ¸ÿ« ºÿ« ÂÿV Ãÿ ÄÿÐ Åÿ« Çÿ« ÍÿÐ Îÿ« Ó åÿB æÿB éÿ ìÿ ïÿB òÿB óÿB ùÿV #ÿØ &ÿØ *ÿØ 2ÿØ 4ÿØ Fÿç Gÿç Hÿç Rÿç Tÿç ‰ÿØ ”ÿØ •ÿØ –ÿØ —ÿØ ˜ÿØ šÿØ ©ÿç ªÿç «ÿç ¬ÿç ­ÿç ²ÿç ´ÿç µÿç ¶ÿç ·ÿç ¸ÿç ºÿç ÄÿØ Åÿç Çÿç ÍÿØ Îÿç ÿV ÿ ÿB ÿ ÿV #ÿÐ $ÿV &ÿÐ *ÿÐ 2ÿÐ 4ÿÐ 97 :7 < ?7 Dÿ Fÿ« Gÿ« Hÿ« Rÿ« Tÿ« mÿB oÿB yÿB }ÿB ‚ÿV ƒÿV „ÿV …ÿV †ÿV ‡ÿV ˆÿV ‰ÿÐ ”ÿÐ •ÿÐ –ÿÐ —ÿÐ ˜ÿÐ šÿÐ Ÿ ¢ÿ £ÿ ¤ÿ ¥ÿ ¦ÿ §ÿ ¨ÿ ©ÿ« ªÿ« «ÿ« ¬ÿ« ­ÿ« ²ÿ« ´ÿ« µÿ« ¶ÿ« ·ÿ« ¸ÿ« ºÿ« ÂÿV Ãÿ ÄÿÐ Åÿ« Çÿ« ÍÿÐ Îÿ« Ó åÿB æÿB éÿ ìÿ ïÿB òÿB óÿB ùÿVÿ  ÿ  ÿ ÿv#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿL9ÿL:ÿ’<ÿy?ÿLYÿƒZÿÎ\ÿƒlÿ mÿvoÿvrÿ yÿv|ÿ }ÿv‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿyÄÿÆÍÿÆÓÿyåÿvæÿvçÿ èÿ êÿ ëÿ ïÿvòÿvóÿvöÿ ÿB ÿÝ ÿB ÿBÿvÿvÿÝ$ÿÝ7ÿL9ÿ—:ÿç;ÿÉ<ÿ`=ÿÕ?ÿ—lÿBrÿB|ÿB‚ÿ݃ÿÝ„ÿÝ…ÿ݆ÿ݇ÿ݈ÿÝŸÿ`ÂÿÝÓÿ`ÔÿÕÖÿÕØÿÕçÿBèÿBéÿvêÿBëÿBìÿvöÿBùÿÝÿ  ÿ  ÿ ÿv#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆ7ÿL9ÿL:ÿ’<ÿy?ÿLYÿƒZÿÎ\ÿƒlÿ mÿvoÿvrÿ yÿv|ÿ }ÿv‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆŸÿyÄÿÆÍÿÆÓÿyåÿvæÿvçÿ èÿ êÿ ëÿ ïÿvòÿvóÿvöÿ 7 ÿ¡ 7 7ÿGÿ—ÿGÿ¡ÿ·ÿ·"<#ÿÓ$ÿ¡&ÿÓ*ÿÓ-ÿo2ÿÓ4ÿÓDÿ”Fÿ”Gÿ”Hÿ”IÿâJÿ{Pÿ·Qÿ·Rÿ”Sÿ·Tÿ”Uÿ·Vÿ™WÿÝXÿ·Yÿß[ÿÝ\ÿß]ÿºl7mÿ—oÿ—r7tFuFwÿ·yÿ—{F|7}ÿ—‚ÿ¡ƒÿ¡„ÿ¡…ÿ¡†ÿ¡‡ÿ¡ˆÿ¡‰ÿÓ”ÿÓ•ÿÓ–ÿÓ—ÿÓ˜ÿÓšÿÓ¢ÿ”£ÿ”¤ÿ”¥ÿ”¦ÿ”§ÿ”¨ÿ”©ÿ”ªÿ”«ÿ”¬ÿ”­ÿ”²ÿ”³ÿ·´ÿ”µÿ”¶ÿ”·ÿ”¸ÿ”ºÿ”»ÿ·¼ÿ·½ÿ·¾ÿ·Âÿ¡Ãÿ”ÄÿÓÅÿ”Çÿ”Ìÿ·ÍÿÓÎÿ”åÿ—æÿ—ç7è7éÿGê7ë7ìÿGïÿ—òÿ—óÿ—ö7ùÿ¡#ÿÐ# ÿâ# ÿÐ# ÿØ# ÿÐ#ÿÆ#ÿÆ#ÿâ#$ÿâ#7ÿˆ#9ÿÓ#;ÿâ#<ÿ°#=ÿµ#?ÿÓ#@ÿØ#`ÿØ#lÿÐ#rÿÐ#|ÿÐ#‚ÿâ#ƒÿâ#„ÿâ#…ÿâ#†ÿâ#‡ÿâ#ˆÿâ#Ÿÿ°#Âÿâ#Óÿ°#Ôÿµ#Öÿµ#Øÿµ#çÿÐ#èÿÐ#éÿÆ#êÿÐ#ëÿÐ#ìÿÆ#öÿÐ#ùÿâ$ÿV$ ÿV$ ÿV$ÿÝ$"ÿÐ$#ÿâ$&ÿâ$*ÿâ$--$2ÿâ$4ÿâ$7ÿ$8ÿÆ$9ÿ¡$:ÿÄ$<ÿt$?ÿ¡$Yÿº$\ÿº$lÿV$mÿÝ$oÿÝ$rÿV$tÿ`$uÿ`$yÿÝ${ÿ`$|ÿV$}ÿÝ$‰ÿâ$”ÿâ$•ÿâ$–ÿâ$—ÿâ$˜ÿâ$šÿâ$›ÿÆ$œÿÆ$ÿÆ$žÿÆ$Ÿÿt$Äÿâ$Íÿâ$Óÿt$åÿÝ$æÿÝ$çÿV$èÿV$êÿV$ëÿV$ïÿÝ$òÿÝ$óÿÝ$öÿV&ÿe&mÿe&oÿe&yÿe&}ÿe&åÿe&æÿe&ïÿe&òÿe&óÿe'ÿÐ' ÿâ' ÿÐ' ÿØ' ÿÐ'ÿÆ'ÿÆ'ÿâ'$ÿâ'7ÿˆ'9ÿÓ';ÿâ'<ÿ°'=ÿµ'?ÿÓ'@ÿØ'`ÿØ'lÿÐ'rÿÐ'|ÿÐ'‚ÿâ'ƒÿâ'„ÿâ'…ÿâ'†ÿâ'‡ÿâ'ˆÿâ'Ÿÿ°'Âÿâ'Óÿ°'Ôÿµ'Öÿµ'Øÿµ'çÿÐ'èÿÐ'éÿÆ'êÿÐ'ëÿÐ'ìÿÆ'öÿÐ'ùÿâ) ÿ)ÿL)ÿL)ÿ)ÿÄ)ÿÄ)")$ÿ)-ÿ.)Fÿµ)Gÿµ)Hÿµ)PÿÄ)QÿÄ)Rÿµ)SÿÄ)Tÿµ)UÿÄ)XÿÄ)wÿÄ)‚ÿ)ƒÿ)„ÿ)…ÿ)†ÿ)‡ÿ)ˆÿ)©ÿµ)ªÿµ)«ÿµ)¬ÿµ)­ÿµ)²ÿµ)³ÿÄ)´ÿµ)µÿµ)¶ÿµ)·ÿµ)¸ÿµ)ºÿµ)»ÿÄ)¼ÿÄ)½ÿÄ)¾ÿÄ)Âÿ)Åÿµ)Çÿµ)ÌÿÄ)Îÿµ)éÿL)ìÿL)ùÿ- ÿÆ-ÿÎ-ÿÎ-ÿÆ-$ÿÆ-‚ÿÆ-ƒÿÆ-„ÿÆ-…ÿÆ-†ÿÆ-‡ÿÆ-ˆÿÆ-ÂÿÆ-éÿÎ-ìÿÎ-ùÿÆ.ÿÉ.#ÿâ.&ÿâ.*ÿâ.2ÿâ.4ÿâ.Fÿä.Gÿä.Hÿä.IÿÓ.Rÿä.Tÿä.Wÿº.YÿÆ.ZÿÆ.\ÿÆ.mÿÉ.oÿÉ.yÿÉ.}ÿÉ.‰ÿâ.”ÿâ.•ÿâ.–ÿâ.—ÿâ.˜ÿâ.šÿâ.©ÿä.ªÿä.«ÿä.¬ÿä.­ÿä.²ÿä.´ÿä.µÿä.¶ÿä.·ÿä.¸ÿä.ºÿä.Äÿâ.Åÿä.Çÿä.Íÿâ.Îÿä.åÿÉ.æÿÉ.ïÿÉ.òÿÉ.óÿÉ/þÙ/ þÙ/ þÙ/4/ÿ$/4/"ÿÎ/#ÿ°/&ÿ°/*ÿ°/2ÿ°/4ÿ°/7ÿ[/9ÿV/:ÿy/<ÿ=/?ÿV/Fÿß/Gÿß/Hÿß/Rÿß/Tÿß/Yÿ£/Zÿ°/\ÿ£/lþÙ/mÿ$/oÿ$/rþÙ/tÿB/uÿB/yÿ$/{ÿB/|þÙ/}ÿ$/‰ÿ°/”ÿ°/•ÿ°/–ÿ°/—ÿ°/˜ÿ°/šÿ°/Ÿÿ=/©ÿß/ªÿß/«ÿß/¬ÿß/­ÿß/²ÿß/´ÿß/µÿß/¶ÿß/·ÿß/¸ÿß/ºÿß/Äÿ°/Åÿß/Çÿß/Íÿ°/Îÿß/Óÿ=/åÿ$/æÿ$/çþÙ/èþÙ/é4/êþÙ/ëþÙ/ì4/ïÿ$/òÿ$/óÿ$/öþÙ2ÿÐ2 ÿâ2 ÿÐ2 ÿØ2 ÿÐ2ÿÆ2ÿÆ2ÿâ2$ÿâ27ÿˆ29ÿÓ2;ÿâ2<ÿ°2=ÿµ2?ÿÓ2@ÿØ2`ÿØ2lÿÐ2rÿÐ2|ÿÐ2‚ÿâ2ƒÿâ2„ÿâ2…ÿâ2†ÿâ2‡ÿâ2ˆÿâ2Ÿÿ°2Âÿâ2Óÿ°2Ôÿµ2Öÿµ2Øÿµ2çÿÐ2èÿÐ2éÿÆ2êÿÐ2ëÿÐ2ìÿÆ2öÿÐ2ùÿâ3 ÿŠ3ÿ3ÿ3ÿŠ3$ÿŠ3-ÿV3DÿÎ3Fÿâ3Gÿâ3Hÿâ3Rÿâ3Tÿâ3‚ÿŠ3ƒÿŠ3„ÿŠ3…ÿŠ3†ÿŠ3‡ÿŠ3ˆÿŠ3¢ÿÎ3£ÿÎ3¤ÿÎ3¥ÿÎ3¦ÿÎ3§ÿÎ3¨ÿÎ3©ÿâ3ªÿâ3«ÿâ3¬ÿâ3­ÿâ3²ÿâ3´ÿâ3µÿâ3¶ÿâ3·ÿâ3¸ÿâ3ºÿâ3ÂÿŠ3ÃÿÎ3Åÿâ3Çÿâ3Îÿâ3éÿ3ìÿ3ùÿŠ4ÿÐ4 ÿâ4 ÿÐ4 ÿØ4 ÿÐ4ÿÆ4ÿÆ4ÿâ4$ÿâ47ÿˆ49ÿÓ4;ÿâ4<ÿ°4=ÿµ4?ÿÓ4@ÿØ4`ÿØ4lÿÐ4rÿÐ4|ÿÐ4‚ÿâ4ƒÿâ4„ÿâ4…ÿâ4†ÿâ4‡ÿâ4ˆÿâ4Ÿÿ°4Âÿâ4Óÿ°4Ôÿµ4Öÿµ4Øÿµ4çÿÐ4èÿÐ4éÿÆ4êÿÐ4ëÿÐ4ìÿÆ4öÿÐ4ùÿâ5#ÿÕ5&ÿÕ5*ÿÕ52ÿÕ54ÿÕ57ÿÓ58ÿâ5‰ÿÕ5”ÿÕ5•ÿÕ5–ÿÕ5—ÿÕ5˜ÿÕ5šÿÕ5›ÿâ5œÿâ5ÿâ5žÿâ5ÄÿÕ5ÍÿÕ7 ÿ7ÿL7ÿL7ÿL7ÿ7ÿ`7ÿ`7#ÿˆ7$ÿ7&ÿˆ7*ÿˆ7-ÿ872ÿˆ74ÿˆ7Dÿ7Fÿ)7Gÿ)7Hÿ)7Jÿ>7Pÿ`7Qÿ`7Rÿ)7Sÿ`7Tÿ)7Uÿ`7VÿD7Xÿ`7YÿL7Zÿt7[ÿg7\ÿL7]ÿˆ7mÿL7oÿL7wÿ`7yÿL7}ÿL7‚ÿ7ƒÿ7„ÿ7…ÿ7†ÿ7‡ÿ7ˆÿ7‰ÿˆ7”ÿˆ7•ÿˆ7–ÿˆ7—ÿˆ7˜ÿˆ7šÿˆ7¢ÿ7£ÿ7¤ÿ7¥ÿ7¦ÿ7§ÿ7¨ÿ7©ÿ)7ªÿ)7«ÿ)7¬ÿ)7­ÿ)7²ÿ)7³ÿ`7´ÿ)7µÿ)7¶ÿ)7·ÿ)7¸ÿ)7ºÿ)7»ÿ`7¼ÿ`7½ÿ`7¾ÿ`7Âÿ7Ãÿ7Äÿˆ7Åÿ)7Çÿ)7Ìÿ`7Íÿˆ7Îÿ)7åÿL7æÿL7éÿL7ìÿL7ïÿL7òÿL7óÿL7ùÿ8 ÿÆ8ÿÎ8ÿÎ8ÿÆ8$ÿÆ8‚ÿÆ8ƒÿÆ8„ÿÆ8…ÿÆ8†ÿÆ8‡ÿÆ8ˆÿÆ8ÂÿÆ8éÿÎ8ìÿÎ8ùÿÆ979 ÿ¡9 79 79ÿG9ÿ—9ÿG9ÿ¡9ÿ·9ÿ·9"<9#ÿÓ9$ÿ¡9&ÿÓ9*ÿÓ9-ÿo92ÿÓ94ÿÓ9Dÿ”9Fÿ”9Gÿ”9Hÿ”9Iÿâ9Jÿ{9Pÿ·9Qÿ·9Rÿ”9Sÿ·9Tÿ”9Uÿ·9Vÿ™9WÿÝ9Xÿ·9Yÿß9[ÿÝ9\ÿß9]ÿº9l79mÿ—9oÿ—9r79tF9uF9wÿ·9yÿ—9{F9|79}ÿ—9‚ÿ¡9ƒÿ¡9„ÿ¡9…ÿ¡9†ÿ¡9‡ÿ¡9ˆÿ¡9‰ÿÓ9”ÿÓ9•ÿÓ9–ÿÓ9—ÿÓ9˜ÿÓ9šÿÓ9¢ÿ”9£ÿ”9¤ÿ”9¥ÿ”9¦ÿ”9§ÿ”9¨ÿ”9©ÿ”9ªÿ”9«ÿ”9¬ÿ”9­ÿ”9²ÿ”9³ÿ·9´ÿ”9µÿ”9¶ÿ”9·ÿ”9¸ÿ”9ºÿ”9»ÿ·9¼ÿ·9½ÿ·9¾ÿ·9Âÿ¡9Ãÿ”9ÄÿÓ9Åÿ”9Çÿ”9Ìÿ·9ÍÿÓ9Îÿ”9åÿ—9æÿ—9ç79è79éÿG9ê79ë79ìÿG9ïÿ—9òÿ—9óÿ—9ö79ùÿ¡:7: ÿ¿: 7: 7:ÿ’:ÿç:ÿ’:ÿ¿:"%:$ÿ¿:-ÿ¦:Dÿ·:Fÿì:Gÿì:Hÿì:Jÿ:Rÿì:Tÿì:VÿÚ:l7:mÿç:oÿç:r7:t7:u7:yÿç:{7:|7:}ÿç:‚ÿ¿:ƒÿ¿:„ÿ¿:…ÿ¿:†ÿ¿:‡ÿ¿:ˆÿ¿:¢ÿ·:£ÿ·:¤ÿ·:¥ÿ·:¦ÿ·:§ÿ·:¨ÿ·:©ÿì:ªÿì:«ÿì:¬ÿì:­ÿì:²ÿì:´ÿì:µÿì:¶ÿì:·ÿì:¸ÿì:ºÿì:Âÿ¿:Ãÿ·:Åÿì:Çÿì:Îÿì:åÿç:æÿç:ç7:è7:éÿ’:ê7:ë7:ìÿ’:ïÿç:òÿç:óÿç:ö7:ùÿ¿;ÿÉ;#ÿâ;&ÿâ;*ÿâ;2ÿâ;4ÿâ;Fÿä;Gÿä;Hÿä;IÿÓ;Rÿä;Tÿä;Wÿº;YÿÆ;ZÿÆ;\ÿÆ;mÿÉ;oÿÉ;yÿÉ;}ÿÉ;‰ÿâ;”ÿâ;•ÿâ;–ÿâ;—ÿâ;˜ÿâ;šÿâ;©ÿä;ªÿä;«ÿä;¬ÿä;­ÿä;²ÿä;´ÿä;µÿä;¶ÿä;·ÿä;¸ÿä;ºÿä;Äÿâ;Åÿä;Çÿä;Íÿâ;Îÿä;åÿÉ;æÿÉ;ïÿÉ;òÿÉ;óÿÉ<< ÿt< < <ÿy<ÿ`<ÿy<ÿt<ÿ’<ÿ’<"%<#ÿ°<$ÿt<&ÿ°<*ÿ°<-ÿ8<2ÿ°<4ÿ°<Dÿ”<Fÿ`<Gÿ`<Hÿ`<Jÿ[<Pÿ’<Qÿ’<Rÿ`<Sÿ’<Tÿ`<Uÿ’<Vÿ<Xÿ’<Yÿœ<Zÿ«<[ÿƒ<\ÿœ<l<mÿ`<oÿ`<r<t-<u-<wÿ’<yÿ`<{-<|<}ÿ`<‚ÿt<ƒÿt<„ÿt<…ÿt<†ÿt<‡ÿt<ˆÿt<‰ÿ°<”ÿ°<•ÿ°<–ÿ°<—ÿ°<˜ÿ°<šÿ°<¢ÿ”<£ÿ”<¤ÿ”<¥ÿ”<¦ÿ”<§ÿ”<¨ÿ”<©ÿ`<ªÿ`<«ÿ`<¬ÿ`<­ÿ`<²ÿ`<³ÿ’<´ÿ`<µÿ`<¶ÿ`<·ÿ`<¸ÿ`<ºÿ`<»ÿ’<¼ÿ’<½ÿ’<¾ÿ’<Âÿt<Ãÿ”<Äÿ°<Åÿ`<Çÿ`<Ìÿ’<Íÿ°<Îÿ`<åÿ`<æÿ`<ç<è<éÿy<ê<ë<ìÿy<ïÿ`<òÿ`<óÿ`<ö<ùÿt=ÿµ="%=#ÿº=&ÿº=*ÿº=2ÿº=4ÿº=FÿÎ=GÿÎ=HÿÎ=RÿÎ=TÿÎ=VÿØ=YÿÓ=\ÿÓ=mÿµ=oÿµ=yÿµ=}ÿµ=‰ÿº=”ÿº=•ÿº=–ÿº=—ÿº=˜ÿº=šÿº=©ÿÎ=ªÿÎ=«ÿÎ=¬ÿÎ=­ÿÎ=²ÿÎ=´ÿÎ=µÿÎ=¶ÿÎ=·ÿÎ=¸ÿÎ=ºÿÎ=Äÿº=ÅÿÎ=ÇÿÎ=Íÿº=ÎÿÎ=åÿµ=æÿµ=ïÿµ=òÿµ=óÿµ>#ÿØ>&ÿØ>*ÿØ>2ÿØ>4ÿØ>Fÿç>Gÿç>Hÿç>Rÿç>Tÿç>‰ÿØ>”ÿØ>•ÿØ>–ÿØ>—ÿØ>˜ÿØ>šÿØ>©ÿç>ªÿç>«ÿç>¬ÿç>­ÿç>²ÿç>´ÿç>µÿç>¶ÿç>·ÿç>¸ÿç>ºÿç>ÄÿØ>Åÿç>Çÿç>ÍÿØ>Îÿç?ÿV? ÿV? ÿV?ÿÝ?"ÿÐ?#ÿâ?&ÿâ?*ÿâ?--?2ÿâ?4ÿâ?7ÿ?8ÿÆ?9ÿ¡?:ÿÄ?<ÿt??ÿ¡?Yÿº?\ÿº?lÿV?mÿÝ?oÿÝ?rÿV?tÿ`?uÿ`?yÿÝ?{ÿ`?|ÿV?}ÿÝ?‰ÿâ?”ÿâ?•ÿâ?–ÿâ?—ÿâ?˜ÿâ?šÿâ?›ÿÆ?œÿÆ?ÿÆ?žÿÆ?Ÿÿt?Äÿâ?Íÿâ?Óÿt?åÿÝ?æÿÝ?çÿV?èÿV?êÿV?ëÿV?ïÿÝ?òÿÝ?óÿÝ?öÿVDÿ¿D ÿ¿D ÿ¿DYÿçDZÿóD\ÿçDlÿ¿Drÿ¿Dtÿ¿Duÿ¿D{ÿ¿D|ÿ¿Dçÿ¿Dèÿ¿Dêÿ¿Dëÿ¿Döÿ¿Eÿ«E ÿ«E ÿçE ÿ«E9ÿ”E:ÿìE?ÿ”E@ÿçEYÿîE[ÿÄE\ÿîE`ÿçElÿ«Erÿ«E|ÿ«Eçÿ«Eèÿ«Eêÿ«Eëÿ«Eöÿ«Hÿ«H ÿ«H ÿçH ÿ«H9ÿ”H:ÿìH?ÿ”H@ÿçHYÿîH[ÿÄH\ÿîH`ÿçHlÿ«Hrÿ«H|ÿ«Hçÿ«Hèÿ«Hêÿ«Hëÿ«Höÿ«IKI KI KIÿyIÿyIlKIrKItdIudI{dI|KIçKIèKIéÿyIêKIëKIìÿyIöKKÿ¿K ÿ¿K ÿ¿KYÿçKZÿóK\ÿçKlÿ¿Krÿ¿Ktÿ¿Kuÿ¿K{ÿ¿K|ÿ¿Kçÿ¿Kèÿ¿Kêÿ¿Këÿ¿Köÿ¿NFÿÄNGÿÄNHÿÄNRÿÄNTÿÄN©ÿÄNªÿÄN«ÿÄN¬ÿÄN­ÿÄN²ÿÄN´ÿÄNµÿÄN¶ÿÄN·ÿÄN¸ÿÄNºÿÄNÅÿÄNÇÿÄNÎÿÄPÿ¿P ÿ¿P ÿ¿PYÿçPZÿóP\ÿçPlÿ¿Prÿ¿Ptÿ¿Puÿ¿P{ÿ¿P|ÿ¿Pçÿ¿Pèÿ¿Pêÿ¿Pëÿ¿Pöÿ¿Qÿ¿Q ÿ¿Q ÿ¿QYÿçQZÿóQ\ÿçQlÿ¿Qrÿ¿Qtÿ¿Quÿ¿Q{ÿ¿Q|ÿ¿Qçÿ¿Qèÿ¿Qêÿ¿Qëÿ¿Qöÿ¿Rÿ«R ÿ«R ÿçR ÿ«R9ÿ”R:ÿìR?ÿ”R@ÿçRYÿîR[ÿÄR\ÿîR`ÿçRlÿ«Rrÿ«R|ÿ«Rçÿ«Rèÿ«Rêÿ«Rëÿ«Röÿ«Sÿ«S ÿ«S ÿçS ÿ«S9ÿ”S:ÿìS?ÿ”S@ÿçSYÿîS[ÿÄS\ÿîS`ÿçSlÿ«Srÿ«S|ÿ«Sçÿ«Sèÿ«Sêÿ«Sëÿ«Söÿ«UÿƒUÿƒUDÿÎU¢ÿÎU£ÿÎU¤ÿÎU¥ÿÎU¦ÿÎU§ÿÎU¨ÿÎUÃÿÎUéÿƒUìÿƒY ÿºYÿƒYÿƒYÿºY$ÿºYFÿîYGÿîYHÿîYRÿîYTÿîY‚ÿºYƒÿºY„ÿºY…ÿºY†ÿºY‡ÿºYˆÿºY©ÿîYªÿîY«ÿîY¬ÿîY­ÿîY²ÿîY´ÿîYµÿîY¶ÿîY·ÿîY¸ÿîYºÿîYÂÿºYÅÿîYÇÿîYÎÿîYéÿƒYìÿƒYùÿºZÿÎZÿÎZéÿÎZìÿÎ[FÿÄ[GÿÄ[HÿÄ[RÿÄ[TÿÄ[©ÿÄ[ªÿÄ[«ÿÄ[¬ÿÄ[­ÿÄ[²ÿÄ[´ÿÄ[µÿÄ[¶ÿÄ[·ÿÄ[¸ÿÄ[ºÿÄ[ÅÿÄ[ÇÿÄ[ÎÿÄ\ ÿº\ÿƒ\ÿƒ\ÿº\$ÿº\Fÿî\Gÿî\Hÿî\Rÿî\Tÿî\‚ÿº\ƒÿº\„ÿº\…ÿº\†ÿº\‡ÿº\ˆÿº\©ÿî\ªÿî\«ÿî\¬ÿî\­ÿî\²ÿî\´ÿî\µÿî\¶ÿî\·ÿî\¸ÿî\ºÿî\Âÿº\Åÿî\Çÿî\Îÿî\éÿƒ\ìÿƒ\ùÿº^#ÿØ^&ÿØ^*ÿØ^2ÿØ^4ÿØ^Fÿç^Gÿç^Hÿç^Rÿç^Tÿç^‰ÿØ^”ÿØ^•ÿØ^–ÿØ^—ÿØ^˜ÿØ^šÿØ^©ÿç^ªÿç^«ÿç^¬ÿç^­ÿç^²ÿç^´ÿç^µÿç^¶ÿç^·ÿç^¸ÿç^ºÿç^ÄÿØ^Åÿç^Çÿç^ÍÿØ^Îÿçl ÿVlÿ lÿBlÿ lÿVl#ÿÐl$ÿVl&ÿÐl*ÿÐl2ÿÐl4ÿÐl97l:7l<l?7lDÿÂlFÿ«lGÿ«lHÿ«lRÿ«lTÿ«lmÿBloÿBlyÿBl}ÿBl‚ÿVlƒÿVl„ÿVl…ÿVl†ÿVl‡ÿVlˆÿVl‰ÿÐl”ÿÐl•ÿÐl–ÿÐl—ÿÐl˜ÿÐlšÿÐlŸl¢ÿÂl£ÿÂl¤ÿÂl¥ÿÂl¦ÿÂl§ÿÂl¨ÿÂl©ÿ«lªÿ«l«ÿ«l¬ÿ«l­ÿ«l²ÿ«l´ÿ«lµÿ«l¶ÿ«l·ÿ«l¸ÿ«lºÿ«lÂÿVlÃÿÂlÄÿÐlÅÿ«lÇÿ«lÍÿÐlÎÿ«lÓlåÿBlæÿBléÿ lìÿ lïÿBlòÿBlóÿBlùÿVmÿBm ÿÝm ÿBm ÿBmÿvmÿvmÿÝm$ÿÝm7ÿLm9ÿ—m:ÿçm;ÿÉm<ÿ`m=ÿÕm?ÿ—mlÿBmrÿBm|ÿBm‚ÿÝmƒÿÝm„ÿÝm…ÿÝm†ÿÝm‡ÿÝmˆÿÝmŸÿ`mÂÿÝmÓÿ`mÔÿÕmÖÿÕmØÿÕmçÿBmèÿBméÿvmêÿBmëÿBmìÿvmöÿBmùÿÝoÿBo ÿÝo ÿBo ÿBoÿvoÿvoÿÝo$ÿÝo7ÿLo9ÿ—o:ÿço;ÿÉo<ÿ`o=ÿÕo?ÿ—olÿBorÿBo|ÿBo‚ÿÝoƒÿÝo„ÿÝo…ÿÝo†ÿÝo‡ÿÝoˆÿÝoŸÿ`oÂÿÝoÓÿ`oÔÿÕoÖÿÕoØÿÕoçÿBoèÿBoéÿvoêÿBoëÿBoìÿvoöÿBoùÿÝpÿÐp ÿâp ÿÐp ÿØp ÿÐpÿÆpÿÆpÿâp$ÿâp7ÿˆp9ÿÓp;ÿâp<ÿ°p=ÿµp?ÿÓp@ÿØp`ÿØplÿÐprÿÐp|ÿÐp‚ÿâpƒÿâp„ÿâp…ÿâp†ÿâp‡ÿâpˆÿâpŸÿ°pÂÿâpÓÿ°pÔÿµpÖÿµpØÿµpçÿÐpèÿÐpéÿÆpêÿÐpëÿÐpìÿÆpöÿÐpùÿâr ÿVrÿ rÿBrÿ rÿVr#ÿÐr$ÿVr&ÿÐr*ÿÐr2ÿÐr4ÿÐr97r:7r<r?7rDÿÂrFÿ«rGÿ«rHÿ«rRÿ«rTÿ«rmÿBroÿBryÿBr}ÿBr‚ÿVrƒÿVr„ÿVr…ÿVr†ÿVr‡ÿVrˆÿVr‰ÿÐr”ÿÐr•ÿÐr–ÿÐr—ÿÐr˜ÿÐršÿÐrŸr¢ÿÂr£ÿÂr¤ÿÂr¥ÿÂr¦ÿÂr§ÿÂr¨ÿÂr©ÿ«rªÿ«r«ÿ«r¬ÿ«r­ÿ«r²ÿ«r´ÿ«rµÿ«r¶ÿ«r·ÿ«r¸ÿ«rºÿ«rÂÿVrÃÿÂrÄÿÐrÅÿ«rÇÿ«rÍÿÐrÎÿ«rÓråÿBræÿBréÿ rìÿ rïÿBròÿBróÿBrùÿVt ÿ`tÿ`t$ÿ`t9Ft:Ft<(t?Ft‚ÿ`tƒÿ`t„ÿ`t…ÿ`t†ÿ`t‡ÿ`tˆÿ`tŸ(tÂÿ`tÓ(tùÿ`u ÿ`uÿ`u$ÿ`u9Fu:Fu<(u?Fu‚ÿ`uƒÿ`u„ÿ`u…ÿ`u†ÿ`u‡ÿ`uˆÿ`uŸ(uÂÿ`uÓ(uùÿ`yÿBy ÿÝy ÿBy ÿByÿvyÿvyÿÝy$ÿÝy7ÿLy9ÿ—y:ÿçy;ÿÉy<ÿ`y=ÿÕy?ÿ—ylÿByrÿBy|ÿBy‚ÿÝyƒÿÝy„ÿÝy…ÿÝy†ÿÝy‡ÿÝyˆÿÝyŸÿ`yÂÿÝyÓÿ`yÔÿÕyÖÿÕyØÿÕyçÿByèÿByéÿvyêÿByëÿByìÿvyöÿByùÿÝ{ ÿ`{ÿ`{$ÿ`{9F{:F{<({?F{‚ÿ`{ƒÿ`{„ÿ`{…ÿ`{†ÿ`{‡ÿ`{ˆÿ`{Ÿ({Âÿ`{Ó({ùÿ`| ÿV|ÿ |ÿB|ÿ |ÿV|#ÿÐ|$ÿV|&ÿÐ|*ÿÐ|2ÿÐ|4ÿÐ|97|:7|<|?7|DÿÂ|Fÿ«|Gÿ«|Hÿ«|Rÿ«|Tÿ«|mÿB|oÿB|yÿB|}ÿB|‚ÿV|ƒÿV|„ÿV|…ÿV|†ÿV|‡ÿV|ˆÿV|‰ÿÐ|”ÿÐ|•ÿÐ|–ÿÐ|—ÿÐ|˜ÿÐ|šÿÐ|Ÿ|¢ÿÂ|£ÿÂ|¤ÿÂ|¥ÿÂ|¦ÿÂ|§ÿÂ|¨ÿÂ|©ÿ«|ªÿ«|«ÿ«|¬ÿ«|­ÿ«|²ÿ«|´ÿ«|µÿ«|¶ÿ«|·ÿ«|¸ÿ«|ºÿ«|ÂÿV|ÃÿÂ|ÄÿÐ|Åÿ«|Çÿ«|ÍÿÐ|Îÿ«|Ó|åÿB|æÿB|éÿ |ìÿ |ïÿB|òÿB|óÿB|ùÿV}ÿB} ÿÝ} ÿB} ÿB}ÿv}ÿv}ÿÝ}$ÿÝ}7ÿL}9ÿ—}:ÿç};ÿÉ}<ÿ`}=ÿÕ}?ÿ—}lÿB}rÿB}|ÿB}‚ÿÝ}ƒÿÝ}„ÿÝ}…ÿÝ}†ÿÝ}‡ÿÝ}ˆÿÝ}Ÿÿ`}ÂÿÝ}Óÿ`}ÔÿÕ}ÖÿÕ}ØÿÕ}çÿB}èÿB}éÿv}êÿB}ëÿB}ìÿv}öÿB}ùÿÝ‚ÿV‚ ÿV‚ ÿV‚ÿÝ‚"ÿЂ#ÿâ‚&ÿâ‚*ÿâ‚--‚2ÿâ‚4ÿâ‚7ÿ‚8ÿÆ‚9ÿ¡‚:ÿÄ‚<ÿt‚?ÿ¡‚Yÿº‚\ÿº‚lÿV‚mÿÝ‚oÿÝ‚rÿV‚tÿ`‚uÿ`‚yÿÝ‚{ÿ`‚|ÿV‚}ÿÝ‚‰ÿâ‚”ÿâ‚•ÿâ‚–ÿâ‚—ÿₘÿₚÿâ‚›ÿÆ‚œÿÆ‚ÿÆ‚žÿÆ‚Ÿÿt‚Äÿâ‚Íÿâ‚Óÿt‚åÿÝ‚æÿÝ‚çÿV‚èÿV‚êÿV‚ëÿV‚ïÿÝ‚òÿÝ‚óÿÝ‚öÿVƒÿVƒ ÿVƒ ÿVƒÿ݃"ÿЃ#ÿâƒ&ÿâƒ*ÿâƒ--ƒ2ÿâƒ4ÿâƒ7ÿƒ8ÿƃ9ÿ¡ƒ:ÿă<ÿtƒ?ÿ¡ƒYÿºƒ\ÿºƒlÿVƒmÿ݃oÿ݃rÿVƒtÿ`ƒuÿ`ƒyÿ݃{ÿ`ƒ|ÿVƒ}ÿ݃‰ÿ⃔ÿ⃕ÿ⃖ÿ⃗ÿ⃘ÿ⃚ÿ⃛ÿƃœÿƃÿƃžÿƃŸÿtƒÄÿâƒÍÿâƒÓÿtƒåÿ݃æÿ݃çÿVƒèÿVƒêÿVƒëÿVƒïÿ݃òÿ݃óÿ݃öÿV„ÿV„ ÿV„ ÿV„ÿÝ„"ÿЄ#ÿâ„&ÿâ„*ÿâ„--„2ÿâ„4ÿâ„7ÿ„8ÿÆ„9ÿ¡„:ÿÄ„<ÿt„?ÿ¡„Yÿº„\ÿº„lÿV„mÿÝ„oÿÝ„rÿV„tÿ`„uÿ`„yÿÝ„{ÿ`„|ÿV„}ÿÝ„‰ÿâ„”ÿâ„•ÿâ„–ÿâ„—ÿ℘ÿℚÿâ„›ÿÆ„œÿÆ„ÿÆ„žÿÆ„Ÿÿt„Äÿâ„Íÿâ„Óÿt„åÿÝ„æÿÝ„çÿV„èÿV„êÿV„ëÿV„ïÿÝ„òÿÝ„óÿÝ„öÿV…ÿV… ÿV… ÿV…ÿÝ…"ÿÐ…#ÿâ…&ÿâ…*ÿâ…--…2ÿâ…4ÿâ…7ÿ…8ÿÆ…9ÿ¡…:ÿÄ…<ÿt…?ÿ¡…Yÿº…\ÿº…lÿV…mÿÝ…oÿÝ…rÿV…tÿ`…uÿ`…yÿÝ…{ÿ`…|ÿV…}ÿÝ…‰ÿâ…”ÿâ…•ÿâ…–ÿâ…—ÿâ…˜ÿâ…šÿâ…›ÿÆ…œÿÆ…ÿÆ…žÿÆ…Ÿÿt…Äÿâ…Íÿâ…Óÿt…åÿÝ…æÿÝ…çÿV…èÿV…êÿV…ëÿV…ïÿÝ…òÿÝ…óÿÝ…öÿV†ÿV† ÿV† ÿV†ÿ݆"ÿІ#ÿâ†&ÿâ†*ÿâ†--†2ÿâ†4ÿâ†7ÿ†8ÿƆ9ÿ¡†:ÿĆ<ÿt†?ÿ¡†Yÿº†\ÿº†lÿV†mÿ݆oÿ݆rÿV†tÿ`†uÿ`†yÿ݆{ÿ`†|ÿV†}ÿ݆‰ÿ↔ÿ↕ÿ↖ÿ↗ÿ↘ÿ↚ÿ↛ÿƆœÿƆÿƆžÿƆŸÿt†Äÿâ†Íÿâ†Óÿt†åÿ݆æÿ݆çÿV†èÿV†êÿV†ëÿV†ïÿ݆òÿ݆óÿ݆öÿV‡ÿV‡ ÿV‡ ÿV‡ÿ݇"ÿЇ#ÿâ‡&ÿâ‡*ÿâ‡--‡2ÿâ‡4ÿâ‡7ÿ‡8ÿƇ9ÿ¡‡:ÿć<ÿt‡?ÿ¡‡Yÿº‡\ÿº‡lÿV‡mÿ݇oÿ݇rÿV‡tÿ`‡uÿ`‡yÿ݇{ÿ`‡|ÿV‡}ÿ݇‰ÿ⇔ÿ⇕ÿ⇖ÿ⇗ÿ⇘ÿ⇚ÿ⇛ÿƇœÿƇÿƇžÿƇŸÿt‡Äÿâ‡Íÿâ‡Óÿt‡åÿ݇æÿ݇çÿV‡èÿV‡êÿV‡ëÿV‡ïÿ݇òÿ݇óÿ݇öÿV‰ÿe‰mÿe‰oÿe‰yÿe‰}ÿe‰åÿe‰æÿe‰ïÿe‰òÿe‰óÿe’ÿÐ’ ÿâ’ ÿÐ’ ÿØ’ ÿÐ’ÿÆ’ÿÆ’ÿâ’$ÿâ’7ÿˆ’9ÿÓ’;ÿâ’<ÿ°’=ÿµ’?ÿÓ’@ÿØ’`ÿØ’lÿÐ’rÿÐ’|ÿÐ’‚ÿâ’ƒÿâ’„ÿâ’…ÿâ’†ÿâ’‡ÿâ’ˆÿâ’Ÿÿ°’Âÿâ’Óÿ°’Ôÿµ’Öÿµ’Øÿµ’çÿÐ’èÿÐ’éÿÆ’êÿÐ’ëÿÐ’ìÿÆ’öÿÐ’ùÿâ”ÿД ÿâ” ÿД ÿØ” ÿДÿÆ”ÿÆ”ÿâ”$ÿâ”7ÿˆ”9ÿÓ”;ÿâ”<ÿ°”=ÿµ”?ÿÓ”@ÿØ”`ÿØ”lÿДrÿД|ÿД‚ÿ┃ÿ┄ÿâ”…ÿ┆ÿ┇ÿ┈ÿ┟ÿ°”Âÿâ”Óÿ°”Ôÿµ”Öÿµ”Øÿµ”çÿДèÿДéÿÆ”êÿДëÿДìÿÆ”öÿДùÿâ•ÿЕ ÿâ• ÿЕ ÿØ• ÿЕÿÆ•ÿÆ•ÿâ•$ÿâ•7ÿˆ•9ÿÓ•;ÿâ•<ÿ°•=ÿµ•?ÿÓ•@ÿØ•`ÿØ•lÿЕrÿЕ|ÿЕ‚ÿ╃ÿâ•„ÿâ•…ÿ╆ÿ╇ÿ╈ÿ╟ÿ°•Âÿâ•Óÿ°•Ôÿµ•Öÿµ•Øÿµ•çÿЕèÿЕéÿÆ•êÿЕëÿЕìÿÆ•öÿЕùÿâ–ÿЖ ÿâ– ÿЖ ÿØ– ÿЖÿÆ–ÿÆ–ÿâ–$ÿâ–7ÿˆ–9ÿÓ–;ÿâ–<ÿ°–=ÿµ–?ÿÓ–@ÿØ–`ÿØ–lÿЖrÿЖ|ÿЖ‚ÿâ–ƒÿâ–„ÿâ–…ÿâ–†ÿâ–‡ÿâ–ˆÿâ–Ÿÿ°–Âÿâ–Óÿ°–Ôÿµ–Öÿµ–Øÿµ–çÿЖèÿЖéÿÆ–êÿЖëÿЖìÿÆ–öÿЖùÿâ—ÿЗ ÿâ— ÿЗ ÿØ— ÿЗÿÆ—ÿÆ—ÿâ—$ÿâ—7ÿˆ—9ÿÓ—;ÿâ—<ÿ°—=ÿµ—?ÿÓ—@ÿØ—`ÿØ—lÿЗrÿЗ|ÿЗ‚ÿâ—ƒÿâ—„ÿâ—…ÿâ—†ÿâ—‡ÿâ—ˆÿâ—Ÿÿ°—Âÿâ—Óÿ°—Ôÿµ—Öÿµ—Øÿµ—çÿЗèÿЗéÿÆ—êÿЗëÿЗìÿÆ—öÿЗùÿâ˜ÿИ ÿ☠ÿИ ÿؘ ÿИÿƘÿƘÿâ˜$ÿâ˜7ÿˆ˜9ÿÓ˜;ÿâ˜<ÿ°˜=ÿµ˜?ÿÓ˜@ÿؘ`ÿؘlÿИrÿИ|ÿИ‚ÿ☃ÿ☄ÿ★ÿ☆ÿ☇ÿ☈ÿ☟ÿ°˜Âÿâ˜Óÿ°˜Ôÿµ˜Öÿµ˜Øÿµ˜çÿИèÿИéÿƘêÿИëÿИìÿƘöÿИùÿâ› ÿÆ›ÿΛÿΛÿÆ›$ÿÆ›‚ÿÆ›ƒÿÆ›„ÿÆ›…ÿÆ›†ÿÆ›‡ÿÆ›ˆÿÆ›ÂÿÆ›éÿΛìÿΛùÿÆœ ÿÆœÿΜÿΜÿÆœ$ÿÆœ‚ÿÆœƒÿÆœ„ÿÆœ…ÿÆœ†ÿÆœ‡ÿÆœˆÿÆœÂÿÆœéÿΜìÿΜùÿÆ ÿÆÿÎÿÎÿÆ$ÿÆ‚ÿƃÿÆ„ÿÆ…ÿƆÿƇÿƈÿÆÂÿÆéÿÎìÿÎùÿÆž ÿÆžÿΞÿΞÿÆž$ÿÆž‚ÿÆžƒÿÆž„ÿÆž…ÿÆž†ÿÆž‡ÿÆžˆÿÆžÂÿÆžéÿΞìÿΞùÿÆŸŸ ÿtŸ Ÿ ŸÿyŸÿ`ŸÿyŸÿtŸÿ’Ÿÿ’Ÿ"%Ÿ#ÿ°Ÿ$ÿtŸ&ÿ°Ÿ*ÿ°Ÿ-ÿ8Ÿ2ÿ°Ÿ4ÿ°ŸDÿ”ŸFÿ`ŸGÿ`ŸHÿ`ŸJÿ[ŸPÿ’ŸQÿ’ŸRÿ`ŸSÿ’ŸTÿ`ŸUÿ’ŸVÿŸXÿ’ŸYÿœŸZÿ«Ÿ[ÿƒŸ\ÿœŸlŸmÿ`Ÿoÿ`ŸrŸt-Ÿu-Ÿwÿ’Ÿyÿ`Ÿ{-Ÿ|Ÿ}ÿ`Ÿ‚ÿtŸƒÿtŸ„ÿtŸ…ÿtŸ†ÿtŸ‡ÿtŸˆÿtŸ‰ÿ°Ÿ”ÿ°Ÿ•ÿ°Ÿ–ÿ°Ÿ—ÿ°Ÿ˜ÿ°Ÿšÿ°Ÿ¢ÿ”Ÿ£ÿ”Ÿ¤ÿ”Ÿ¥ÿ”Ÿ¦ÿ”Ÿ§ÿ”Ÿ¨ÿ”Ÿ©ÿ`Ÿªÿ`Ÿ«ÿ`Ÿ¬ÿ`Ÿ­ÿ`Ÿ²ÿ`Ÿ³ÿ’Ÿ´ÿ`Ÿµÿ`Ÿ¶ÿ`Ÿ·ÿ`Ÿ¸ÿ`Ÿºÿ`Ÿ»ÿ’Ÿ¼ÿ’Ÿ½ÿ’Ÿ¾ÿ’ŸÂÿtŸÃÿ”ŸÄÿ°ŸÅÿ`ŸÇÿ`ŸÌÿ’ŸÍÿ°ŸÎÿ`Ÿåÿ`Ÿæÿ`ŸçŸèŸéÿyŸêŸëŸìÿyŸïÿ`Ÿòÿ`Ÿóÿ`ŸöŸùÿt ÿР ÿâ  ÿР ÿØ  ÿРÿÆ ÿÆ ÿâ $ÿâ 7ÿˆ 9ÿÓ ;ÿâ <ÿ° =ÿµ ?ÿÓ @ÿØ `ÿØ lÿРrÿР|ÿР‚ÿâ ƒÿâ „ÿâ …ÿâ †ÿâ ‡ÿâ ˆÿâ Ÿÿ° Âÿâ Óÿ° Ôÿµ Öÿµ Øÿµ çÿРèÿРéÿÆ êÿРëÿРìÿÆ öÿРùÿâ¢ÿ¿¢ ÿ¿¢ ÿ¿¢Yÿç¢Zÿó¢\ÿç¢lÿ¿¢rÿ¿¢tÿ¿¢uÿ¿¢{ÿ¿¢|ÿ¿¢çÿ¿¢èÿ¿¢êÿ¿¢ëÿ¿¢öÿ¿£ÿ¿£ ÿ¿£ ÿ¿£Yÿç£Zÿó£\ÿç£lÿ¿£rÿ¿£tÿ¿£uÿ¿£{ÿ¿£|ÿ¿£çÿ¿£èÿ¿£êÿ¿£ëÿ¿£öÿ¿¤ÿ¿¤ ÿ¿¤ ÿ¿¤Yÿç¤Zÿó¤\ÿç¤lÿ¿¤rÿ¿¤tÿ¿¤uÿ¿¤{ÿ¿¤|ÿ¿¤çÿ¿¤èÿ¿¤êÿ¿¤ëÿ¿¤öÿ¿¥ÿ¿¥ ÿ¿¥ ÿ¿¥Yÿç¥Zÿó¥\ÿç¥lÿ¿¥rÿ¿¥tÿ¿¥uÿ¿¥{ÿ¿¥|ÿ¿¥çÿ¿¥èÿ¿¥êÿ¿¥ëÿ¿¥öÿ¿¦ÿ¿¦ ÿ¿¦ ÿ¿¦Yÿç¦Zÿó¦\ÿç¦lÿ¿¦rÿ¿¦tÿ¿¦uÿ¿¦{ÿ¿¦|ÿ¿¦çÿ¿¦èÿ¿¦êÿ¿¦ëÿ¿¦öÿ¿§ÿ¿§ ÿ¿§ ÿ¿§Yÿç§Zÿó§\ÿç§lÿ¿§rÿ¿§tÿ¿§uÿ¿§{ÿ¿§|ÿ¿§çÿ¿§èÿ¿§êÿ¿§ëÿ¿§öÿ¿¨ÿ«¨ ÿ«¨ ÿç¨ ÿ«¨9ÿ”¨:ÿì¨?ÿ”¨@ÿç¨Yÿî¨[ÿĨ\ÿî¨`ÿç¨lÿ«¨rÿ«¨|ÿ«¨çÿ«¨èÿ«¨êÿ«¨ëÿ«¨öÿ«ªÿ«ª ÿ«ª ÿçª ÿ«ª9ÿ”ª:ÿìª?ÿ”ª@ÿçªYÿîª[ÿĪ\ÿîª`ÿçªlÿ«ªrÿ«ª|ÿ«ªçÿ«ªèÿ«ªêÿ«ªëÿ«ªöÿ««ÿ«« ÿ«« ÿç« ÿ««9ÿ”«:ÿì«?ÿ”«@ÿç«Yÿî«[ÿÄ«\ÿî«`ÿç«lÿ««rÿ««|ÿ««çÿ««èÿ««êÿ««ëÿ««öÿ«¬ÿ«¬ ÿ«¬ ÿç¬ ÿ«¬9ÿ”¬:ÿì¬?ÿ”¬@ÿç¬Yÿî¬[ÿĬ\ÿî¬`ÿç¬lÿ«¬rÿ«¬|ÿ«¬çÿ«¬èÿ«¬êÿ«¬ëÿ«¬öÿ«­ÿ«­ ÿ«­ ÿç­ ÿ«­9ÿ”­:ÿì­?ÿ”­@ÿç­Yÿî­[ÿÄ­\ÿî­`ÿç­lÿ«­rÿ«­|ÿ«­çÿ«­èÿ«­êÿ«­ëÿ«­öÿ«³ÿ¿³ ÿ¿³ ÿ¿³Yÿç³Zÿó³\ÿç³lÿ¿³rÿ¿³tÿ¿³uÿ¿³{ÿ¿³|ÿ¿³çÿ¿³èÿ¿³êÿ¿³ëÿ¿³öÿ¿´ÿ«´ ÿ«´ ÿç´ ÿ«´9ÿ”´:ÿì´?ÿ”´@ÿç´Yÿî´[ÿÄ´\ÿî´`ÿç´lÿ«´rÿ«´|ÿ«´çÿ«´èÿ«´êÿ«´ëÿ«´öÿ«µÿ«µ ÿ«µ ÿçµ ÿ«µ9ÿ”µ:ÿìµ?ÿ”µ@ÿçµYÿîµ[ÿĵ\ÿîµ`ÿçµlÿ«µrÿ«µ|ÿ«µçÿ«µèÿ«µêÿ«µëÿ«µöÿ«¶ÿ«¶ ÿ«¶ ÿç¶ ÿ«¶9ÿ”¶:ÿì¶?ÿ”¶@ÿç¶Yÿî¶[ÿĶ\ÿî¶`ÿç¶lÿ«¶rÿ«¶|ÿ«¶çÿ«¶èÿ«¶êÿ«¶ëÿ«¶öÿ«·ÿ«· ÿ«· ÿç· ÿ«·9ÿ”·:ÿì·?ÿ”·@ÿç·Yÿî·[ÿÄ·\ÿî·`ÿç·lÿ«·rÿ«·|ÿ«·çÿ«·èÿ«·êÿ«·ëÿ«·öÿ«¸ÿ«¸ ÿ«¸ ÿç¸ ÿ«¸9ÿ”¸:ÿì¸?ÿ”¸@ÿç¸Yÿî¸[ÿĸ\ÿî¸`ÿç¸lÿ«¸rÿ«¸|ÿ«¸çÿ«¸èÿ«¸êÿ«¸ëÿ«¸öÿ«ºÿ«º ÿ«º ÿçº ÿ«º9ÿ”º:ÿìº?ÿ”º@ÿçºYÿîº[ÿĺ\ÿîº`ÿçºlÿ«ºrÿ«º|ÿ«ºçÿ«ºèÿ«ºêÿ«ºëÿ«ºöÿ«Àÿ«À ÿ«À ÿçÀ ÿ«À9ÿ”À:ÿìÀ?ÿ”À@ÿçÀYÿîÀ[ÿÄÀ\ÿîÀ`ÿçÀlÿ«Àrÿ«À|ÿ«Àçÿ«Àèÿ«Àêÿ«Àëÿ«Àöÿ«ÂÿV ÿV ÿVÂÿÝÂ"ÿÐÂ#ÿâÂ&ÿâÂ*ÿâÂ--Â2ÿâÂ4ÿâÂ7ÿÂ8ÿÆÂ9ÿ¡Â:ÿÄÂ<ÿtÂ?ÿ¡ÂYÿºÂ\ÿºÂlÿVÂmÿÝÂoÿÝÂrÿVÂtÿ`Âuÿ`ÂyÿÝÂ{ÿ`Â|ÿVÂ}ÿ݉ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ›ÿÆÂœÿÆÂÿÆÂžÿÆÂŸÿtÂÄÿâÂÍÿâÂÓÿtÂåÿÝÂæÿÝÂçÿVÂèÿVÂêÿVÂëÿVÂïÿÝÂòÿÝÂóÿÝÂöÿVÃÿ¿Ã ÿ¿Ã ÿ¿ÃYÿçÃZÿóÃ\ÿçÃlÿ¿Ãrÿ¿Ãtÿ¿Ãuÿ¿Ã{ÿ¿Ã|ÿ¿Ãçÿ¿Ãèÿ¿Ãêÿ¿Ãëÿ¿Ãöÿ¿ÄÿeÄmÿeÄoÿeÄyÿeÄ}ÿeÄåÿeÄæÿeÄïÿeÄòÿeÄóÿeÇÿ«Ç ÿ«Ç ÿçÇ ÿ«Ç9ÿ”Ç:ÿìÇ?ÿ”Ç@ÿçÇYÿîÇ[ÿÄÇ\ÿîÇ`ÿçÇlÿ«Çrÿ«Ç|ÿ«Ççÿ«Çèÿ«Çêÿ«Çëÿ«Çöÿ«ÉÿtÉ ÿtÉ ÿtÉÿoÉ9ÿjÉ:ÿ’É<ÿyÉ?ÿjÉYÿËÉ\ÿËÉlÿtÉmÿoÉoÿoÉrÿtÉtÿƒÉuÿƒÉyÿoÉ{ÿƒÉ|ÿtÉ}ÿoÉŸÿyÉÓÿyÉåÿoÉæÿoÉçÿtÉèÿtÉêÿtÉëÿtÉïÿoÉòÿoÉóÿoÉöÿtÌÿ¿Ì ÿ¿Ì ÿ¿ÌYÿçÌZÿóÌ\ÿçÌlÿ¿Ìrÿ¿Ìtÿ¿Ìuÿ¿Ì{ÿ¿Ì|ÿ¿Ìçÿ¿Ìèÿ¿Ìêÿ¿Ìëÿ¿Ìöÿ¿Îÿ«Î ÿ«Î ÿçÎ ÿ«Î9ÿ”Î:ÿìÎ?ÿ”Î@ÿçÎYÿîÎ[ÿÄÎ\ÿîÎ`ÿçÎlÿ«Îrÿ«Î|ÿ«Îçÿ«Îèÿ«Îêÿ«Îëÿ«Îöÿ«ÓÓ ÿtÓ Ó ÓÿyÓÿ`ÓÿyÓÿtÓÿ’Óÿ’Ó"%Ó#ÿ°Ó$ÿtÓ&ÿ°Ó*ÿ°Ó-ÿ8Ó2ÿ°Ó4ÿ°ÓDÿ”ÓFÿ`ÓGÿ`ÓHÿ`ÓJÿ[ÓPÿ’ÓQÿ’ÓRÿ`ÓSÿ’ÓTÿ`ÓUÿ’ÓVÿÓXÿ’ÓYÿœÓZÿ«Ó[ÿƒÓ\ÿœÓlÓmÿ`Óoÿ`ÓrÓt-Óu-Ówÿ’Óyÿ`Ó{-Ó|Ó}ÿ`Ó‚ÿtÓƒÿtÓ„ÿtÓ…ÿtÓ†ÿtÓ‡ÿtÓˆÿtÓ‰ÿ°Ó”ÿ°Ó•ÿ°Ó–ÿ°Ó—ÿ°Ó˜ÿ°Óšÿ°Ó¢ÿ”Ó£ÿ”Ó¤ÿ”Ó¥ÿ”Ó¦ÿ”Ó§ÿ”Ó¨ÿ”Ó©ÿ`Óªÿ`Ó«ÿ`Ó¬ÿ`Ó­ÿ`Ó²ÿ`Ó³ÿ’Ó´ÿ`Óµÿ`Ó¶ÿ`Ó·ÿ`Ó¸ÿ`Óºÿ`Ó»ÿ’Ó¼ÿ’Ó½ÿ’Ó¾ÿ’ÓÂÿtÓÃÿ”ÓÄÿ°ÓÅÿ`ÓÇÿ`ÓÌÿ’ÓÍÿ°ÓÎÿ`Óåÿ`Óæÿ`ÓçÓèÓéÿyÓêÓëÓìÿyÓïÿ`Óòÿ`Óóÿ`ÓöÓùÿtÔÿµÔ"%Ô#ÿºÔ&ÿºÔ*ÿºÔ2ÿºÔ4ÿºÔFÿÎÔGÿÎÔHÿÎÔRÿÎÔTÿÎÔVÿØÔYÿÓÔ\ÿÓÔmÿµÔoÿµÔyÿµÔ}ÿµÔ‰ÿºÔ”ÿºÔ•ÿºÔ–ÿºÔ—ÿºÔ˜ÿºÔšÿºÔ©ÿÎÔªÿÎÔ«ÿÎÔ¬ÿÎÔ­ÿÎÔ²ÿÎÔ´ÿÎÔµÿÎÔ¶ÿÎÔ·ÿÎÔ¸ÿÎÔºÿÎÔÄÿºÔÅÿÎÔÇÿÎÔÍÿºÔÎÿÎÔåÿµÔæÿµÔïÿµÔòÿµÔóÿµÖÿµÖ"%Ö#ÿºÖ&ÿºÖ*ÿºÖ2ÿºÖ4ÿºÖFÿÎÖGÿÎÖHÿÎÖRÿÎÖTÿÎÖVÿØÖYÿÓÖ\ÿÓÖmÿµÖoÿµÖyÿµÖ}ÿµÖ‰ÿºÖ”ÿºÖ•ÿºÖ–ÿºÖ—ÿºÖ˜ÿºÖšÿºÖ©ÿÎÖªÿÎÖ«ÿÎÖ¬ÿÎÖ­ÿÎÖ²ÿÎÖ´ÿÎÖµÿÎÖ¶ÿÎÖ·ÿÎÖ¸ÿÎÖºÿÎÖÄÿºÖÅÿÎÖÇÿÎÖÍÿºÖÎÿÎÖåÿµÖæÿµÖïÿµÖòÿµÖóÿµØÿµØ"%Ø#ÿºØ&ÿºØ*ÿºØ2ÿºØ4ÿºØFÿÎØGÿÎØHÿÎØRÿÎØTÿÎØVÿØØYÿÓØ\ÿÓØmÿµØoÿµØyÿµØ}ÿµØ‰ÿºØ”ÿºØ•ÿºØ–ÿºØ—ÿºØ˜ÿºØšÿºØ©ÿÎØªÿÎØ«ÿÎØ¬ÿÎØ­ÿÎØ²ÿÎØ´ÿÎØµÿÎØ¶ÿÎØ·ÿÎØ¸ÿÎØºÿÎØÄÿºØÅÿÎØÇÿÎØÍÿºØÎÿÎØåÿµØæÿµØïÿµØòÿµØóÿµåÿBå ÿÝå ÿBå ÿBåÿvåÿvåÿÝå$ÿÝå7ÿLå9ÿ—å:ÿçå;ÿÉå<ÿ`å=ÿÕå?ÿ—ålÿBårÿBå|ÿBå‚ÿÝåƒÿÝå„ÿÝå…ÿÝå†ÿÝå‡ÿÝåˆÿÝåŸÿ`åÂÿÝåÓÿ`åÔÿÕåÖÿÕåØÿÕåçÿBåèÿBåéÿvåêÿBåëÿBåìÿvåöÿBåùÿÝæÿBæ ÿÝæ ÿBæ ÿBæÿvæÿvæÿÝæ$ÿÝæ7ÿLæ9ÿ—æ:ÿçæ;ÿÉæ<ÿ`æ=ÿÕæ?ÿ—ælÿBærÿBæ|ÿBæ‚ÿÝæƒÿÝæ„ÿÝæ…ÿÝæ†ÿÝæ‡ÿÝæˆÿÝæŸÿ`æÂÿÝæÓÿ`æÔÿÕæÖÿÕæØÿÕæçÿBæèÿBæéÿvæêÿBæëÿBæìÿvæöÿBæùÿÝç ÿVçÿ çÿBçÿ çÿVç#ÿÐç$ÿVç&ÿÐç*ÿÐç2ÿÐç4ÿÐç97ç:7ç<ç?7çDÿÂçFÿ«çGÿ«çHÿ«çRÿ«çTÿ«çmÿBçoÿBçyÿBç}ÿBç‚ÿVçƒÿVç„ÿVç…ÿVç†ÿVç‡ÿVçˆÿVç‰ÿÐç”ÿÐç•ÿÐç–ÿÐç—ÿÐç˜ÿÐçšÿÐçŸç¢ÿÂç£ÿÂç¤ÿÂç¥ÿÂç¦ÿÂç§ÿÂç¨ÿÂç©ÿ«çªÿ«ç«ÿ«ç¬ÿ«ç­ÿ«ç²ÿ«ç´ÿ«çµÿ«ç¶ÿ«ç·ÿ«ç¸ÿ«çºÿ«çÂÿVçÃÿÂçÄÿÐçÅÿ«çÇÿ«çÍÿÐçÎÿ«çÓçåÿBçæÿBçéÿ çìÿ çïÿBçòÿBçóÿBçùÿVè ÿVèÿ èÿBèÿ èÿVè#ÿÐè$ÿVè&ÿÐè*ÿÐè2ÿÐè4ÿÐè97è:7è<è?7èDÿÂèFÿ«èGÿ«èHÿ«èRÿ«èTÿ«èmÿBèoÿBèyÿBè}ÿBè‚ÿVèƒÿVè„ÿVè…ÿVè†ÿVè‡ÿVèˆÿVè‰ÿÐè”ÿÐè•ÿÐè–ÿÐè—ÿÐè˜ÿÐèšÿÐèŸè¢ÿÂè£ÿÂè¤ÿÂè¥ÿÂè¦ÿÂè§ÿÂè¨ÿÂè©ÿ«èªÿ«è«ÿ«è¬ÿ«è­ÿ«è²ÿ«è´ÿ«èµÿ«è¶ÿ«è·ÿ«è¸ÿ«èºÿ«èÂÿVèÃÿÂèÄÿÐèÅÿ«èÇÿ«èÍÿÐèÎÿ«èÓèåÿBèæÿBèéÿ èìÿ èïÿBèòÿBèóÿBèùÿVéÿ é ÿ é ÿ éÿvé#ÿÆé&ÿÆé*ÿÆé2ÿÆé4ÿÆé7ÿLé9ÿLé:ÿ’é<ÿyé?ÿLéYÿƒéZÿÎé\ÿƒélÿ émÿvéoÿvérÿ éyÿvé|ÿ é}ÿvé‰ÿÆé”ÿÆé•ÿÆé–ÿÆé—ÿÆé˜ÿÆéšÿÆéŸÿyéÄÿÆéÍÿÆéÓÿyéåÿvéæÿvéçÿ éèÿ éêÿ éëÿ éïÿvéòÿvéóÿvéöÿ ê ÿVêÿ êÿBêÿ êÿVê#ÿÐê$ÿVê&ÿÐê*ÿÐê2ÿÐê4ÿÐê97ê:7ê<ê?7êDÿÂêFÿ«êGÿ«êHÿ«êRÿ«êTÿ«êmÿBêoÿBêyÿBê}ÿBê‚ÿVêƒÿVê„ÿVê…ÿVê†ÿVê‡ÿVêˆÿVê‰ÿÐê”ÿÐê•ÿÐê–ÿÐê—ÿÐê˜ÿÐêšÿÐêŸê¢ÿÂê£ÿÂê¤ÿÂê¥ÿÂê¦ÿÂê§ÿÂê¨ÿÂê©ÿ«êªÿ«ê«ÿ«ê¬ÿ«ê­ÿ«ê²ÿ«ê´ÿ«êµÿ«ê¶ÿ«ê·ÿ«ê¸ÿ«êºÿ«êÂÿVêÃÿÂêÄÿÐêÅÿ«êÇÿ«êÍÿÐêÎÿ«êÓêåÿBêæÿBêéÿ êìÿ êïÿBêòÿBêóÿBêùÿVë ÿVëÿ ëÿBëÿ ëÿVë#ÿÐë$ÿVë&ÿÐë*ÿÐë2ÿÐë4ÿÐë97ë:7ë<ë?7ëDÿÂëFÿ«ëGÿ«ëHÿ«ëRÿ«ëTÿ«ëmÿBëoÿBëyÿBë}ÿBë‚ÿVëƒÿVë„ÿVë…ÿVë†ÿVë‡ÿVëˆÿVë‰ÿÐë”ÿÐë•ÿÐë–ÿÐë—ÿÐë˜ÿÐëšÿÐëŸë¢ÿÂë£ÿÂë¤ÿÂë¥ÿÂë¦ÿÂë§ÿÂë¨ÿÂë©ÿ«ëªÿ«ë«ÿ«ë¬ÿ«ë­ÿ«ë²ÿ«ë´ÿ«ëµÿ«ë¶ÿ«ë·ÿ«ë¸ÿ«ëºÿ«ëÂÿVëÃÿÂëÄÿÐëÅÿ«ëÇÿ«ëÍÿÐëÎÿ«ëÓëåÿBëæÿBëéÿ ëìÿ ëïÿBëòÿBëóÿBëùÿVìÿ ì ÿ ì ÿ ìÿvì#ÿÆì&ÿÆì*ÿÆì2ÿÆì4ÿÆì7ÿLì9ÿLì:ÿ’ì<ÿyì?ÿLìYÿƒìZÿÎì\ÿƒìlÿ ìmÿvìoÿvìrÿ ìyÿvì|ÿ ì}ÿvì‰ÿÆì”ÿÆì•ÿÆì–ÿÆì—ÿÆì˜ÿÆìšÿÆìŸÿyìÄÿÆìÍÿÆìÓÿyìåÿvìæÿvìçÿ ìèÿ ìêÿ ìëÿ ìïÿvìòÿvìóÿvìöÿ ïÿBï ÿÝï ÿBï ÿBïÿvïÿvïÿÝï$ÿÝï7ÿLï9ÿ—ï:ÿçï;ÿÉï<ÿ`ï=ÿÕï?ÿ—ïlÿBïrÿBï|ÿBï‚ÿÝïƒÿÝï„ÿÝï…ÿÝï†ÿÝï‡ÿÝïˆÿÝïŸÿ`ïÂÿÝïÓÿ`ïÔÿÕïÖÿÕïØÿÕïçÿBïèÿBïéÿvïêÿBïëÿBïìÿvïöÿBïùÿÝòÿBò ÿÝò ÿBò ÿBòÿvòÿvòÿÝò$ÿÝò7ÿLò9ÿ—ò:ÿçò;ÿÉò<ÿ`ò=ÿÕò?ÿ—òlÿBòrÿBò|ÿBò‚ÿÝòƒÿÝò„ÿÝò…ÿÝò†ÿÝò‡ÿÝòˆÿÝòŸÿ`òÂÿÝòÓÿ`òÔÿÕòÖÿÕòØÿÕòçÿBòèÿBòéÿvòêÿBòëÿBòìÿvòöÿBòùÿÝóÿBó ÿÝó ÿBó ÿBóÿvóÿvóÿÝó$ÿÝó7ÿLó9ÿ—ó:ÿçó;ÿÉó<ÿ`ó=ÿÕó?ÿ—ólÿBórÿBó|ÿBó‚ÿÝóƒÿÝó„ÿÝó…ÿÝó†ÿÝó‡ÿÝóˆÿÝóŸÿ`óÂÿÝóÓÿ`óÔÿÕóÖÿÕóØÿÕóçÿBóèÿBóéÿvóêÿBóëÿBóìÿvóöÿBóùÿÝö ÿVöÿ öÿBöÿ öÿVö#ÿÐö$ÿVö&ÿÐö*ÿÐö2ÿÐö4ÿÐö97ö:7ö<ö?7öDÿÂöFÿ«öGÿ«öHÿ«öRÿ«öTÿ«ömÿBöoÿBöyÿBö}ÿBö‚ÿVöƒÿVö„ÿVö…ÿVö†ÿVö‡ÿVöˆÿVö‰ÿÐö”ÿÐö•ÿÐö–ÿÐö—ÿÐö˜ÿÐöšÿÐöŸö¢ÿÂö£ÿÂö¤ÿÂö¥ÿÂö¦ÿÂö§ÿÂö¨ÿÂö©ÿ«öªÿ«ö«ÿ«ö¬ÿ«ö­ÿ«ö²ÿ«ö´ÿ«öµÿ«ö¶ÿ«ö·ÿ«ö¸ÿ«öºÿ«öÂÿVöÃÿÂöÄÿÐöÅÿ«öÇÿ«öÍÿÐöÎÿ«öÓöåÿBöæÿBöéÿ öìÿ öïÿBöòÿBöóÿBöùÿVùÿVù ÿVù ÿVùÿÝù"ÿÐù#ÿâù&ÿâù*ÿâù--ù2ÿâù4ÿâù7ÿù8ÿÆù9ÿ¡ù:ÿÄù<ÿtù?ÿ¡ùYÿºù\ÿºùlÿVùmÿÝùoÿÝùrÿVùtÿ`ùuÿ`ùyÿÝù{ÿ`ù|ÿVù}ÿÝù‰ÿâù”ÿâù•ÿâù–ÿâù—ÿâù˜ÿâùšÿâù›ÿÆùœÿÆùÿÆùžÿÆùŸÿtùÄÿâùÍÿâùÓÿtùåÿÝùæÿÝùçÿVùèÿVùêÿVùëÿVùïÿÝùòÿÝùóÿÝùöÿVžžžžä"¼†"ÆîJ¸è"<b€Înü6œ2 ( z â  , R À ~ ¼  ˆ Ä ô  ¸ Î ZvÂò@~ôF¸ÜFœâFrºì*¤zêT²ˆÂþT¢¼V¨`¤†ÄöP”ÎúrˆNN†öJl ^!B!°!ô"$"># #&#n#ª$"$Ä$æ%.%d%%ê&.&t&¾'0'Ì))r)~)Š)–)¢)®)º**¦*²*¾*Ê*Ö*â*î*ú++T+`+l+x+„++œ+Â,P,\,h,t,€,Œ,Î-P-\-h-t-€-Œ-˜.x///(/4/@/L/X/d/p/ö0000&020>0Œ11 11$101<1’1ž22¾2Ê2Ö3<3Ü3ö4*4\4h4t5<5à5ì5ø6666(646@6L6X6d6´6à777D7n7¼7þ8R8ˆ8ä8þ99>9d9Š9Î::V:Ê;t; ;ú<Ð<ö= =>=Ö>4>š??F?p?¦?À?ú@Ž@âA^AºAøB6BxBŽBîCbCŠC¬CòD D.D\DŠD¸DäE$EhE¢EÉ`"/n‹ ‘ †Š Š”(› Š(à ë0õ% % Ü4  2( ÂZ 6 J ^ Pl ¼ PÐ ¼ `  € € ¸ž 0 V d † „ ê 4n ¢  ªCopyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.Lato LightRegulartyPolandLukaszDziedzic: Lato Light: 2013Version 1.105; Western+Polish opensourceLato-LightLato is a trademark of tyPoland Lukasz Dziedzic.Lukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2013-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLCopyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.Lato LightRegulartyPolandLukaszDziedzic: Lato Light: 2013Lato-LightVersion 1.105; Western+Polish opensourceLato is a trademark of tyPoland Lukasz Dziedzic.Lukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2013-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLLatoLightÿZD  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a£„…½–膎‹©¤Šƒ“òó—ˆÞñžªõôö¢­ÉÇ®bcdËeÈÊÏÌÍÎéfÓÐѯgð‘ÖÔÕhëí‰jikmln oqprsutvwêxzy{}|¸¡~€ìîºýþ  ×âã  °± äå»æç¦ØáÛÜÝàÙß›²³¶·Ä´µÅ‚‡«Æ¾¿¼ŒŸ˜š™ï¥’œ§”•¹ÀÁ !"#NULLuni00A0uni00ADmacronperiodcenteredAogonekaogonekEogonekeogonekNacutenacuteSacutesacuteZacutezacute Zdotaccent zdotaccentuni02C9EuroDeltauni2669undercommaaccent grave.case dieresis.case macron.case acute.casecircumflex.case caron.case breve.casedotaccent.case ring.case tilde.casehungarumlaut.case caron.saltÿÿbKbK‡¡ãþ ¶þV—ÿð¡óÿóþ¶þV°,° `f-°, d °ÀP°&Z°E[X!#!ŠX °PPX!°@Y °8PX!°8YY ° Ead°(PX!° E °0PX!°0Y °ÀPX f ŠŠa ° PX` ° PX!° ` °6PX!°6``YYY°+YY#°PXeYY-°, E °%ad °CPX°#B°#B!!Y°`-°,#!#! d±bB °#B² *! °C Ра+±0%ŠQX`PaRYX#Y! °@SX°+!°@Y#°PXeY-°,°C+²C`B-°,°#B# °#Ba°€b°`°*-°, E °Ec°Eb`D°`-°, E °+#±%` EŠ#a d ° PX!°°0PX° °@YY#°PXeY°%#aDD°`-°,±E°aD-° ,°` ° CJ°PX ° #BY° CJ°RX ° #BY-° , ¸b ¸cŠ#a° C` Š` ° #B#-° ,KTX±DY$° e#x-° ,KQXKSX±DY!Y$°e#x-° ,± CUX± C°aB° +Y°C°%B± %B± %B°# °%PX±C`°%BŠŠ Š#a° *!#°a Š#a° *!±C`°%B°%a° *!Y° CG° CG`°€b °Ec°Eb`±#D°C°>²C`B-°,±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,± +-°,°+±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-° ,±+-°!,±+-°",±+-°#,± +-°$, <°`-°%, `° ` C#°`C°%a°`°$*!-°&,°%+°%*-°', G °Ec°Eb`#a8# ŠUX G °Ec°Eb`#a8!Y-°(,±ETX°°'*°0"Y-°),°+±ETX°°'*°0"Y-°*, 5°`-°+,°Ec°Eb°+°Ec°Eb°+°´D>#8±**-°,, < G °Ec°Eb`°Ca8-°-,.<-°., < G °Ec°Eb`°Ca°Cc8-°/,±% . G°#B°%IŠŠG#G#a Xb!Y°#B².*-°0,°°%°%G#G#a°E+eŠ.# <Š8-°1,°°%°% .G#G#a °#B°E+ °`PX °@QX³  ³&YBB# °C Š#G#G#a#F`°C°€b` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°%°€ba# °&#Fa8#°CF°%°CG#G#a` °C°€b`# °+#°C`°+°%a°%°€b°&a °%`d#°%`dPX!#!Y# °&#Fa8Y-°2,° °& .G#G#a#<8-°3,° °#B F#G°+#a8-°4,°°%°%G#G#a°TX. <#!°%°%G#G#a °%°%G#G#a°%°%I°%a°Ec# Xb!Yc°Eb`#.# <Š8#!Y-°5,° °C .G#G#a `° `f°€b# <Š8-°6,# .F°%FRX ,°1+!# <°#B#8±&+°C.°&+-°?,° G°#B².°,*-°@,° G°#B².°,*-°A,±°-*-°B,°/*-°C,°E# . FŠ#a8±&+-°D,°#B°C+-°E,²<+-°F,²<+-°G,²<+-°H,²<+-°I,²=+-°J,²=+-°K,²=+-°L,²=+-°M,²9+-°N,²9+-°O,²9+-°P,²9+-°Q,²;+-°R,²;+-°S,²;+-°T,²;+-°U,²>+-°V,²>+-°W,²>+-°X,²>+-°Y,²:+-°Z,²:+-°[,²:+-°\,²:+-°],°2+.±&+-°^,°2+°6+-°_,°2+°7+-°`,°°2+°8+-°a,°3+.±&+-°b,°3+°6+-°c,°3+°7+-°d,°3+°8+-°e,°4+.±&+-°f,°4+°6+-°g,°4+°7+-°h,°4+°8+-°i,°5+.±&+-°j,°5+°6+-°k,°5+°7+-°l,°5+°8+-°m,+°e°$Px°0-K¸KRX±ŽY¹c °#D°#p°E °(`f ŠUX°%a°Ec#b°#D² *² *²*Y²( ERD² *±D±$ˆQX°@ˆX±D±&ˆQX¸ˆX±DYYYY¸ÿ…°±Dmcollective-2.12.1/doc/fonts/Lato-RegularItalic.ttf0000644005276200011600000027212413265671731022057 0ustar jenkinsjenkins DSIGtLGPOSâ¯,HPGSUBV.TI|OS/2Ù®©æJŒ`cmapRÔŸ×Jìæcvt 'çiü8fpgm‹ zAj4 ‘gaspiôglyfmr™êOÔ–>headþŸdúæ6hheaÈ`æL$hmtx4WãæpTkern@µB¼êÄgòloca•E¼/R¸,maxpC §Tä nameü¤UTpost:í]fXšprepœx9½sÈ 0JDFLTlatnÿÿÿÿkernkernGrîè☒Tî°v  ò  ® ¨ Ú ˆ V è¦8’,^$^ Â0æÐ DŽÔJÖJÔŽÔJºL F à!z" ##P#š$4$~%x&&ü'æ(Ð)º*¤+Ž+¸,J,Ü-n..’/$/V/ˆ/º/ì1¢242n2¨2â33V3œ3Ö44J4„4¾4ø5²5ì6¦77º7ô8~8Ä8þ:´;";;þ<˜=2>,?&?è@âAÜBžC8CúD”E.F(> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU-#ÿØ&ÿØ*ÿØ2ÿØ4ÿØDÿáFÿáGÿáHÿáRÿáTÿákÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿá£ÿá¤ÿá¥ÿá¦ÿá§ÿá¨ÿá©ÿáªÿá«ÿá¬ÿá­ÿá²ÿá´ÿáµÿá¶ÿá·ÿá¸ÿáºÿáÃÿáÄÿØÅÿáÇÿáÍÿØÎÿá> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU0ÿˆ ÿˆ ÿˆÿ;#ÿÉ&ÿÉ*ÿÉ2ÿÉ4ÿÉ7ÿ59ÿ,:ÿ|<ÿ;?ÿ,YÿrZÿ¸\ÿ|kÿÉlÿˆmÿ;oÿ;pÿÉrÿˆyÿ;|ÿˆ}ÿ;‰ÿÉ”ÿÉ•ÿÉ–ÿÉ—ÿɘÿÉšÿÉŸÿ;¿ÿrÁÿrÄÿÉÍÿÉÓÿ;åÿ;æÿ;çÿˆèÿˆêÿˆëÿˆïÿ;òÿ;óÿ;&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ0ÿˆ ÿˆ ÿˆÿ;#ÿÉ&ÿÉ*ÿÉ2ÿÉ4ÿÉ7ÿ59ÿ,:ÿ|<ÿ;?ÿ,YÿrZÿ¸\ÿ|kÿÉlÿˆmÿ;oÿ;pÿÉrÿˆyÿ;|ÿˆ}ÿ;‰ÿÉ”ÿÉ•ÿÉ–ÿÉ—ÿɘÿÉšÿÉŸÿ;¿ÿrÁÿrÄÿÉÍÿÉÓÿ;åÿ;æÿ;çÿˆèÿˆêÿˆëÿˆïÿ;òÿ;óÿ;q6 ÿŽ 6 6ÿAÿ†ÿAÿŽÿ«ÿ«":#ÿØ$ÿŽ&ÿØ*ÿØ-ÿi2ÿØ4ÿØDÿ‹Fÿ‹Gÿ‹Hÿ‹Pÿ«Qÿ«Rÿ‹Sÿ«Tÿ‹Uÿ«VÿXÿ«YÿÜZÿÜ\ÿÜ]ÿ¸kÿØl6mÿ†oÿ†pÿØr6tPuPwÿ«yÿ†{P|6}ÿ†‚ÿŽƒÿŽ„ÿŽ…ÿކÿއÿŽˆÿމÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿ‹£ÿ‹¤ÿ‹¥ÿ‹¦ÿ‹§ÿ‹¨ÿ‹©ÿ‹ªÿ‹«ÿ‹¬ÿ‹­ÿ‹²ÿ‹³ÿ«´ÿ‹µÿ‹¶ÿ‹·ÿ‹¸ÿ‹ºÿ‹»ÿ«¼ÿ«½ÿ«¾ÿ«¿ÿÜÁÿÜÂÿŽÃÿ‹ÄÿØÅÿ‹Çÿ‹Ìÿ«ÍÿØÎÿ‹ÐÿÒÿÕÿ¸×ÿ¸Ùÿ¸åÿ†æÿ†ç6è6éÿAê6ë6ìÿAïÿ†ðÿAòÿ†óÿ†ùÿŽ$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ ÿ€mÿ€oÿ€yÿ€}ÿ€åÿ€æÿ€ïÿ€òÿ€óÿ€$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ> ÿ|ÿLÿLÿ|ÿÄÿÄ"$ÿ|-ÿ:Dÿ»Fÿ»Gÿ»Hÿ»PÿÄQÿÄRÿ»SÿÄTÿ»UÿÄXÿÄwÿÄ‚ÿ|ƒÿ|„ÿ|…ÿ|†ÿ|‡ÿ|ˆÿ|¢ÿ»£ÿ»¤ÿ»¥ÿ»¦ÿ»§ÿ»¨ÿ»©ÿ»ªÿ»«ÿ»¬ÿ»­ÿ»²ÿ»³ÿÄ´ÿ»µÿ»¶ÿ»·ÿ»¸ÿ»ºÿ»»ÿļÿĽÿľÿÄÂÿ|Ãÿ»Åÿ»Çÿ»ÌÿÄÎÿ»éÿLìÿLðÿLùÿ| ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ+  ÿº#ÿš&ÿš*ÿš2ÿš4ÿšIÿÍWÿYÿÂZÿÓ\ÿÂkÿšlmÿºoÿºpÿšryÿº|}ÿº‰ÿš”ÿš•ÿš–ÿš—ÿš˜ÿššÿš¿ÿÂÁÿÂÄÿšÍÿšåÿºæÿºçèêëïÿºòÿºóÿº3þÿ þÿ þÿÿ;#ÿ¥&ÿ¥*ÿ¥2ÿ¥4ÿ¥7ÿ89ÿJ:ÿh<ÿ,?ÿJYÿ|Zÿ«\ÿ|kÿ¥lþÿmÿ;oÿ;pÿ¥rþÿtÿ,uÿ,yÿ;{ÿ,|þÿ}ÿ;‰ÿ¥”ÿ¥•ÿ¥–ÿ¥—ÿ¥˜ÿ¥šÿ¥Ÿÿ,¿ÿ|Áÿ|Äÿ¥Íÿ¥Óÿ,åÿ;æÿ;çþÿèþÿêþÿëþÿïÿ;òÿ;óÿ;$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ/ ÿvÿCÿCÿv$ÿv-ÿJDÿâFÿâGÿâHÿâRÿâTÿâ‚ÿvƒÿv„ÿv…ÿv†ÿv‡ÿvˆÿv¢ÿâ£ÿâ¤ÿâ¥ÿâ¦ÿâ§ÿâ¨ÿâ©ÿâªÿâ«ÿâ¬ÿâ­ÿâ²ÿâ´ÿâµÿâ¶ÿâ·ÿâ¸ÿâºÿâÂÿvÃÿâÅÿâÇÿâÎÿâéÿCìÿCðÿCùÿv$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ#ÿØ&ÿØ*ÿØ2ÿØ4ÿØ7ÿÍ8ÿÖkÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ›ÿÖœÿÖÿÖžÿÖÄÿØÍÿØf ÿ|ÿLÿLÿLÿ|ÿfÿf"'#ÿŸ$ÿ|&ÿŸ*ÿŸ-ÿ82ÿŸ4ÿŸDÿ/Fÿ/Gÿ/Hÿ/JÿEPÿfQÿfRÿ/SÿfTÿ/UÿfVÿJXÿfYÿWZÿ[ÿZ\ÿL]ÿ_kÿŸmÿLoÿLpÿŸwÿfyÿL}ÿL‚ÿ|ƒÿ|„ÿ|…ÿ|†ÿ|‡ÿ|ˆÿ|‰ÿŸ”ÿŸ•ÿŸ–ÿŸ—ÿŸ˜ÿŸšÿŸ¢ÿ/£ÿ/¤ÿ/¥ÿ/¦ÿ/§ÿ/¨ÿ/©ÿ/ªÿ/«ÿ/¬ÿ/­ÿ/²ÿ/³ÿf´ÿ/µÿ/¶ÿ/·ÿ/¸ÿ/ºÿ/»ÿf¼ÿf½ÿf¾ÿf¿ÿWÁÿWÂÿ|Ãÿ/ÄÿŸÅÿ/Çÿ/ÌÿfÍÿŸÎÿ/ÐÿJÒÿJÕÿ_×ÿ_Ùÿ_åÿLæÿLéÿLìÿLïÿLðÿLòÿLóÿLùÿ| ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØq6 ÿŽ 6 6ÿAÿ†ÿAÿŽÿ«ÿ«":#ÿØ$ÿŽ&ÿØ*ÿØ-ÿi2ÿØ4ÿØDÿ‹Fÿ‹Gÿ‹Hÿ‹Pÿ«Qÿ«Rÿ‹Sÿ«Tÿ‹Uÿ«VÿXÿ«YÿÜZÿÜ\ÿÜ]ÿ¸kÿØl6mÿ†oÿ†pÿØr6tPuPwÿ«yÿ†{P|6}ÿ†‚ÿŽƒÿŽ„ÿŽ…ÿކÿއÿŽˆÿމÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿ‹£ÿ‹¤ÿ‹¥ÿ‹¦ÿ‹§ÿ‹¨ÿ‹©ÿ‹ªÿ‹«ÿ‹¬ÿ‹­ÿ‹²ÿ‹³ÿ«´ÿ‹µÿ‹¶ÿ‹·ÿ‹¸ÿ‹ºÿ‹»ÿ«¼ÿ«½ÿ«¾ÿ«¿ÿÜÁÿÜÂÿŽÃÿ‹ÄÿØÅÿ‹Çÿ‹Ìÿ«ÍÿØÎÿ‹ÐÿÒÿÕÿ¸×ÿ¸Ùÿ¸åÿ†æÿ†ç6è6éÿAê6ë6ìÿAïÿ†ðÿAòÿ†óÿ†ùÿŽN: ÿ¸ : :ÿœÿœÿ¸ÿ¾ÿ¾$ÿ¸-ÿšDÿ—Fÿ—Gÿ—Hÿ—Jÿ¬Pÿ¾Qÿ¾Rÿ—Sÿ¾Tÿ—Uÿ¾VÿªXÿ¾l:r:t<u<wÿ¾{<|:‚ÿ¸ƒÿ¸„ÿ¸…ÿ¸†ÿ¸‡ÿ¸ˆÿ¸¢ÿ—£ÿ—¤ÿ—¥ÿ—¦ÿ—§ÿ—¨ÿ—©ÿ—ªÿ—«ÿ—¬ÿ—­ÿ—²ÿ—³ÿ¾´ÿ—µÿ—¶ÿ—·ÿ—¸ÿ—ºÿ—»ÿ¾¼ÿ¾½ÿ¾¾ÿ¾Âÿ¸Ãÿ—Åÿ—Çÿ—Ìÿ¾Îÿ—ÐÿªÒÿªç:è:éÿœê:ë:ìÿœðÿœùÿ¸+  ÿº#ÿš&ÿš*ÿš2ÿš4ÿšIÿÍWÿYÿÂZÿÓ\ÿÂkÿšlmÿºoÿºpÿšryÿº|}ÿº‰ÿš”ÿš•ÿš–ÿš—ÿš˜ÿššÿš¿ÿÂÁÿÂÄÿšÍÿšåÿºæÿºçèêëïÿºòÿºóÿºm7 ÿr 7 7ÿ'ÿJÿ'ÿrÿÿ"2#ÿ»$ÿr&ÿ»*ÿ»-ÿ82ÿ»4ÿ»DÿAFÿAGÿAHÿAJÿWPÿQÿRÿASÿTÿAUÿVÿAXÿ]ÿœkÿ»l7mÿJoÿJpÿ»r7t>u>wÿyÿJ{>|7}ÿJ‚ÿrƒÿr„ÿr…ÿr†ÿr‡ÿrˆÿr‰ÿ»”ÿ»•ÿ»–ÿ»—ÿ»˜ÿ»šÿ»¢ÿA£ÿA¤ÿA¥ÿA¦ÿA§ÿA¨ÿA©ÿAªÿA«ÿA¬ÿA­ÿA²ÿA³ÿ´ÿAµÿA¶ÿA·ÿA¸ÿAºÿA»ÿ¼ÿ½ÿ¾ÿÂÿrÃÿAÄÿ»ÅÿAÇÿAÌÿÍÿ»ÎÿAÐÿAÒÿAÕÿœ×ÿœÙÿœåÿJæÿJç7è7éÿ'ê7ë7ìÿ'ïÿJðÿ'òÿJóÿJùÿrÿ«"##ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆkÿÆmÿ«oÿ«pÿÆyÿ«}ÿ«‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆÄÿÆÍÿÆåÿ«æÿ«ïÿ«òÿ«óÿ«-#ÿØ&ÿØ*ÿØ2ÿØ4ÿØDÿáFÿáGÿáHÿáRÿáTÿákÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿá£ÿá¤ÿá¥ÿá¦ÿá§ÿá¨ÿá©ÿáªÿá«ÿá¬ÿá­ÿá²ÿá´ÿáµÿá¶ÿá·ÿá¸ÿáºÿáÃÿáÄÿØÅÿáÇÿáÍÿØÎÿá:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°E E EÿÿlErEtdud{d|EçEèEéÿêEëEìÿðÿÿÄ ÿÄ ÿÄYÿá\ÿÖlÿÄrÿÄtÿ™uÿ™{ÿ™|ÿÄ¿ÿáÁÿáçÿÄèÿÄêÿÄëÿÄDÿÍFÿÍGÿÍHÿÍRÿÍTÿÍ¢ÿÍ£ÿͤÿÍ¥ÿͦÿͧÿͨÿÍ©ÿͪÿÍ«ÿͬÿÍ­ÿͲÿÍ´ÿ͵ÿͶÿÍ·ÿ͸ÿͺÿÍÃÿÍÅÿÍÇÿÍÎÿÍÿÄ ÿÄ ÿÄYÿá\ÿÖlÿÄrÿÄtÿ™uÿ™{ÿ™|ÿÄ¿ÿáÁÿáçÿÄèÿÄêÿÄëÿÄÿÄ ÿÄ ÿÄYÿá\ÿÖlÿÄrÿÄtÿ™uÿ™{ÿ™|ÿÄ¿ÿáÁÿáçÿÄèÿÄêÿÄëÿÄÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°"ÿhÿhDÿØFÿØGÿØHÿØRÿØTÿØ¢ÿØ£ÿؤÿØ¥ÿئÿاÿبÿØ©ÿتÿØ«ÿجÿØ­ÿزÿØ´ÿصÿضÿØ·ÿظÿغÿØÃÿØÅÿØÇÿØÎÿØéÿhìÿhðÿh. ÿÑÿrÿrÿÑ$ÿÑDÿæFÿæGÿæHÿæRÿæTÿæ‚ÿуÿÑ„ÿÑ…ÿцÿчÿшÿÑ¢ÿæ£ÿæ¤ÿæ¥ÿæ¦ÿæ§ÿæ¨ÿæ©ÿæªÿæ«ÿæ¬ÿæ­ÿæ²ÿæ´ÿæµÿæ¶ÿæ·ÿæ¸ÿæºÿæÂÿÑÃÿæÅÿæÇÿæÎÿæéÿrìÿrðÿrùÿÑ ÿàÿ­ÿ­ÿà$ÿà‚ÿàƒÿà„ÿà…ÿà†ÿà‡ÿàˆÿàÂÿàéÿ­ìÿ­ðÿ­ùÿàDÿÍFÿÍGÿÍHÿÍRÿÍTÿÍ¢ÿÍ£ÿͤÿÍ¥ÿͦÿͧÿͨÿÍ©ÿͪÿÍ«ÿͬÿÍ­ÿͲÿÍ´ÿ͵ÿͶÿÍ·ÿ͸ÿͺÿÍÃÿÍÅÿÍÇÿÍÎÿÍ. ÿÑÿhÿhÿÑ$ÿÑDÿæFÿæGÿæHÿæRÿæTÿæ‚ÿуÿÑ„ÿÑ…ÿцÿчÿшÿÑ¢ÿæ£ÿæ¤ÿæ¥ÿæ¦ÿæ§ÿæ¨ÿæ©ÿæªÿæ«ÿæ¬ÿæ­ÿæ²ÿæ´ÿæµÿæ¶ÿæ·ÿæ¸ÿæºÿæÂÿÑÃÿæÅÿæÇÿæÎÿæéÿhìÿhðÿhùÿÑ-#ÿØ&ÿØ*ÿØ2ÿØ4ÿØDÿáFÿáGÿáHÿáRÿáTÿákÿØpÿ؉ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿá£ÿá¤ÿá¥ÿá¦ÿá§ÿá¨ÿá©ÿáªÿá«ÿá¬ÿá­ÿá²ÿá´ÿáµÿá¶ÿá·ÿá¸ÿáºÿáÃÿáÄÿØÅÿáÇÿáÍÿØÎÿá$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU ÿTÿT$ÿT9:::<(?:‚ÿTƒÿT„ÿT…ÿT†ÿT‡ÿTˆÿTŸ(ÂÿTÓ(ùÿT ÿTÿT$ÿT9:::<(?:‚ÿTƒÿT„ÿT…ÿT†ÿT‡ÿTˆÿTŸ(ÂÿTÓ(ùÿT&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ ÿTÿT$ÿT9:::<(?:‚ÿTƒÿT„ÿT…ÿT†ÿT‡ÿTˆÿTŸ(ÂÿTÓ(ùÿT> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ ÿ€mÿ€oÿ€yÿ€}ÿ€åÿ€æÿ€ïÿ€òÿ€óÿ€$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØm7 ÿr 7 7ÿ'ÿJÿ'ÿrÿÿ"2#ÿ»$ÿr&ÿ»*ÿ»-ÿ82ÿ»4ÿ»DÿAFÿAGÿAHÿAJÿWPÿQÿRÿASÿTÿAUÿVÿAXÿ]ÿœkÿ»l7mÿJoÿJpÿ»r7t>u>wÿyÿJ{>|7}ÿJ‚ÿrƒÿr„ÿr…ÿr†ÿr‡ÿrˆÿr‰ÿ»”ÿ»•ÿ»–ÿ»—ÿ»˜ÿ»šÿ»¢ÿA£ÿA¤ÿA¥ÿA¦ÿA§ÿA¨ÿA©ÿAªÿA«ÿA¬ÿA­ÿA²ÿA³ÿ´ÿAµÿA¶ÿA·ÿA¸ÿAºÿA»ÿ¼ÿ½ÿ¾ÿÂÿrÃÿAÄÿ»ÅÿAÇÿAÌÿÍÿ»ÎÿAÐÿAÒÿAÕÿœ×ÿœÙÿœåÿJæÿJç7è7éÿ'ê7ë7ìÿ'ïÿJðÿ'òÿJóÿJùÿr$ÿÉ ÿÜ ÿÉ ÿØ ÿÉÿÜ$ÿÜ7ÿŸ9ÿÍ;ÿ¼<ÿ°=ÿ»?ÿÍ@ÿØ`ÿØlÿÉrÿÉ|ÿÉ‚ÿ܃ÿÜ„ÿÜ…ÿ܆ÿ܇ÿ܈ÿÜŸÿ°ÂÿÜÓÿ°Ôÿ»Öÿ»Øÿ»çÿÉèÿÉêÿÉëÿÉùÿÜÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿÄ ÿÄ ÿÄYÿá\ÿÖlÿÄrÿÄtÿ™uÿ™{ÿ™|ÿÄ¿ÿáÁÿáçÿÄèÿÄêÿÄëÿÄÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°. ÿÑÿrÿrÿÑ$ÿÑDÿæFÿæGÿæHÿæRÿæTÿæ‚ÿуÿÑ„ÿÑ…ÿцÿчÿшÿÑ¢ÿæ£ÿæ¤ÿæ¥ÿæ¦ÿæ§ÿæ¨ÿæ©ÿæªÿæ«ÿæ¬ÿæ­ÿæ²ÿæ´ÿæµÿæ¶ÿæ·ÿæ¸ÿæºÿæÂÿÑÃÿæÅÿæÇÿæÎÿæéÿrìÿrðÿrùÿÑÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°. ÿÑÿrÿrÿÑ$ÿÑDÿæFÿæGÿæHÿæRÿæTÿæ‚ÿуÿÑ„ÿÑ…ÿцÿчÿшÿÑ¢ÿæ£ÿæ¤ÿæ¥ÿæ¦ÿæ§ÿæ¨ÿæ©ÿæªÿæ«ÿæ¬ÿæ­ÿæ²ÿæ´ÿæµÿæ¶ÿæ·ÿæ¸ÿæºÿæÂÿÑÃÿæÅÿæÇÿæÎÿæéÿrìÿrðÿrùÿÑ:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌ ÿ€mÿ€oÿ€yÿ€}ÿ€åÿ€æÿ€ïÿ€òÿ€óÿ€ÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°"ÿi ÿi ÿiÿ‹9ÿ^:ÿ†<ÿh?ÿ^Yÿ°ZÿÍ\ÿ°lÿimÿ‹oÿ‹rÿitÿ}uÿ}yÿ‹{ÿ}|ÿi}ÿ‹Ÿÿh¿ÿ°Áÿ°Óÿhåÿ‹æÿ‹çÿièÿiêÿiëÿiïÿ‹òÿ‹óÿ‹ÿÄ ÿÄ ÿÄYÿá\ÿÖlÿÄrÿÄtÿ™uÿ™{ÿ™|ÿÄ¿ÿáÁÿáçÿÄèÿÄêÿÄëÿÄÿ° ÿ° ÿá ÿ°@ÿá[ÿÍ`ÿálÿ°rÿ°|ÿ°çÿ°èÿ°êÿ°ëÿ°m7 ÿr 7 7ÿ'ÿJÿ'ÿrÿÿ"2#ÿ»$ÿr&ÿ»*ÿ»-ÿ82ÿ»4ÿ»DÿAFÿAGÿAHÿAJÿWPÿQÿRÿASÿTÿAUÿVÿAXÿ]ÿœkÿ»l7mÿJoÿJpÿ»r7t>u>wÿyÿJ{>|7}ÿJ‚ÿrƒÿr„ÿr…ÿr†ÿr‡ÿrˆÿr‰ÿ»”ÿ»•ÿ»–ÿ»—ÿ»˜ÿ»šÿ»¢ÿA£ÿA¤ÿA¥ÿA¦ÿA§ÿA¨ÿA©ÿAªÿA«ÿA¬ÿA­ÿA²ÿA³ÿ´ÿAµÿA¶ÿA·ÿA¸ÿAºÿA»ÿ¼ÿ½ÿ¾ÿÂÿrÃÿAÄÿ»ÅÿAÇÿAÌÿÍÿ»ÎÿAÐÿAÒÿAÕÿœ×ÿœÙÿœåÿJæÿJç7è7éÿ'ê7ë7ìÿ'ïÿJðÿ'òÿJóÿJùÿrÿ«"##ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆkÿÆmÿ«oÿ«pÿÆyÿ«}ÿ«‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆÄÿÆÍÿÆåÿ«æÿ«ïÿ«òÿ«óÿ«ÿ«"##ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆkÿÆmÿ«oÿ«pÿÆyÿ«}ÿ«‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆÄÿÆÍÿÆåÿ«æÿ«ïÿ«òÿ«óÿ«ÿ«"##ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆkÿÆmÿ«oÿ«pÿÆyÿ«}ÿ«‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆÄÿÆÍÿÆåÿ«æÿ«ïÿ«òÿ«óÿ«&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU0ÿˆ ÿˆ ÿˆÿ;#ÿÉ&ÿÉ*ÿÉ2ÿÉ4ÿÉ7ÿ59ÿ,:ÿ|<ÿ;?ÿ,YÿrZÿ¸\ÿ|kÿÉlÿˆmÿ;oÿ;pÿÉrÿˆyÿ;|ÿˆ}ÿ;‰ÿÉ”ÿÉ•ÿÉ–ÿÉ—ÿɘÿÉšÿÉŸÿ;¿ÿrÁÿrÄÿÉÍÿÉÓÿ;åÿ;æÿ;çÿˆèÿˆêÿˆëÿˆïÿ;òÿ;óÿ;> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU0ÿˆ ÿˆ ÿˆÿ;#ÿÉ&ÿÉ*ÿÉ2ÿÉ4ÿÉ7ÿ59ÿ,:ÿ|<ÿ;?ÿ,YÿrZÿ¸\ÿ|kÿÉlÿˆmÿ;oÿ;pÿÉrÿˆyÿ;|ÿˆ}ÿ;‰ÿÉ”ÿÉ•ÿÉ–ÿÉ—ÿɘÿÉšÿÉŸÿ;¿ÿrÁÿrÄÿÉÍÿÉÓÿ;åÿ;æÿ;çÿˆèÿˆêÿˆëÿˆïÿ;òÿ;óÿ;&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ0ÿˆ ÿˆ ÿˆÿ;#ÿÉ&ÿÉ*ÿÉ2ÿÉ4ÿÉ7ÿ59ÿ,:ÿ|<ÿ;?ÿ,YÿrZÿ¸\ÿ|kÿÉlÿˆmÿ;oÿ;pÿÉrÿˆyÿ;|ÿˆ}ÿ;‰ÿÉ”ÿÉ•ÿÉ–ÿÉ—ÿɘÿÉšÿÉŸÿ;¿ÿrÁÿrÄÿÉÍÿÉÓÿ;åÿ;æÿ;çÿˆèÿˆêÿˆëÿˆïÿ;òÿ;óÿ;&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ&ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌ> ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU:ÿJ ÿJ ÿJÿÌ#ÿÑ&ÿÑ*ÿÑ->2ÿÑ4ÿÑ7ÿ|8ÿØ9ÿŽ:ÿ­<ÿh?ÿŽWÿÇYÿÑZÿà\ÿÑkÿÑlÿJmÿÌoÿÌpÿÑrÿJtÿIuÿIyÿÌ{ÿI|ÿJ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØœÿØÿØžÿØŸÿh¿ÿÑÁÿÑÄÿÑÍÿÑÓÿhåÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌr #$&')-./2345789:;<=>?EHIKNPQRSUYZ[\^klmoprtuy{|}‚ƒ„…†‡‰’”•–—˜›œžŸ ¨ª«¬­³´µ¶·¸º¿ÀÁÂÄÇÉÌÎÓÔÖØåæçèéêëìïðòóöù 8‚DFLTlatnÿÿÿÿcase&case,liga2liga8sups>supsD,>B      @ LO,{tu CjqvÛÜÞßàâãIÒxxºtô ¯P`KtyPLûJþz¶ª “õ™ †&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a†‡‰‹“˜ž£¢¤¦¥§©«ª¬­¯®°±³µ´¶¸·¼»½¾írdeiïx¡pkövjˆšþsgwøûúäÿl|÷¨ºcnýÚùm}ðb‚…—ÍÎåæêëçè¹ÁÓôõòóîyéìñ„ŒƒŠ‘Ž•–”œ›ÈÛâqÞßàzãáÜ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ-û™(8<@J@GBhf[[ Q CQ D@?($#-$ +>32#'.54>54&#"#"'4632#"&!!7!!ú9DO.?gI)-60#z -5-I9)8(  c>0((0>þ¡Îü22cüu&#@[87P;+&%ia /(&.8'3<üû/@)(?¤úg6,Ÿÿñ™ !&@#Q CSD +#>74>32#".F uFÄ!-."".-!™ýÂ-UV\44\VU->úÕ."".-""-ښ˗ *@' BQ D   $+#"&=!#"&=“$"%Ì$"%—þß›""›!þß›""›!6—<@H@E  Y  C Q C D@?>=<<6431.-(&##!#!+#"&547#+#"546?3#7>;>;3323+32%3# ‰Nqút 0M‰“+Íjé'&¡v -NŠúŠMvÖ&%j¶ýªûjú¨þX ]þš#¨( 7KGk þX¨ þ˜Gþµ 7KGÿbf8CN£@J) ?BAK° PX@$jhfkCDK° PX@$jhfkCD@$jhfkCDYY·#'#%+.'7632.54>?>;#".'+4.'>¸w»?=+A\Dg>w]9B|±o@&g30 %6I2]@~c>F„¼u"@ê":N,`JsP)þ5H)WHkG" aKN&1/1KoRSšxL‘Å Q;@"!þ2IlO_«…S²Ÿ,A0#þ2Oh¤*?1%Ê-DUZÿ'1EY¥K°PX@'[ [SC S DK° PX@+[ [SC C SD@/[ [ CSC C SDYY@ VT((%#&((($ +#".54>324.#"32>>;+#".54>324.#"32>Ü;`{A8^C%6]}F8^C&‡$1(G4$1'G5w |ûŒ ~:`|A8]C%6]}F8]D%†$1(G5$1'G6xcšj7)MoFc›k8)NpH2H."JsQ1E-!GqU ú… c›i7)MnFc›l8)NpH2G."JrQ1F-!Hr9ÿðѧBN„@;LK.&BK°PX@*hSCS CS D@(hSCS CSDY@IG20)'#! BB+2#"'.#">7>;#"./#".54>7.54>3267ÔFrR-e)<)3R; /2B)6w[Gî” `^æRˆb65]}H'%9i”þq#=T1_¨Fþ³sr§,Nj>!8-&C[49x?þgBGsÞ`þÓ zWf1[‚QOŒu\ B€?R‘j>û¾8W<VF©>³Úš“— @ BQ D $+#"&=“$"%—þß›""›!zþÝS ³ (+.547* Q*;& U”uG JrN)¹JŸ šC .V·¸´SŒ ¢/ rãðþûÿøþÝÐ ³ (+4.'&546? '.547>9) R);& T•uF IsN)1I  šC /W¶¸´TŒþ÷þòþâ¢. rèõ”aáá6C@0,+'$# BK°PX@ kD@ jaY@ 66+7>7'767&/7&546?3>?'.'i ©!©$$$‘3‘T © ©# ‘2‘aÆ!  dIefIe  ÇÆ! dIe fHd  Çs¯A .@+jkMRF +!!#!7!Ü5šþf55þi—6þS‚þQ¯‚­2þñ$ì@ ?S D"+74632'&54>7#"&2A6.1F- *' 4A{/B'3-a_Z&  &5E*Ea< @MQE+!!sÉþ8 ’-ÿñ'ì@SD($+74>32#".-"--""--"n."".-""-ÿ¯ÿ¥.« @kD#"++>;]:IÔ4 I "Ç!Oÿñj§',@)SCSD'' +2#".54>2>54.#"·_ sAb¨ß~` sAb¨à T—qB-Le8T—qB-Le§I‘Û’ßþµÚkI‘Û“ßJÚkúÛY´ºx©j0Yµþíºx©i0åòœ*@' Bh CR D$+7!7#"&/3!!ô6{þÛ,ÖŒ ý„íMå Kqúè„/J§4;@80 BhSCQ D-+'% 44+2>3!2!7>7>54.#"#"&/>ËSf99bƒJþQ)R&êüHIrO)#>U2p% # Wc†¥§/ZR[–†~Cþx  "5;)ÏAusxE6R6ufb–f4]ÿðX§CU@R? Bhh[SCSD<:64.-,+#!CC +2#".'763232>54.#7>54.#"#"&/>áT‹b6*MmCz{V’Ájd–jBP,B\AR€W.N„f¾º">T2rœ& # Xc†¤§.UxIOyZ;#›pl­{B.\Œ_:W:8Zr;0Q;"{›†5P5veb–f4-f™'@$ B\ C D!#+3+#!"&/3>7!Ù±.œ/ýŠ"+¦Â ý™bþ…{W þÄ7ý9DÿðF™.@@=,+Bh[Q CSD(#&(""+#!632#".'763232>54.#"'!<37þ9so\ii4]žÓu?p_N=/C_CS‹d7$HnK2sDgŤM$/þ9d‰Q~ÍO)6J%8eV9]C$œhÿð0—22@/B[ CSD/-%# +2#".54>7>;>32>54.#"¹O‰e:T”Évbšl99W<«3™þ.%7ƒþœ$Dd@O…a6'Ge>O„^5l2`ŠWo¾ŒPwy}Eëýç3,$)þ!;cH(5^€L=cE%8_~»°™$@!BQ C D$'++>7!"&=7° ý 'Žñý™D-û+¦)uRÿð>§3GD@AB[SCSD54! ?=4G5G+) 3!3 +".5467.54>32'2>54.#"2>54.#"c¡r>­œaaH°iZe6‹†t|QÄcN~Z0-Lc5Bz_9$Fd–QqG :X;HoL'9Y3^…Q¡Ê)&’bX™q@4[{Fƒ¾-#šui¨w?‰.RtF@Z9$LwS5W?#²4Sf3-N;"-Lh:+P=%Ñw§12@/B[SC D.,&$ +".54>32+>74.#"32>5K‚`7RÂp^•h8;T6þc0žù.=‘P%D_:I{Z3…xO~Y0M0\…Ti·‡N;j”XH}vvBþ<6-/Ê:_D%2Y{Ju‚5Zv-ÿñ‹Ú';K°$PX@SCSD@[SDYµ((($+74>32#".4>32#".-"--""--"d"--""--"n."".-""- ."".-""-.þñ‘Ú,D³ ?K°$PX@SCS D@[S DY@ )'"+74632'&54>7#"&4>32#"..A6.1F- *' 4Ai"--""--"{/B'3-a_Z&  &5E*E."".-""-ŸêÞW³(+¨6þ#/+¦ý-Æ‘ ä  ã '0’ºŠ!@YMQE+!!!!Zü¦:Xü§=ƒЃ}ë¼X³(+ 7>7%>7.'%.54>7³üÊÝ/+þZÓ|þo ä  ã '0þn„ÿñ§%95@2BhfSCSD(&#*$+>32#7>54&#"#"'4>32#".„!KWc8FqP*/HWO= %w 5LWI1_Q8R;'  !-."".-!4(*Je;TvW?;@+š¦0JA>H[=NZ$ûž."".-""-Mÿ°MTdf@c [ <Bh[  [  [OSGVU^\UdVdLJ@>9731)'TT +%"&'#".54>3232>54.#"3267>32#".54>32%2>7&#"FR?†H0G. ?[wT?[)„!3aK.Dz©e‡ó·lPÆvÖL  kþû ‘ð­_0 3HR_³ÿ éÉ¥u@O–ØŠ×Yu3WC( GrI9HÿÁœ™ $@!BZ C D# +!#"&'!+3!.'œNý¸¤ %’¿½þ>ë„ ^þ¢™üzQB&&Cae™*=@: B[S CS D*(" !+3!2# !2>54&#%32>54&+a±¨p¡h2%JoJ‚G†ÁyÌ?OxP(€þ÷çQyQ(|‚î™-SwI@tbL‘pc¥xBþ*NnE_q„+MlAggdÿðø©/D@A BhfSCSD'%"  //+%2>32#".546$32#".#"§BfN7) A^õ£€ÌKtËžL|fS$@(EmWtÊ”U:gŽ‘ ' Qfq\¥ä‰»6ß{2F+N (/(]­ö™o°{Ba=™ @S CS D!(!$+#!!24.#!!2>=qÇþí¢þ±î„Õ”PÀ7g”]þÎŒ2xÆŽO1·þÓ×v™Z£ãŒl®yAû–[«õaA™ (@%YQ CQ D+!!!!!!.ý<üþ=uüϱ/þ“þ˜™aA™ "@YQ C D+!!!#!.ý?ýêL½±/þ˜ý–™dÿð%©:G@D& Bh[SCSD20+)" ::+%2>7#"&50>17!#".546$32#"'.#"À4ZOI$'Ç šC6s€ŽR‚Ó–QsË¥SˆnY%< 5KjK{Í“R;l˜‡ D OýÝ':']§èй3Ýz2D(N  )%^®ö—q´~CaE™ @Z C D+!#!#3!3•½QýEP½±¼O»O¼Žýr™ý}ƒì™@ C D+!#3=¼¯¼™ÿûÿð™QK°PXµBµBYK°PX@ CSD@h CSDYµ#%"+#"&'7>3232>73‘ïÆ6^0 !+4\J3 u»Ûîým"N~\Âr%™ &@# B\ C D'(% +3267>;#".'.+#3ŽB#-ð,ŸýÄ%#°¡ þˆ'+QQº¯»" ý” #ýY  <ýn™af™@ CR D+%!!3/7ý±»žž™av™"&@#Bh C D!6(+67>;#>7+"'#324 3‹°¤ƒ ýÓ/.þ§‚¥±‰[ô'*'… úg#.ü~++„.ûÚ™ ü{aE™@B C D!+2>73#"&'#3r j€¥°^ ý—‚¥±`™ û¹)úgHûÜ™dÿñŽ©)@SCSD(((&+#".546$324.#"32>Ž4_‡§Ãk„Ó•OrÉ¡„Ó•OÀ8g•]vÆŽP8g”]xÆŽO2zÚ¹”h8^§æ‰·3Þ|_¨çm±|D_°ùšm°|C^¯ùri™/@,[S C D !+#!2# 32>54.#nBº¯‹r¨m6JÏ…tJÐU‚X."DhEýæ™6dŒVq½ˆMìý¨4]L:\A#dþ׎©0P¶ BK°PX@SCSCD@kSCSDY¶(((%&+#"&'#".546$324.#"32>Ž3^…R!—"5Å6q<„Ó•OrÉ¡„Ó•OÀ8g•]vÆŽP8g”]xÆŽO2yظ•4þw^§æ‰·3Þ|_¨çm±|D_°ùšm°|C^¯ùrU™#7@4B[S C D#",!+#!2#"'.# 32>54&#vJº¯yr§m54bŠW 5¦1þò "%/D·U‚Y-‰‰Yý§™0YNW–wS&ýÄ(­ýÛ0WwGms ÿðÕ§==@:=BhfSCSD;9(&#!#"+#".#"#"&'7>3232>54.54>32ž &;T?@dE$0NeheN0Fƒºt€ÉAB,DdKEoM*0NcicN0@x¬ln°9»#)#(E\54G3')2JhJfµˆPeVY -7-,NmB7I2%&0JlOX¢|JTI{™™ @Q C D+!#!7™þYœºœþW™ûü’ÿï$™#@  CSD +%2>73#".5473vP‡eA k»jcœÏ{o¬w>j»k'Ko“>m˜Ziü—{Ó›XH²j-/iü˜&K|Z1|W™@B C D, +32>7>;#|•ïé%–ý7©™ü M**L!úgÁ™,!@' B C D,> +32>7>;2>7>;#.'#’š  ­$+³  “'’ýµ©Â þ/¨™ü ( <öü : :öúgO&&û±ÿºì™@B C D'"(!+ 3267>; #"&'+ôþ¯© l Áþ z¨þÜ þQ$»ýœýäöýlüû q ý­$yû™@ B C D,"+#32>7>;®G»Gþ†¦î  } !›=ýÃ;^ýÂ44>«™$@!Q CQ D+!!7>7!7« ü”âü" lý7™< ûl˜; •™!þáWü'@$[OQE!#+!+32!ßW—Å™þáCùÈ  lÿ¥q« @kD# +32#"&'lI +iH2«"ú9" ÿñþá)ü'@$ B[OQE!#+!!7>;#"&57ÑXàþ¨ ˜Ä˜üøåC80×™@ Bk D+!+3#"&'.'+>rg‚Ä Áˆ™ýz`+,+þ ÿ¡þä…ÿX@MQE+!7…ý+¨ttM‹ § @kD  +2#"&'ð kc ¹§ò 0ÿò©+^@ BK°"PX@SCS D@SC CSDY@ $!++*' +!#"&57#".54>322>7.#"0\#$S]f732#"&'#"32>54&O²¯X&X`h6"?Ym€FQƒ* ã0b[P &j74\L<)VÁý4=eF'¼µR¤•_6D?Au32#".#"32>3234]^e:X‡[/Mоrdˆ38 +A3E{\58Q6-F6)!,¶9L-eG'!65ÿòÁ*s@ BK°"PX@CSCS D@!CSC CSDY@$"** +!"&57#"&54>323%2>7.#"ô#&Zck7‚"?Ym€FJ{+E¯¶þ90a[O!&j6OY0V$ÓAjK)½´R¤–^6:6-ú?;i‘V 5,^•¸Yw};ÿòq*9b@ 0BK° PX@hSCSD@hSCSDY@ ,++9,9*&%*+32>32#".54>32%">54.q6üÆuv0K;-& ,3afo@WŠ`4!?[uQMpH#þÎ?jR:šºd #8BoYB  †#75K2:k˜^N›ŒxW2,DRB4[{G19B$' ?ÿÌ®#g@ BK°2PX@"SCQCQD@MWSDY@ ##T%#++'.5737>32#"&#"3ve.KF‡d‘ AcI>3  +I8& ýaüÀÏKW NbW‡]0\8ZA]€ÿ¾þ”Ë;K^@9$BK°PX@+ [[CSCSD@.h [[SCSDY@=<[YSQEC32.5467#".54>32!2>54&#"4&'.#"32>0"(")1)Bz¯mWe7R†«Y$G# &^<:eL,5f–bnQ~ þ~6R9YN6R8W 6_*RxN&yoBmM+Ü2PD<32#>54&#"O²²SOÁes|L²L=B-_ZNAÁýTvy˜‘+ý{…%RP1ZNýç_˳GK° PX@SCC D@SCC DY@  +##".54>32ˆz¯zò#,,!",,#õü õ>-##-.##/ÿgþ”Ƴ(Yµ BK° PX@SCCSD@SCCSDY@%#U%+#"&'7>323267#".54>32‡†-LiC#2 GE …î#-+!"+,#õûÀ=iN- ` IQ@>-##-.##/N×Á0@-B\CC D%(%!+3267>;#"&'.+#²jOþy 0™ þþ ";°µÁü Wþs þ ŸþÁXºÁ@C D+33X³¯³Áú?GÁ0Z@  BK°"PX@SC D@CSC DY@00&%$$! +332>32>32#654&#"#>54&#"GzY<K²cgbL½hpoL¯L2;+VPEA°M/;0YOD?ö<µ~ƒ‹…’‰2ý{…1(KG+S{PýÙ…0GD0[ƒRýðG½P@ BK°"PX@SC D@CSC DY@ &$!+332>32#>54&#"GyY<QËmq}L²L=A0c\O:ö<Ćˆ˜’*ý{…%RO4_‡Sýý7ÿò¶#NK° PX@SCSD@SCSDY@## +%2>54&#"".54>32ÀIxT.nhJwT.mYSŠd8P‹½nSŠd8P‹¾}P…®_ŒŽO…®_Œ‹9lœd€âªb9lcâªcþ©Ý+–@BK° PX@SCSCDK°"PX@SCSCD@!CSCSCDYY@#!++(&!+32>32#"&'"32>54&¥Y<&Zcl8"?Ym€FK|*6À0c\O &k74\L<)Vþ©M<ØBkL)¼µR¤•_6;7þEÌ5#".54>322>7.#"p ;#PZb432&#"GzY A¡Y** /,a˜38ö ¸Œ˜«½Àþ* ÿð99@69BhfSCSD/#%/#"+#".#"#"&'7>3232>54.54>32Ó !/D0-J6>^m^>7g‘Za•/, 0I81O7>^l^>3`‰V[‹2S ,;!/8)&:ZHF€a:E6D #2C&3<(#7XI@w\7=4_ÿðµ>0bµ+BK°2PX@#jhQCSD@!jh\SDY@ %#(+&+74>7#"&54>?>;!!32>32#"&Ÿ?q œK^, þõ=/)( %0}?ap¿$:/û 9þ£þ)2 31 U+1j`ÿòÕõ#L¶BK°"PX@CT D@C CTDY@ ##*!&+32>73#"&54>57#"&5467bL=B.a[N?±zX"QÈjq}Lõý|$RP2\‚Pü "("Q…š*„Káõ@BC D, +32>7>;#K— :‹þ•õýt%J$$I&Œü Q¹÷. @'BC D*!,< +32>7>;2>7>;#"'.'+Q…] Jq €þW‡{ þÛ‚õýt#A A#ýp#B! C#Œü "¯! ýR"ÿβõ@BC D(")!+32>7>; #"&'+hóŸª å  ¡þ–Ÿ¿ þì œ!Ô þ¤  /þ+ýà•þQþ©èõ@ BCD,"!++32>7>;<)‚Ûþú“£  @þÒ)“¹ý‚*+Yõ@QCQ D+!!7>7!7!P ý¸ôý6  Kþ¾©#ý&‹J #ߌ1þáYüE7@4&:B[[OSG=;303++4𑯎.54>;+";2#".54>ž85 JI-Y„V1  'B/"3;5@#C= 2EfC" & Å4Bhw|575a˜i7MEoQ;?<MgA![?;2>5<&454>7.54>54&+"&54>5732«85 JI-Y„V1  'B/"3;5@#C= 2EfC" & 4Bhw|575a˜i7M EoQ32¼AI%Ef@4f_V$AI%EeA4f_VeUFCpP, '!TGCpP-!'!”þ¨í !&@#SCQD +>73 4>32#".”C  hB."--""--"þ¨-UW\45\VU-ýâß."".-""-Šÿ&æ/8…@%BK° PX@.jhfkSCSD@.jhfkSCSDY@ ##'#+.54>?>;#".'>32+÷R‡`4MÑ„"@-R}04 +=*“@[?* '!S`i6!@™tj’Z‰]0 Dq›`~מ\µê ?1< üý"<#6&±¤„¡Dv£„§>?@<+Bh[SCS D&&%#%&" +#!>3!#!7>7#7>;>32#"&'.#"!zþY 60<®  ü;#9+$£q! P°mT{X9K#2H4AkQ3 Ø•ÿKj*I p /C..F ^¥zF&B[5, 0$+OpEþöF«à+`#7?@< !B @ ?WSD42*((+467'7>327'#"&''7.732>54.#"!™[—,h:9f+™Y—"!˜[˜,h99e,™Z˜!„#>Q//S=$$=S//Q># 9e,™Z˜"!™[˜,g:9f+—\˜!!™[˜,g:.Q=$$=Q./R>##>R~Ä—"8@5 B Z Y C D"! ,! +!32>7>;!!!!#!7!7!£6þÊÓ  _ "‘þ6 þ© V þª'¬'þ© V þ©p'ýÃ!:; =üÙcicþ¿Aci«þ©5ý@YQD+3#3#«ŠŠŠŠýüæþáüå;ÿƒ¦FVA@>FTL;!BhfWSDDB+)&$#!+#".#"#"&'7>3232>54.5467.54>32>54.'R!/B0/K6DfvfDY_%-6f“^a™00 !0I:2O7)BUYUB)ag%.3a‹W[Œ/ýœ:Zn3:26Ug1E9ó.>".A88JdH[)!U9J`7D6B "3E))<0(*0?R8YŒ'"X>AvZ5>6ý•/B82V51D70#P^°¦‘'@S D((($+#".54>32#".54>32? )(() g))))((**((**]ÿò¨+Gaž@ BK° PX@5h f[[ SCSD@5h f[[ SCSDY@\ZPNB@42(&++ +2#".54>32#".#"32>%4>32#".732>54.#"; <9¥tb¡s?Dz§bm—9. 2M:GpP**Kg>?V9!ü.4`…¢ºee»¢†_44_†¢»ed»¢…`4e,Qs‹¢XX£sR-c«è„X¢‹sQ,Ú@BIDz¨de©yCC8A -TxKMyR+òe»£…`44`…£»ed»¢…`44`…¢ºeY¤tS--St¤Y†ë¯e.Sv¦?ƒ©-9L@I!Bh[ WSD/.32.9/9%# -- +"&/#"&54>?6454&#"#"&/>32'26?.04AR%VŽj(0"0$6xD,D//ÿ.G$ B\9*H 1  ED(K;&& .6 (2.4G(þƒP&#m*"…ý %µ%(+77…4¯ o ;]4¯ o ;„ þù þù …„ þù þù ¥>à=K° PX@_MQE@kMQEY´+!#!´[4“$ý9àþ^ a< @MQE+!!sÉþ8 ’^ÿò¨3IV†µ>BK° PX@/h  [ [SCSD@/h  [ [SCSDY@44VTLJ4I4H)!*,,& +4>32#".732>54.#"#!2#"'.#'32>54.+^4`…£ºeeº£…`44`…£ºeeº£…`4e,RrŒ¢XX¢sR-c«ç„„æªbæœ ¬¦kj ã”!É Ps8M/+F4„Ìe»£…`44`…£»ed»¢…`44`…¢ºeY¤tS--St¤Y†ë¯ee¯ëàþž|}z^„ þ². r(:&%8$pÐA@MQE+!!~ýîAqŽ'ª'@WSD((($+4>32#".732>54.#"Ž3XvDEwX22XwEDvX3}6I**I66I**I6hCvW22WvCBuW33WuA*I66I**J77J3PK¯ <@9jhZMQE  +!!#!7!!!ß/›þe-Œ-þj—/ýï½üD¯þ…ƒþŽrƒ{ü$ƒ„¯d-9@6+ Bh[SD(&#! --+26;2!7>?>54&#"#"&/>â`m0?"Ç2*Êþ ú6(6**A!C•ddS,HA<µ  *  Ü89:-0+2 ji¥|Àd:S@P6Bhh[[SD31.,('&%:: +2#".'763232>54>54&#"#"&/>ö.K5‹980Nd48Q7" : +!"5%FV [S5,2=@5HWd->$Œ-E7<]?!1G/!%011Y=>,-.+ 4P4‹¤§ @kD #++7>3¤øf¦"§þÿ òþ¨½ó%2@/BCT CD%%'%!&+32673#"&=#"&'#"&5<7\SVQF„=]«|gF‡PA^#U %žóýY  RYJBîü lHC/+$H þå kÿ7‰™*@'hiS D+##!#".54>3‰ܵ¶þë¶j]–i9Hƒ¸q™™ú7Éú7]2Z~MZtC”½¾è@OSG($+4>32#".”)67((76)Q8((86))6þ¡w K°PX@  B@  BYK° PX@^TDK°PX@jTD@jjTDYY@ +232654&'73#"&'76E(+GB:k JC!;Q0&Bù (# R?.$9'5ä„z^N¶ BK°2PX@jjQD@jjMRFY¶$+37#"&/733!ð“6 y   õhNy þuá´,\ 6½ýƒ]Ž<Ш!)@&WSD!! +2#".54>2654&#"Ö:]A"0UxH;^A#0Wy ZYA?2E+@¨%Ea;37#"&/733!>73ýn UjþÍ’wü„/Fœ1!Gûu“6 y  õhNy þuîþêèI ±± >Íý] [ý…´,\ 6½ýƒ]½,þ»É\™ 7Hb@_B;5Bhh   Z [  CT D HGFEDC?=9820,* 7 7#" +%+>;26;2!7>?>54&#"#"&/>%37#"&/733!“/Fœ1!G¦`m3C$»2*Êþ ú6(6*/=  +•ü¬“6 y   õhNy þu5 [ýIdS-LC>!ª8*  Ü89:-04) ji<´,\ 6½ýƒ]“zŸTZ~@{P " / X B  h h  h [  [\ S C DZYMKGEA@?>8631+)TT##!#+3+#7!"&/3+>;%2#".'763232>54>54&#"#"&/>>73 n UjþÍ’wü‰/Fœ1!Güu.K5‹980Nd48Q7" : 46"5%FV [S5,2@ 15HW þêèI ±± >Íý] [->$Œ-E7<]?!1G/,2%011Y=>,-0% 4P4üc,þ»ÿçþ›u';5@2BhfSCSD(&#,$+#".54>?332>324>32#".u!LWb8DoQ,/IWN: $u 1HSF/-;!7S<'  þÿ!-."".-!Õ4''HgAQsS<56$›¨-B:8CV;)?+$."".-""-ÿÿÿÁœõ&$ OÿÿÿÁœõ&$ _ÿÿÿÁœÛ&$ OÿÿÿÁœÐ&$OÿÿÿÁœè&$ OÿÿÿÁœ-&$Pÿ¨¾™9@6AYYQ CS D# +!!!!!!!+!ºýLþ*ý:þÍ &‘÷©™™þ“þ˜‹þ¢í$@dþ¡ø©JùK°PX@9= HB@9= HBYK° PX@0hfSCSC SDK°PX@0hfSCSC SD@7hf hSCSCSDYY@FD<;64/-%# JJ +232654&'7.546$32#".#"32>32#"&'76û(+GB0rµ~BtËžL|fS$@(EmWtÊ”U:gŽUBfN7) AWß’JC!;Q0&Bù (# v d¢Ú€»6ß{2F+N (/(]­ö™o°{B ' Q^o:?.$9'5ÿÿaAõ&( $ÿÿaAõ&( $ÿÿaAÛ&( $ÿÿaAè&( $ÿÿV õ&, ÿÿõ&, ÿÿgÈÛ&, ÿÿ©è&, E€™!,@)YS CS D!%(!+3!2#!#%4.#!!!!2>S³Pî„Ô•OpÈþï¢þQ³{7f”]þÍ>\ þ¤?2xÅŽN Z£ãˆ·þÓ×v›’l®yAþnýü[«õÿÿaEÐ&1šÿÿdÿñŽõ&2 ¾ÿÿdÿñŽõ&2 ¾ÿÿdÿñŽÛ&2 ¾ÿÿdÿñŽÐ&2¾ÿÿdÿñŽè&2 ¾lÝ9W ³ (+  ' 7 9þ|8fþÇþnP‘þÐg0„øþ§þ]dþš]e[]þ¤Z0ÿ“¿Ù%1=g@5*)# BK° PX@kCSCSD@jkSCSDY·**'(%&+#"&'+.546$327>;.#"%4&'32>Ž4_‡§Ãka¤Cm=JÓMRrÉ¡h¯EY `ÀEJû•+)É2‚OvÆŽP«$"ý<0wHxÆŽO2zÚ¹”h82/‰ T錷3Þ|;6p ðTßþ´aŸ<.1_°ù*X“;ü‡&'^¯ùÿÿ’ÿï$õ&8 †ÿÿ’ÿï$õ&8 †ÿÿ’ÿï$Û&8 †ÿÿ’ÿï$è&8 †ÿÿyûô&< <ÿÿ ±¸ÿÿ°++®…™,@)\[ C D"( +32+#3 32>54&#÷Ñr§n6JŽÎ…Ñ!º¯»3JÑU‚X.ˆŒ6c‹Uq½‰Lþï™þaý«4]Ltƒ3ÿ&­P}@ JGBAK°,PX@(hSCSCQD@%hWSCSDY@LKFD=;%# PP+2#"&'7>3232>54.54>54.#"+'&573>ØY~Q&/FQF/+@K@+>iŒOYŠ10 "-@0*G3.DQD.1JWJ1-I4>mU: l.KFˆn(£^„¤­6Se/D`H638&#/)+=XAV‰_3E6B "6I*0?.(4H8>XE:@P8;0Ct™Uü˜ÏKW!M\§€Kÿÿ0ÿò©§&DCiÿÿ0ÿòϧ&Dv+ÿÿ0ÿò¯—&DÛ"ÿÿ0ÿò†&Dâ"ÿÿ0ÿòÈ‘&Dj"ÿÿ0ÿò©Þ&Dà6ÿð„GUbG@ E?"BK° PX@5hh  [ S C SDK°PX@5hh  [ S C SDK°,PX@?hh  [ S CSC SD@Jhh  [ S CS CSC SDYYY@&WV\[VbWbQOIHCA<:75/.(&  GG+232>32#"&'#"&54>7>54#"#"&/>32>32>7">54&]W8wxG€c>(³!)!7QP`UT^ýÜ%32#".#"32>32#"&'76(+GB2JqL'Mоrdˆ38 +A3E{\58Q6-F6)!,/TTW1JC!;Q0&Bù (# y CkŽT{â®hGDC K‚²f>eG'!63H-<?.$9'5ÿÿ;ÿòq§&HCüÿÿ;ÿò§&HvÝÿÿ;ÿòq—&HÛÝÿÿ;ÿòƒ‘&HjÝÿÿ=§&ÈCðÿÿ_u§&ÈvÑÿÿ"^—&ÈÛÑÿÿ/w‘&ÈjÑ<ÿóÍ„1E6@37-B10@[SD32=;2E3E+)!+.54?.'&54?7#".54>32.'2>7.#"É)`7%X£F®qQ^FˆÈ‚RŠe8E}®iaœ0LL¾!AoX> &32#"&4>32#"&ƒ¾üB—&2'&21;X&2'&20<à‚–2&(1$>ýf2&(1$>ÿÇä%)4Ï@32#"BK° PX@!CSCSCDK°PX@!CSCSCDK°PX@!kCSCSD@!jkSCSDYYY@+**4+4&$+"'+7.54>327>;&#"2>54&'°|Z$:C„(+P‹½n}[" Z€'+P‹¾þº8NJzY1áI{Y1 þF7=1µ5ŽX€âªb@.­5ŒWâªc¦YBZ-L…´þsM…´g,K ý¨,ÿÿ`ÿòÕ©&XCü±°°++ÿÿ`ÿòÕ©&Xvݱ°°++ÿÿ`ÿòÕ™&XÛݱ°°++ÿÿ`ÿòÕ“&Xjݱ°°++ÿÿQþ©è©&\v»±°°++%þ©æÁ,p@ BK° PX@!CSCSCD@!CSCSCDY@$",,(%+3>32#"&'#"32>54&%ݰY&X`h6"?Ym€FK{+.'0b\O!&k74\L<)Vþ©ý4=eF'¼µR¤•_6;6þ†&Ì!.'¤ Y0LRVB Ný¸¤ %’¿½_-&(  üÜë„ Ö @F:?n)^þ¢™úg !*3 ' éQB&&C0þª©4F(K°PX@&;BK°"PX@&;B@&;BYYK° PX@#SC SCSDK°PX@#SC SCSDK°"PX@*hSC SCSD@.hSC C SCSDYYY@65?<5F6F1/('$"44 +2#"&54>7.57#".54>32#32>2>7.#"> Y0LR*:"$S]f7G Y0LRVBý¥±/ý<üþ=ui-&(  Ö @F:?n)™™þ“þ˜ !*3 ' ;þªqAPÝK°PX@G2B@G2BYK° PX@*h SCSCSDK°PX@*h SCSCSD@1hh SCSCSDYY@CBBPCP><0.)' AA +2#"&5467.54>3232>3232>">54.I Y0LRI9U‡_2!?[uQMpH#6üÆuv0K;-& ,M’T+$(  ?jR:šºd #8Ö @F::f(324.#"32>ñýŽ;÷þ=qüèZ÷–u¼ƒFh¸û’KkS h/Z‚Sm¶„I0[ƒSmµƒH™™þ“þ˜âr~\£ã‡¹6á~&Ge?ýŸj¯|Dcµþ›k­|Cb´ý.ÿòæ4FU…@ L2 "BK° PX@$h S C SD@$h S C SDY@ HG65GUHU><5F6F0.&$ 44 +232>32#"&'#".54>32>2>54&#"">54.ÌAhJ'8\Œ¾~ym0K;-&,2afp@iŸ'EÎQzQ)U‘¿kk#?ºýIQ}U+]aN|X/.JH;fP8 ‘·h&'="?X5)LC;0& ››#74L1tsny;e„H¤ý¬Xe[ZfüvNŒ¾qs‡L‹Äy/VB( .YQ3:A!*!ÿÿ ÿðõ&6 þÿÿ ÿð#©&Vv±°°++ÿÿ ÿðÞÛ&6ùÿÿ ÿð,™&VÜ~±°°++ÿÿyûç&< <ÿÿ ±¸ÿÿ°++ÿÿ«õ&= 8ÿÿY©&]v–±°°++ÿÿ«&=8ÿÿY¼&]ß–±°°++ÿÿ«Û&=8ÿÿY™&]Ü•±°°++þŸi­'6@3 BYSCSD''#"+#763>7'.546767376$3#"!Á³2þûÒ :.UH8±›Ö*0Ù /UI9+Gý!˹[7:^EÔ  ªÅ¿_;^E¤}Q‘—@Bk D, +#"&/.'+3r d… wï ‘€€ q‘®—@Bk D( +32?>;#qx d ƒ sþúÿÿpÐAqŒ’— @W D +".5467332>73r>V6y4B'6#z&EgŒ!:M- 3?)9!6aI+½º@SD($+#".54>32#.-""-.#:-""-/##/¹kJÞ=K°PX@WSD@[OSGYµ$&($+4>32#".732654&#"¹ 7H()I8 8I)(H7 d6/-77-/6#*D22D*)D00D),88,-88/þªt YK°PX@ B@@ B@YK°PX@ SD@jSDY@ +2#"&54>732>R Y0LR.@%W-&(  Ö @F:"@:2 !*3 ' h¯ †QK°*PX@WS D@O[SGY@ +273#".#"#>32èAi!2B% 5-) (j"3A%!4-()X/M7#-,.N8#§‹)§ #@ SD   #++7>3!+7>3ÖHœ"—ÿSà §þÿ òþÿ òÿóÿô–ó!YµBK°(PX@SCS D@SC CSDY@! 6##++#!#"&'7>3267#7>3–"|l¬kþ›O€z=<;N¬ óD#ü‹uý}w‡ H ?C?  –†Œ@MQE+!!¥áýŒ}–oŒ@MQE+!!¥Êû7Œ}»ø¾ö³(+.5467Ûj^/ ! ø%J%dºL  09@"6 áß³(+'&5467>54&'&547pj^0 ! ß%J%dºL  08@#6 þîì³(+7'&5467>54&'&547äj^0 ! ì%J%dºL  08@#6 »øÚö1µ(+.5467.5467Ûj^/ ! ­j^/ ! ø%J%dºL  09@"6 *%J%dºL  09@"6 –á´ß1µ(+'&5467>54&'&547%'&5467>54&'&547yj^0 ! Šj^0 ! ß%J%dºL  08@#6 *%J%dºL  08@#6 ÿÜþîûì1µ(+7'&5467>54&'&547%'&5467>54&'&547¿j^0 ! ‹j^0 ! ì%J%dºL  08@#6 *%J%dºL  08@#6 ™þšúÀ%,@)  BCSCD$&$"+>3632>32! #"&'!¡,*?G#)33#P$KIF &!þ•6H0+u6þ–¬/Ôþ, ) þDüú ¼BþšûÀ9E@B  )!("B[C SCD98'%#&$" +>3632>32!!#.'#"&'#"&5<>7!!¡,*?G#)33#P$KIF &"þ•Pj.*?G!0+O$KIG&!kPþ•¬/Ôþ, '(ýv:/þ/ Ñ (ŠçàÍÈ,K°PX@ SD@OSGY³($+4>32#".ç:e†LMˆe;;eˆML†e:SMˆe;;eˆMM‡d;;d‡-ÿñ,ì';@SD((((($+74>32#".%4>32#".%4>32#".-"--""--""--""--"!..""..!n."".-""-."".-""-."".-""-Zÿïÿ¦'1EYm¹K°PX@+[   [SC  S  DK° PX@/[   [SC C  S D@3[   [ CSC C  S DYY@~|trjh`^VT((%#&((($+#".54>324.#"32>>;+#".54>324.#"32>%#".54>324.#"32>Ü;`{A8^C%6]}F8^C&‡$1(G4$1'G5w |ûŒ ~:`|A8]C%6]}F8]D%†$1(G5$1'G6):`{A8^C%6]}F8]D%†$1(F5#1'F6xcšj7)MoFc›k8)NpH2H."JsQ1E-!GqU ú… c›i7)MnFc›l8)NpH2G."JrQ1F-!HrRc›i7)MnFc›l8)NpH2G."JrQ1F-!Hr…é ³(+7…4¯ o ;„ þù þù wÚ ³(+'&54767&'&54?Úþë5 ² p!;þ{   ÿc— @ C D#"+'+>;?/Fœ1!G5 [*ÿñÁ¦B[@X 4 Bh  h [   [SC S  DBA?>=<861/"##%%$+3>32#".#"!#!!#!32>32#".'#7367#T!w¡Åp…²;C  &6J3MˆoT0ýúÚþd§‘RoI. ASÞŽq­v>‘ ‡ „ÌŽLcWB  4f•`7#H&6Ì×-6-DeqR•Ò€cJG I6—%B@?BhS  CS  D%%!4( +>7>;#7+"'#32'###7 ½ fGk.Î }.iGgâ ¤54.#"!"&546767!7.54>32!Ã5Ti<5a‡Re®€H*Lj@4þ/LxR,0X|˜¯`zÈŽNAw¦f~ (±Jx¨pZŠ_0B€¼zMyY:þO%³Tv“Ta¬’tQ+Lˆ½prÄ›l²a Aÿó¨3HB@?"Bh[SCSD54?=4H5H#*++$+>32#".5467>32>7>54&#"#"&'2>7.#"¡'JMT0ItQ,n¦Ý‰HwT._‘¸g^" e\'C7) :qdU .I5K{\: ]9*8lœc?!Êþ¯ó‡/X~P#sÊ”VRQ"=9 û‰7oªr-S@'>m•WgpÿÑÁ— @ B CR D+)3!.'ÁûÔ©ýŒå   —úÿÎ9 !9–þ¨Œ— $@!Q CD +##!##7Œ½È²Çþɱȼ—’ù£]ù£]’ÿðþ©™$@!BQ CQD+!!!!7>7 &5<7ÊRü½±ý¢Aû® œþ! ™™ý;4ý;™A ý¥^à@MQE+!!´[ü¦à‚Ll±"@Bj[ D,'!+!##"&5<>7!2>7>;p©°"Ne [n™  þ[ A 7Q=þÊ ';OL@IK-B[  O  SG=<)(GE32>32%2>7.#"!2>54.#"{3P?1DNX35[C'7\xB3O@2DMX35\D'7]yý“!<86$,4!&A1$1S%B1$1!;86%+4þ!8K))K8!(KhARŽi=!9K))K9!(KiARŽi<’5E''E4!32#"#"&'7>32>7Ú)¼‰#A*\n÷Og}F D 6Q=,C®¸Wnuûúf’_- L :^B¶‚ÿÂ7^@[0!/"B[[ O[ SG42+)&$77  +2>7#".#"'>322>7#".#"'>32@80% (u=4c_\-90% )wB4d^\81% 'v=4c_\-81% )wB5c_[W  l0."(" i31!)!þ¯  m/-!(!  h31!)!¾}kK° PX@)^_ ZMQE@'jk ZMQEY@  +!733!!!#7!7!7!Çë}ðþÑwþ5•}•þîPwþSŠóóƒÊƒüüƒÊZPå @ @MQE+!!²3þ-2/Ÿý*>üûyz Ï  Ñ z{þ ‡iPÆ’@ @MQE+%!7!7>7%>7.'%.54657nüû>üÍÓ2/þaÖP‡öþ‡z Ï  Ñ zþ…ÿ‰ò"@ BMQE+3 #>7 &'†|‡þy|ò þè ½5üËüÌ4ý»&&EF,#&ÿŠþ ~Ü-K°PX@ kD@ jaY@ +3vòòþ Ðø0>ÿ·»'s@  BK°2PX@(SCQC CQD@MWSC DY@''W%#+#!+'&5737>32#"&#"·y°kþ}f.KF‰c(” Iz¨k''#  SzU1 áü`üÀÏKX #O8]šn=Z $HnK5>ÿ³!ß@ BK° PX@(SCQC CQDK°&PX@(SCQC CQDK°2PX@,CSCQC CQD@$MWCSC DYYY@!!#!%# ++'&5737>32;#.#"3wg.KFˆb(’ Bn˜aGŒ5w±­ -_+u‘îaü¿ÏKX !Q7T–pA úZ$ “‡7€Gþ”2ÿ®5K°0PX@ QD@MQEY@ +#2~mQRþæU  õ @ja  +2#"&/§ëõ Ë Ò†¨è'@OSG((($+#".54>32#".54>32P%##%X$$$$$$%%$$%%†(ÇŽ@MQE+!!“4ýÍŽf õ @ja #++7>3þà‘Ú õÒ Ë f ÇÛ @Bja& +#"&/+73ǃ rŽ  ‚ö© kkÑ„ åÛ @Bja !+#'327>3åö©Âƒ rŽ  ÛÑÑkk¯ø»Ü(@%jOSG +"&547332673›syo‰NOp,Ieø^^ c><4T< *@OSG($+#".54>32"-,!!,-"˜,!!,,"",ÛÍV-!@[OSG$&($+4>32#".732654&#"Û4D&'E44E'&D4Y6/-77-/6{'B//B'&@..@&+99+-88™ÅÐ1@.O[SG +2673#".#"#>32)^.>% 82.'a.>%!82-|*%*G5 -%*H5 È bë +@(OSG   #++7>3!+7>3(ÐS˜'«ù]»)ëÇ µÇ µ}òÁ @kD  +2+Ö +$ <]Á 9^I D ¢{ðè_<õ ÐÊ“^pÍÓ¡ãÿþ ÿ- ¶þVOÿþÖÿÐö'-‚{ŸàÚˆ6ˆG¬Z9ªÚ(z(ÿøà”ˆs©2a©-·ÿ¯ˆOˆåˆ/ˆ]ˆ-ˆDˆhˆ»ˆRˆÑ©-©.+Ÿˆ+}á„ÙMÕÿÁ§aÒdia+aaEdpa61ÿûér³a aoa¹dhr¸d¤rÑ ?{?’ë|Z¨ÿºy|(!¶l(ÿñˆ×Öÿ¡8Mò0Ob<5š;]?¡ÿ¾$Oç_Ûÿg¿NÛXGGí7 ò0¾G ­_`¸K‹Q©ÿθQZ(1X«(ÿðˆ9‚{”ˆŠˆˆ«ˆ~X«Ÿ;8^=]xk…ˆ¥a=^8pŽˆ3˜˜¥8;k#”8˜äÀŽkwÃÄÃÉÓáÿçÕÿÁÕÿÁÕÿÁÕÿÁÕÿÁÕÿÁ©ÿ¨Òd+a+a+a+a6V66g6¬Eoa¹d¹d¹d¹d¹dˆl¸0?’?’?’?’yh®I3ò0ò0ò0ò0ò0ò0¦b<š;š;š;š;ç=ç_ç"ç/ü<Gí7í7í7í7í7ˆsí````¸Q%¸QÕÿÁô0êdb<+aš;ç_éYLoaGÛd.Ñ  Ñ  y|Z|Z|Zˆ8Q8q8p888¹8/8h8§xÿó–ì–Š»Š«¤»¤–¤ÿÜ,™,Bˆç®-OZV…VwAÿˆ*/ ]+A ÿÑY–íÿðˆ¥xLì=æÿ{ˆ¶ˆ+Z+iˆÿŠ>G>8G8U8†8†88f8„8¯8*8Û8™8È8gîP` à ÿUÿ¤ÿ­ÿ¤ÿU$ÿU96:0<-?6Dÿ¥Fÿ¥Gÿ¥Hÿ¥Rÿ¥Tÿ¥mÿ­oÿ­yÿ­}ÿ­‚ÿUƒÿU„ÿU…ÿU†ÿU‡ÿUˆÿUŸ-¢ÿ¥£ÿ¥¤ÿ¥¥ÿ¥¦ÿ¥§ÿ¥¨ÿ¥©ÿ¥ªÿ¥«ÿ¥¬ÿ¥­ÿ¥²ÿ¥´ÿ¥µÿ¥¶ÿ¥·ÿ¥¸ÿ¥ºÿ¥ÂÿUÃÿ¥Åÿ¥Çÿ¥Îÿ¥Ó-åÿ­æÿ­éÿ¤ìÿ¤ïÿ­ðÿ¤òÿ­óÿ­ùÿU ÿU ÿ¤ ÿ­ ÿ¤ ÿU $ÿU 96 :0 <- ?6 Dÿ¥ Fÿ¥ Gÿ¥ Hÿ¥ Rÿ¥ Tÿ¥ mÿ­ oÿ­ yÿ­ }ÿ­ ‚ÿU ƒÿU „ÿU …ÿU †ÿU ‡ÿU ˆÿU Ÿ- ¢ÿ¥ £ÿ¥ ¤ÿ¥ ¥ÿ¥ ¦ÿ¥ §ÿ¥ ¨ÿ¥ ©ÿ¥ ªÿ¥ «ÿ¥ ¬ÿ¥ ­ÿ¥ ²ÿ¥ ´ÿ¥ µÿ¥ ¶ÿ¥ ·ÿ¥ ¸ÿ¥ ºÿ¥ ÂÿU Ãÿ¥ Åÿ¥ Çÿ¥ Îÿ¥ Ó- åÿ­ æÿ­ éÿ¤ ìÿ¤ ïÿ­ ðÿ¤ òÿ­ óÿ­ ùÿU #ÿØ &ÿØ *ÿØ 2ÿØ 4ÿØ Dÿá Fÿá Gÿá Hÿá Rÿá Tÿá kÿØ pÿØ ‰ÿØ ”ÿØ •ÿØ –ÿØ —ÿØ ˜ÿØ šÿØ ¢ÿá £ÿá ¤ÿá ¥ÿá ¦ÿá §ÿá ¨ÿá ©ÿá ªÿá «ÿá ¬ÿá ­ÿá ²ÿá ´ÿá µÿá ¶ÿá ·ÿá ¸ÿá ºÿá Ãÿá ÄÿØ Åÿá Çÿá ÍÿØ Îÿá ÿU ÿ¤ ÿ­ ÿ¤ ÿU $ÿU 96 :0 <- ?6 Dÿ¥ Fÿ¥ Gÿ¥ Hÿ¥ Rÿ¥ Tÿ¥ mÿ­ oÿ­ yÿ­ }ÿ­ ‚ÿU ƒÿU „ÿU …ÿU †ÿU ‡ÿU ˆÿU Ÿ- ¢ÿ¥ £ÿ¥ ¤ÿ¥ ¥ÿ¥ ¦ÿ¥ §ÿ¥ ¨ÿ¥ ©ÿ¥ ªÿ¥ «ÿ¥ ¬ÿ¥ ­ÿ¥ ²ÿ¥ ´ÿ¥ µÿ¥ ¶ÿ¥ ·ÿ¥ ¸ÿ¥ ºÿ¥ ÂÿU Ãÿ¥ Åÿ¥ Çÿ¥ Îÿ¥ Ó- åÿ­ æÿ­ éÿ¤ ìÿ¤ ïÿ­ ðÿ¤ òÿ­ óÿ­ ùÿUÿˆ ÿˆ ÿˆÿ;#ÿÉ&ÿÉ*ÿÉ2ÿÉ4ÿÉ7ÿ59ÿ,:ÿ|<ÿ;?ÿ,YÿrZÿ¸\ÿ|kÿÉlÿˆmÿ;oÿ;pÿÉrÿˆyÿ;|ÿˆ}ÿ;‰ÿÉ”ÿÉ•ÿÉ–ÿÉ—ÿɘÿÉšÿÉŸÿ;¿ÿrÁÿrÄÿÉÍÿÉÓÿ;åÿ;æÿ;çÿˆèÿˆêÿˆëÿˆïÿ;òÿ;óÿ;ÿ­ ÿÌ ÿ­ ÿ­ÿ^ÿ^ÿÌ$ÿÌ7ÿA9ÿ†;ÿº<ÿ@=ÿÉ?ÿ†lÿ­rÿ­|ÿ­‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ@ÂÿÌÓÿ@ÔÿÉÖÿÉØÿÉçÿ­èÿ­éÿ^êÿ­ëÿ­ìÿ^ðÿ^ùÿÌÿˆ ÿˆ ÿˆÿ;#ÿÉ&ÿÉ*ÿÉ2ÿÉ4ÿÉ7ÿ59ÿ,:ÿ|<ÿ;?ÿ,YÿrZÿ¸\ÿ|kÿÉlÿˆmÿ;oÿ;pÿÉrÿˆyÿ;|ÿˆ}ÿ;‰ÿÉ”ÿÉ•ÿÉ–ÿÉ—ÿɘÿÉšÿÉŸÿ;¿ÿrÁÿrÄÿÉÍÿÉÓÿ;åÿ;æÿ;çÿˆèÿˆêÿˆëÿˆïÿ;òÿ;óÿ;6 ÿŽ 6 6ÿAÿ†ÿAÿŽÿ«ÿ«":#ÿØ$ÿŽ&ÿØ*ÿØ-ÿi2ÿØ4ÿØDÿ‹Fÿ‹Gÿ‹Hÿ‹Pÿ«Qÿ«Rÿ‹Sÿ«Tÿ‹Uÿ«VÿXÿ«YÿÜZÿÜ\ÿÜ]ÿ¸kÿØl6mÿ†oÿ†pÿØr6tPuPwÿ«yÿ†{P|6}ÿ†‚ÿŽƒÿŽ„ÿŽ…ÿކÿއÿŽˆÿމÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ¢ÿ‹£ÿ‹¤ÿ‹¥ÿ‹¦ÿ‹§ÿ‹¨ÿ‹©ÿ‹ªÿ‹«ÿ‹¬ÿ‹­ÿ‹²ÿ‹³ÿ«´ÿ‹µÿ‹¶ÿ‹·ÿ‹¸ÿ‹ºÿ‹»ÿ«¼ÿ«½ÿ«¾ÿ«¿ÿÜÁÿÜÂÿŽÃÿ‹ÄÿØÅÿ‹Çÿ‹Ìÿ«ÍÿØÎÿ‹ÐÿÒÿÕÿ¸×ÿ¸Ùÿ¸åÿ†æÿ†ç6è6éÿAê6ë6ìÿAïÿ†ðÿAòÿ†óÿ†ùÿŽ#ÿÉ# ÿÜ# ÿÉ# ÿØ# ÿÉ#ÿÜ#$ÿÜ#7ÿŸ#9ÿÍ#;ÿ¼#<ÿ°#=ÿ»#?ÿÍ#@ÿØ#`ÿØ#lÿÉ#rÿÉ#|ÿÉ#‚ÿÜ#ƒÿÜ#„ÿÜ#…ÿÜ#†ÿÜ#‡ÿÜ#ˆÿÜ#Ÿÿ°#ÂÿÜ#Óÿ°#Ôÿ»#Öÿ»#Øÿ»#çÿÉ#èÿÉ#êÿÉ#ëÿÉ#ùÿÜ$ÿJ$ ÿJ$ ÿJ$ÿÌ$#ÿÑ$&ÿÑ$*ÿÑ$->$2ÿÑ$4ÿÑ$7ÿ|$8ÿØ$9ÿŽ$:ÿ­$<ÿh$?ÿŽ$WÿÇ$YÿÑ$Zÿà$\ÿÑ$kÿÑ$lÿJ$mÿÌ$oÿÌ$pÿÑ$rÿJ$tÿI$uÿI$yÿÌ${ÿI$|ÿJ$}ÿÌ$‰ÿÑ$”ÿÑ$•ÿÑ$–ÿÑ$—ÿÑ$˜ÿÑ$šÿÑ$›ÿØ$œÿØ$ÿØ$žÿØ$Ÿÿh$¿ÿÑ$ÁÿÑ$ÄÿÑ$ÍÿÑ$Óÿh$åÿÌ$æÿÌ$çÿJ$èÿJ$êÿJ$ëÿJ$ïÿÌ$òÿÌ$óÿÌ&ÿ€&mÿ€&oÿ€&yÿ€&}ÿ€&åÿ€&æÿ€&ïÿ€&òÿ€&óÿ€'ÿÉ' ÿÜ' ÿÉ' ÿØ' ÿÉ'ÿÜ'$ÿÜ'7ÿŸ'9ÿÍ';ÿ¼'<ÿ°'=ÿ»'?ÿÍ'@ÿØ'`ÿØ'lÿÉ'rÿÉ'|ÿÉ'‚ÿÜ'ƒÿÜ'„ÿÜ'…ÿÜ'†ÿÜ'‡ÿÜ'ˆÿÜ'Ÿÿ°'ÂÿÜ'Óÿ°'Ôÿ»'Öÿ»'Øÿ»'çÿÉ'èÿÉ'êÿÉ'ëÿÉ'ùÿÜ) ÿ|)ÿL)ÿL)ÿ|)ÿÄ)ÿÄ)")$ÿ|)-ÿ:)Dÿ»)Fÿ»)Gÿ»)Hÿ»)PÿÄ)QÿÄ)Rÿ»)SÿÄ)Tÿ»)UÿÄ)XÿÄ)wÿÄ)‚ÿ|)ƒÿ|)„ÿ|)…ÿ|)†ÿ|)‡ÿ|)ˆÿ|)¢ÿ»)£ÿ»)¤ÿ»)¥ÿ»)¦ÿ»)§ÿ»)¨ÿ»)©ÿ»)ªÿ»)«ÿ»)¬ÿ»)­ÿ»)²ÿ»)³ÿÄ)´ÿ»)µÿ»)¶ÿ»)·ÿ»)¸ÿ»)ºÿ»)»ÿÄ)¼ÿÄ)½ÿÄ)¾ÿÄ)Âÿ|)Ãÿ»)Åÿ»)Çÿ»)ÌÿÄ)Îÿ»)éÿL)ìÿL)ðÿL)ùÿ|- ÿØ-ÿØ-$ÿØ-‚ÿØ-ƒÿØ-„ÿØ-…ÿØ-†ÿØ-‡ÿØ-ˆÿØ-ÂÿØ-ùÿØ.. . .ÿº.#ÿš.&ÿš.*ÿš.2ÿš.4ÿš.IÿÍ.Wÿ.YÿÂ.ZÿÓ.\ÿÂ.kÿš.l.mÿº.oÿº.pÿš.r.yÿº.|.}ÿº.‰ÿš.”ÿš.•ÿš.–ÿš.—ÿš.˜ÿš.šÿš.¿ÿÂ.ÁÿÂ.Äÿš.Íÿš.åÿº.æÿº.ç.è.ê.ë.ïÿº.òÿº.óÿº/þÿ/ þÿ/ þÿ/ÿ;/#ÿ¥/&ÿ¥/*ÿ¥/2ÿ¥/4ÿ¥/7ÿ8/9ÿJ/:ÿh/<ÿ,/?ÿJ/Yÿ|/Zÿ«/\ÿ|/kÿ¥/lþÿ/mÿ;/oÿ;/pÿ¥/rþÿ/tÿ,/uÿ,/yÿ;/{ÿ,/|þÿ/}ÿ;/‰ÿ¥/”ÿ¥/•ÿ¥/–ÿ¥/—ÿ¥/˜ÿ¥/šÿ¥/Ÿÿ,/¿ÿ|/Áÿ|/Äÿ¥/Íÿ¥/Óÿ,/åÿ;/æÿ;/çþÿ/èþÿ/êþÿ/ëþÿ/ïÿ;/òÿ;/óÿ;2ÿÉ2 ÿÜ2 ÿÉ2 ÿØ2 ÿÉ2ÿÜ2$ÿÜ27ÿŸ29ÿÍ2;ÿ¼2<ÿ°2=ÿ»2?ÿÍ2@ÿØ2`ÿØ2lÿÉ2rÿÉ2|ÿÉ2‚ÿÜ2ƒÿÜ2„ÿÜ2…ÿÜ2†ÿÜ2‡ÿÜ2ˆÿÜ2Ÿÿ°2ÂÿÜ2Óÿ°2Ôÿ»2Öÿ»2Øÿ»2çÿÉ2èÿÉ2êÿÉ2ëÿÉ2ùÿÜ3 ÿv3ÿC3ÿC3ÿv3$ÿv3-ÿJ3Dÿâ3Fÿâ3Gÿâ3Hÿâ3Rÿâ3Tÿâ3‚ÿv3ƒÿv3„ÿv3…ÿv3†ÿv3‡ÿv3ˆÿv3¢ÿâ3£ÿâ3¤ÿâ3¥ÿâ3¦ÿâ3§ÿâ3¨ÿâ3©ÿâ3ªÿâ3«ÿâ3¬ÿâ3­ÿâ3²ÿâ3´ÿâ3µÿâ3¶ÿâ3·ÿâ3¸ÿâ3ºÿâ3Âÿv3Ãÿâ3Åÿâ3Çÿâ3Îÿâ3éÿC3ìÿC3ðÿC3ùÿv4ÿÉ4 ÿÜ4 ÿÉ4 ÿØ4 ÿÉ4ÿÜ4$ÿÜ47ÿŸ49ÿÍ4;ÿ¼4<ÿ°4=ÿ»4?ÿÍ4@ÿØ4`ÿØ4lÿÉ4rÿÉ4|ÿÉ4‚ÿÜ4ƒÿÜ4„ÿÜ4…ÿÜ4†ÿÜ4‡ÿÜ4ˆÿÜ4Ÿÿ°4ÂÿÜ4Óÿ°4Ôÿ»4Öÿ»4Øÿ»4çÿÉ4èÿÉ4êÿÉ4ëÿÉ4ùÿÜ5#ÿØ5&ÿØ5*ÿØ52ÿØ54ÿØ57ÿÍ58ÿÖ5kÿØ5pÿØ5‰ÿØ5”ÿØ5•ÿØ5–ÿØ5—ÿØ5˜ÿØ5šÿØ5›ÿÖ5œÿÖ5ÿÖ5žÿÖ5ÄÿØ5ÍÿØ7 ÿ|7ÿL7ÿL7ÿL7ÿ|7ÿf7ÿf7"'7#ÿŸ7$ÿ|7&ÿŸ7*ÿŸ7-ÿ872ÿŸ74ÿŸ7Dÿ/7Fÿ/7Gÿ/7Hÿ/7JÿE7Pÿf7Qÿf7Rÿ/7Sÿf7Tÿ/7Uÿf7VÿJ7Xÿf7YÿW7Zÿ7[ÿZ7\ÿL7]ÿ_7kÿŸ7mÿL7oÿL7pÿŸ7wÿf7yÿL7}ÿL7‚ÿ|7ƒÿ|7„ÿ|7…ÿ|7†ÿ|7‡ÿ|7ˆÿ|7‰ÿŸ7”ÿŸ7•ÿŸ7–ÿŸ7—ÿŸ7˜ÿŸ7šÿŸ7¢ÿ/7£ÿ/7¤ÿ/7¥ÿ/7¦ÿ/7§ÿ/7¨ÿ/7©ÿ/7ªÿ/7«ÿ/7¬ÿ/7­ÿ/7²ÿ/7³ÿf7´ÿ/7µÿ/7¶ÿ/7·ÿ/7¸ÿ/7ºÿ/7»ÿf7¼ÿf7½ÿf7¾ÿf7¿ÿW7ÁÿW7Âÿ|7Ãÿ/7ÄÿŸ7Åÿ/7Çÿ/7Ìÿf7ÍÿŸ7Îÿ/7ÐÿJ7ÒÿJ7Õÿ_7×ÿ_7Ùÿ_7åÿL7æÿL7éÿL7ìÿL7ïÿL7ðÿL7òÿL7óÿL7ùÿ|8 ÿØ8ÿØ8$ÿØ8‚ÿØ8ƒÿØ8„ÿØ8…ÿØ8†ÿØ8‡ÿØ8ˆÿØ8ÂÿØ8ùÿØ969 ÿŽ9 69 69ÿA9ÿ†9ÿA9ÿŽ9ÿ«9ÿ«9":9#ÿØ9$ÿŽ9&ÿØ9*ÿØ9-ÿi92ÿØ94ÿØ9Dÿ‹9Fÿ‹9Gÿ‹9Hÿ‹9Pÿ«9Qÿ«9Rÿ‹9Sÿ«9Tÿ‹9Uÿ«9Vÿ9Xÿ«9YÿÜ9ZÿÜ9\ÿÜ9]ÿ¸9kÿØ9l69mÿ†9oÿ†9pÿØ9r69tP9uP9wÿ«9yÿ†9{P9|69}ÿ†9‚ÿŽ9ƒÿŽ9„ÿŽ9…ÿŽ9†ÿŽ9‡ÿŽ9ˆÿŽ9‰ÿØ9”ÿØ9•ÿØ9–ÿØ9—ÿØ9˜ÿØ9šÿØ9¢ÿ‹9£ÿ‹9¤ÿ‹9¥ÿ‹9¦ÿ‹9§ÿ‹9¨ÿ‹9©ÿ‹9ªÿ‹9«ÿ‹9¬ÿ‹9­ÿ‹9²ÿ‹9³ÿ«9´ÿ‹9µÿ‹9¶ÿ‹9·ÿ‹9¸ÿ‹9ºÿ‹9»ÿ«9¼ÿ«9½ÿ«9¾ÿ«9¿ÿÜ9ÁÿÜ9ÂÿŽ9Ãÿ‹9ÄÿØ9Åÿ‹9Çÿ‹9Ìÿ«9ÍÿØ9Îÿ‹9Ðÿ9Òÿ9Õÿ¸9×ÿ¸9Ùÿ¸9åÿ†9æÿ†9ç69è69éÿA9ê69ë69ìÿA9ïÿ†9ðÿA9òÿ†9óÿ†9ùÿŽ::: ÿ¸: :: ::ÿœ:ÿœ:ÿ¸:ÿ¾:ÿ¾:$ÿ¸:-ÿš:Dÿ—:Fÿ—:Gÿ—:Hÿ—:Jÿ¬:Pÿ¾:Qÿ¾:Rÿ—:Sÿ¾:Tÿ—:Uÿ¾:Vÿª:Xÿ¾:l::r::t<:u<:wÿ¾:{<:|::‚ÿ¸:ƒÿ¸:„ÿ¸:…ÿ¸:†ÿ¸:‡ÿ¸:ˆÿ¸:¢ÿ—:£ÿ—:¤ÿ—:¥ÿ—:¦ÿ—:§ÿ—:¨ÿ—:©ÿ—:ªÿ—:«ÿ—:¬ÿ—:­ÿ—:²ÿ—:³ÿ¾:´ÿ—:µÿ—:¶ÿ—:·ÿ—:¸ÿ—:ºÿ—:»ÿ¾:¼ÿ¾:½ÿ¾:¾ÿ¾:Âÿ¸:Ãÿ—:Åÿ—:Çÿ—:Ìÿ¾:Îÿ—:Ðÿª:Òÿª:ç::è::éÿœ:ê::ë::ìÿœ:ðÿœ:ùÿ¸;; ; ;ÿº;#ÿš;&ÿš;*ÿš;2ÿš;4ÿš;IÿÍ;Wÿ;YÿÂ;ZÿÓ;\ÿÂ;kÿš;l;mÿº;oÿº;pÿš;r;yÿº;|;}ÿº;‰ÿš;”ÿš;•ÿš;–ÿš;—ÿš;˜ÿš;šÿš;¿ÿÂ;ÁÿÂ;Äÿš;Íÿš;åÿº;æÿº;ç;è;ê;ë;ïÿº;òÿº;óÿº<7< ÿr< 7< 7<ÿ'<ÿJ<ÿ'<ÿr<ÿ<ÿ<"2<#ÿ»<$ÿr<&ÿ»<*ÿ»<-ÿ8<2ÿ»<4ÿ»<DÿA<FÿA<GÿA<HÿA<JÿW<Pÿ<Qÿ<RÿA<Sÿ<TÿA<Uÿ<VÿA<Xÿ<]ÿœ<kÿ»<l7<mÿJ<oÿJ<pÿ»<r7<t><u><wÿ<yÿJ<{><|7<}ÿJ<‚ÿr<ƒÿr<„ÿr<…ÿr<†ÿr<‡ÿr<ˆÿr<‰ÿ»<”ÿ»<•ÿ»<–ÿ»<—ÿ»<˜ÿ»<šÿ»<¢ÿA<£ÿA<¤ÿA<¥ÿA<¦ÿA<§ÿA<¨ÿA<©ÿA<ªÿA<«ÿA<¬ÿA<­ÿA<²ÿA<³ÿ<´ÿA<µÿA<¶ÿA<·ÿA<¸ÿA<ºÿA<»ÿ<¼ÿ<½ÿ<¾ÿ<Âÿr<ÃÿA<Äÿ»<ÅÿA<ÇÿA<Ìÿ<Íÿ»<ÎÿA<ÐÿA<ÒÿA<Õÿœ<×ÿœ<Ùÿœ<åÿJ<æÿJ<ç7<è7<éÿ'<ê7<ë7<ìÿ'<ïÿJ<ðÿ'<òÿJ<óÿJ<ùÿr=ÿ«="#=#ÿÆ=&ÿÆ=*ÿÆ=2ÿÆ=4ÿÆ=kÿÆ=mÿ«=oÿ«=pÿÆ=yÿ«=}ÿ«=‰ÿÆ=”ÿÆ=•ÿÆ=–ÿÆ=—ÿÆ=˜ÿÆ=šÿÆ=ÄÿÆ=ÍÿÆ=åÿ«=æÿ«=ïÿ«=òÿ«=óÿ«>#ÿØ>&ÿØ>*ÿØ>2ÿØ>4ÿØ>Dÿá>Fÿá>Gÿá>Hÿá>Rÿá>Tÿá>kÿØ>pÿØ>‰ÿØ>”ÿØ>•ÿØ>–ÿØ>—ÿØ>˜ÿØ>šÿØ>¢ÿá>£ÿá>¤ÿá>¥ÿá>¦ÿá>§ÿá>¨ÿá>©ÿá>ªÿá>«ÿá>¬ÿá>­ÿá>²ÿá>´ÿá>µÿá>¶ÿá>·ÿá>¸ÿá>ºÿá>Ãÿá>ÄÿØ>Åÿá>Çÿá>ÍÿØ>Îÿá?ÿJ? ÿJ? ÿJ?ÿÌ?#ÿÑ?&ÿÑ?*ÿÑ?->?2ÿÑ?4ÿÑ?7ÿ|?8ÿØ?9ÿŽ?:ÿ­?<ÿh??ÿŽ?WÿÇ?YÿÑ?Zÿà?\ÿÑ?kÿÑ?lÿJ?mÿÌ?oÿÌ?pÿÑ?rÿJ?tÿI?uÿI?yÿÌ?{ÿI?|ÿJ?}ÿÌ?‰ÿÑ?”ÿÑ?•ÿÑ?–ÿÑ?—ÿÑ?˜ÿÑ?šÿÑ?›ÿØ?œÿØ?ÿØ?žÿØ?Ÿÿh?¿ÿÑ?ÁÿÑ?ÄÿÑ?ÍÿÑ?Óÿh?åÿÌ?æÿÌ?çÿJ?èÿJ?êÿJ?ëÿJ?ïÿÌ?òÿÌ?óÿÌEÿ°E ÿ°E ÿáE ÿ°E@ÿáE[ÿÍE`ÿáElÿ°Erÿ°E|ÿ°Eçÿ°Eèÿ°Eêÿ°Eëÿ°Hÿ°H ÿ°H ÿáH ÿ°H@ÿáH[ÿÍH`ÿáHlÿ°Hrÿ°H|ÿ°Hçÿ°Hèÿ°Hêÿ°Hëÿ°IEI EI EIÿIÿIlEIrEItdIudI{dI|EIçEIèEIéÿIêEIëEIìÿIðÿKÿÄK ÿÄK ÿÄKYÿáK\ÿÖKlÿÄKrÿÄKtÿ™Kuÿ™K{ÿ™K|ÿÄK¿ÿáKÁÿáKçÿÄKèÿÄKêÿÄKëÿÄNDÿÍNFÿÍNGÿÍNHÿÍNRÿÍNTÿÍN¢ÿÍN£ÿÍN¤ÿÍN¥ÿÍN¦ÿÍN§ÿÍN¨ÿÍN©ÿÍNªÿÍN«ÿÍN¬ÿÍN­ÿÍN²ÿÍN´ÿÍNµÿÍN¶ÿÍN·ÿÍN¸ÿÍNºÿÍNÃÿÍNÅÿÍNÇÿÍNÎÿÍPÿÄP ÿÄP ÿÄPYÿáP\ÿÖPlÿÄPrÿÄPtÿ™Puÿ™P{ÿ™P|ÿÄP¿ÿáPÁÿáPçÿÄPèÿÄPêÿÄPëÿÄQÿÄQ ÿÄQ ÿÄQYÿáQ\ÿÖQlÿÄQrÿÄQtÿ™Quÿ™Q{ÿ™Q|ÿÄQ¿ÿáQÁÿáQçÿÄQèÿÄQêÿÄQëÿÄRÿ°R ÿ°R ÿáR ÿ°R@ÿáR[ÿÍR`ÿáRlÿ°Rrÿ°R|ÿ°Rçÿ°Rèÿ°Rêÿ°Rëÿ°Sÿ°S ÿ°S ÿáS ÿ°S@ÿáS[ÿÍS`ÿáSlÿ°Srÿ°S|ÿ°Sçÿ°Sèÿ°Sêÿ°Sëÿ°UÿhUÿhUDÿØUFÿØUGÿØUHÿØURÿØUTÿØU¢ÿØU£ÿØU¤ÿØU¥ÿØU¦ÿØU§ÿØU¨ÿØU©ÿØUªÿØU«ÿØU¬ÿØU­ÿØU²ÿØU´ÿØUµÿØU¶ÿØU·ÿØU¸ÿØUºÿØUÃÿØUÅÿØUÇÿØUÎÿØUéÿhUìÿhUðÿhY ÿÑYÿrYÿrYÿÑY$ÿÑYDÿæYFÿæYGÿæYHÿæYRÿæYTÿæY‚ÿÑYƒÿÑY„ÿÑY…ÿÑY†ÿÑY‡ÿÑYˆÿÑY¢ÿæY£ÿæY¤ÿæY¥ÿæY¦ÿæY§ÿæY¨ÿæY©ÿæYªÿæY«ÿæY¬ÿæY­ÿæY²ÿæY´ÿæYµÿæY¶ÿæY·ÿæY¸ÿæYºÿæYÂÿÑYÃÿæYÅÿæYÇÿæYÎÿæYéÿrYìÿrYðÿrYùÿÑZ ÿàZÿ­Zÿ­ZÿàZ$ÿàZ‚ÿàZƒÿàZ„ÿàZ…ÿàZ†ÿàZ‡ÿàZˆÿàZÂÿàZéÿ­Zìÿ­Zðÿ­Zùÿà[DÿÍ[FÿÍ[GÿÍ[HÿÍ[RÿÍ[TÿÍ[¢ÿÍ[£ÿÍ[¤ÿÍ[¥ÿÍ[¦ÿÍ[§ÿÍ[¨ÿÍ[©ÿÍ[ªÿÍ[«ÿÍ[¬ÿÍ[­ÿÍ[²ÿÍ[´ÿÍ[µÿÍ[¶ÿÍ[·ÿÍ[¸ÿÍ[ºÿÍ[ÃÿÍ[ÅÿÍ[ÇÿÍ[ÎÿÍ\ ÿÑ\ÿh\ÿh\ÿÑ\$ÿÑ\Dÿæ\Fÿæ\Gÿæ\Hÿæ\Rÿæ\Tÿæ\‚ÿÑ\ƒÿÑ\„ÿÑ\…ÿÑ\†ÿÑ\‡ÿÑ\ˆÿÑ\¢ÿæ\£ÿæ\¤ÿæ\¥ÿæ\¦ÿæ\§ÿæ\¨ÿæ\©ÿæ\ªÿæ\«ÿæ\¬ÿæ\­ÿæ\²ÿæ\´ÿæ\µÿæ\¶ÿæ\·ÿæ\¸ÿæ\ºÿæ\ÂÿÑ\Ãÿæ\Åÿæ\Çÿæ\Îÿæ\éÿh\ìÿh\ðÿh\ùÿÑ^#ÿØ^&ÿØ^*ÿØ^2ÿØ^4ÿØ^Dÿá^Fÿá^Gÿá^Hÿá^Rÿá^Tÿá^kÿØ^pÿØ^‰ÿØ^”ÿØ^•ÿØ^–ÿØ^—ÿØ^˜ÿØ^šÿØ^¢ÿá^£ÿá^¤ÿá^¥ÿá^¦ÿá^§ÿá^¨ÿá^©ÿá^ªÿá^«ÿá^¬ÿá^­ÿá^²ÿá^´ÿá^µÿá^¶ÿá^·ÿá^¸ÿá^ºÿá^Ãÿá^ÄÿØ^Åÿá^Çÿá^ÍÿØ^ÎÿákÿÉk ÿÜk ÿÉk ÿØk ÿÉkÿÜk$ÿÜk7ÿŸk9ÿÍk;ÿ¼k<ÿ°k=ÿ»k?ÿÍk@ÿØk`ÿØklÿÉkrÿÉk|ÿÉk‚ÿÜkƒÿÜk„ÿÜk…ÿÜk†ÿÜk‡ÿÜkˆÿÜkŸÿ°kÂÿÜkÓÿ°kÔÿ»kÖÿ»kØÿ»kçÿÉkèÿÉkêÿÉkëÿÉkùÿÜl ÿUlÿ¤lÿ­lÿ¤lÿUl$ÿUl96l:0l<-l?6lDÿ¥lFÿ¥lGÿ¥lHÿ¥lRÿ¥lTÿ¥lmÿ­loÿ­lyÿ­l}ÿ­l‚ÿUlƒÿUl„ÿUl…ÿUl†ÿUl‡ÿUlˆÿUlŸ-l¢ÿ¥l£ÿ¥l¤ÿ¥l¥ÿ¥l¦ÿ¥l§ÿ¥l¨ÿ¥l©ÿ¥lªÿ¥l«ÿ¥l¬ÿ¥l­ÿ¥l²ÿ¥l´ÿ¥lµÿ¥l¶ÿ¥l·ÿ¥l¸ÿ¥lºÿ¥lÂÿUlÃÿ¥lÅÿ¥lÇÿ¥lÎÿ¥lÓ-låÿ­læÿ­léÿ¤lìÿ¤lïÿ­lðÿ¤lòÿ­lóÿ­lùÿUmÿ­m ÿÌm ÿ­m ÿ­mÿ^mÿ^mÿÌm$ÿÌm7ÿAm9ÿ†m;ÿºm<ÿ@m=ÿÉm?ÿ†mlÿ­mrÿ­m|ÿ­m‚ÿÌmƒÿÌm„ÿÌm…ÿÌm†ÿÌm‡ÿÌmˆÿÌmŸÿ@mÂÿÌmÓÿ@mÔÿÉmÖÿÉmØÿÉmçÿ­mèÿ­méÿ^mêÿ­mëÿ­mìÿ^mðÿ^mùÿÌoÿ­o ÿÌo ÿ­o ÿ­oÿ^oÿ^oÿÌo$ÿÌo7ÿAo9ÿ†o;ÿºo<ÿ@o=ÿÉo?ÿ†olÿ­orÿ­o|ÿ­o‚ÿÌoƒÿÌo„ÿÌo…ÿÌo†ÿÌo‡ÿÌoˆÿÌoŸÿ@oÂÿÌoÓÿ@oÔÿÉoÖÿÉoØÿÉoçÿ­oèÿ­oéÿ^oêÿ­oëÿ­oìÿ^oðÿ^oùÿÌpÿÉp ÿÜp ÿÉp ÿØp ÿÉpÿÜp$ÿÜp7ÿŸp9ÿÍp;ÿ¼p<ÿ°p=ÿ»p?ÿÍp@ÿØp`ÿØplÿÉprÿÉp|ÿÉp‚ÿÜpƒÿÜp„ÿÜp…ÿÜp†ÿÜp‡ÿÜpˆÿÜpŸÿ°pÂÿÜpÓÿ°pÔÿ»pÖÿ»pØÿ»pçÿÉpèÿÉpêÿÉpëÿÉpùÿÜr ÿUrÿ¤rÿ­rÿ¤rÿUr$ÿUr96r:0r<-r?6rDÿ¥rFÿ¥rGÿ¥rHÿ¥rRÿ¥rTÿ¥rmÿ­roÿ­ryÿ­r}ÿ­r‚ÿUrƒÿUr„ÿUr…ÿUr†ÿUr‡ÿUrˆÿUrŸ-r¢ÿ¥r£ÿ¥r¤ÿ¥r¥ÿ¥r¦ÿ¥r§ÿ¥r¨ÿ¥r©ÿ¥rªÿ¥r«ÿ¥r¬ÿ¥r­ÿ¥r²ÿ¥r´ÿ¥rµÿ¥r¶ÿ¥r·ÿ¥r¸ÿ¥rºÿ¥rÂÿUrÃÿ¥rÅÿ¥rÇÿ¥rÎÿ¥rÓ-råÿ­ræÿ­réÿ¤rìÿ¤rïÿ­rðÿ¤ròÿ­róÿ­rùÿUt ÿTtÿTt$ÿTt9:t::t<(t?:t‚ÿTtƒÿTt„ÿTt…ÿTt†ÿTt‡ÿTtˆÿTtŸ(tÂÿTtÓ(tùÿTu ÿTuÿTu$ÿTu9:u::u<(u?:u‚ÿTuƒÿTu„ÿTu…ÿTu†ÿTu‡ÿTuˆÿTuŸ(uÂÿTuÓ(uùÿTyÿ­y ÿÌy ÿ­y ÿ­yÿ^yÿ^yÿÌy$ÿÌy7ÿAy9ÿ†y;ÿºy<ÿ@y=ÿÉy?ÿ†ylÿ­yrÿ­y|ÿ­y‚ÿÌyƒÿÌy„ÿÌy…ÿÌy†ÿÌy‡ÿÌyˆÿÌyŸÿ@yÂÿÌyÓÿ@yÔÿÉyÖÿÉyØÿÉyçÿ­yèÿ­yéÿ^yêÿ­yëÿ­yìÿ^yðÿ^yùÿÌ{ ÿT{ÿT{$ÿT{9:{::{<({?:{‚ÿT{ƒÿT{„ÿT{…ÿT{†ÿT{‡ÿT{ˆÿT{Ÿ({ÂÿT{Ó({ùÿT| ÿU|ÿ¤|ÿ­|ÿ¤|ÿU|$ÿU|96|:0|<-|?6|Dÿ¥|Fÿ¥|Gÿ¥|Hÿ¥|Rÿ¥|Tÿ¥|mÿ­|oÿ­|yÿ­|}ÿ­|‚ÿU|ƒÿU|„ÿU|…ÿU|†ÿU|‡ÿU|ˆÿU|Ÿ-|¢ÿ¥|£ÿ¥|¤ÿ¥|¥ÿ¥|¦ÿ¥|§ÿ¥|¨ÿ¥|©ÿ¥|ªÿ¥|«ÿ¥|¬ÿ¥|­ÿ¥|²ÿ¥|´ÿ¥|µÿ¥|¶ÿ¥|·ÿ¥|¸ÿ¥|ºÿ¥|ÂÿU|Ãÿ¥|Åÿ¥|Çÿ¥|Îÿ¥|Ó-|åÿ­|æÿ­|éÿ¤|ìÿ¤|ïÿ­|ðÿ¤|òÿ­|óÿ­|ùÿU}ÿ­} ÿÌ} ÿ­} ÿ­}ÿ^}ÿ^}ÿÌ}$ÿÌ}7ÿA}9ÿ†};ÿº}<ÿ@}=ÿÉ}?ÿ†}lÿ­}rÿ­}|ÿ­}‚ÿÌ}ƒÿÌ}„ÿÌ}…ÿÌ}†ÿÌ}‡ÿÌ}ˆÿÌ}Ÿÿ@}ÂÿÌ}Óÿ@}ÔÿÉ}ÖÿÉ}ØÿÉ}çÿ­}èÿ­}éÿ^}êÿ­}ëÿ­}ìÿ^}ðÿ^}ùÿÌ‚ÿJ‚ ÿJ‚ ÿJ‚ÿÌ‚#ÿÑ‚&ÿÑ‚*ÿÑ‚->‚2ÿÑ‚4ÿÑ‚7ÿ|‚8ÿØ‚9ÿŽ‚:ÿ­‚<ÿh‚?ÿŽ‚WÿÇ‚YÿÑ‚Zÿà‚\ÿÑ‚kÿÑ‚lÿJ‚mÿÌ‚oÿÌ‚pÿÑ‚rÿJ‚tÿI‚uÿI‚yÿÌ‚{ÿI‚|ÿJ‚}ÿÌ‚‰ÿÑ‚”ÿÑ‚•ÿÑ‚–ÿÑ‚—ÿÑ‚˜ÿÑ‚šÿÑ‚›ÿØ‚œÿØ‚ÿØ‚žÿØ‚Ÿÿh‚¿ÿÑ‚ÁÿÑ‚ÄÿÑ‚ÍÿÑ‚Óÿh‚åÿÌ‚æÿÌ‚çÿJ‚èÿJ‚êÿJ‚ëÿJ‚ïÿÌ‚òÿÌ‚óÿ̃ÿJƒ ÿJƒ ÿJƒÿ̃#ÿу&ÿу*ÿу->ƒ2ÿу4ÿу7ÿ|ƒ8ÿ؃9ÿŽƒ:ÿ­ƒ<ÿhƒ?ÿŽƒWÿǃYÿуZÿàƒ\ÿуkÿуlÿJƒmÿ̃oÿ̃pÿуrÿJƒtÿIƒuÿIƒyÿ̃{ÿIƒ|ÿJƒ}ÿ̃‰ÿу”ÿу•ÿу–ÿу—ÿу˜ÿуšÿу›ÿ؃œÿ؃ÿ؃žÿ؃Ÿÿhƒ¿ÿуÁÿуÄÿуÍÿуÓÿhƒåÿ̃æÿ̃çÿJƒèÿJƒêÿJƒëÿJƒïÿ̃òÿ̃óÿÌ„ÿJ„ ÿJ„ ÿJ„ÿÌ„#ÿÑ„&ÿÑ„*ÿÑ„->„2ÿÑ„4ÿÑ„7ÿ|„8ÿØ„9ÿŽ„:ÿ­„<ÿh„?ÿŽ„WÿÇ„YÿÑ„Zÿà„\ÿÑ„kÿÑ„lÿJ„mÿÌ„oÿÌ„pÿÑ„rÿJ„tÿI„uÿI„yÿÌ„{ÿI„|ÿJ„}ÿÌ„‰ÿÑ„”ÿÑ„•ÿÑ„–ÿÑ„—ÿÑ„˜ÿÑ„šÿÑ„›ÿØ„œÿØ„ÿØ„žÿØ„Ÿÿh„¿ÿÑ„ÁÿÑ„ÄÿÑ„ÍÿÑ„Óÿh„åÿÌ„æÿÌ„çÿJ„èÿJ„êÿJ„ëÿJ„ïÿÌ„òÿÌ„óÿÌ…ÿJ… ÿJ… ÿJ…ÿÌ…#ÿÑ…&ÿÑ…*ÿÑ…->…2ÿÑ…4ÿÑ…7ÿ|…8ÿØ…9ÿŽ…:ÿ­…<ÿh…?ÿŽ…WÿÇ…YÿÑ…Zÿà…\ÿÑ…kÿÑ…lÿJ…mÿÌ…oÿÌ…pÿÑ…rÿJ…tÿI…uÿI…yÿÌ…{ÿI…|ÿJ…}ÿÌ…‰ÿÑ…”ÿÑ…•ÿÑ…–ÿÑ…—ÿÑ…˜ÿÑ…šÿÑ…›ÿØ…œÿØ…ÿØ…žÿØ…Ÿÿh…¿ÿÑ…ÁÿÑ…ÄÿÑ…ÍÿÑ…Óÿh…åÿÌ…æÿÌ…çÿJ…èÿJ…êÿJ…ëÿJ…ïÿÌ…òÿÌ…óÿ̆ÿJ† ÿJ† ÿJ†ÿ̆#ÿц&ÿц*ÿц->†2ÿц4ÿц7ÿ|†8ÿ؆9ÿކ:ÿ­†<ÿh†?ÿކWÿdžYÿцZÿà†\ÿцkÿцlÿJ†mÿ̆oÿ̆pÿцrÿJ†tÿI†uÿI†yÿ̆{ÿI†|ÿJ†}ÿ̆‰ÿц”ÿц•ÿц–ÿц—ÿц˜ÿцšÿц›ÿ؆œÿ؆ÿ؆žÿ؆Ÿÿh†¿ÿцÁÿцÄÿцÍÿцÓÿh†åÿ̆æÿ̆çÿJ†èÿJ†êÿJ†ëÿJ†ïÿ̆òÿ̆óÿ̇ÿJ‡ ÿJ‡ ÿJ‡ÿ̇#ÿч&ÿч*ÿч->‡2ÿч4ÿч7ÿ|‡8ÿ؇9ÿއ:ÿ­‡<ÿh‡?ÿއWÿLJYÿчZÿà‡\ÿчkÿчlÿJ‡mÿ̇oÿ̇pÿчrÿJ‡tÿI‡uÿI‡yÿ̇{ÿI‡|ÿJ‡}ÿ̇‰ÿч”ÿч•ÿч–ÿч—ÿч˜ÿчšÿч›ÿ؇œÿ؇ÿ؇žÿ؇Ÿÿh‡¿ÿчÁÿчÄÿчÍÿчÓÿh‡åÿ̇æÿ̇çÿJ‡èÿJ‡êÿJ‡ëÿJ‡ïÿ̇òÿ̇óÿ̉ÿ€‰mÿ€‰oÿ€‰yÿ€‰}ÿ€‰åÿ€‰æÿ€‰ïÿ€‰òÿ€‰óÿ€’ÿÉ’ ÿÜ’ ÿÉ’ ÿØ’ ÿÉ’ÿÜ’$ÿÜ’7ÿŸ’9ÿÍ’;ÿ¼’<ÿ°’=ÿ»’?ÿÍ’@ÿØ’`ÿØ’lÿÉ’rÿÉ’|ÿÉ’‚ÿÜ’ƒÿÜ’„ÿÜ’…ÿÜ’†ÿÜ’‡ÿÜ’ˆÿÜ’Ÿÿ°’ÂÿÜ’Óÿ°’Ôÿ»’Öÿ»’Øÿ»’çÿÉ’èÿÉ’êÿÉ’ëÿÉ’ùÿÜ”ÿÉ” ÿÜ” ÿÉ” ÿØ” ÿÉ”ÿÜ”$ÿÜ”7ÿŸ”9ÿÍ”;ÿ¼”<ÿ°”=ÿ»”?ÿÍ”@ÿØ”`ÿØ”lÿÉ”rÿÉ”|ÿÉ”‚ÿÜ”ƒÿÜ”„ÿÜ”…ÿÜ”†ÿÜ”‡ÿÜ”ˆÿÜ”Ÿÿ°”ÂÿÜ”Óÿ°”Ôÿ»”Öÿ»”Øÿ»”çÿÉ”èÿÉ”êÿÉ”ëÿÉ”ùÿÜ•ÿÉ• ÿÜ• ÿÉ• ÿØ• ÿÉ•ÿÜ•$ÿÜ•7ÿŸ•9ÿÍ•;ÿ¼•<ÿ°•=ÿ»•?ÿÍ•@ÿØ•`ÿØ•lÿÉ•rÿÉ•|ÿÉ•‚ÿÜ•ƒÿÜ•„ÿÜ•…ÿÜ•†ÿÜ•‡ÿÜ•ˆÿÜ•Ÿÿ°•ÂÿÜ•Óÿ°•Ôÿ»•Öÿ»•Øÿ»•çÿÉ•èÿÉ•êÿÉ•ëÿÉ•ùÿÜ–ÿÉ– ÿÜ– ÿÉ– ÿØ– ÿÉ–ÿÜ–$ÿÜ–7ÿŸ–9ÿÍ–;ÿ¼–<ÿ°–=ÿ»–?ÿÍ–@ÿØ–`ÿØ–lÿÉ–rÿÉ–|ÿÉ–‚ÿÜ–ƒÿÜ–„ÿÜ–…ÿÜ–†ÿÜ–‡ÿÜ–ˆÿÜ–Ÿÿ°–ÂÿÜ–Óÿ°–Ôÿ»–Öÿ»–Øÿ»–çÿÉ–èÿÉ–êÿÉ–ëÿÉ–ùÿÜ—ÿÉ— ÿÜ— ÿÉ— ÿØ— ÿÉ—ÿÜ—$ÿÜ—7ÿŸ—9ÿÍ—;ÿ¼—<ÿ°—=ÿ»—?ÿÍ—@ÿØ—`ÿØ—lÿÉ—rÿÉ—|ÿÉ—‚ÿÜ—ƒÿÜ—„ÿÜ—…ÿÜ—†ÿÜ—‡ÿÜ—ˆÿÜ—Ÿÿ°—ÂÿÜ—Óÿ°—Ôÿ»—Öÿ»—Øÿ»—çÿÉ—èÿÉ—êÿÉ—ëÿÉ—ùÿܘÿɘ ÿܘ ÿɘ ÿؘ ÿɘÿܘ$ÿܘ7ÿŸ˜9ÿ͘;ÿ¼˜<ÿ°˜=ÿ»˜?ÿ͘@ÿؘ`ÿؘlÿɘrÿɘ|ÿɘ‚ÿܘƒÿܘ„ÿܘ…ÿܘ†ÿܘ‡ÿܘˆÿܘŸÿ°˜ÂÿܘÓÿ°˜Ôÿ»˜Öÿ»˜Øÿ»˜çÿɘèÿɘêÿɘëÿɘùÿÜ› ÿØ›ÿØ›$ÿØ›‚ÿØ›ƒÿØ›„ÿØ›…ÿØ›†ÿØ›‡ÿØ›ˆÿØ›ÂÿØ›ùÿØœ ÿØœÿØœ$ÿØœ‚ÿØœƒÿØœ„ÿØœ…ÿØœ†ÿØœ‡ÿØœˆÿØœÂÿØœùÿØ ÿØÿØ$ÿØ‚ÿ؃ÿØ„ÿØ…ÿ؆ÿ؇ÿ؈ÿØÂÿØùÿØž ÿØžÿØž$ÿØž‚ÿØžƒÿØž„ÿØž…ÿØž†ÿØž‡ÿØžˆÿØžÂÿØžùÿØŸ7Ÿ ÿrŸ 7Ÿ 7Ÿÿ'ŸÿJŸÿ'ŸÿrŸÿŸÿŸ"2Ÿ#ÿ»Ÿ$ÿrŸ&ÿ»Ÿ*ÿ»Ÿ-ÿ8Ÿ2ÿ»Ÿ4ÿ»ŸDÿAŸFÿAŸGÿAŸHÿAŸJÿWŸPÿŸQÿŸRÿAŸSÿŸTÿAŸUÿŸVÿAŸXÿŸ]ÿœŸkÿ»Ÿl7ŸmÿJŸoÿJŸpÿ»Ÿr7Ÿt>Ÿu>ŸwÿŸyÿJŸ{>Ÿ|7Ÿ}ÿJŸ‚ÿrŸƒÿrŸ„ÿrŸ…ÿrŸ†ÿrŸ‡ÿrŸˆÿrŸ‰ÿ»Ÿ”ÿ»Ÿ•ÿ»Ÿ–ÿ»Ÿ—ÿ»Ÿ˜ÿ»Ÿšÿ»Ÿ¢ÿAŸ£ÿAŸ¤ÿAŸ¥ÿAŸ¦ÿAŸ§ÿAŸ¨ÿAŸ©ÿAŸªÿAŸ«ÿAŸ¬ÿAŸ­ÿAŸ²ÿAŸ³ÿŸ´ÿAŸµÿAŸ¶ÿAŸ·ÿAŸ¸ÿAŸºÿAŸ»ÿŸ¼ÿŸ½ÿŸ¾ÿŸÂÿrŸÃÿAŸÄÿ»ŸÅÿAŸÇÿAŸÌÿŸÍÿ»ŸÎÿAŸÐÿAŸÒÿAŸÕÿœŸ×ÿœŸÙÿœŸåÿJŸæÿJŸç7Ÿè7Ÿéÿ'Ÿê7Ÿë7Ÿìÿ'ŸïÿJŸðÿ'ŸòÿJŸóÿJŸùÿr ÿÉ  ÿÜ  ÿÉ  ÿØ  ÿÉ ÿÜ $ÿÜ 7ÿŸ 9ÿÍ ;ÿ¼ <ÿ° =ÿ» ?ÿÍ @ÿØ `ÿØ lÿÉ rÿÉ |ÿÉ ‚ÿÜ ƒÿÜ „ÿÜ …ÿÜ †ÿÜ ‡ÿÜ ˆÿÜ Ÿÿ° ÂÿÜ Óÿ° Ôÿ» Öÿ» Øÿ» çÿÉ èÿÉ êÿÉ ëÿÉ ùÿܨÿ°¨ ÿ°¨ ÿᨠÿ°¨@ÿá¨[ÿͨ`ÿá¨lÿ°¨rÿ°¨|ÿ°¨çÿ°¨èÿ°¨êÿ°¨ëÿ°ªÿ°ª ÿ°ª ÿ᪠ÿ°ª@ÿáª[ÿͪ`ÿáªlÿ°ªrÿ°ª|ÿ°ªçÿ°ªèÿ°ªêÿ°ªëÿ°«ÿ°« ÿ°« ÿá« ÿ°«@ÿá«[ÿÍ«`ÿá«lÿ°«rÿ°«|ÿ°«çÿ°«èÿ°«êÿ°«ëÿ°¬ÿ°¬ ÿ°¬ ÿᬠÿ°¬@ÿá¬[ÿͬ`ÿá¬lÿ°¬rÿ°¬|ÿ°¬çÿ°¬èÿ°¬êÿ°¬ëÿ°­ÿ°­ ÿ°­ ÿá­ ÿ°­@ÿá­[ÿÍ­`ÿá­lÿ°­rÿ°­|ÿ°­çÿ°­èÿ°­êÿ°­ëÿ°³ÿij ÿij ÿijYÿá³\ÿÖ³lÿijrÿijtÿ™³uÿ™³{ÿ™³|ÿij¿ÿá³Áÿá³çÿijèÿijêÿijëÿÄ´ÿ°´ ÿ°´ ÿá´ ÿ°´@ÿá´[ÿÍ´`ÿá´lÿ°´rÿ°´|ÿ°´çÿ°´èÿ°´êÿ°´ëÿ°µÿ°µ ÿ°µ ÿáµ ÿ°µ@ÿáµ[ÿ͵`ÿáµlÿ°µrÿ°µ|ÿ°µçÿ°µèÿ°µêÿ°µëÿ°¶ÿ°¶ ÿ°¶ ÿá¶ ÿ°¶@ÿá¶[ÿͶ`ÿá¶lÿ°¶rÿ°¶|ÿ°¶çÿ°¶èÿ°¶êÿ°¶ëÿ°·ÿ°· ÿ°· ÿá· ÿ°·@ÿá·[ÿÍ·`ÿá·lÿ°·rÿ°·|ÿ°·çÿ°·èÿ°·êÿ°·ëÿ°¸ÿ°¸ ÿ°¸ ÿḠÿ°¸@ÿá¸[ÿ͸`ÿá¸lÿ°¸rÿ°¸|ÿ°¸çÿ°¸èÿ°¸êÿ°¸ëÿ°ºÿ°º ÿ°º ÿẠÿ°º@ÿáº[ÿͺ`ÿáºlÿ°ºrÿ°º|ÿ°ºçÿ°ºèÿ°ºêÿ°ºëÿ°¿ ÿÑ¿ÿr¿ÿr¿ÿÑ¿$ÿÑ¿Dÿæ¿Fÿæ¿Gÿæ¿Hÿæ¿Rÿæ¿Tÿæ¿‚ÿÑ¿ƒÿÑ¿„ÿÑ¿…ÿÑ¿†ÿÑ¿‡ÿÑ¿ˆÿÑ¿¢ÿæ¿£ÿ濤ÿæ¿¥ÿ濦ÿæ¿§ÿ濨ÿæ¿©ÿ濪ÿæ¿«ÿ濬ÿæ¿­ÿ濲ÿæ¿´ÿ濵ÿæ¿¶ÿæ¿·ÿ濸ÿ濺ÿæ¿ÂÿÑ¿Ãÿæ¿Åÿæ¿Çÿæ¿Îÿæ¿éÿr¿ìÿr¿ðÿr¿ùÿÑÀÿ°À ÿ°À ÿáÀ ÿ°À@ÿáÀ[ÿÍÀ`ÿáÀlÿ°Àrÿ°À|ÿ°Àçÿ°Àèÿ°Àêÿ°Àëÿ°Á ÿÑÁÿrÁÿrÁÿÑÁ$ÿÑÁDÿæÁFÿæÁGÿæÁHÿæÁRÿæÁTÿæÁ‚ÿÑÁƒÿÑÁ„ÿÑÁ…ÿÑÁ†ÿÑÁ‡ÿÑÁˆÿÑÁ¢ÿæÁ£ÿæÁ¤ÿæÁ¥ÿæÁ¦ÿæÁ§ÿæÁ¨ÿæÁ©ÿæÁªÿæÁ«ÿæÁ¬ÿæÁ­ÿæÁ²ÿæÁ´ÿæÁµÿæÁ¶ÿæÁ·ÿæÁ¸ÿæÁºÿæÁÂÿÑÁÃÿæÁÅÿæÁÇÿæÁÎÿæÁéÿrÁìÿrÁðÿrÁùÿÑÂÿJ ÿJ ÿJÂÿÌÂ#ÿÑÂ&ÿÑÂ*ÿÑÂ->Â2ÿÑÂ4ÿÑÂ7ÿ|Â8ÿØÂ9ÿŽÂ:ÿ­Â<ÿhÂ?ÿŽÂWÿÇÂYÿÑÂZÿàÂ\ÿÑÂkÿÑÂlÿJÂmÿÌÂoÿÌÂpÿÑÂrÿJÂtÿIÂuÿIÂyÿÌÂ{ÿIÂ|ÿJÂ}ÿ̉ÿÑ”ÿÑ•ÿÑ–ÿÑ—ÿјÿÑšÿÑ›ÿØÂœÿØÂÿØÂžÿØÂŸÿh¿ÿÑÂÁÿÑÂÄÿÑÂÍÿÑÂÓÿhÂåÿÌÂæÿÌÂçÿJÂèÿJÂêÿJÂëÿJÂïÿÌÂòÿÌÂóÿÌÄÿ€Ämÿ€Äoÿ€Äyÿ€Ä}ÿ€Äåÿ€Äæÿ€Äïÿ€Äòÿ€Äóÿ€Çÿ°Ç ÿ°Ç ÿáÇ ÿ°Ç@ÿáÇ[ÿÍÇ`ÿáÇlÿ°Çrÿ°Ç|ÿ°Ççÿ°Çèÿ°Çêÿ°Çëÿ°ÉÿiÉ ÿiÉ ÿiÉÿ‹É9ÿ^É:ÿ†É<ÿhÉ?ÿ^ÉYÿ°ÉZÿÍÉ\ÿ°ÉlÿiÉmÿ‹Éoÿ‹ÉrÿiÉtÿ}Éuÿ}Éyÿ‹É{ÿ}É|ÿiÉ}ÿ‹ÉŸÿhÉ¿ÿ°ÉÁÿ°ÉÓÿhÉåÿ‹Éæÿ‹ÉçÿiÉèÿiÉêÿiÉëÿiÉïÿ‹Éòÿ‹Éóÿ‹ÌÿÄÌ ÿÄÌ ÿÄÌYÿáÌ\ÿÖÌlÿÄÌrÿÄÌtÿ™Ìuÿ™Ì{ÿ™Ì|ÿÄÌ¿ÿáÌÁÿáÌçÿÄÌèÿÄÌêÿÄÌëÿÄÎÿ°Î ÿ°Î ÿáÎ ÿ°Î@ÿáÎ[ÿÍÎ`ÿáÎlÿ°Îrÿ°Î|ÿ°Îçÿ°Îèÿ°Îêÿ°Îëÿ°Ó7Ó ÿrÓ 7Ó 7Óÿ'ÓÿJÓÿ'ÓÿrÓÿÓÿÓ"2Ó#ÿ»Ó$ÿrÓ&ÿ»Ó*ÿ»Ó-ÿ8Ó2ÿ»Ó4ÿ»ÓDÿAÓFÿAÓGÿAÓHÿAÓJÿWÓPÿÓQÿÓRÿAÓSÿÓTÿAÓUÿÓVÿAÓXÿÓ]ÿœÓkÿ»Ól7ÓmÿJÓoÿJÓpÿ»Ór7Ót>Óu>ÓwÿÓyÿJÓ{>Ó|7Ó}ÿJÓ‚ÿrÓƒÿrÓ„ÿrÓ…ÿrÓ†ÿrÓ‡ÿrÓˆÿrÓ‰ÿ»Ó”ÿ»Ó•ÿ»Ó–ÿ»Ó—ÿ»Ó˜ÿ»Óšÿ»Ó¢ÿAÓ£ÿAÓ¤ÿAÓ¥ÿAÓ¦ÿAÓ§ÿAÓ¨ÿAÓ©ÿAÓªÿAÓ«ÿAÓ¬ÿAÓ­ÿAÓ²ÿAÓ³ÿÓ´ÿAÓµÿAÓ¶ÿAÓ·ÿAÓ¸ÿAÓºÿAÓ»ÿÓ¼ÿÓ½ÿÓ¾ÿÓÂÿrÓÃÿAÓÄÿ»ÓÅÿAÓÇÿAÓÌÿÓÍÿ»ÓÎÿAÓÐÿAÓÒÿAÓÕÿœÓ×ÿœÓÙÿœÓåÿJÓæÿJÓç7Óè7Óéÿ'Óê7Óë7Óìÿ'ÓïÿJÓðÿ'ÓòÿJÓóÿJÓùÿrÔÿ«Ô"#Ô#ÿÆÔ&ÿÆÔ*ÿÆÔ2ÿÆÔ4ÿÆÔkÿÆÔmÿ«Ôoÿ«ÔpÿÆÔyÿ«Ô}ÿ«Ô‰ÿÆÔ”ÿÆÔ•ÿÆÔ–ÿÆÔ—ÿÆÔ˜ÿÆÔšÿÆÔÄÿÆÔÍÿÆÔåÿ«Ôæÿ«Ôïÿ«Ôòÿ«Ôóÿ«Öÿ«Ö"#Ö#ÿÆÖ&ÿÆÖ*ÿÆÖ2ÿÆÖ4ÿÆÖkÿÆÖmÿ«Öoÿ«ÖpÿÆÖyÿ«Ö}ÿ«Ö‰ÿÆÖ”ÿÆÖ•ÿÆÖ–ÿÆÖ—ÿÆÖ˜ÿÆÖšÿÆÖÄÿÆÖÍÿÆÖåÿ«Öæÿ«Öïÿ«Öòÿ«Öóÿ«Øÿ«Ø"#Ø#ÿÆØ&ÿÆØ*ÿÆØ2ÿÆØ4ÿÆØkÿÆØmÿ«Øoÿ«ØpÿÆØyÿ«Ø}ÿ«Ø‰ÿÆØ”ÿÆØ•ÿÆØ–ÿÆØ—ÿÆØ˜ÿÆØšÿÆØÄÿÆØÍÿÆØåÿ«Øæÿ«Øïÿ«Øòÿ«Øóÿ«åÿ­å ÿÌå ÿ­å ÿ­åÿ^åÿ^åÿÌå$ÿÌå7ÿAå9ÿ†å;ÿºå<ÿ@å=ÿÉå?ÿ†ålÿ­årÿ­å|ÿ­å‚ÿÌåƒÿÌå„ÿÌå…ÿÌå†ÿÌå‡ÿÌåˆÿÌåŸÿ@åÂÿÌåÓÿ@åÔÿÉåÖÿÉåØÿÉåçÿ­åèÿ­åéÿ^åêÿ­åëÿ­åìÿ^åðÿ^åùÿÌæÿ­æ ÿÌæ ÿ­æ ÿ­æÿ^æÿ^æÿÌæ$ÿÌæ7ÿAæ9ÿ†æ;ÿºæ<ÿ@æ=ÿÉæ?ÿ†ælÿ­ærÿ­æ|ÿ­æ‚ÿÌæƒÿÌæ„ÿÌæ…ÿÌæ†ÿÌæ‡ÿÌæˆÿÌæŸÿ@æÂÿÌæÓÿ@æÔÿÉæÖÿÉæØÿÉæçÿ­æèÿ­æéÿ^æêÿ­æëÿ­æìÿ^æðÿ^æùÿÌç ÿUçÿ¤çÿ­çÿ¤çÿUç$ÿUç96ç:0ç<-ç?6çDÿ¥çFÿ¥çGÿ¥çHÿ¥çRÿ¥çTÿ¥çmÿ­çoÿ­çyÿ­ç}ÿ­ç‚ÿUçƒÿUç„ÿUç…ÿUç†ÿUç‡ÿUçˆÿUçŸ-ç¢ÿ¥ç£ÿ¥ç¤ÿ¥ç¥ÿ¥ç¦ÿ¥ç§ÿ¥ç¨ÿ¥ç©ÿ¥çªÿ¥ç«ÿ¥ç¬ÿ¥ç­ÿ¥ç²ÿ¥ç´ÿ¥çµÿ¥ç¶ÿ¥ç·ÿ¥ç¸ÿ¥çºÿ¥çÂÿUçÃÿ¥çÅÿ¥çÇÿ¥çÎÿ¥çÓ-çåÿ­çæÿ­çéÿ¤çìÿ¤çïÿ­çðÿ¤çòÿ­çóÿ­çùÿUè ÿUèÿ¤èÿ­èÿ¤èÿUè$ÿUè96è:0è<-è?6èDÿ¥èFÿ¥èGÿ¥èHÿ¥èRÿ¥èTÿ¥èmÿ­èoÿ­èyÿ­è}ÿ­è‚ÿUèƒÿUè„ÿUè…ÿUè†ÿUè‡ÿUèˆÿUèŸ-è¢ÿ¥è£ÿ¥è¤ÿ¥è¥ÿ¥è¦ÿ¥è§ÿ¥è¨ÿ¥è©ÿ¥èªÿ¥è«ÿ¥è¬ÿ¥è­ÿ¥è²ÿ¥è´ÿ¥èµÿ¥è¶ÿ¥è·ÿ¥è¸ÿ¥èºÿ¥èÂÿUèÃÿ¥èÅÿ¥èÇÿ¥èÎÿ¥èÓ-èåÿ­èæÿ­èéÿ¤èìÿ¤èïÿ­èðÿ¤èòÿ­èóÿ­èùÿUéÿˆé ÿˆé ÿˆéÿ;é#ÿÉé&ÿÉé*ÿÉé2ÿÉé4ÿÉé7ÿ5é9ÿ,é:ÿ|é<ÿ;é?ÿ,éYÿréZÿ¸é\ÿ|ékÿÉélÿˆémÿ;éoÿ;épÿÉérÿˆéyÿ;é|ÿˆé}ÿ;é‰ÿÉé”ÿÉé•ÿÉé–ÿÉé—ÿÉé˜ÿÉéšÿÉéŸÿ;é¿ÿréÁÿréÄÿÉéÍÿÉéÓÿ;éåÿ;éæÿ;éçÿˆéèÿˆéêÿˆéëÿˆéïÿ;éòÿ;éóÿ;ê ÿUêÿ¤êÿ­êÿ¤êÿUê$ÿUê96ê:0ê<-ê?6êDÿ¥êFÿ¥êGÿ¥êHÿ¥êRÿ¥êTÿ¥êmÿ­êoÿ­êyÿ­ê}ÿ­ê‚ÿUêƒÿUê„ÿUê…ÿUê†ÿUê‡ÿUêˆÿUêŸ-ê¢ÿ¥ê£ÿ¥ê¤ÿ¥ê¥ÿ¥ê¦ÿ¥ê§ÿ¥ê¨ÿ¥ê©ÿ¥êªÿ¥ê«ÿ¥ê¬ÿ¥ê­ÿ¥ê²ÿ¥ê´ÿ¥êµÿ¥ê¶ÿ¥ê·ÿ¥ê¸ÿ¥êºÿ¥êÂÿUêÃÿ¥êÅÿ¥êÇÿ¥êÎÿ¥êÓ-êåÿ­êæÿ­êéÿ¤êìÿ¤êïÿ­êðÿ¤êòÿ­êóÿ­êùÿUë ÿUëÿ¤ëÿ­ëÿ¤ëÿUë$ÿUë96ë:0ë<-ë?6ëDÿ¥ëFÿ¥ëGÿ¥ëHÿ¥ëRÿ¥ëTÿ¥ëmÿ­ëoÿ­ëyÿ­ë}ÿ­ë‚ÿUëƒÿUë„ÿUë…ÿUë†ÿUë‡ÿUëˆÿUëŸ-ë¢ÿ¥ë£ÿ¥ë¤ÿ¥ë¥ÿ¥ë¦ÿ¥ë§ÿ¥ë¨ÿ¥ë©ÿ¥ëªÿ¥ë«ÿ¥ë¬ÿ¥ë­ÿ¥ë²ÿ¥ë´ÿ¥ëµÿ¥ë¶ÿ¥ë·ÿ¥ë¸ÿ¥ëºÿ¥ëÂÿUëÃÿ¥ëÅÿ¥ëÇÿ¥ëÎÿ¥ëÓ-ëåÿ­ëæÿ­ëéÿ¤ëìÿ¤ëïÿ­ëðÿ¤ëòÿ­ëóÿ­ëùÿUìÿˆì ÿˆì ÿˆìÿ;ì#ÿÉì&ÿÉì*ÿÉì2ÿÉì4ÿÉì7ÿ5ì9ÿ,ì:ÿ|ì<ÿ;ì?ÿ,ìYÿrìZÿ¸ì\ÿ|ìkÿÉìlÿˆìmÿ;ìoÿ;ìpÿÉìrÿˆìyÿ;ì|ÿˆì}ÿ;ì‰ÿÉì”ÿÉì•ÿÉì–ÿÉì—ÿÉì˜ÿÉìšÿÉìŸÿ;ì¿ÿrìÁÿrìÄÿÉìÍÿÉìÓÿ;ìåÿ;ìæÿ;ìçÿˆìèÿˆìêÿˆìëÿˆìïÿ;ìòÿ;ìóÿ;ïÿ­ï ÿÌï ÿ­ï ÿ­ïÿ^ïÿ^ïÿÌï$ÿÌï7ÿAï9ÿ†ï;ÿºï<ÿ@ï=ÿÉï?ÿ†ïlÿ­ïrÿ­ï|ÿ­ï‚ÿÌïƒÿÌï„ÿÌï…ÿÌï†ÿÌï‡ÿÌïˆÿÌïŸÿ@ïÂÿÌïÓÿ@ïÔÿÉïÖÿÉïØÿÉïçÿ­ïèÿ­ïéÿ^ïêÿ­ïëÿ­ïìÿ^ïðÿ^ïùÿÌðÿˆð ÿˆð ÿˆðÿ;ð#ÿÉð&ÿÉð*ÿÉð2ÿÉð4ÿÉð7ÿ5ð9ÿ,ð:ÿ|ð<ÿ;ð?ÿ,ðYÿrðZÿ¸ð\ÿ|ðkÿÉðlÿˆðmÿ;ðoÿ;ðpÿÉðrÿˆðyÿ;ð|ÿˆð}ÿ;ð‰ÿÉð”ÿÉð•ÿÉð–ÿÉð—ÿÉð˜ÿÉðšÿÉðŸÿ;ð¿ÿrðÁÿrðÄÿÉðÍÿÉðÓÿ;ðåÿ;ðæÿ;ðçÿˆðèÿˆðêÿˆðëÿˆðïÿ;ðòÿ;ðóÿ;òÿ­ò ÿÌò ÿ­ò ÿ­òÿ^òÿ^òÿÌò$ÿÌò7ÿAò9ÿ†ò;ÿºò<ÿ@ò=ÿÉò?ÿ†òlÿ­òrÿ­ò|ÿ­ò‚ÿÌòƒÿÌò„ÿÌò…ÿÌò†ÿÌò‡ÿÌòˆÿÌòŸÿ@òÂÿÌòÓÿ@òÔÿÉòÖÿÉòØÿÉòçÿ­òèÿ­òéÿ^òêÿ­òëÿ­òìÿ^òðÿ^òùÿÌóÿ­ó ÿÌó ÿ­ó ÿ­óÿ^óÿ^óÿÌó$ÿÌó7ÿAó9ÿ†ó;ÿºó<ÿ@ó=ÿÉó?ÿ†ólÿ­órÿ­ó|ÿ­ó‚ÿÌóƒÿÌó„ÿÌó…ÿÌó†ÿÌó‡ÿÌóˆÿÌóŸÿ@óÂÿÌóÓÿ@óÔÿÉóÖÿÉóØÿÉóçÿ­óèÿ­óéÿ^óêÿ­óëÿ­óìÿ^óðÿ^óùÿÌö ÿUöÿ¤öÿ­öÿ¤öÿUö$ÿUö96ö:0ö<-ö?6öDÿ¥öFÿ¥öGÿ¥öHÿ¥öRÿ¥öTÿ¥ömÿ­öoÿ­öyÿ­ö}ÿ­ö‚ÿUöƒÿUö„ÿUö…ÿUö†ÿUö‡ÿUöˆÿUöŸ-ö¢ÿ¥ö£ÿ¥ö¤ÿ¥ö¥ÿ¥ö¦ÿ¥ö§ÿ¥ö¨ÿ¥ö©ÿ¥öªÿ¥ö«ÿ¥ö¬ÿ¥ö­ÿ¥ö²ÿ¥ö´ÿ¥öµÿ¥ö¶ÿ¥ö·ÿ¥ö¸ÿ¥öºÿ¥öÂÿUöÃÿ¥öÅÿ¥öÇÿ¥öÎÿ¥öÓ-öåÿ­öæÿ­öéÿ¤öìÿ¤öïÿ­öðÿ¤öòÿ­öóÿ­öùÿUùÿJù ÿJù ÿJùÿÌù#ÿÑù&ÿÑù*ÿÑù->ù2ÿÑù4ÿÑù7ÿ|ù8ÿØù9ÿŽù:ÿ­ù<ÿhù?ÿŽùWÿÇùYÿÑùZÿàù\ÿÑùkÿÑùlÿJùmÿÌùoÿÌùpÿÑùrÿJùtÿIùuÿIùyÿÌù{ÿIù|ÿJù}ÿÌù‰ÿÑù”ÿÑù•ÿÑù–ÿÑù—ÿÑù˜ÿÑùšÿÑù›ÿØùœÿØùÿØùžÿØùŸÿhù¿ÿÑùÁÿÑùÄÿÑùÍÿÑùÓÿhùåÿÌùæÿÌùçÿJùèÿJùêÿJùëÿJùïÿÌùòÿÌùóÿÌ„„„„Î ’Z,â Bzô(Ztž¼J¸B‚èN† r Ê . ^ „ ´  Ü  z â P z ò2‚Îì>|Ê‚ÚLr¶èFˆ¾ò(Hx¨ÆèXîdÞ`̘Ü(”äþtÌ(¶.lØR°â@„¼îf|ø@@ˆ ž!!l!Ž"&"l#<#¶#ü$,$F%%%b%¤&&‚&¤&ø'2'^'Ê((Z(¢)")À*†*ô++ ++$+0+<+ˆ,n,z,†,’,ž,ª,¶,Â,Î--*-6-B-N-Z-f-Œ. .,.8.D.P.b.¦/R/^/j/v/‚/Ž/š0Æ1ª1¶1Â1Î1Ú1æ1ò1þ2 2Œ2ž2°2Â2Ô2æ2ø3L444*4<4N4`4Ü4î5„6|6ˆ6š7"7þ88P8~8Š8œ9~:8:D:T:`:p:‚:Ž: :¬:¾:Ê:Ü;8;h;’;š;Ò;ü>H>–>æ?4?ˆ@@:@œAªAÒAúBB¦CCxDD0D\D˜D²DòE†EÐFRF¬FîG0GpG–HH´HÞIIJIdIˆI®IØJJ:JzJ¾JøK‚b"/n ‘nŠŠŽ)” ½(È ð0û+ C ÜR . 2F Âx: T h  p R| Î Pä Î `4 0” Ä ¸â 0 š d Ê „. 4²Copyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.LatoItalictyPolandLukaszDziedzic: Lato Italic: 2013Lato ItalicVersion 1.105; Western+Polish opensourceLato-ItalicLato is a trademark of tyPoland Lukasz Dziedzic.tyPoland Lukasz DziedzicLukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2010-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLCopyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.LatoItalictyPolandLukaszDziedzic: Lato Italic: 2013Lato-ItalicVersion 1.105; Western+Polish opensourceLato is a trademark of tyPoland Lukasz Dziedzic.tyPoland Lukasz DziedzicLukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2010-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLÿùÿrt  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a£„…½–膎‹©¤Šƒ“òó—ˆÞñžªõôö¢­ÉÇ®bcdËeÈÊÏÌÍÎéfÓÐѯgð‘ÖÔÕhëí‰jikmln oqprsutvwêxzy{}|¸¡~€ìîºýþ  ×âã  °± äå»æç¦ØáÛÜÝàÙß›²³¶·Ä´µÅ‚‡«Æ¾¿¼ŒŸ˜š™ï¥’œ§”•¹ÀÁ !"#NULLuni00A0uni00ADmacronperiodcenteredAogonekaogonekEogonekeogonekNacutenacuteSacutesacuteZacutezacute Zdotaccent zdotaccentuni02C9EuroDeltauni2669undercommaaccent grave.case dieresis.case macron.case acute.casecircumflex.case caron.case breve.casedotaccent.case ring.case tilde.casehungarumlaut.case caron.saltÿÿ³‹³‹™ºõþ©¶þV©ÿðºÿòþ”¶þV°,° `f-°, d °ÀP°&Z°E[X!#!ŠX °PPX!°@Y °8PX!°8YY ° Ead°(PX!° E °0PX!°0Y °ÀPX f ŠŠa ° PX` ° PX!° ` °6PX!°6``YYY°+YY#°PXeYY-°, E °%ad °CPX°#B°#B!!Y°`-°,#!#! d±bB °#B² *! °C Ра+±0%ŠQX`PaRYX#Y! °@SX°+!°@Y#°PXeY-°,°C+²C`B-°,°#B# °#Ba°€b°`°*-°, E °Ec°Eb`D°`-°, E °+#±%` EŠ#a d ° PX!°°0PX° °@YY#°PXeY°%#aDD°`-°,±E°aD-° ,°` ° CJ°PX ° #BY° CJ°RX ° #BY-° , ¸b ¸cŠ#a° C` Š` ° #B#-° ,KTX±DY$° e#x-° ,KQXKSX±DY!Y$°e#x-° ,± CUX± C°aB° +Y°C°%B± %B± %B°# °%PX±C`°%BŠŠ Š#a° *!#°a Š#a° *!±C`°%B°%a° *!Y° CG° CG`°€b °Ec°Eb`±#D°C°>²C`B-°,±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,± +-°,°+±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-° ,±+-°!,±+-°",±+-°#,± +-°$, <°`-°%, `° ` C#°`C°%a°`°$*!-°&,°%+°%*-°', G °Ec°Eb`#a8# ŠUX G °Ec°Eb`#a8!Y-°(,±ETX°°'*°0"Y-°),°+±ETX°°'*°0"Y-°*, 5°`-°+,°Ec°Eb°+°Ec°Eb°+°´D>#8±**-°,, < G °Ec°Eb`°Ca8-°-,.<-°., < G °Ec°Eb`°Ca°Cc8-°/,±% . G°#B°%IŠŠG#G#a Xb!Y°#B².*-°0,°°%°%G#G#a°E+eŠ.# <Š8-°1,°°%°% .G#G#a °#B°E+ °`PX °@QX³  ³&YBB# °C Š#G#G#a#F`°C°€b` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°%°€ba# °&#Fa8#°CF°%°CG#G#a` °C°€b`# °+#°C`°+°%a°%°€b°&a °%`d#°%`dPX!#!Y# °&#Fa8Y-°2,° °& .G#G#a#<8-°3,° °#B F#G°+#a8-°4,°°%°%G#G#a°TX. <#!°%°%G#G#a °%°%G#G#a°%°%I°%a°Ec# Xb!Yc°Eb`#.# <Š8#!Y-°5,° °C .G#G#a `° `f°€b# <Š8-°6,# .F°%FRX ,°1+!# <°#B#8±&+°C.°&+-°?,° G°#B².°,*-°@,° G°#B².°,*-°A,±°-*-°B,°/*-°C,°E# . FŠ#a8±&+-°D,°#B°C+-°E,²<+-°F,²<+-°G,²<+-°H,²<+-°I,²=+-°J,²=+-°K,²=+-°L,²=+-°M,²9+-°N,²9+-°O,²9+-°P,²9+-°Q,²;+-°R,²;+-°S,²;+-°T,²;+-°U,²>+-°V,²>+-°W,²>+-°X,²>+-°Y,²:+-°Z,²:+-°[,²:+-°\,²:+-°],°2+.±&+-°^,°2+°6+-°_,°2+°7+-°`,°°2+°8+-°a,°3+.±&+-°b,°3+°6+-°c,°3+°7+-°d,°3+°8+-°e,°4+.±&+-°f,°4+°6+-°g,°4+°7+-°h,°4+°8+-°i,°5+.±&+-°j,°5+°6+-°k,°5+°7+-°l,°5+°8+-°m,+°e°$Px°0-K¸KRX±ŽY¹c °#D°#p°E °(`f ŠUX°%a°Ec#b°#D² *² *²*Y²( ERD² *±D±$ˆQX°@ˆX±D±&ˆQX¸ˆX±DYYYY¸ÿ…°±Dmcollective-2.12.1/doc/fonts/SourceCodePro-Regular.ttf0000644005276200011600000021401413265671731022540 0ustar jenkinsjenkins€pFFTMf¢h·ðOS/2r¼ø x`cmap_…ceXcvt ¿ q "fpgmYœ7 `sgaspÿÿèglyfÝ'Ëb´ºœheadûÑu»ü6hhea;¿4$hmtxz€pFØ~locaóâ <vmaxpSX nameqB±üÌP;dpost6z ´3prep&³#° ÔAZ½jV_<õèÍâÍâÿêÿv¶Øþïèÿêÿâv:Vj ËXŠXKŠX^2    ADBE@ "îÿØ`“æ èMXãƒWU*õÐzTUÅUÛcGbE9'8MFDCÛÅxUkm1 gBUs‡5O_Qb†QS0f1dC*O+ 6&Aâcco<¹Q]P<EgH]Z7jQ<]<]<’HEM3@1GxcLãqM:5[–§SUUp±¯U®¬ëMHÛÝÐŽa'{ ÿÿBssss____S00000f-OOOO&eXQQQQQQPEEEEZZZZ<]<<<<<U<MMMM1]1 Q Q QBPBPBPBPU3<sEsEsEsEsE5H5H5H5HO _Z_Z_Z_Z_ZQ7bjj†Q†Q†Q†+5QS]S]S]ÿê0<0<0<! d’dtd’CHCHCHCH*E*EOMOMOMOMOMOM &1&AGAGAG>10<OM Q_Z0<OMOMOMOMOM5H0<CH*E7^<]=?E<M.?631¯ŒóRŒÑÙõÛ¨¨ù±ë¹ùªêÏêžÊÄ¢Ÿ¹ë¨ž±ªê–ìÏʨl÷ý#ê¥××ê®°§¬‰ •½‚ެ¤Ÿ“µÆ’U<U<5HO]O]†QCQ†QQ<S]S]S]d‡d‡dMCHCH*E*E   &1AGEO&1&1&1&1PÑÙÙ_gg—ÅÓ¡¯¬¬°·¯­ìÕ¯¡Ð®¬¬¬°·¯­ìÕš`N/+m:+T`l8U¡Ð­¬¬¬°·¯­ìÕ©ù¤›°±ó¥ìÏ¿¤Ò¨¨¨¨¨¤¨¨çüà´€4 ~1Ie~€’¡°Üçë7CRTYaeoy‡Žž°³¸¼¿ÌÝã $(.1CIMPRX[œ »!%+;Ico…“—žù    " : D q y ‰ Ž ” ¡ ¤ § ¬ ² µ º""ÿÿ  4Lh€’ ¯Íæê7CPTXaeoy‡Œž°²·»¾ÆØá#&.1CGMORV[œ »  $*6BZl€Ž’—žò    " 9 D p t } ” ¡ ¤ ¦ « ± µ ¹""ÿÿÿ÷ÿåÿÄÿÂÿÀÿ¾ÿ½ÿ¯ÿ­ÿ ÿ“ÿwÿnÿlÿ@ÿ%ÿÿÿ ÿ ÿÿþ÷þîþáþÝþÎþ½þ¼þ¹þ·þ¶þ°þ¥þ¢þ†þ…þƒþþzþsþrþmþkäZäWäTäSäRäOäMä ä ãðã ããŽãŠã€ãzãjãbãRãJãHãEã?âìáÓáÐáÏáÌá¶á­á‚á€á}ázáuáiágáfácá_á]áZàà   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcˆ‰‹•𠥤¦¨§©«­¬®¯±°²³µ·¶¸º¹¾½¿Àtfgkîz£rmxlŠœuiyn~ª¼ƒep?od„‡™æçëìèé»Ã6ñïð{ê톎…Œ‘’“—˜–žŸõvs}~|‚€w¸,K¸ PX±ŽY¸ÿ…¸D¹ _^-¸, EiD°`-¸,¸*!-¸, F°%FRX#Y Š ŠIdŠ F had°%F hadRX#eŠY/ °SXi °TX!°@Yi °TX!°@eYY:-¸, F°%FRX#ŠY F jad°%F jadRX#ŠY/ý-¸,K °&PXQX°€D°@DY!! E°ÀPX°ÀD!YY-¸, EiD°` E}iD°`-¸,¸*-¸,K °&SX°@°YŠŠ °&SX#!°€ŠŠŠ#Y °&SX#!¸ÀŠŠŠ#Y °&SX#!¸ŠŠŠ#Y °&SX#!¸@ŠŠŠ#Y ¸&SX°%E¸€PX#!¸€#!°%E#!#!Y!YD-¸ ,KSXED!!Y-°+²+²+·@6*!+·M@2$+²+° E}iDK°`RX°°Y°ŽDV ÿ3 æ > ~  È 8Dº$4æü>t˜¾Æèdœî`²ŒÂH¼ÈÔD– P´  B € ¶  R ˆ Ä  : à 4 ~ ð D ² Þ X¸Rˆ¦ÀÞ$ª&vîLœjÂV¦ÞbºˆL²V`¾ôVjÌ  8¢ dÆäpx*2ª²î,6@H¾ðú   0 @ P   ¬ ¸ Ä Ð Ü è!B!N!Z!f!r!~!Š!–!¢!®!þ" """".":"F"t"ü### #,#8#|#ø$$$$($4$@$ø%%%%(%4%@%L%X%d%ð%ü&&& &,&8&x'' ''$'0'<'¶'Â'Î'Ú'æ'ò(b))))&)2)>)J)V)b)n)z)‚**"*.*:*F*R*^*À+H+T+`+l+x+„++œ+¨+´+À+Ì+Ø,@,²,¾,Ê,Ö,â,î,ú-X-Æ-Ò-ú....*.|.ˆ.”. .¬.¸.Ä.Ð.Ü//l/x/„//œ/¨/´0&020>0J0V0b0n0À1h1t1€1Œ1˜1¤1°1¼1È1Ô1à1ì1ø2222(242@2L2X2d2p2|2ˆ2”2 2¬2¸3 3œ3¨3´3À3Ì3Ø3ä3ð3ü444 4°5 5h5Ø6H6ª777*767B7N7Z7f7r7~7Š7–7¢7®7º7Æ7Ò7Þ7ê8`8ä8ð8ü999T9Æ:H:¾;8;†;ä<<<È==¢=ì>4>p>Ü?8?„?Â@ @F@ª@úAA A0AVA^AfA€AˆAA˜A¬A´A¼AÄAÌAÔAÜBBhBºBÌBàCC8CLCzC˜CÌCöD(DJDhDŠD²DØEEERE~E¬EÜFFFjFÆG"GfHHDHœHØI2I|I¾IôJ.JpJžJªJ¶JÂJÎJÚJæJòJþK KK"K.KY¸EX¸/¹>Y¸ ܸÜ01'3#4632#"&P 8-++++@^^þ¨¦#))#$**ÿÿƒ`Ö¯& Ž sWŠ‹¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y»+»+¸¸ ии¸ии¸ии¸иÐ017#537#53733733#3##7##7#¦OVU[5„5QWV]6…6Ù…Ì9”:····:”9ÌÌÌ””Uÿ’ì1G¸/¸,/¸+ܹô¸¸ܺ+9¸и¹ôº!+9¸+¸.Ð01.#"#5.'732654.546753Ã"(3;/GRG//A'<8d#'&[9<=/FSF/YI<7Jí -&$*<. 7)’‘,9)1&) )9,?P„ƒ*ÿÿÿô=Š'ÿ{M&ñ†*ÿôAœ G¸EX¸3/¹3 >Y¸EX¸!/¹!>Y¸EX¸/¹>Y¸!¹ôº!39º3!9¸¸+и и3¹ô¸¸и¹;ô¸¸Eи>и¸GÐ0173267.'>54&#"&'#".54>7.54>32>73z".:0U" *F%!#&jAH$W8,H3#,%5!=B(2 S-)L4$<5¯- *d6=-:!%+6ýÉ7#(0C( 6-')O#!8*H: 4/)3^'(_9Av4.õ`c¯ º+01'3#÷n8AnnáÐÿPÞÜ º+01.5467±hyyh-e__e-°Qä‘‘äQ*UÈÈU*zÿPˆÜ º +01>54&'7ze__e-hyyh†UÈÈU*Qä‘‘äQTo,/¸/º9¸¹ô¸и¸ и¸ Ð01?'7737'Šl¢§ 0 §¢l*xx¥F.7½½7.F¥ŸŸUh, »+¸¸и¸ Ð01#53533## ¶¶B¶¶B+>ÃÃ>ÃÅÿ+Œ› º +01>7#"&54632Å>>  .0 --]S¡O<%&%'E;XyÿÿU+iÛÿô} ¸EX¸ /¹ >Y¸Ü0174632#"&Û/""//""/H&//&&..cÿ`õƸEX¸/¹>Y¸Ü01#3­JHJ fGÿôŠ 'K¸EX¸/¹ >Y¸EX¸/¹>Y¸¹ ô¸¹ôº 9¸/¸"Ü01"&54632"32654."&54632,kzzkkzzk!8)UCCU)8!%%%% ­ ¡¨¨¡ ­TAcE‰‚‚‰EcAþ½# ## #b~ =¸EX¸ /¹ >Y¸EX¸/¹>Y¹ô¸Ð¸ ¸йô01%!53#5>73þRµˆ3L=DDDÖ5ýÆE ŠC¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸Ðº9¸¹ô017>54&#"'>32>;!IQ}U,DG-M/+cD0M6+NlA=Òþ?1HtaT(7F- /,51F*-[ai;G9ÿôŠ3S¸EX¸/¹ >Y¸EX¸0/¹0>Y¹ôº 09¸ /¹ô¸¹ôº& 901732>54.#52>54&#"'>32#"&'c Y>!8)8X?9O2G;-P ,(f>-M9 L< 9,$?T0Sp#„.+/"? ,/6$4#-)<':J)6!*D/7#'!~ Y¸EX¸/¹ >Y¸EX¸ /¹ >Y»+¸¹ô¸¸ и¸ и ¸и¸Ð01%5>7###5!533p"­ŸcNþ·?XcòÆ=/ÚB°°7—þt8ÿô ~(U¸EX¸/¹ >Y¸EX¸#/¹#>Y» +¸#¹ôº#9¸¸и¹ô01732>54&#"'!!>32#".'a$+5 ";,UH(8",iþà5%.P;"'BU.+E7- &6!BJ3G½ 1K44P7MÿôŠ 0W¸EX¸-/¹- >Y¸EX¸#/¹#>Y»+¸#¹ôº#-9¸¹ ô¸-¹ô01%2>54&#".#">32#".54>32A2$FB&T) UáB$&F5!&_0,I5#:M+4YB%,Ja4;W 5%3 BE'/]`Þ@iM&-1J1.K6&MsM`‡U''F~3¸EX¸/¹ >Y¸EX¸/¹>Y¸¹ô¸ Ð013>7!5!#â2M7þÎ?R2V[•†~CG3H„‰˜^DÿôŠ @W¸EX¸)/¹) >Y¸EX¸Y¹ô¸)¹ôº 9¸ /¸1и1/¸ܸ ¸ Ü01732654.'7654&#"4>75.54>32#".WJHL6I*/>ÑPB?6B.=ø&1(95H+/I39(.":U76W2!- A:F0A8/) Ä!4* I3%<+-?%-O &3"$>./@Cÿô Š 0W¸EX¸#/¹# >Y¸EX¸-/¹->Y»+º-#9¸¹ô¸#¹ô¸-¹ô01267.#"32>7#".54>32#"&'%T* SG1$EYB%&E5 ']1,I4";M*4ZB%-J`4;W 6'.^`%4BEËAhM&,1J1.K6&MsM`‡U'&ÿÿÛÿô}'fÿÿÅÿ+Œ'fx0íh;»+¸¸и/¹ôº9¸¸и/¹ô01%%xuþÓ-þ‹kýOËËOýÿÿUÁÔ&k–k0àh;»+¸¸и/¹ôº9¸¸и/¹ô015%5%5àþ‹-þÓu-ýOËËOýmÿôߪ)*¸EX¸'/¹'>Y» +¸'¸!ܸÜ017&>54&#"'>324632#"&ò#,'77&A1"\:*D1'-%f++++è$8.('**7-#.)9$!3+(,3 ¦#))#$**1ÿp"{4=?»%+»/+»8+»;+º/9¸¹:ô01%#'##".546754.#"3267#".54>323275"2G'/#ˆ€$9'-R?%$>T1-C'R2;jP/-Pk>4L2ÿ+%88hXd:*"1MN$@0*S}SP}V--3c”```1$@W3°#(@~7 8 A¸EX¸/¹ >Y¸EX¸ /¹ >Y»  +¸¹ô¸ ¸Ð01'.'###3#  Ùî?UÝ^ÝX d7m99m7dCÈýpg!%W¸EX¸/¹ >Y¸EX¸/¹>Yº$9¸$/¹ôº $9¸¹ô¸¹%ô0132+2654&+2654&+g´2S;!9:HP$A[7æTIMLVcU\ZWc&=+1O ND0H0x:76/ÖþÊ?C=9øBÿô*œ!9¸EX¸/¹ >Y¸EX¸/¹>Y¸¹ ô¸¹ô014>32.#"3267#".B+Nl@iM,HO~X/0 5!%Eb=>cF&-2.WU% 9¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ô¸ ¹ô0132+72654&+U •›'LpI¤›poopH¨N{U-DŠ}}„ýøs M¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ôº 9¸/¹ô¸ ¹ô01!!!!!!s•þ¿þñKþaFÎGîG‡ C¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ôº 9¸/¹ô01!!!!#‡‘þ þóSFÞFþÚ5ÿôœ'M¸EX¸/¹ >Y¸EX¸#/¹#>Y¸¹ô¸#¹ôº#9¸/¹ô014>32.#"32675#53#".5+Mk@!6-#/>0.K63L0#<ƒÐ d@>hL*HOW/5"%Eb=>cF&«Eþì ,.WO  I¸EX¸/¹ >Y¸EX¸ /¹ >Yº 9¸ /¹ô¸¸и ¸Ð013!3#!#OTTTþîTþíýp5þË_ù A¸EX¸/¹ >Y¸EX¸ /¹ >Y¹ô¸¹ô¸и¸ Ð0173#5!#3!_££š££þfGFFýýGQÿôí‘5¸EX¸ /¹ >Y¸EX¸/¹>Y¹ô¸ ¹ô0173265!5!#"&'‡H&GAþíf3S=8i"“,*KQrFþA.Q<#49bC k¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Yº9º 9¸ ¸и¸ Ð01333##bT^Ïç]¾rTþ·IúþjU…І+¸EX¸/¹ >Y¸EX¸/¹>Y¹ô013!!†REþiý·GQM¸EX¸/¹ >Y¸EX¸/¹>Y»+¸¸и¸ и¹ô¸Ð013373#4>7##/##Q\]! \\G+Z-Z,Fþéjjýpq675ŒýýŒ576þSY¸EX¸/¹ >Y¸EX¸ /¹ >Yº 9¹ô¸¸к 9¸¹ ô¸ ¸Ð0133.53#'##SUÕB OUÕB Oþb†1k4Týpž†3g3þ©0ÿô(œ'5¸EX¸ /¹ >Y¸EX¸/¹>Y¹ô¸ ¹ô01".54>32'2>54.#",8\C%%C\87]C%%C]7%>++>%&=++= 0YOO}W./W}NOY0I&Gc>=bD%%Db=>cG&f!G¸EX¸/¹ >Y¸EX¸/¹>Yº 9¸ /¸¹ô¸ ¹ô0132+#2654&+fÉ6Z?##@Y6vS¾WSUUk-J64L2þüHAFG7þû1ÿ])4K¸EX¸$/¹$ >Y¸EX¸/¹>Y»1+¸¹ô¸$¹ô¸¸.Ð0132>54.#"#"&'.54>323267†+=&%=++=%&=+£$Wm/M7$B\87\B$4K-J3 K=dG''Gd==cE%%EcýãXC7WvGO}W//W~NFtW8 *+d)S¸EX¸/¹ >Y¸EX¸ /¹ >Yº 9¸ /¹ô¸¹ô¸ ¸ и ¸Ð0132654&+##32·mMQQMmžuSÌ2T="PC§Y?@A4ý³þë,F3M\þâCÿôœ3I¸EX¸/¹ >Y¸EX¸0/¹0>Y¹ôº09¸¹ôº 0901732654./.54>32.#"#"&'u%b6FL +^2) 9O/>h$, M1Y¸EX¸/¹>Y¸¹ô¸Ð01#5!##ØØTJFFý¶Oÿô 3¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸¸ Ð01332>53#".5OT%22&Q!:Q00R;!þf3G++G3šþhGb??bG+-3¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸¸ Ð0133>73#+Xj iUÐaþž;d:546býp N‘!M¸EX¸/¹ >Y¸EX¸ /¹ >Y» +¸ ¹ô¸и¸и ¸Ð0133>?33>73#.'## S5   E;E  2Oa\K  HZ‘þd*O))P)òò(Q))P)œýo<<þí6"]¸EX¸/¹ >Y¸EX¸/¹>Yº9º9¸¸ к 9¸¸к90133>?3#'.'##ú·\\  ZX·Ä\c bXS=¨++¨þ¿þ±±33±&2@¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Yº901%33>?3#ÜXc$&_VÜTê¦Ã&K((L&ÂþZêA‘ =¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸Ð¸¹ô¸Ð017!5!!!Aoþ±µþuþ&2F2ýèGâÿhöÄ»+»+01!#3!âÓÓþìÄ0ý0cÿ`õƸEX¸/¹>Y¸Ü013#cJHJÆüšcÿhwÄ»+»+01#5!!55Òþìhü0ü¤0oéž &¸EX¸/¹ >Y¸ܺ9¸ Ð013#/##H™HB11BHžþ~°……°<ÿtÿ» »+01!5þ EGGÿÿ¹=mцQÿôò!/€¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸/¸¹ ô¸¹'ôº '9º9¸¹*ô¸¹+ô0174>7.#"'>32#'##".732675Q'U†^1#0X" 17> daC)c4"<-P'*Q*Ni?~)=,,!%8  m[þÖB .#3' %#€']ÿôÈ'ƒ¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9º9¸¹ô¸¹ô¸¹$ô¸¹'ô013>32#"&'##732>54.#"]R#W+1L3$=P+#Q#BR#G 6(!4$ I&ÈÂ^"(#A[8>bD##6r1H-(B/#&Pÿôò!9¸EX¸/¹>Y¸EX¸/¹>Y¸¹ ô¸¹ô0174>32.#"3267#".P+Jc7Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Yº9º9¸¹ô¸¹ô¸¹ô¸¹ ô0174>32'53#'##".73275.#"<%=P+-D"RDS-0M8UKDHA!? 7)ò;_B$"Z»ý8@-"A^>XbIò1DEÿôò'C¸EX¸/¹>Y¸EX¸/¹>Y»' +¸¹ô¸¹"ô0174>32!3267#".%4&#"E*EY/4R9þ„1C(+G"$\;6_G)ˆLD7,ò<_B#!,6"#A^dIN'8$gBÔV¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸¹ô¸¸и¹ ô¸ Ð01.#"3###5754632/3 B9ÌÌQ‹‹_eHDz C<,Cþ]£>)YlHÿ 6òEU²¸EX¸%/¹%>Y¸EX¸(/¹(>Y¸EX¸'/¹'>Y¸EX¸C/¹C>Y¸EX¸ /¹ >Y¸C¹ô¸ ¸ии ¹:ô¸2ܹFôº5F29¸5¸и(¹)ô¸'¹*ô¸%¹Nô0132>54&+"&'4675.54675.54>323##"&';2#"&2654.#"QN*D078_%#G)'#4E'( É‚3E'+&50m^[&Fd>kuÛ/A))BQ&0$#)8 &1?,(@- ?4 '>+  4>">.Fk>5**5>] Èe¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Yº 9¸¹ô¸¹ô013>32#4&#"#]R'X9WQR4<%$&RÈÂs)6caþÒ#EC !þ­ZžÉ;¸EX¸/¹>Y¸EX¸/¹>Y¸¹ô¸¸ܸ Ü01!##%"&54632Z4Râ%%&&æþ£§"##"7ÿ'žÉ!A¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸¹ô¸¸ܸÜ01!#"&'732>5#%"&54632Z4/N;&H9$0 â%%&&æþ-K7= "1µ§"##"j>È m¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Yº 9º 9¸ ¸и¹ ô01333#'#jR^ÂÜ\³sRÈþÁþÛóo„QÿôÈ5¸EX¸/¹>Y¸EX¸ /¹ >Y¹ô¸¹ô013327#"&5#Qø3,(4!:(NQ¦ÈýÕ6/> XWâ<,ò £¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº 9¸¸и ¹ô¸¹ô¸¹ô¸¹ô0133>32>32#4#"#4#"#<@1*J4)37O5&B7$Oæ@"*T&.MIþ¤UV%&þ UV%&þ ] òe¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Yº9¸¹ô¸¹ô0133>32#4&#"#]D&X9WQR4<%$&RæS)6caþÒ#EC !þ­<ÿôò'5¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸¹#ô0174>32#".732>54.#"<'BW00WB''BW00WB'U)9##9))9##9)ò=_B""B_=<_A""A_<*D11D**E22E]ÿ3ò%ƒ¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº 9º 9¸¹ô¸¹ô¸ ¹"ô¸¹%ô01#33>32#"&'732654.#"¯RD"Y-1K3$=P,"O!#FBT!4$ I&)¤³> *#A[9>aD#!?f[(B/#&<ÿ3ûò$ƒ¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Yº9º9¸¹ô¸¹ô¸¹ô¸¹ ô0174>32373#57#".73275.#"<%=P+-F#BR Q-0M8UKDHA!? 7)ò;_B$" 6ýM³X+"A^>XbIò1D’òT¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¹ ô¸¹ô0133>32.#"#’D&oD.#7e,Ræs;D G ?LþãHÿôò-I¸EX¸/¹>Y¸EX¸*/¹*>Y¹ôº*9¸¹ôº*901732654&'.54>32.#"#"&'p)^BB@EY&C23M27h$( N-". RBc_7Q4Hy-v$, , #*3%%5 '@84(-Eÿô"nM¸/¸EX¸/¹>Y¸EX¸/¹>Y¸¹ô¸¸и¸и¹ô01#5?33#3267#".5ΉŒ Dïï 0##8!P(5H+£>ˆˆCç!1" < 5J-Mÿôùæe¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¹ô¸¹ô01!#'##"&5332673ùC%W9XQS3=*D)RU+6ca.þÝEC+/Q3%æ <¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸ /¹ >Y¹ô0133>73#3Sp  pOÉ\æþì%G##G%þPæ!t¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸!/¹!>Y¸EX¸/¹>Yº!9¸¸к90133>?33>73#'.'##T; ;F< ;Njc:  8bæþç#B""C"üü#B""B#þö#E% D*õ@æe¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¸Ð¸¸ иÐ017'33>?3#'.'##ù«[M  IW­ºZU!PXüêk*,iñõp.+p1ÿ/'æ[¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ôº901326?33>73#"'7T  3@ãSw  jNÖ$2A)$†;-$çþó J##I! ýò$>- AGæ =¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸Ð¸¹ô¸Ð017!5!!!GMþØžþ²Xþ3,wC,þ‰CxÿhöÄ9+»34+»+»  +º' 9014>54.#52>54&54>;#";#". 9,,9 .F/=4&1 '44' 1&4=/F.1..40]4'3 0 #+[/13  313T.# 0 3ÿQî º+013#JJîücÿhàÄ9+»7+»+»'(+º ('9012>54&54675.54654.+5323"+5–&1 &44& 1&3=/E. 9,,9 .E/=h #.T313  31/[+# 0 3'4]04..1'3 0Lÿ •'»+»+¸¸ и¸Ð01>3232>7#".#"LH&/)'  5H&/)')F7 "F6 "4ãÿHuò¸EX¸/¹>Y¸ ܸÜ01#737#"&54632RP 8-++++Z^^X§$))$#**qÿßú%\¸EX¸/¹ >Y»" +»!+¸!¸Ð¸/¸"¸и/¸ ¸ и /¸ ܸ¸и/01#5.54>753.'>7:8@?9ÀH'3-J56J*3,@(- 4Ý XBCX "gh$=T54S<$jg"4þ¨MŠ)W¸EX¸/¹ >Y¸EX¸/¹>Y» +¸¹ô¸и¹ô¸ ¸ и¸#Ð01%!5>54'#57.54>32.#"3#þ;?=uc 7M0>U0;*BE ½­%&GG2_94 = *D0+ /A4 ; 85F:SA 4»&+» 0+01?.5467'76327'#"'732>54.#":TS,W0?>1W,TT,X9>1WP ++  ++ €U:##;V-Z%%Z-V;##:U-Y&Yö1$$11$$15#~l¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Yº 9¸¸ йô¸иܸйô¸и ¸Ð013>?33#3##5#535#53Š\!"\Rº¢···Rµµµ¡¹~«!C##C!«þÀ/A0žž0A/@ÿQîº+º+013##JJJJîþ5Mþ0Ð[ÿÀý¬GO»/(+»D+º2(9ºD/9º 29¸ ¸Ðº<29¸<¸Ð01%>54.'.#"#"&'732654.5467.54>32s #)>I %)?IT8#*&*?I?*1)*;$7\ 2=*)-*>J>*3(&8&2QÀ&"",!)!+!k%$+=.0<'2$&!-(&*=.-@'/$"ÿÿ–Lºÿõ:'EM¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸¹#ô¸-й4ô¸¸Aй:ô014>32#".732>54.#"4>32.#"3267#".)Ic99cI))Ic99cI). ;S22S; ;S22S; F.;!#1".85-&4&";,CL{U..U{LM{W//W{MBkM**MkBBjL))LjB+F2'K;BM*3Iÿÿ§üºNÿÿS4øÄ&ïŽïsUhi »+01!#5!U®Bþ”iþÿÃÿÿU+ip?èÉ'5>=»+» +»62+¸¸)и2¸/и¸4и1Ð01".54>32'2>54.#"32#'##72654&+,'E33E''D33D'7''7 6((6)L /..#))C?4H,,H44H,,H4%*;$#;++;#$;*$SFFfCÿÿ±Y§’Н­ª­» +»+01".54>32'2654&#",-##-.""."**"!**­!//""//!..#%..%#.U, D¸EX¸/¹>Y»+¸¸ܸ¸и¸и¹ ô¸ Ü01#53533##!! ¶¶B¶¶B¶®þR0>¾¾>±A>ÿÿ®¸§õ¸ÿÿ¬¬¤õ¸ÿÿë=ŸÑ‡MÿE.æ(s¸(/¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ôº 9¸¹ ô¸¹ô01332>733:7#"&'##"&'#MS49&&'S  &$ Q/#;SæþÝCE +#5:85 &;43Hÿ°å%¸EX¸/¹ >Y¸EX¸/¹ >Y013##".54>;”QQ6 5ZB%$?V2+ý 25R9;Q3ÿÿÛ}°ÿÿÝÿ+v™ÿÿиaé¸ÿÿŽüÊN¤ÿÿa4Ä&ðŽðsÿÿ?~'ÿ{M&ñ†ÿÿ<~'ÿ{M&ñ‡ÿÿ'UŠ'ÿ{M&ñ†{ÿ<ìò'*¸EX¸%/¹%>Y» +¸%¸ܸÜ01%3267#"&54>'7#"&54632g#,'87&A0!]:Te'-$g,++,þ$8.('+*6-#.TH!3+(,2!§$))$#**ÿÿ 82&&#ÿÿ 82&&$ÿÿ 82&&%ÿÿ 83&&&ÿÿ 8-&&*ÿÿ 8k&&,ÿÿOf¸EX¸/¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Y» +»+¸¹ô¸¹ô¸¸Ð01#!5##!#3#=+.–þîžIWC½““L6i6w»G¿¿FÍGïÿÿBÿ+*œ&(//ÿÿs2&*#ÿÿs2&*$ÿÿs2&*%ÿÿs-&**ÿÿ_ù2&.#ÿÿ_ù2&.$ÿÿ_ù2&.%ÿÿ_ù-&.*+ S¸EX¸/¹ >Y¸EX¸ /¹ >Y»+¸ ¹ ô¸¹ô¸¸и¸Ð01#5732+72654&+3#[KK¡”››‘¤›poopHA* ¨®DŠ}}„Ü/ýÿÿS3&3&ÿÿ0ÿô(2&4#ÿÿ0ÿô(2&4$ÿÿ0ÿô(2&4%ÿÿ0ÿô(3&4&ÿÿ0ÿô(-&4*f~ò )º +º 9¸ ¸и ¸иÐ01?'77'fšš,šš,šš,šš«Ÿž-ŸŸ-žŸ-  -ÿâ,® /}¸EX¸+/¹+ >Y¸EX¸/¹>Yº+9¸¸Ð¸¹ôº +9¸ ¸ и+¹ô¸ ¸и¸ и¸#и ¸-Ð01732>54/.#"#"''7.54>327À7 %>+8!&=+L%C]7Z@50?%C\8\?6/t&Gc>Y@8%Db=_@…+sHOY0=O ]-vIO}W.=O ÿÿOÿô 2&:#ÿÿOÿô 2&:$ÿÿOÿô 2&:%ÿÿOÿô -&:*ÿÿ&22&>$e!9¸EX¸/¹ >Y¸EX¸/¹>Y» +»+01332+#72654&+eTv6Z?##@Y6vT¿WSUUkn.I64M2–Ú@GG6þüXÿô4Ô9Z¸EX¸/¹>Y¸EX¸9/¹9>Y¸EX¸/¹>Y¹"ôº"9¸¹4ôº%49014>32#"&'732654.54>54&#"#X4J/(=*",5,+<%*F !4*--4-",*7?R.L6)5&5,) $5' 6(:/&!-#"1-/ %1KKþÿÿQÿôÑ&F†ÿÿQÿôÑ&F‡ÿÿQÿôÑ&FˆÿÿQÿô°&F‰ÿÿQÿôº&FÿÿQÿô×&FÿôUò.7D¥¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸&/¹&>Y¸EX¸,/¹,>Y»/+º ,9¸/¸ ¹ôº& 9¸&¹ôº)& 9¸¹4ô¸,¹;ô¸¹Bô017467.#"'>32>32!3267#"&'#"&%4.#"3267./||'/AP-0;E0%7%þþ<;/A&5H)O&\OIW6E8 8*/3$=P- L] 60+-.K×8+RI‘'($#5:ÿÿPÿ+ò&H™*ÿÿEÿôÑ&J†ÿÿEÿôÑ&J‡ÿÿEÿôÑ&JˆÿÿEÿôº&JÿÿZÑ&õ†0ÿÿZÏÑ&õ‡0ÿÿZàÑ&õˆ0ÿÿZòº&õ0<ÿôÚ9k¸EX¸4/¹4>Y¸EX¸/¹>Y»' +¸¹ôº*49¸*¹ôº49¸¸-и¸7и0Ð01%2>5<'.#"#".54>32.''7.'77,(;'#R'(=),<{BP!>X7/VB'"=S2/T=.–„<"&(J"‡85H,  /&,;"&=+*=«y<ÿéý 0}¸EX¸,/¹,>Y¸EX¸/¹>Yº,9¸¸Ð¸¹ôº ,9¸ ¸ и,¹ô¸ ¸и¸!и¸$и ¸.Ð01732>54&/.#"#"''7.54>327É):#9) 2#9) 5 'BW0S?3%5 'BW0S?3%\&1E*!:&2F*!8 Y7<_A"0;> X6=_B"0;ÿÿMÿôùÑ&Z†üÿÿMÿôùÑ&Z‡üÿÿMÿôùÑ&ZˆüÿÿMÿôùº&Züÿÿ1ÿ/'Ñ&^‡]ÿ3È%ƒ¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9º9¸¹ô¸¹ô¸¹"ô¸¹%ô01#3>32#"&'532>54&#"¯RR#V+1L3%=P+$L!#E 7(AI H&Í•ÉW"(#A[9>aD#"\›1H-Pc#&ÿÿ1ÿ/'º&^ÿÿ 8&&'ÿÿQÿô’&FŠÿÿ 82&&(ÿÿQÿôÅ&F‹ ÿ,W %e¸EX¸/¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y»# +»+¸¹ô01'.'##"&54>7#'##3327  Ž.(7 >î?UÝ^Ý#- d7m99m7dþ= ,+&  ÈÈýp= Qÿ2ò4B…¸EX¸/¹>Y¸EX¸0/¹0>Y¸EX¸/¹>Y¸EX¸#/¹#>Yº09¸/¸¹ ô¸#¹ô¸¸+и0¹:ô¸¹>ô0174>7.#"'>32327#"&54>7'##".732675Q'U†^1#0X" 17> da*,.&4)c4"<-P'*Q*Ni?~)=,,!%8  m[þÖ; ) +*%  > .#3' %#€'ÿÿBÿô*2&($/ÿÿPÿôÑ&H‡+ÿÿBÿô*2&(%/ÿÿPÿôÑ&Hˆ+ÿÿBÿô*5&()/ÿÿPÿôÉ&HŒ+ÿÿBÿô*2&(./ÿÿPÿôÑ&H‘+ÿÿU%2&).ÿÿ3ÿôTø&I÷8ÿÿ+”<ÿôCÈ ,¥¸EX¸ /¹ >Y¸EX¸)/¹)>Y¸EX¸/¹>Y¸EX¸/¹>Y»++º# 9¸#¹ô¸ ¹ô¸¹ ôº 9¸¹ ô¸¸и¸&и+¸'Ð01.#"327#'##".54>32'5#53533©!? 8)KDHAšHDS-0M8%=P+-D"®®RH`.A'S]I¾ýÅ@- ?Z;9[@""ZB0]]ÿÿs&*'ÿÿEÿô’&JŠÿÿs2&*(ÿÿEÿôÅ&J‹ÿÿs5&*)ÿÿEÿôÉ&JŒsÿ,!!^¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸ /¹ >Y»+»+¸¹ô¸ ¹ô01!!!!!#3267#"&5467!s•þ¿þñK!  .'8/þ¶FÎGîG%- ,+*AEÿ2ò5>^¸EX¸$/¹$>Y¸EX¸/¹>Y¸EX¸/¹>Y»>,+¸¹ô¸¹2ô¸$¹9ô01%3267#"&54>7#".54>32!3267'4&#""-   -&4  "6_G)*EY/4R9þ„1C(+G"LD7,-)$!) +*$  #A^<<_B#!,·IN'8$ÿÿs2&*.ÿÿEÿôÑ&J‘ÿÿ5ÿô2&,%ÿÿHÿ 6Ñ&Lˆÿÿ5ÿô2&,(ÿÿHÿ 6Å&L‹ÿÿ5ÿô5&,)ÿÿHÿ 6É&LŒÿÿ5ÿ(œ&,˜#ÿÿHÿ 6Æ&L9ÿÿO 2&-%ÿÿ  \&M%ÿf* Nƒ¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸¸ܹô¸ܸ¸и¹ô¸¸ и¸и¸Ð01!!7##!##5753!533µþî™ETþîTEETTEìooþ5þËì*uuuu ȇ¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y»+º9¸¹ ô¸¹ô¸¸и¸и¸Ð01>32#4&#"##57533#«'X9WQR4<%$&RIIRÔÔ)6caþæEC !þÁ;+]]0Iÿÿ_ù3&.&ÿÿZê°&õ‰0ÿÿ_ù&.'ÿÿZ×’&õŠ0ÿÿ_ù2&.(ÿÿZÞÅ&õ‹0_ÿ,ù`¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Y»+¸¹ô¸¹ô¸и¸Ð0173#5!#3#327#"&5467#_££𣣣 ".(7*·GFFýýG4- ,++<Zÿ2¹É&g¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ô¸¹ ô¸¸ܸ!Ü01#5!3267#"&54>7#"&54632<â4#  -&5   %%&&£Cþ6) +*# J"##"ÿÿ_ù5&.)ZŽæ/¸EX¸/¹>Y¸EX¸/¹>Y¸¹ô01!##Z4Râæþ£ÿÿQÿôí2&/%ÿÿ7ÿ'àÑ&\ˆ0ÿÿbÿ(C&0˜&ÿÿjÿ(>È&P˜#j>æ m¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Yº 9º 9¸ ¸и¹ ô01373#'#jR^ÂÜ\³sRæÿÿÁþÛðn‚ÿÿ†2&1$“ÿÿQÿôf&Q$é4ÿÿ†ÿ(&1˜.ÿÿQÿ(È&Q˜Gÿÿ†×&18|ßÿÿQÿôø&Q8ˆÿÿ†&1Œ‚þÓÿÿ+ÿô÷È&QÚŒ‰þÒ5 I¸EX¸/¹ >Y¸EX¸/¹>Y¹ôº9¸¸и¸ и Ð01%!55737þiQQRââGGã+C,iþ¿xDxÄQÿôÈW¸EX¸ /¹ >Y¸EX¸/¹>Yº 9¸¸ и ¹ ô¸¸ии¹ô01%#"&=575#537327!:(NQ||¦ø££3,(4 XW©GFGóCþõ[G[Ù6/ÿÿS2&3$ÿÿ] Ñ&S‡ÿÿSÿ(&3˜ÿÿ]ÿ( ò&S˜ ÿÿS2&3.ÿÿ] Ñ&S‘ÿêV»&m¸EX¸/¹>Y¸EX¸$/¹$>Y¸EX¸#/¹#>Y¸EX¸/¹>Yº +¸¹ôº&#9¸&¹!ô01>7#"&54632%>32#4&#"#305  '* (2LC #P5OLS.4'='RDŽO5' #+@9Kv#<%3`^þÌ)B@)(þ¦æLÿÿ0ÿô(&4'ÿÿ<ÿô’&TŠÿÿ0ÿô(2&4(ÿÿ<ÿôÅ&T‹ÿÿ0ÿô(2&4-ÿÿ<ÿôÊ&T!O‘O¸EX¸/¹ >Y¸EX¸ /¹ >Y» +¸¹ô¸ ¹ ô¸и¸Ð01463!#3#3!".7;#"!}””ÌþÙ>aD$VTVVTKž¨FÏGîG-U{N~‰ƒ ÿôTò;D¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸2/¹2>Y¸EX¸7/¹7>Y»<'+¸7¹ô¸¹ôº29¸2¹+ôº529¸¹Aô01732>54.#"4>32>32#3267#"&'#".%4.#"X &%%& M0@%/EA0"5#ñ:6';#0H+]&@/-0ó*E11E**D22D*=_A"=87>$Y¸EX¸ /¹ >Y¸EX¸'/¹'>Y»+¸'¹ô¸'¸Ð01332>533267#"&54>7.5OT$23%QE750  -(8  nkþf3G++G3šþhft=- ,+! „}Mÿ2æ%ˆ¸EX¸/¹>Y¸EX¸$/¹$>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸ ¹ô¸¸к9¸¹ ô¸¹#ô01!327#"&5467'##"&5332673ù-).&40 %W9XQS3=*D)R6 ) +*)=Q+6ca.þÝEC+/Qÿÿ N2&<%ÿÿPÑ&\ˆÿÿ&22&>%ÿÿ1ÿ/'Ñ&^ˆÿÿ&2-&>*ÿÿA2&?$ÿÿGÑ&_‡ÿÿA5&?)ÿÿGÉ&_ŒÿÿA2&?.ÿÿGÑ&_‘ÿôÈ +¥¸EX¸/¹>Y¸EX¸%/¹%>Y¸EX¸ /¹ >Y¸EX¸/¹>Y»!$+º9¸¹ô¸¹ô¸¹ ôº 9¸ ¹ ô¸$¸#и$¸(и!¸)Ð01732654&#"'>32#"&'###57533#¯#G@T@I I&#W+1L3$=P+#Q#BIIRÔÔraVL]#&D"("=X6<^A"#6;+]]0I>ÿô&œ#C¸EX¸ /¹ >Y¸EX¸/¹>Y»+¸¹ô¸ ¹ ô013267>32#".5467!.#"“RBDWþ²V:9Z?"$A\77Y>"“TP)Ajvxi7 )/W}OO~Y00Y|L v„!1ÿŸœ(B¸EX¸%/¹% >Y»+»  +¸%¹ô¸ ¸и ¸!Ð01.#"3##"&'7326?#5737>32#Ž•(>,*4- hF) PT0J$/??í.K6 >OMÛ;8dh 0ÿô24G¸EX¸,/¹, >Y¸EX¸"/¹">Y¹ô¸,¹ ôº",9¸¸.Ð01%2>54.#"#".54>32>54&',%>++>%&=++= 9-,0%C]78\C%%C\8?5'(=&Gc>=bD%%Db=>cG&È -2 +‰[OY00YOO}W.  <ÿô,e4G¸EX¸,/¹,>Y¸EX¸"/¹">Y¹ô¸,¹ ôº",9¸¸.Ð01%2>54.#"#".54>32>54&',#9))9##9))9 8,&.'BW00WB''BW061,)81D**E22E**D1- ,2 !eC<_A""A_<=_B"  Oÿôv(J¸EX¸/¹ >Y¸EX¸!/¹! >Y¸EX¸/¹>Y¸!¹ô¸¹ô01#".5332>53>54&'_ '!:Q00R;!T%22&#(0 $þŒGb??bG˜þf3G++G3š#  MÿôVo#a¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Yº 9¹ô¸ ¹ô01#'##"&5332673>54&'? "C%W9XQS3=*D)#0o #þ:U+6ca.þÝEC+/Q"  ÿÿ 82&&.ÿÿQÿôÑ&F‘ÿÿ_ù2&..ÿÿZàÑ&õ‘0ÿÿ0ÿô(2&4.ÿÿ<ÿôÑ&T‘ÿÿOÿô 2&:.ÿÿMÿôùÑ&Z‘üÿÿOÿô ‹&:1ÿÿMÿôù&Z0üÿÿOÿô ¶&:3ÿÿMÿôù7&Z2üÿÿOÿô ¶&:5ÿÿMÿôù7&Z4üÿÿOÿô ¶&:7ÿÿMÿôù7&Z6üÿÿ5ÿô2&,.ÿÿHÿ 6Ñ&L‘0ÿ,(œ%9K¸EX¸/¹ >Y¸EX¸ /¹ >Y»#+¸ ¸и ¹&ô¸¹0ô01#"&5467.54>32327'2>54.#"š-(8$A]=%C\87]C%`O W%>++>%&=++=¸ ,+!;:_zAO}W./W}N…¤"  È&Gc>=bD%%Db=>cG&<ÿ2ò(<\¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸&/¹&>Y¸¸и&¹ô¸¹.ô¸¹8ô014>7.54>323267#"&32>54.#"ê /T>$'BW00WB'-@(#$  .&4Y)9##9))9##9)y!$A\:=_B""B_=3M;+8) +•*D11D**E22EÿÿCÿ(œ&8˜ ÿÿHÿ(ò&X˜ ÿÿ*ÿ(.&9˜ÿÿEÿ("n&Y˜O7ÿ'Žæ5¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸¹ô01!#"&'732>5#Z4/N;&H9$0 âæþ-K7= "1µ) -c¸EX¸%/¹% >Y¸EX¸/¹>Y¹ô¸ иии и%¹ôº 9¸ ¸!и¸#и"Ð01%2654&+3#32654&++5#5732&U^\WkZTJNL^ÖHP$A[7ÊMM»2S;!8;?CFAY¸EX¸/¹>Y¸EX¸/¹>Yº9¸/¸¹ ôº9¸¹'ô¸¹*ô¸¹+ô013267#"&533>324.#">(U…^0#0Y" 17> dbD(d3"=-P'*Q*Nh?h)=,-!%8  m[*B .$3' %#€'<ÿôûò $¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¹ôº 9¸¹ôº 9¸¹ô¸¹ ô0173275.#"4>32373#'##".‘KDHA!? 7)U$>P,+H"BDT./M8óXbIò1D+;_B$" 6þ@-"A^]ÿôò%ƒ¸EX¸/¹>Y¸EX¸!/¹!>Y¸EX¸/¹>Y¸EX¸/¹>Y¸!¹ôº!9¸¹ôº!9¸¹ô¸¹ ô01%4&#"32>7#"&'##33>32ÇAI$J #E 6(U$=P+$P#BDZ11K3÷Sc%$ò 2F0>bD##6æ@-#A[=ÿôò!5¸EX¸/¹>Y¸EX¸/¹>Y¹ ô¸¹ô01%#"&'732>54.#"'>32)Fa79d'%M-)E1.B',F*_B5]F)ò<_A"($5#1D**E25*"B_?ÿôò'C¸EX¸/¹>Y¸EX¸/¹>Y»+¸¹ ô¸¹"ô01%#"&'732>7!.54>32.#")G_6;\$"G+(B0þ„">U31V@%T PB7*ò<^A#"6+?'  3TY¸EX¸/¹>Y»#+¸¹ô¸¹ ô017467!.#"'>32#".73267E{UT*J!$^99\A$$AY53S; LQBHRÞ JY8"B^<<_B# =W+ONRK<ÿ'ûò!/¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸¹ôº9º9¸ ¹"ô¸¹$ô¸¹%ô¸¹(ô01326?#".54>32373#"&'7275.#"}&P%FKT-/M8$=Q,+J Bvm0a(ÁFC!@ 7*MjF;b*"?[99\@##6þ\jîIâ0A'R`Mÿ3ûæa¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº 9¹ô¸¹ô01#57#"&5332>73ûR'X9WQR4<%$&RÍ®r(7ca.þÝEC !S.ÿôæ ¥¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Yº9º 9¸ ¹ô¸¹ô¸¹ô¸¹ô01!#'##"'#"&533267332673@1*J4)38O5&C6%N@"*T&.MI\þ«V%&`þ«V%&`?ÿôÆæT¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¹ ô¸¹ô01!#'##"&'732673ÆD&oD.#7e,Rs;D G ?L6ÿxòA¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸Ð¸¸и¹ ô01%3##5#5354&#"'>32ЉŒ Dïï5F#8!P(5H+CCˆˆCçAD = 5J-3%æ @¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Yº 901!#.'##3%Sp  pOÉ\%H##H%þìæPæ!i¸ /¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº 9º9º901!#.'##'.'##33>?3PT; ;F< ;Njc:  8b#B""C"üü#B""B#þçæö#E% D*õ1'ÔR¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ôº9¸¸Ð01.#"#.'##>32  0=ãSw  jNâ $0>'!‰<.?þ J##I!þó*$>.Aÿ3îæ c¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº9º 9¸ ¸Ð01###73753îRþú^ÂÜ\³sRÍÌÿÁ%ðn‚¯ÌØ:¸/¸ /¸EX¸/¹>Y»+º9¸¹ô013>32#54&#"#¯@8$94@$'@Ø}E!A>ËÃ*'ãŒw€à4¸EX¸/¹ >Y» +¸¹ô¸¸ܸÜ013#"&'73265#7"&54632¤Ì 6(-" / ¬Fþ¼2& .+(róðN6¸/¸EX¸/¹ >Y» +º9¸¹ô0133>32.#"#ó6B'# =@FK', 6(2·RÜF!Z¸/¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹ >Y¸¹ô¸и ¹ô¸¸ Ð0133>?33>?3#'.'##R@% $3%$Y¸EX¸ /¹ >Y»+º9¸¸Ð017326?33>?3#"&'7¨% Š@E <>‚<5  ´#@ª//ªþ©3@1ÿÿÑ^|»èÿÿÙ^„»éõö‚× » +»+0126548'õ3,,3EHHE*#"*$>45>Ûöh× »+» +01"3&546h3++3EHHE³*"#*$>54>ÿÿ¨=°Ñˆÿÿ¨=°Ñ‘ù_̸EX¸/¹>Y¸Ü013#ùf8ÌþÁÿÿ±Y§’Šÿÿë=ŸÑ‡ÿÿ¹=mцùÿ_B º+01#3_f8þ@ÿÿª?®Å‹ÿÿêJnÉŒÿÿω×ÿÿêÿ2“šÿÿžCº°‰ÿÿÊ;ìÊÄüÊØ(¸EX¸/¹>Y» +¸¹ô0133267#"&5#Ä‘  "62QØþ” 2<86¢üÃN-+»*+»+º*9º90132654.'.54632.#"#"&'À7%(& +"G@$?-&!,#IF+LS  (5(   )9ŸËFK¸/¸/¸EX¸/¹ >Y¸EX¸ /¹ >Yº 9¸¸Ð¸¸ иÐ01'33>?3#'.'##fD+&DhoE/  +C¬šBB¡¡FF¹=mÑ º+013#¹ZZ?Ñ”ë=ŸÑ º+01#73*?ZZ=”¨=°Ñ»+¸¸ܸÐ013#'## Fa?CC?Ñ”ccžCº°'»+¸¸ܹô¸ и¸Ð01>323273#".#"#ž+(  /+(  /C0=6/>7±Y§’ »+013#±öö’9ª?®Å» +¸ ¸ܸÐ01".'332>73,!/ 3&% 3 /?'/ 3/'êJnÉ »+01"&54632,&&&&J"##"–Lº »+¸¸ и¸Ð01"&546323"&54632Ì©L     ì)×»+¸¸ܹô01>54'7 S BH(O'0)& Ï‰× º +¸ ¸ܸÜ01"&54632'2654&#",)44))44)3**22**3%Ê;ìÊ»+¸¸и¸Ð013#73#IX7ÚHX7ʨ=°Ñ»+¸¸ܸÐ013373#¨?CC?aFÑcc”l;ŽÊ»+¸¸и¸Ð01#'3#'3Ž7XIM7XH;÷!ZÜ» +¸¸Ü01632#"5467Z3&+¼#E#?ý _Ú» +¸¸Ü0167#"&54632ý62&+@.E#>#³»e»+¸¸Ü01>54&'7#**; '4Ü   (êÿnÿ »+01"&54632,&&&&â"##"¥ÿ7³ÿ™ »+¸¸ и¸Ð01"&546323"&54632Ö—É×ÿ(pÿл+¸¸ ܹô01'>54&'73.(5(10#"  (&×ÿ+pº+¸¸и ܹ ô01%3'>54&'5#(5(1!5   )êÿ2“¸EX¸/¹>Y» +014673327#"&ê.: $.&4y)@7 ) +®ÿ2ªÿ± » +01".'332673, /3$##$3/Î%-//-%°ÿU§ÿ »+01#53§÷÷«:§üºN#9¸/»+» +»!+º 9¸¹ ô01467.#"'>32#'##"&732675§fm'4H(?=49 +Y»+»+º9º9¸¹ô¸¹ ô013>32#"&'##732654&#"¬@5BD(522@((4'.),Ø}8YK)A- K ?93=(‰ü¶Ø T¸ /¸EX¸ /¹ >Y»+»+º9º9¸¹ô¸¹ô014>32'53#'##"&73275.#"‰(5+@55?LB.*+(&'5¥'?,6yþ,%XQ7=(¡> üÉN !»+»+» +014>32#3267#".74&#" +9DHç=/*;##<-ñ+(#7¥'?,SE 17 )+?B-00-•râNAMd¸EX¸%/¹% >Y»?+»"H+»7 +»B0+º 79¸¸ и0¸2ии%¹(ô01732654&+"&'475.54675.54>323##"';2#"&72654&#"Ë326?%#; 65 #/ †R ".?G@>0D*FO”((((Ò% (  )+0 ) "$))-õ& (( &½õØ S¸/¸ /¸EX¸/¹>Y¸EX¸/¹ >Yº 9¸ ¹ôº 9¸ ¸Ð013373#'#½@ŸG|ŒGkF@ØþΠ€Â˜CU‚ÔNT¸/¸/¸/¸EX¸/¹ >Y» +»+º9¸¸и¹ô¸Ð0133>32>32#54#"#54#"#‚2 !1 $"%=5  =F*740æá3.æá3æŽüÊN»+»+014>32#".732654&#"Ž+9 9,,9 9+B1++11++1¥(?++?((?++?(5@@54AA¬ØNT¸/¸EX¸/¹ >Y» +»+º 9º 9¸¹ô¸¹ô017#33>32#"'732654&#"ì@46AD(5/.((4'.'.êkÇ$YK)A-%. ?93=(¤üÏ I¸EX¸/¹ >Y¸EX¸/¹ >Y» +¸¹ô¸¸и¸ Ð01#5?33#3267#"&5ñMP5‘‘"- 0H80ZZ3**. J=Ÿü»FA¸EX¸ /¹ >Y¸EX¸/¹ >Y»+º9¸¸Ð01#'##"&=3326753»49%74@#)@3!A>ËÃ*&à“ÍF 3¸ /¸EX¸ /¹ >Y¸EX¸/¹ >Y¸ ¹ô0133>?3#“B= >=vJF°00°þ¾µüÍN»+» +014>32.#"3267#".µ/="#2 !0<:0(; #=,¥(?+) A45@ )+?Æ÷à8¸ /¸EX¸/¹ >Y»+¸¹ ô¸ и¸Ð01.#"3###5754632é"*$€€?OOAE- *#3þñ0:G’ÈF 4¸ /¸EX¸/¹ >Y¸ ¹ô¸Ð¸¹ô¸Ð017#5!3!’Õ½ÔÛþÊ&í3"í3ÿÿUÿ%&)–÷ÿÿ<ÿûÈ&I–ÿÿUÿU%&)œ÷ÿÿ<ÿUûÈ&Iœÿÿ5ÿô&,'ÿÿHÿ 6’&LŠÿÿOÿ &-–ÿÿ]ÿ È&M–ÿÿOÿ2 &-›ÿÿ]ÿ2 È&M› ÿÿ†ÿ&1–.ÿÿQÿÈ&Q–GÿÿCÿ&1&'“–.ÿÿQÿp&Q'ŠÿñÞ–Gÿÿ†ÿU&1œ.ÿÿQÿUÈ&QœGÿÿQÿ&2–ÿÿ<ÿ,ò&R–ÿÿS5&3)ÿÿ] É&SŒÿÿSÿ&3–ÿÿ]ÿ ò&S– ÿÿSÿU&3œÿÿ]ÿU ò&Sœ ÿÿdÿ)&7–ÿÿ‡ÿò&W–ÿÿdÿ)&7&'–ÿÿ‡ÿ’&W&Š*–ÿÿdÿU)&7œÿÿMÿUò&WœÿÿCÿô5&8) ÿÿHÿôÉ&XŒÿÿCÿœ&8– ÿÿHÿò&X– ÿÿ*ÿ.&9–ÿÿEÿ"n&Y–Oÿÿ*ÿU.&9œÿÿEÿU"n&YœOÿÿ N2&<#ÿÿPÑ&\†ÿÿ N2&<$ÿÿPÑ&\‡ÿÿ N-&<*ÿÿPº&\ÿÿ&25&>)ÿÿ1ÿ/'É&^ŒÿÿAÿ‘&?– ÿÿGÿæ&_–ÿÿEÿô"@&Yÿ܆Oÿô3œ*h¸EX¸/¹ >Y¸EX¸)/¹)>Y¸EX¸/¹>Y»! +º ! 9¸¹ô¸¹%ôº%9014>32#"&'732654./7.#"#O9X;NbuXR-?(4S03,0&?1v9,DLU°2W?$TF˜bH'C1(!5?2*$ 6™"0Y^þ`ÿÿ&22&>#ÿÿ1ÿ/'Ñ&^†ÿÿ&ÿ2&>–ÿÿ1ÿ"'æ&^–žÿÿ&2h&>+ÿÿ1ÿ/'×&^Ž ÿÿ&23&>&ÿÿ1ÿ/'°&^‰PØ  »+01!!P¸þH HØD  »+01!!0ýÐ HÑ^|» º +01632#"&5467|/5 '* '3LD‹O4( #*?9Kw#Ù^„» º +01>7#"&54632Ù05  '* (2LCŽO5' #+@9Kv#ÿÿÙÿ„|éýÁÿÿ_^ï»&èŽèsÿÿg^÷»&éŽésÿÿgÿ÷|'éÿŽýÁésýÁ—sÁ™ º +01%".54>32,6))66))6s'6!!6''6!!6'Å4…Ä º+01757Å™''ÔP #¥¦"Ó4“Ä º+01%'7'R'™™'ü¥# P " <p º+01?'7·!§ï·!§M¼Ë#¼Ëÿÿ¡¬·õ¸¯‹à*¸/¸EX¸/¹ >Y¹ô¸¸ܸ Ü013##7"&54632¯Í@¬Fþ¾rÿÿ¬¸¹é¸ÿÿ¬¬©é¸ÿÿ°¬­õ¸ÿÿ·¸ªé¸ÿÿ¯¬©õ¸ÿÿ­¬©õ ¸ÿÿìlƒC!¸ÿÿÕllC"¸¯ÌN:¸/¸ /¸EX¸/¹ >Y»+º9¸¹ô0133>32#54&#"#¯57%85@$'@F0!A>ËÃ*'ãÿÿ¡ÿK·”ÿWÿÿÐÿWaˆÿWÿÿ®ÿW§”ÿWÿÿ¬ÿK¤”ÿWÿÿ¬ÿW¹ˆÿWÿÿ¬ÿK©ˆÿWÿÿ°ÿK­”ÿWÿÿ·ÿWªˆÿWÿÿ¯ÿK©”ÿWÿÿ­ÿK©” ÿWÿÿìÿ ƒâ!ÿWÿÿÕÿ lâ"ÿWšüÂN!»+»+»+014673.#"'>32#"&73267šå3/*;#GV)7 CM9-)'/˜ /5 )XQ'?+T>001/`ÿ’"ì 7±¸EX¸&/¹& >Y¸EX¸/¹>Y¸йô¸¹ô¸&¹ ô¸и ¸ и¸и¸и¸ܸи¸и&¸#и&¸%ܸ¹)ô¸%¸*и)¸,и¸3и¸4и4/01%.#*#7.'#7.546?33273.'>7!>  '>A$#&#T6 &  &DNvg &  &-1  <#7G  …dLs!(0ccm|#œqŠ«gbgr / þ#NŠ4‡¸EX¸/¹ >Y¸EX¸/¹>Y»+¸¹ô¸и¹ ô¹ô¸ ¸ и¸и¹ ô¸¸'и¸(и ¸-и¸.Ð01%!5>=#573.'#57.54>32.#"3#3#þ;?=|W aP7M0>U0;*BEξ ­§%&GG2_9,-%*D0+ /A4#115F/'~ /˸EX¸'/¹' >Y¸EX¸+/¹+ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸'¸ܹ ô¸и ¸ии¹ô¸и¹ô¸'¹ô¸¸и¸и¸и¸и¸"и ¸#и¸&и¸)и-Ð015#3#3/5#'3'#3##'##5#575#57533533žZD7>•H;C5HHTapAJJJJTfkAH=.+¦Ñ;&f²Ü<+ðððð&<%ýýýý*+*~ !c¸EX¸/¹ >Y¸EX¸/¹>Y»+»+¸¹ ô¸¸ и¸и¸и¸ Ð01267#3.##+##575323ENÖÖNE*@%Y»$+¸¹ô¹ô¹ô¸$¸ܸ)и¹/ô¸+и/¸.Ü017!!.#"3267#'##"&54>32'5#5353yzþ†-)944•N:>'L[1?!'5››G11y +>Ex,þS+a\)B/S"1CC:ÿô6Š1m¸EX¸/¹ >Y¸EX¸/¹>Yº("+¸(¹+ô¸и(¸ и"¸и"¹ô¸и¹ô¸¹.ô01%#"&'#57&45<7#57>32.#"!!!#32676&\>b‡@;;@j3X1;&JZ&þÕýXE+AQ,1v+  ,v…-!/!bW1  0U`$#+*~ 4¸EX¸//¹/ >Y¸EX¸%/¹%>Y» #+¸/¹ô¸йô¸Ü¹ ô¸¸и¸и ¸и ¸'и¸*и¸+и¸.и¸3Ð01>54&'#27#3.##3#+##575#575323›Ö;p!ÌÉG6*A@H )8F'EOKKKK”NsJ¢  ;tKK?"h  )"1!õy$;%x7ATÿ’ì*u¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y»*'+¸¸ܸ¸ܸ¹ô¸¹#ô01%#5.54>753.#"32675#53CX<2T dd2TtHFsU3ec,.!%Db<>bD%©E`ÿ’"é%{¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸ /¹ >Y¸¹ô¸ ¹ô¸ ¸ ܸ¸ܸ¸!и¸"Ð017#5.54>753.'>7JHMMHØ!O354V>"!=W55/O12 "6EŠlpŒ &0dc1TvIGtT4a`+/ýñ#l ~p¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ô¸иии¹ô¸и¸й ô¸ и /01#3##'#53267!573.+5! !+IG]IÐ^ÇYOS`þùL¸ ]LO M5#1LXþýüC:<,.&D8ÿô~$]¸EX¸/¹ >Y¸EX¸/¹>Y¹ôº 9¸ ¸#ии ¸иии¸иÐ017>54&'7'575575377ô%NA)E:f‰OhhhhT¶¶¶¶B)=' ?^>050H041ÇžV5VHV4WìU+i »+01!!U®þRi>ÿÿ <pñ¡ÿô·= (¸EX¸/¹>Y»+¸¹ ô01"&54632'2654&#",=NN==NN= .. .. XONTTNOX29<<55<<9Ða1¸EX¸/¹>Y»+01%#5>73#"R!)2?æ* þÏ­¦=,¸EX¸/¹>Y» +¸¹ô¸Ð017>54&#"'>323#·NS&';$6>!,ˆï$;N! !#52'')5¬ÿô¤=$<¸EX¸!/¹!>Y»+» +¸!¹ôº 901732654#52654&#"'>32#"&'Ì1#`*'(=#/;8 'F3#CQ3&&,(7&-0¬¹18¸EX¸ /¹ >Y»+» +¸¸Ð¸¸ Ð01%57###5#5733N-6Í29¢”G2t8RAI+IIɽ¬ÿô©1D¸EX¸/¹>Y»+» +¸¹ôº 9¸¸ Ð01732654&#"'73#632#"&'Ì-%$½‹  2>F6*<Q ›6F 300:°ÿô­= &F¸EX¸/¹>Y»#+»+¸¹ôº#9¸¹ ô01%2654&#"7.#">32#"&546328#,o+4-34+;NUE,&&"Î ;95*&QGV[·ª1&¸EX¸/¹>Y»+¸¸ Ð013>7#53#ý $©ó ( A'A;:5$#??D(¯ÿô©= 8<¸EX¸4/¹4>Y»%+¸4¹ôº49º%901732654.'7>54&#"4675.54>32#".é')/W,r'*2A!%"-.!P < n %  /%!  %!  ­ÿô©= $F¸EX¸!/¹!>Y» +»+º!9¸¹ô¸!¹ô0173267.#"3267#"&54632#"&'ç#,*2-33@3;NUE-Õ&$± 995*,54&'7l72.-((-.27ŸKo1 -c;;c-!2o©Â_2 º+013#©^XE2pù¯2 º+01#73>EX^Âp¤Â´2»+¸¸ܸÐ0173#'#¤`P`CCCÂppDD›Ç½3+»+¸¸ܸ¸и¹ ô¸Ð01".#"#>3232673h .,$ .,Ç/=.>°Ù¨ »+013#°øø9±Ä§2» +¸ ¸ܸ Ð01".'332673,,2""""2,Ä(''(óÊe5 »+01"&54632,!!!!ʥ˳- »+¸¸ и¸Ð01"&546323"&54632Ö—Ëìºhº+¸¸ܹô01>54'7 S BH(à'0)& Ï»‰k º +¸ ¸ܸÜ01"&54632'2654&#",)44)(55(»/)(00()/$¿Âì2»+¸¸и¸Ð017373#¿BMS¤MT;Âpppp¤Â´2»+¸¸ܸÐ01#'337´`P`CCC2ppDDÒÿ+kº+¸¸и ܹ ô01%3'>54&'6#(5(1!5   )¨L° +»+¸¸ и¸и¸ܹô01"&546323"&54632'3#Ö˜æ÷÷LÑ/¨Ë°‹+» +¸ ¸ܹô¸¸и ¸Ð013#"&546323"&54632°øø&˜‹.’¨L°7%º+» +¸¸и ¸Ð013#"&546323"&54632IOg:!˜7g„¨Ë°¶)» +¸ ¸ܸܸ¸и ¸Ð013#"&546323"&54632R[pD#˜¶o|¨L°7+»+¸¸ܹô¸¸и¸Ð013373#"&546323"&54632®=??=[F3˜7@@g„¤Ë´¶ +»+¸¸ и¸и¸ܹô01"&546323"&546327#'337Ö˜`P`CCCËëooDD¨L°7)» +¸ ¸ܸܸ¸и ¸Ð01#'3"&54632#"&54632a:gOsÀÐgë¨Ë°¶)» +¸ ¸Ü¸ܸ¸и ¸Ð01#'3"&54632#"&54632_Dp[|ÀGoë$Aø º+01'3 ;$Ô9›çÆ»+¸¸ ܹô01.54>7H3.(4'1$!  )&>.e…&Û"A¶$`ü“ È   Û'Û$: , E u L  ‚2 *ø À: 4] ® 2Õ #¶# H9·Typographic alternatesTypographic alternatesSource Code ProSource Code ProRegularRegular1.017;ADBE;SourceCodePro-Regular;ADOBE1.017;ADBE;SourceCodePro-Regular;ADOBESource Code ProSource Code ProVersion 1.017;PS Version 1.000;hotconv 1.0.70;makeotf.lib2.5.5900Version 1.017;PS Version 1.000;hotconv 1.0.70;makeotf.lib2.5.5900SourceCodePro-RegularSourceCodePro-RegularSource is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Adobe Systems IncorporatedAdobe Systems IncorporatedPaul D. HuntPaul D. Hunthttp://www.adobe.com/typehttp://www.adobe.com/typeCopyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. http://www.adobe.com/type/legal.htmlhttp://www.adobe.com/type/legal.htmlÿµ2:  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a£„…½–膎‹©¤ŠÚƒ“ˆÃÞ žªõôö¢­ÉÇ®bcdËeÈÊÏÌÍÎéfÓÐѯgð‘ÖÔÕhëí‰jikmln oqprsutvwêxzy{}|¸¡~€ìîº    ýþÿ !"øù#$%&'()*+,-./012ú×3456789:;<=>?âã@ABCDEFGHIJKL°±MNOPQRSTUVWXäåYZ[\]^_`abcdefghijkl»mnopæçqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©Øáª«¬­®ÛÜÝàÙ߯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ     ²³¶·Ä´µÅ‡¾¿¼ !"#$%&'()*+,-./01234ï56789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXNULLCRuni00A0uni00ADtwo.sups three.supsuni00B5one.supsAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccentuni0122uni0123 Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronuni012Cuni012DIogonekiogonek Jcircumflex jcircumflexuni0136uni0137 kgreenlandicLacutelacuteuni013Buni013CLcaronlcaronLdotldotNacutenacuteuni0145uni0146Ncaronncaron napostropheOmacronomacronuni014Euni014F Ohungarumlaut ohungarumlautRacuteracuteuni0156uni0157RcaronrcaronSacutesacute Scircumflex scircumflexuni015Euni015Funi0162uni0163TcarontcaronUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentuni0180uni018Funi0192OhornohornUhornuhornuni01CDuni01CEuni01CFuni01D0uni01D1uni01D2uni01D3uni01D4uni01D5uni01D6uni01D7uni01D8uni01D9uni01DAuni01DBuni01DCGcarongcaronuni01EAuni01EBuni0218uni0219uni021Auni021Buni0237uni0243uni0250uni0251uni0252uni0254uni0258uni0259uni0261uni0265uni026Funi0279uni0287uni028Cuni028Duni028Euni029Eh.supsj.supsr.supsw.supsy.supsuni02BBuni02BCuni02BEuni02BFuni02C8uni02C9uni02CAuni02CBuni02CCl.supss.supsx.supsuni0300uni0301uni0302uni0303uni0304uni0306uni0307uni0308uni0309uni030Auni030Buni030Cuni030Funi0312uni0313uni031Buni0323uni0324uni0326uni0327uni0328uni032Euni0331a.supsb.supsd.supse.supsg.supsk.supsm.supso.supsp.supst.supsu.supsv.supsc.supsf.supsz.supsuni1E0Cuni1E0Duni1E0Euni1E0Funi1E20uni1E21uni1E24uni1E25uni1E2Auni1E2Buni1E36uni1E37uni1E38uni1E39uni1E3Auni1E3Buni1E42uni1E43uni1E44uni1E45uni1E46uni1E47uni1E48uni1E49uni1E5Auni1E5Buni1E5Cuni1E5Duni1E5Euni1E5Funi1E60uni1E61uni1E62uni1E63uni1E6Cuni1E6Duni1E6Euni1E6FWgravewgraveWacutewacute Wdieresis wdieresisuni1E8Euni1E8Funi1E92uni1E93uni1E97uni1E9EYgraveygraveuni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9 zero.supsi.sups four.sups five.supssix.sups seven.sups eight.sups nine.supsparenleft.supsparenright.supsn.sups zero.subsone.substwo.subs three.subs four.subs five.subssix.subs seven.subs eight.subs nine.subsparenleft.subsparenright.subs uni0259.sups colonmonetarylirauni20A6pesetadongEurouni20B1uni20B2uni20B5uni20B9uni20BAuni2215 zero.dnomone.dnomtwo.dnom three.dnom four.dnom five.dnomsix.dnom seven.dnom eight.dnom nine.dnomparenleft.dnomparenright.dnom uni0300.cap uni0301.cap uni0302.cap uni0303.cap uni0304.cap uni0306.cap uni0307.cap uni0308.cap uni0309.cap uni030A.cap uni030B.cap uni030C.cap uni0327.cap uni03080304uni03080304.cap uni03080301uni03080301.cap uni0308030Cuni0308030C.cap uni03080300uni03080300.cap uni030C.a uni0326.aÿÿÌm±UÍ Íâmcollective-2.12.1/doc/fonts/Lato-Regular.ttf0000644005276200011600000027367013265671731020740 0ustar jenkinsjenkins DSIGw°GPOS¶Ñ,K´GSUBV.TLàOS/2Ù®ªiMð`cmapRÔŸ×NPæcvt '‰ém`8fpgm‹ zAm˜ ‘gaspmXglyf*~Ê™S8‘Bheadÿ‘eä|6hhea¶±ä´$hmtx[Ø`½äØTkernlBjTé,läloca•.ÏV,maxp< §X< name¾ $íX\^post:ö]i¼šprepœx9½w, 0JDFLTlatnÿÿÿÿkernkernJnvö$RÜ À^ h B l  ä & ì¸rjØÈî´Z¦€Æj´úL’Ø*|²DV¨:Äò !.!Ô##L#–$4$~%¬&J'$'þ(Ø)²*Œ+f+,6,Ü-‚.(.Î/t/¶/ø0:0|2"2È33T3š3à4&4l4¾55b5´66L6ž6ð7B7”7æ888Š9d9ª9Ô:&:¨:î;@<æ=¨>j?,?Ê@hA–BÄCzD¨EÖFŒG*GÈHfI”K ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ"#ÿØ&ÿØ*ÿØ2ÿØ4ÿØFÿàGÿàHÿàRÿàTÿà‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ©ÿàªÿà«ÿà¬ÿà­ÿà²ÿà´ÿàµÿà¶ÿà·ÿà¸ÿàºÿàÄÿØÅÿàÇÿàÍÿØÎÿàK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ-ÿ ÿ ÿÿx#ÿÈ&ÿÈ*ÿÈ2ÿÈ4ÿÈ7ÿL9ÿL:ÿ†<ÿh?ÿLYÿ|ZÿÂ\ÿ|lÿmÿxoÿxrÿyÿx|ÿ}ÿx‰ÿÈ”ÿÈ•ÿÈ–ÿÈ—ÿȘÿÈšÿÈŸÿhÄÿÈÍÿÈÓÿhåÿxæÿxçÿèÿêÿëÿïÿxòÿxóÿxöÿ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ-ÿ ÿ ÿÿx#ÿÈ&ÿÈ*ÿÈ2ÿÈ4ÿÈ7ÿL9ÿL:ÿ†<ÿh?ÿLYÿ|ZÿÂ\ÿ|lÿmÿxoÿxrÿyÿx|ÿ}ÿx‰ÿÈ”ÿÈ•ÿÈ–ÿÈ—ÿȘÿÈšÿÈŸÿhÄÿÈÍÿÈÓÿhåÿxæÿxçÿèÿêÿëÿïÿxòÿxóÿxöÿk0 ÿx 0 0ÿ@ÿÿ@ÿxÿ¨ÿ¨"0#ÿÌ$ÿx&ÿÌ*ÿÌ-ÿh2ÿÌ4ÿÌDÿŒFÿŒGÿŒHÿŒIÿâJÿxPÿ¨Qÿ¨RÿŒSÿ¨TÿŒUÿ¨Vÿ–WÿÖXÿ¨YÿÐ[ÿÌ\ÿÐ]ÿ®l0mÿoÿr0t:u:wÿ¨yÿ{:|0}ÿ‚ÿxƒÿx„ÿx…ÿx†ÿx‡ÿxˆÿx‰ÿÌ”ÿÌ•ÿÌ–ÿÌ—ÿ̘ÿÌšÿÌ¢ÿŒ£ÿŒ¤ÿŒ¥ÿŒ¦ÿŒ§ÿŒ¨ÿŒ©ÿŒªÿŒ«ÿŒ¬ÿŒ­ÿŒ²ÿŒ³ÿ¨´ÿŒµÿŒ¶ÿŒ·ÿŒ¸ÿŒºÿŒ»ÿ¨¼ÿ¨½ÿ¨¾ÿ¨ÂÿxÃÿŒÄÿÌÅÿŒÇÿŒÌÿ¨ÍÿÌÎÿŒåÿæÿç0è0éÿ@ê0ë0ìÿ@ïÿòÿóÿö0ùÿx)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJ ÿjmÿjoÿjyÿj}ÿjåÿjæÿjïÿjòÿjóÿj)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ4 ÿ|ÿLÿLÿ|ÿÄÿÄ"$ÿ|-ÿ:FÿºGÿºHÿºPÿÄQÿÄRÿºSÿÄTÿºUÿÄXÿÄwÿÄ‚ÿ|ƒÿ|„ÿ|…ÿ|†ÿ|‡ÿ|ˆÿ|©ÿºªÿº«ÿº¬ÿº­ÿº²ÿº³ÿÄ´ÿºµÿº¶ÿº·ÿº¸ÿººÿº»ÿļÿĽÿľÿÄÂÿ|ÅÿºÇÿºÌÿÄÎÿºéÿLìÿLùÿ| ÿÈÿÎÿÎÿÈ$ÿÈ‚ÿȃÿÈ„ÿÈ…ÿȆÿȇÿȈÿÈÂÿÈéÿÎìÿÎùÿÈ1ÿÂ#ÿâ&ÿâ*ÿâ2ÿâ4ÿâFÿÜGÿÜHÿÜIÿÌRÿÜTÿÜWÿ®Yÿ¾ZÿÈ\ÿ¾mÿÂoÿÂyÿÂ}ÿ‰ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ©ÿܪÿÜ«ÿܬÿÜ­ÿܲÿÜ´ÿܵÿܶÿÜ·ÿܸÿܺÿÜÄÿâÅÿÜÇÿÜÍÿâÎÿÜåÿÂæÿÂïÿÂòÿÂóÿÂIþÞ þÞ þÞ6ÿ:6"ÿÎ#ÿ°&ÿ°*ÿ°2ÿ°4ÿ°7ÿT9ÿJ:ÿh<ÿ,?ÿJFÿÜGÿÜHÿÜRÿÜTÿÜYÿ”Zÿ°\ÿ”lþÞmÿ:oÿ:rþÞtÿ6uÿ6yÿ:{ÿ6|þÞ}ÿ:‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°Ÿÿ,©ÿܪÿÜ«ÿܬÿÜ­ÿܲÿÜ´ÿܵÿܶÿÜ·ÿܸÿܺÿÜÄÿ°ÅÿÜÇÿÜÍÿ°ÎÿÜÓÿ,åÿ:æÿ:çþÞèþÞé6êþÞëþÞì6ïÿ:òÿ:óÿ:öþÞ)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ. ÿvÿÿÿv$ÿv-ÿJDÿÎFÿâGÿâHÿâRÿâTÿâ‚ÿvƒÿv„ÿv…ÿv†ÿv‡ÿvˆÿv¢ÿΣÿΤÿÎ¥ÿΦÿΧÿΨÿΩÿâªÿâ«ÿâ¬ÿâ­ÿâ²ÿâ´ÿâµÿâ¶ÿâ·ÿâ¸ÿâºÿâÂÿvÃÿÎÅÿâÇÿâÎÿâéÿìÿùÿv)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ#ÿÒ&ÿÒ*ÿÒ2ÿÒ4ÿÒ7ÿÌ8ÿÖ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒ›ÿÖœÿÖÿÖžÿÖÄÿÒÍÿÒ[ ÿ|ÿLÿLÿLÿ|ÿ`ÿ`#ÿž$ÿ|&ÿž*ÿž-ÿ82ÿž4ÿžDÿFÿ.Gÿ.Hÿ.JÿDPÿ`Qÿ`Rÿ.Sÿ`Tÿ.Uÿ`Vÿ^Xÿ`YÿLZÿt[ÿp\ÿL]ÿˆmÿLoÿLwÿ`yÿL}ÿL‚ÿ|ƒÿ|„ÿ|…ÿ|†ÿ|‡ÿ|ˆÿ|‰ÿž”ÿž•ÿž–ÿž—ÿž˜ÿžšÿž¢ÿ£ÿ¤ÿ¥ÿ¦ÿ§ÿ¨ÿ©ÿ.ªÿ.«ÿ.¬ÿ.­ÿ.²ÿ.³ÿ`´ÿ.µÿ.¶ÿ.·ÿ.¸ÿ.ºÿ.»ÿ`¼ÿ`½ÿ`¾ÿ`Âÿ|ÃÿÄÿžÅÿ.Çÿ.Ìÿ`ÍÿžÎÿ.åÿLæÿLéÿLìÿLïÿLòÿLóÿLùÿ| ÿÈÿÎÿÎÿÈ$ÿÈ‚ÿȃÿÈ„ÿÈ…ÿȆÿȇÿȈÿÈÂÿÈéÿÎìÿÎùÿÈk0 ÿx 0 0ÿ@ÿÿ@ÿxÿ¨ÿ¨"0#ÿÌ$ÿx&ÿÌ*ÿÌ-ÿh2ÿÌ4ÿÌDÿŒFÿŒGÿŒHÿŒIÿâJÿxPÿ¨Qÿ¨RÿŒSÿ¨TÿŒUÿ¨Vÿ–WÿÖXÿ¨YÿÐ[ÿÌ\ÿÐ]ÿ®l0mÿoÿr0t:u:wÿ¨yÿ{:|0}ÿ‚ÿxƒÿx„ÿx…ÿx†ÿx‡ÿxˆÿx‰ÿÌ”ÿÌ•ÿÌ–ÿÌ—ÿ̘ÿÌšÿÌ¢ÿŒ£ÿŒ¤ÿŒ¥ÿŒ¦ÿŒ§ÿŒ¨ÿŒ©ÿŒªÿŒ«ÿŒ¬ÿŒ­ÿŒ²ÿŒ³ÿ¨´ÿŒµÿŒ¶ÿŒ·ÿŒ¸ÿŒºÿŒ»ÿ¨¼ÿ¨½ÿ¨¾ÿ¨ÂÿxÃÿŒÄÿÌÅÿŒÇÿŒÌÿ¨ÍÿÌÎÿŒåÿæÿç0è0éÿ@ê0ë0ìÿ@ïÿòÿóÿö0ùÿxI0 ÿ¢ 0 0ÿ†ÿàÿ†ÿ¢""$ÿ¢-ÿšDÿ¨FÿàGÿàHÿàJÿžRÿàTÿàVÿÒl0mÿàoÿàr0t0u0yÿà{0|0}ÿà‚ÿ¢ƒÿ¢„ÿ¢…ÿ¢†ÿ¢‡ÿ¢ˆÿ¢¢ÿ¨£ÿ¨¤ÿ¨¥ÿ¨¦ÿ¨§ÿ¨¨ÿ¨©ÿàªÿà«ÿà¬ÿà­ÿà²ÿà´ÿàµÿà¶ÿà·ÿà¸ÿàºÿàÂÿ¢Ãÿ¨ÅÿàÇÿàÎÿàåÿàæÿàç0è0éÿ†ê0ë0ìÿ†ïÿàòÿàóÿàö0ùÿ¢1ÿÂ#ÿâ&ÿâ*ÿâ2ÿâ4ÿâFÿÜGÿÜHÿÜIÿÌRÿÜTÿÜWÿ®Yÿ¾ZÿÈ\ÿ¾mÿÂoÿÂyÿÂ}ÿ‰ÿâ”ÿâ•ÿâ–ÿâ—ÿâ˜ÿâšÿâ©ÿܪÿÜ«ÿܬÿÜ­ÿܲÿÜ´ÿܵÿܶÿÜ·ÿܸÿܺÿÜÄÿâÅÿÜÇÿÜÍÿâÎÿÜåÿÂæÿÂïÿÂòÿÂóÿÂi ÿ\  ÿhÿ`ÿhÿ\ÿ†ÿ†""#ÿ°$ÿ\&ÿ°*ÿ°-ÿ82ÿ°4ÿ°Dÿ€Fÿ`Gÿ`Hÿ`JÿTPÿ†Qÿ†Rÿ`Sÿ†Tÿ`Uÿ†Vÿ€Xÿ†YÿœZÿ¤[ÿ|\ÿœlmÿ`oÿ`rt2u2wÿ†yÿ`{2|}ÿ`‚ÿ\ƒÿ\„ÿ\…ÿ\†ÿ\‡ÿ\ˆÿ\‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°¢ÿ€£ÿ€¤ÿ€¥ÿ€¦ÿ€§ÿ€¨ÿ€©ÿ`ªÿ`«ÿ`¬ÿ`­ÿ`²ÿ`³ÿ†´ÿ`µÿ`¶ÿ`·ÿ`¸ÿ`ºÿ`»ÿ†¼ÿ†½ÿ†¾ÿ†Âÿ\Ãÿ€Äÿ°Åÿ`Çÿ`Ìÿ†Íÿ°Îÿ`åÿ`æÿ`çèéÿhêëìÿhïÿ`òÿ`óÿ`öùÿ\0ÿº""#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆFÿÚGÿÚHÿÚRÿÚTÿÚVÿäYÿØ\ÿØmÿºoÿºyÿº}ÿº‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆ©ÿÚªÿÚ«ÿÚ¬ÿÚ­ÿÚ²ÿÚ´ÿÚµÿÚ¶ÿÚ·ÿÚ¸ÿÚºÿÚÄÿÆÅÿÚÇÿÚÍÿÆÎÿÚåÿºæÿºïÿºòÿºóÿº"#ÿØ&ÿØ*ÿØ2ÿØ4ÿØFÿàGÿàHÿàRÿàTÿà‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ©ÿàªÿà«ÿà¬ÿà­ÿà²ÿà´ÿàµÿà¶ÿà·ÿà¸ÿàºÿàÄÿØÅÿàÇÿàÍÿØÎÿà6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤D D Dÿ~ÿ~lDrDtdud{d|DçDèDéÿ~êDëDìÿ~öDÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸FÿÄGÿÄHÿÄRÿÄTÿÄ©ÿĪÿÄ«ÿĬÿÄ­ÿIJÿÄ´ÿĵÿĶÿÄ·ÿĸÿĺÿÄÅÿÄÇÿÄÎÿÄÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ ÿ|ÿ|DÿÚ¢ÿÚ£ÿÚ¤ÿÚ¥ÿÚ¦ÿÚ§ÿÚ¨ÿÚÃÿÚéÿ|ìÿ|$ ÿ®ÿ|ÿ|ÿ®$ÿ®FÿæGÿæHÿæRÿæTÿæ‚ÿ®ƒÿ®„ÿ®…ÿ®†ÿ®‡ÿ®ˆÿ®©ÿæªÿæ«ÿæ¬ÿæ­ÿæ²ÿæ´ÿæµÿæ¶ÿæ·ÿæ¸ÿæºÿæÂÿ®ÅÿæÇÿæÎÿæéÿ|ìÿ|ùÿ®ÿÂÿÂéÿÂìÿÂFÿÄGÿÄHÿÄRÿÄTÿÄ©ÿĪÿÄ«ÿĬÿÄ­ÿIJÿÄ´ÿĵÿĶÿÄ·ÿĸÿĺÿÄÅÿÄÇÿÄÎÿÄ$ ÿ®ÿ|ÿ|ÿ®$ÿ®FÿæGÿæHÿæRÿæTÿæ‚ÿ®ƒÿ®„ÿ®…ÿ®†ÿ®‡ÿ®ˆÿ®©ÿæªÿæ«ÿæ¬ÿæ­ÿæ²ÿæ´ÿæµÿæ¶ÿæ·ÿæ¸ÿæºÿæÂÿ®ÅÿæÇÿæÎÿæéÿ|ìÿ|ùÿ®"#ÿØ&ÿØ*ÿØ2ÿØ4ÿØFÿàGÿàHÿàRÿàTÿà‰ÿØ”ÿØ•ÿØ–ÿØ—ÿؘÿØšÿØ©ÿàªÿà«ÿà¬ÿà­ÿà²ÿà´ÿàµÿà¶ÿà·ÿà¸ÿàºÿàÄÿØÅÿàÇÿàÍÿØÎÿàK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ ÿHÿH$ÿH9:::<(?:‚ÿHƒÿH„ÿH…ÿH†ÿH‡ÿHˆÿHŸ(ÂÿHÓ(ùÿH ÿHÿH$ÿH9:::<(?:‚ÿHƒÿH„ÿH…ÿH†ÿH‡ÿHˆÿHŸ(ÂÿHÓ(ùÿH'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ ÿHÿH$ÿH9:::<(?:‚ÿHƒÿH„ÿH…ÿH†ÿH‡ÿHˆÿHŸ(ÂÿHÓ(ùÿHK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJ ÿjmÿjoÿjyÿj}ÿjåÿjæÿjïÿjòÿjóÿj)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖ ÿÈÿÎÿÎÿÈ$ÿÈ‚ÿȃÿÈ„ÿÈ…ÿȆÿȇÿȈÿÈÂÿÈéÿÎìÿÎùÿÈ ÿÈÿÎÿÎÿÈ$ÿÈ‚ÿȃÿÈ„ÿÈ…ÿȆÿȇÿȈÿÈÂÿÈéÿÎìÿÎùÿÈ ÿÈÿÎÿÎÿÈ$ÿÈ‚ÿȃÿÈ„ÿÈ…ÿȆÿȇÿȈÿÈÂÿÈéÿÎìÿÎùÿÈ ÿÈÿÎÿÎÿÈ$ÿÈ‚ÿȃÿÈ„ÿÈ…ÿȆÿȇÿȈÿÈÂÿÈéÿÎìÿÎùÿÈi ÿ\  ÿhÿ`ÿhÿ\ÿ†ÿ†""#ÿ°$ÿ\&ÿ°*ÿ°-ÿ82ÿ°4ÿ°Dÿ€Fÿ`Gÿ`Hÿ`JÿTPÿ†Qÿ†Rÿ`Sÿ†Tÿ`Uÿ†Vÿ€Xÿ†YÿœZÿ¤[ÿ|\ÿœlmÿ`oÿ`rt2u2wÿ†yÿ`{2|}ÿ`‚ÿ\ƒÿ\„ÿ\…ÿ\†ÿ\‡ÿ\ˆÿ\‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°¢ÿ€£ÿ€¤ÿ€¥ÿ€¦ÿ€§ÿ€¨ÿ€©ÿ`ªÿ`«ÿ`¬ÿ`­ÿ`²ÿ`³ÿ†´ÿ`µÿ`¶ÿ`·ÿ`¸ÿ`ºÿ`»ÿ†¼ÿ†½ÿ†¾ÿ†Âÿ\Ãÿ€Äÿ°Åÿ`Çÿ`Ìÿ†Íÿ°Îÿ`åÿ`æÿ`çèéÿhêëìÿhïÿ`òÿ`óÿ`öùÿ\)ÿÒ ÿÖ ÿÒ ÿØ ÿÒÿÈÿÈÿÖ$ÿÖ7ÿž9ÿÌ;ÿâ<ÿ°=ÿº?ÿÌ@ÿØ`ÿØlÿÒrÿÒ|ÿÒ‚ÿÖƒÿÖ„ÿÖ…ÿÖ†ÿÖ‡ÿÖˆÿÖŸÿ°ÂÿÖÓÿ°ÔÿºÖÿºØÿºçÿÒèÿÒéÿÈêÿÒëÿÒìÿÈöÿÒùÿÖÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ ÿjmÿjoÿjyÿj}ÿjåÿjæÿjïÿjòÿjóÿjÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤ ÿt ÿt ÿtÿ€9ÿ^:ÿ†<ÿh?ÿ^YÿÈ\ÿÈlÿtmÿ€oÿ€rÿttÿ|uÿ|yÿ€{ÿ||ÿt}ÿ€ŸÿhÓÿhåÿ€æÿ€çÿtèÿtêÿtëÿtïÿ€òÿ€óÿ€öÿtÿ¸ ÿ¸ ÿ¸YÿàZÿð\ÿàlÿ¸rÿ¸tÿ¸uÿ¸{ÿ¸|ÿ¸çÿ¸èÿ¸êÿ¸ëÿ¸öÿ¸ÿ¤ ÿ¤ ÿà ÿ¤9ÿŒ:ÿà?ÿŒ@ÿàYÿæ[ÿÄ\ÿæ`ÿàlÿ¤rÿ¤|ÿ¤çÿ¤èÿ¤êÿ¤ëÿ¤öÿ¤i ÿ\  ÿhÿ`ÿhÿ\ÿ†ÿ†""#ÿ°$ÿ\&ÿ°*ÿ°-ÿ82ÿ°4ÿ°Dÿ€Fÿ`Gÿ`Hÿ`JÿTPÿ†Qÿ†Rÿ`Sÿ†Tÿ`Uÿ†Vÿ€Xÿ†YÿœZÿ¤[ÿ|\ÿœlmÿ`oÿ`rt2u2wÿ†yÿ`{2|}ÿ`‚ÿ\ƒÿ\„ÿ\…ÿ\†ÿ\‡ÿ\ˆÿ\‰ÿ°”ÿ°•ÿ°–ÿ°—ÿ°˜ÿ°šÿ°¢ÿ€£ÿ€¤ÿ€¥ÿ€¦ÿ€§ÿ€¨ÿ€©ÿ`ªÿ`«ÿ`¬ÿ`­ÿ`²ÿ`³ÿ†´ÿ`µÿ`¶ÿ`·ÿ`¸ÿ`ºÿ`»ÿ†¼ÿ†½ÿ†¾ÿ†Âÿ\Ãÿ€Äÿ°Åÿ`Çÿ`Ìÿ†Íÿ°Îÿ`åÿ`æÿ`çèéÿhêëìÿhïÿ`òÿ`óÿ`öùÿ\0ÿº""#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆFÿÚGÿÚHÿÚRÿÚTÿÚVÿäYÿØ\ÿØmÿºoÿºyÿº}ÿº‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆ©ÿÚªÿÚ«ÿÚ¬ÿÚ­ÿÚ²ÿÚ´ÿÚµÿÚ¶ÿÚ·ÿÚ¸ÿÚºÿÚÄÿÆÅÿÚÇÿÚÍÿÆÎÿÚåÿºæÿºïÿºòÿºóÿº0ÿº""#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆFÿÚGÿÚHÿÚRÿÚTÿÚVÿäYÿØ\ÿØmÿºoÿºyÿº}ÿº‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆ©ÿÚªÿÚ«ÿÚ¬ÿÚ­ÿÚ²ÿÚ´ÿÚµÿÚ¶ÿÚ·ÿÚ¸ÿÚºÿÚÄÿÆÅÿÚÇÿÚÍÿÆÎÿÚåÿºæÿºïÿºòÿºóÿº0ÿº""#ÿÆ&ÿÆ*ÿÆ2ÿÆ4ÿÆFÿÚGÿÚHÿÚRÿÚTÿÚVÿäYÿØ\ÿØmÿºoÿºyÿº}ÿº‰ÿÆ”ÿÆ•ÿÆ–ÿÆ—ÿƘÿÆšÿÆ©ÿÚªÿÚ«ÿÚ¬ÿÚ­ÿÚ²ÿÚ´ÿÚµÿÚ¶ÿÚ·ÿÚ¸ÿÚºÿÚÄÿÆÅÿÚÇÿÚÍÿÆÎÿÚåÿºæÿºïÿºòÿºóÿº'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ-ÿ ÿ ÿÿx#ÿÈ&ÿÈ*ÿÈ2ÿÈ4ÿÈ7ÿL9ÿL:ÿ†<ÿh?ÿLYÿ|ZÿÂ\ÿ|lÿmÿxoÿxrÿyÿx|ÿ}ÿx‰ÿÈ”ÿÈ•ÿÈ–ÿÈ—ÿȘÿÈšÿÈŸÿhÄÿÈÍÿÈÓÿhåÿxæÿxçÿèÿêÿëÿïÿxòÿxóÿxöÿK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ-ÿ ÿ ÿÿx#ÿÈ&ÿÈ*ÿÈ2ÿÈ4ÿÈ7ÿL9ÿL:ÿ†<ÿh?ÿLYÿ|ZÿÂ\ÿ|lÿmÿxoÿxrÿyÿx|ÿ}ÿx‰ÿÈ”ÿÈ•ÿÈ–ÿÈ—ÿȘÿÈšÿÈŸÿhÄÿÈÍÿÈÓÿhåÿxæÿxçÿèÿêÿëÿïÿxòÿxóÿxöÿ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌ'ÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌK ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ6ÿJ ÿJ ÿJÿÌ"ÿÈ#ÿÖ&ÿÖ*ÿÖ-22ÿÖ4ÿÖ7ÿ|8ÿÈ9ÿx:ÿ¬<ÿ\?ÿxYÿ®\ÿ®lÿJmÿÌoÿÌrÿJtÿHuÿHyÿÌ{ÿH|ÿJ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÿÈžÿÈŸÿ\ÄÿÖÍÿÖÓÿ\åÿÌæÿÌçÿJèÿJêÿJëÿJïÿÌòÿÌóÿÌöÿJv #$&')-./2345789:;<=>?DEHIKNPQRSUYZ[\^lmoprtuy{|}‚ƒ„…†‡‰’”•–—˜›œžŸ ¢£¤¥¦§¨ª«¬­³´µ¶·¸ºÀÂÃÄÇÉÌÎÓÔÖØåæçèéêëìïòóöù 8‚DFLTlatnÿÿÿÿcase&case,liga2liga8sups>supsD,>B      @ LO,{tu CjqvÛÜÞßàâãIxxºxô ¯P`KtyPL@ûJþz¶ª “õ™ †&   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a†‡‰‹“˜ž£¢¤¦¥§©«ª¬­¯®°±³µ´¶¸·¼»½¾írdeiïx¡pkövjˆšþsgwøûúäÿl|÷¨ºcnýÚùm}ðb‚…—ÍÎåæêëçè¹ÁÓôõòóîyéìñ„ŒƒŠ‘Ž•–”œ›ÈÛâqÞßàzãáÜ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ`T@ ~ÿ1DS[a~’ÇÉÝÀ    " & 0 : D ¬!"!&"""""""+"H"`"e%Ê&iûÿÿ  1ARZ`x’ÆÉØÀ    & 0 9 D ¬!"!&"""""""+"H"`"d%Ê&iûÿÿÿõÿãÿÂÿ¾ÿ®ÿ—ÿˆÿ{ÿuÿqÿ[ÿHþþþý$àÒàÏàÎàÍàÊàÁà¹à°àIßÔßÑÞöÞóÞëÞêÞãÞàÞÔÞ¸Þ¡ÞžÛ:Úœ-û™%59=J@GBhf[[ Q CQ D=<($#*$ +>32#'&>54&#"#"'4632#"&!!7!!ú9DO.?gI)-60#z -70 I9)8(  c>0((0>þ¡Îü22cüu&#@[87P;+&%iu"3+(.:(3<üû/@)(?¤úg6,ÚÿñÓ™ !&@#Q CSD +#.54>32#".® y +!..""..!™ýÄ-VW[44[WV-<úÕ."".-""-˜™€™ ,@) BQ D   $+#"&/!#"&/3è™þÞ› ## ›"þÞ› ## ›"6Q™>BH@E  Y  C Q C DBA@?>>86530/*(##!#!+#"&5467#+#"&546?3#7>;>;3323+32%3#TQ G÷G-OU’ÌAè $'žH+PT÷TO!IÔ %&ŠA³ ýœ÷A÷§þY"Zþ%§ 9FJf"þZ¦þKþº 9ƒFjÿ$g8CN~@J( I>) ? 4BK° PX@$hfSCSD@$hfSCSDY·#&#&+.'7>32.54>?>;#".'+4.'>òyÇH50FaD%F‡kA9m h Bi˜<+):L1!HŒpE324.#"32>>;+#".54>324.#"32>Ã4Wt?DsV00VsDCuU1‹1A%%A00A%%A1€ €ûé „54Ws?DsV00VsDCtV0Š1A%%A00A%%A1?T…[00[…TV†\00\†VB\;;\BA[99[wú„ RT„[00[„TV‡\00\‡VB]::]BAZ99ZRÿðx©?Kˆ@8IH+%BK°PX@*hSCS CS D@(hSCS CSDY@FD/-(&"  ??+2#"&'.#">7>;#"&/#".54>7.54>3267”O‚_7o 1E.2P9"6&œ&-nFB,¬$^õ“PšyJ/SrD=:5dþÌ0Nd4p²DþYjk©3To<8. 9M-#@AE&þ]C“JsÞaþБ[j6g”]F}jTM’NI€_7ûãAcD#RD«9Ÿ˜™3™ @ BQ D $+#"&/3™þÞ› ## ›"†þÛ³ (+.54>7!nh OKiAAiKO  imuÖþm· 0sâäçzyèãât1¶þlJþÛų (+4'&546?'.54676*mi  OKiAAiKO hnuÖ”¶1tâãèyzçäâs0 ·“`_¼â0J@-)($  BK°PX@ QD@MQEY@ 00+5467'767./7.=3>?'.'b!¬,¬$%#­,­# X ¬,¬!!­,­ _Å"cKd eKd %ÆÅ) cKd  eKd !'Æd®"Ž ,@)MYQE +!!#!5!‹—þi’þk•ŽþU‡þR®‡«^þñPì@?S D$+74>32'&547>7#".^,/0G-    *{)'3-a_Z&   %0:!!-d R£@MQE+!!dîþ£—XÿñQì@SD($+74>32#".X!..""..!n."".-""-ÿôÿ¦öÁ @kD#"++>;¡7KY 0!K#"Ù "<ÿñL©'@SCSD((($+#".54>324.#"32>LQŒ¿mn½ŒPPŒ½nm¿ŒQ¹7]zBBz\77\zBBz]7̼þí´XX´¼¼µXXµþ켤߈;;ˆß¤¤Þˆ;;ˆÞÊœ*@' Bh CR D&+%!47#"&/3!!4ÿ  8ª‘ýˆÑ,-Û Mqúìˆh$©3;@8/ BhSCQ D,*$" 33+2>3!2!5467>54.#"#"&/>Y[žsB0Rk<þ‡(R&à"üDË9^C$(F^66\G1   ]P{Ÿ©6g”^Pˆ}u=þ~ "l=(Í:klo??_> 9N/b—f5lÿð.©JU@RF Bhh[SCSDCA;910/.&$JJ +2#".'763232>54.#5>54.#"#"&/>l[šo>#A\9ŒK‚­cr¢pHL(DeKKqK&Qp[R%'D]66\G0   ]P{Ÿ©4`ˆSDkQ8%®ƒcžo;9d‰P  I@,1N`/:`F(&B\8>\< 9O.b—f5(`™&@#B[ C D!#+3+#!"&/3467!‡Ù²ý…¹¦ý÷fþ‡y[þ»< ý;lÿðþ™.@@=,+Bh[Q CSD(#&(""+#!632#".'763232>54.#"'!Ò09þ>Bp_p«t;P‹¼m?tdV!63HaCKxU.'OvO6t>pt§K&1þˆBtž]r¸ƒF*6L&0Y|MClL*!žlÿð2™.2@/B[ CSD+)! +2#".5467>;>32>54.#"ŠVštDH¶ml¯|CT[k2 žþ3|þÖ(MoGHtS-,PpCHtQ+n9mŸfcª~HEµp^Ôzéý‹#'þLErR-.RpBFqO*1Smn<™@Q C D$'++>7!"&=<ý¯ .'Z ý™P",ûS%ž*y`ÿð&©3GD@AB[SCSD54! ?=4G5G+) 3!3 +".5467.54>32'2>54.#"2>54.#"Ck²€F†qs>rŸba r>tp†G²kFoM)1Sl;;lS1)MoFFc>!Aa@@aA!>c9j—^г&*§tOŠf::fŠOt§*&³Š^—j9Ž'Gc32+>74.#"32>%Q‘nAF~®hg¨xA,='þ£ 0¤´&7Š+Li?BmM*'IiAHoL(L6i™c^¤zFDzªg>oji8þ;4,.£CmL)+Lj?DkJ&/Nf€ÿñyÚ';K°$PX@SCSD@[SDYµ((($+74>32#".4>32#".€!..""..!!..""..!n."".-""- ."".-""-€þñyÚ2D³?K°$PX@SCS D@[S DY@ /-%#$+74>32'&547>7#".4>32#".†,/0G-    *!..""..!{)'3-a_Z&   %0:!!-ü."".-""-”êšW³(+”þ?--ÁüúÆ‘ ä  ã €’–·ñ!@YMQE+!!!!–[ü¥[ü¥>‡Ö‡îêóW³(+75467%>7.'%.=îÁ++þ?ê€ ã  ä þoJ"ÿñø©(<9@6BhfSCSD(&#-$+>32#'54>54.#"#"'4>32#"."KYg3232>54.#"3267632#"$&54>32%2>7&#"Nb :ˆN54&#%!2>54&#!®É„¿{;!CeD C»xþÇ6SwM$ŸþËRxO&˜ þù™4`‹W5bTB¤†[–l;þ &E_9oŠ$@[6~vZÿð ©.D@ABhfSCSD)'..+2#".546$32#".#"32>76  LXû±›ü²bi¾  žåY? (6Jb@s¿ŠMM…¶i@fWK&( SfrkÁ¢¢ÂkbTY  O’Ò‚†Ò‘L 1"®ˆ™ @S CS D!(!$+#!!24.#!!2>ˆfºþüžý螺fÇH„¼sþ«Us¼„HÌ¡þø¼g™g½þø¡„ÐLû¡LЮ!™ .@+YQ CQ D +!!!!!!ýP-ýÓ°ü™žþ$˜þž™®!™ (@%YQ C D +!!!#!ýPLý´Ã™žþ žý˜™Zÿð@©4K@H! Bh[SCSD,*%# 44+%2>7#"&=!#"$&546$32#"'.#"-:aVL&Þ¸6u…˜Yœþü¼ig¿¨U’}j.7>YySyÄŠJMŒÀ <nýÚ':'kÁ¢¤Áj/C*X (%O“Ñ‚ˆÕ”N®8™ @Y C D+!#!#3!38ÃüüÃÃÃŒýt™ýÒ”™@ C D+!#3”™<ÿðÉ™QK°PXµBµBYK°PX@ CSD@h CSDYµ!&$+#"'>7>3232>53É;s¨mai<2BgG%Áïx¾ƒF9(TƒZ®Â:™"&@# B[ C D)(% +3267>;#".'.+#3ƒI&-Ý) ¥ýÞ%*:¨þ !XÁÁ%ý— $ýY  9  ýp™®Ü™@ CR D+%!!3plüÒ££™®™#%@"Bh C D!6)+>7>;#467+"'#32o å ªþ--þ ª ï53q úg0ü€--ƒ2ûã™ üŽ®8™@ C D!+2.53#"&'#3>ªbüêd™ ûÈ1÷úg70ü™\ÿñá©'@SCSD((($+#"$&546$324.#"32>áfºþûžžþüºffºžžºfÇH„¼ts¼…HH…¼st¼„HÌ¡þóÂkk ¡¡ ÃllÃþó¡„Ò‘NN‘Ò„„Ñ‘MM‘Ñ™*@'[S C D !+#!2#'32>54&+ƒÁ§ˆÉ„AF‡ÈææSV,©«æýè™?t¤ed¦xCš,OnB‰š\þØ$©0s¶ BK° PX@kSCSDK°PX@SCSCD@kSCSDYY¶(((%&+#"&'#"$&546$324.#"32>á)NpFp $8ü9{CžþüºffºžžºfÇH„¼ts¼…HH…¼st¼„HÌe¶€/þsk ¡¡ ÃllÃþó¡„Ò‘NN‘Ò„„Ñ‘MM‘ÑÂå™#2@/B[S C D#!,!+#!2#"'.#'32>54&+ƒÁ•ˆÆ>0[ƒS$¢¬5þŒ((“ËUW,©§ÔVýª™7h“[L„iJ(ýÇ))Kh?€‚:ÿðÛ©==@:=BhfSCSD;9(&#!#"+#".#"#"&'7>3232>54.54>32Œ -EaEAdC";a{{a;@{³r‹åQ86QsSElK(;`{{`;;p¥kxÆJ¹")"#53#".53ÛYŒa3ÁO“Ô„„Ô”OÁ3aš7>;#› • “"œý¸¯™ü "P++P"óúgç™( @#B C D+< +32>7>;2>7>;#&'#¡"(  Q#8!O )#—þA®þ•  þ“®™ü>""?äü4C!<äúgE)%û»ö™@B C D("(!+ 3267>; #"&'+ûþ'Áva ¹þ%ëÀþ€ þŠ ´à¹ ý ýPýYýÏä™@ B C D,"+#32>7>;ÖÁýóª H G ¬:ýÆ:_ýÓ#>>"-V”™ $@!Q CQ D +!!547!5”üÕ2ûÐ,üç™H"ûžLvžŽþßþý '@$[OQE !#+!+32Žp©©þßFùÍFÿìÿ¦ïÁ @kD# +32#"&'L!0 YK8 Á" ú'"#ZþßÊý !@[OQE!"+46;#"&=!!Z©©pþÛ3FøâžÝ™@ Bk D+!+3#"&'.'+sfÄ Âˆ™ýz`+,+þ þãÿ[@MQE+!5üì¥xx&‹³© @kD  +2#"&'Ï! •fê©ò \ÿðz)9}@ !/BK°PX@'h[SCS D@+h[SC CSDY@+*10*9+9%##' +!#"&/#".54>754&#"#"&/>322>75zO (LT_:;gL-B“î¬ecAYA/ TÂvU„Z.þ2/NE?{¬l1,<^$9'!BeE32#"&'#"32654.˜³?£iXŽd632#".54>32E#6M8JrM'*LmDAT8$ 2BÆn_£xE?y²sj¤?A 5dŽX\a3&AQKF…Â|qÀ‹NE?HÿòÅÁ%p@ BK°"PX@CSCS D@!CSC CSDY@%%+!"/#".54>323%267.#"[& A§lWŽd632#".54>"!4.#[šp?ý^0TtHCaF/ 2!\ip7i±HAz°r”'"B_=s©l*`Ž_/$A(;&G‰Êƒj¸‡Mƒ•„>gK)”®]@ BK°2PX@SCQC D@YSC DY@ 4%+3'.=354>32+"!!ºp 1[€PD: .K6%þá] IbW‡]0Y6XA]ü 2þ“Þ9M]—@2A*BK°PX@, [[CS CSD@/h [[S CSDY@ONWUN]O]JH@>#!99 +2!#"'#".5467.54>7.54>4.'32>2>54&#"çBs/*s"9e‹SG? !:`zz`:Az°oo§n7_S+3!0 KU9f*H^hl19G#HmJHrO*þÄ6S8qlkq8RB! APJyV..$% 2XFAz_9,Ja5KiC8/.**‹]JyU.ûÃ&. N6";+0BN6K-]nn]-K6’ÝÁ-@*BCSC D##+33>32#4&#"’²AžgSU,²ilO‰:Áý¬ES7eŽVý{…sLAý‚€³GK° PX@SCC D@SCC DY@  +##".54>32X²Ú#.-##-.#õü õ>-##-/##/ÿÈþ”€³(Yµ BK° PX@SCCSD@SCCSDY@%#U%+#"&'7>323265#".54>32X EmL!6 NBÚ#.-##-.#õûÀ=iN- ` IQ@>-##-/##/˜øÁ0@-B[CC D%(%!+3267>;#"&'.+#K.@¢þ‹ Œ þ³2³Áü Wþs þ Ÿ þÁ¦XÁ@C D+#X²Áú?Á’ï*V·) BK°PX@SC D@CSC DY@**##&$! +332>32>32#4&#"#4&#"’j& 8‹\gEVa2P}W.²hc,O<#²b^Bq/õ%hEXra7P43b\ý{…w{<[<ý{…zxG=ý ’ÝL¶BK°PX@SC D@CSC DY@ #$!+332>32#4&#"’j& B£kSU,²ilO‰:õ%nIZ7eŽVý{…sLAýHÿò#NK° PX@SCSD@SCSDY@## +2#".54>2654&#",o³}CC}³oo³~DD~³o–””–LpK%%KpJˆÁwxÀˆIIˆÀxwÁˆJüxÉ´µÊ4bZZŽa4’þ©%“@ BK° PX@SCSCDK°PX@SCSCD@!CSCSCDYY@%%($!+32>32#"&'"32654.’j& A§mWŽd69þ@ÎPIþB6Ê»cŽ[*Hþ©Å%¨K°PX@ B@ BYK° PX@SCSCDK°PX@SCSCD@!CSCSCDYY@%%(#+##".54>32763267.#"Ų@£iWŽd632#"&#"’f 4™g*D:4]}*õžjw…lgý{>ÿð<=@:<BhfSCSD:8'%" #!+#".#"#"&'7>3232>54.54>32Ö &7L4-H3-J^c^J-2bŽ]j¬<*(9Q=4N4-J_c_J-0\†VdŸ:N(5'4&!([A:kQ0?7,ÿðº>!t@ BK°2PX@$jhQCTD@"jh[TDY@ !!+"&5#"&=7>;!!32>32Åxz¦)Z"þÞ>1) 4.‚†~lG9þ£ý @>U+1zÿðÅõL¶BK°PX@CS D@C CSDY@ $!#+32673#"/#".5,jkNŠ:²j& B¤jSV+õýzs~JBëü %mIY7dŽV†íõ@BC D, +32>7>;#’‹þc¡õýt$H##H$Œü ï÷. @'BC D*!,< +32>7>;2>7>;#"'.'+ŒÂ ÖMÑ  Ɔþ¸ à  ã †õýt$C""C$ýp#D!!HŒü "¯/0ýR"Òõ@BC D("(!+ 3267>; #"&'+þ««ø Ú ¤þ«c«ÿì Ÿî þ„@þýïþ§þ©ðõ@BCD,""++32>7>;» „¹þ^š  ŽþÕ’ºý‚,,}FUõ@QCQ D+!!5467!5!U ýÜ)ý 'ýßð©#ý&‹J #ߌ,þßý@3@0$B[[OSG86303++4춮.54>;+";2#".54>µFCCF)S{R5 MY)7!!7)YM 5R{S)©?QkP@2bbd4EtT.OeV8hcb2&A3% %4@%2bch8WdP/TtE4ccbæþ©pý@QD+3#把ýø¬Xþß,ý@5@2B[[OSG?>=<1/,)3)++546;2654.54>7.54>54&+"&=323"£*R{R5 MY)7!!7)YM 5R{R*FCCF©2bcc4EtT/PdW8hcb2%@4% %3A&2bch8VeO.TtE4dbb2@PkQtž9@6jkO[SG +2673#".#"#4>32÷AI%Ef@4f_V$AI%EeA4f_VeUFCpP, '!TGCpP-!'!Úþ©Ô !&@#SCQD +4>734>32#". y Õ"--""--"þ©-UW\44\WU-ýãß-""-."".Šÿæ.7´@ 32&*BK° PX@)jhfTCSDK° PX@)jhfTCSD@)jhfTCSDYY@ ##'#+.54>?>;#".'>32+1\›q?B~¸w BR„6. !-?*4?U;& 0<¹k Bç‡y4LsN' O„¶ro»ŠQ³é ?1> ý" ?HJ¯å¢Àø9cˆ4[¨>@@=7+Bh[SCS D%&#&'%" +46;4>32#"&'.#"!#!>3!#!5>5#4 †6n¤nNy^EH   )3B-?`@ ¹þ{929¤ ü<">0à$^¥{G'DZ4. /#*NnDþùHóKm-Ls "3E.!„à`#7?@< !B @ ?WSD42*((+467'7>327'#"&''7.732>54.#"ß!™[—,h:9f+™Y—"!˜[˜,h99e,™Y—"„#>Q//S=$$=S//Q># 9e,™Z˜"!™[˜,g:9f+—\˜"!™[˜,g:.Q=$$=Q./R>##>R,S™"8@5 B Z Y C D"! ,! +!32>7>;!!!!#!5!5!’2þh• !–þg3þ¬Tþ¬³þ¬Tþ¬q(ýÊ#:;"6üØfigþÅ;giæþ©pý@YQD+3#3#æŠŠŠŠýüæþáüårÿƒ‡§HZA@>HXN=#BhfWSDFD-+(&!#!+#".#"#"&'7>3232>54.5467.54>32>54.'1 &7L40M51OfifO1NT1>2a\j¬<)(:U?2O62RhnhR2V]2?0\†VdŸ:ý·Fm„>604FOT(B6ñ*8&9/+.7G\=Q&%bEFwW2E6D #->&-B3*,3F]@N}#&iK:kP0>7ý¤3G95K/$8.&##IšV{'3K° PX@ S D@OSGYµ((($+#".54>32#".54>32ï )(() g)))) ((**((**Dÿòù¨.Jb@ BK° PX@4hf[[ SCSD@4hf[[ SCSDY@ _],,*(#%(%" +>32#".54>32#".#"32>4>32#".732>54.#"  =9¦tb¡s?Ez§bl˜9. 2L;FqO++Lj>0B0%üR4_†¢ºee»¢†_44_†¢»eeº¢†_4d,RrŒ¢X„ç«c-RsŒ£X„æªbÏ@BIDz¨de©yCD7A -TxKMyR+  e»£…`44`…£»ed»¢…`44`…¢ºeY¤tS-d­é†Y¦vS.e¯ë\?Tª)5E@B!-Bh[WSD+*/.*5+5%##' +#"&/#".54>754&#"#"&/>322675T< .28"&A0&X‘k:9&2%4yI6T:þá3J$Fa<4H 1  )<)"C5#%?< *1."BK° PX@/h  [ [SCSD@/h  [ [SCSDY@44VTLJ4I4H)!*,,& +4>32#".732>54.#"#!2#"'.#'32>54.+D4_†¢ºee»¢†_44_†¢»eeº¢†_4d,RrŒ¢X„ç«c-RsŒ£X„æªbæœ ¬¦kj ä”!É Pt7M/+F4„Ìe»£…`44`…£»ed»¢…`44`…¢ºeY¤tS-d­é†Y¦vS.e¯ëàþž|}z^„ þ². r(:&%8$ÏRD@MQE+!!>ýÂDuF'Òª'@WSD((($+4>32#".732>54.#"F2XwEEwX22XwEEwX26I**H66H**I6hCvW22WvCBuW33WuA*I66I**J77JdP"² 7@4YYMQE  +!!#!5!!!‹—þi’þk•þk¾üB²þˆˆþpˆxü%‡R„Qe-9@6+ Bh[SD(&"  --+2>;2!546?>54&#"#"&/>Z4U:¥M+  Û455370* jjT|Re=S@P9Bhh[[SD640.*)('== +2#".'763232>54.#5>54&#"#"&/>b3R; wBE*E[09T=+7 + / 'A/WG:009  C,ATe3D(€-N>7T91H/ (+W<424/( 5O5Ä‹U© @kD #++7>3Uéj”! ©þþ òzþ©Åõ3@0BCS CD&$!#+32673#"/#"&'#"&5,liNŠ:²j& CWJp'Y&)õýnmxJBëü %mHD3.*W&þé($*ÿ7™*@'hiS D+##!#".54>3Ûþëh¦u??u¦h™™ú7Éú7]=iŽQVe8|½§è@OSG($+4>32#".|)68((86)Q8((86))6„þ¡ï K°PX@  B@  BYK° PX@^TDK°PX@jTD@jjTDYY@+232654.'73#"&'76¬ *+)<&+pZQ 9P0)J÷ ! PE6 3$7x„D_O· BK°$PX@jjQD@jjMRFY¶$+37#"/733!­“k  'Þl‚þiÙ¸+X 8¾ýzUH<±©)@&WSD +2#".54>2654&#"~FqP,,PqFGrQ,,QrGTSSTWSS©+PsGHtQ++QtHGsP+ýýiddhhddi– ¢%µ%(+7'&54767&'&54?%'&54767&'&54?ì: Ÿ  Ÿ :ù(ù: Ÿ  Ÿ :   þ{þ{   f|š &0O@L$ Bh  Z [  CS  D0.+)&% $!# +3+#5!"&/3%37#"/733!4673+>;m RmþÎ V|ûŒ“k  'Þl‚þiñìý,L2. M A °° 9Ô;¸+X 8¾ýzUÃ,þ¹Ö\ f]š-=Ge@b710+ Bhh  Z [  CS  DGEB@=<;:9853/.(&"  -- +2>;2!546?>54&#"#"&/>%37#"/733!+>;f4U:¥M+  Û455370* jj3¸+X 8¾ýzUýv\ D} NT^x@uJ  %RB h  h  [[ \ S CS D^\YWTSGEA?;:980.)'!NN!#+3+#5!"&/32#".'763232>54.#5>54&#"#"&/>4673+>;m RmþÎ V|üB3R; wBE*E[09T=+7 + / 'A/WG:009  C,AT‚ñìý,L2. M A °° 9ÔÇ3D(€-N>7T91H/ (+W<424/( 5O5ü\,þ¹Ö\ ,þœ)=9@6BhfSCTD('#-$+#".54>?332>324>32#".KXh2/ IB@:> IBYK° PX@0hfSCSC SDK° PX@0hfSCSC SDK°PX@0hfSCSC SD@7hf hSCSCSDYYY@GE=<861/'% KK +232654.'7.546$32#".#"32>7632#"&'76ƒ *+)<&$‹âŸVi¾  žåY? (6Jb@s¿ŠMM…¶i@fWK& LSé¢ZQ 9P0)J÷ ! v u¿™¢ÂkbTY  O’Ò‚†Ò‘L 1" Sap7E6 3$7ÿÿ®!ö&( 7ÿÿ®!ö&( 7ÿÿ®!Ü&( Bÿÿ®!ò&( Bÿÿÿ̼ö&, øÿÿšŠö&, øÿÿÿï{Ü&, ÿÿÿòxò&, 2Ñ™!,@)YS CS D!%(!+3!2#!#%4.#!!!!2>2ÅžºffºþûžýéÅØH„¼tþ«}þƒUt¼„H g½þø¡¡þø¼gš2„ÐLþrþLÐÿÿ®8Ò&1Úÿÿ\ÿñáö&2 ãÿÿ\ÿñáö&2 ãÿÿ\ÿñáÜ&2 îÿÿ\ÿñáÒ&2îÿÿ\ÿñáò&2 î~ÛX ³ (+  ' 7 ùþ¨b_þžþ›_dþ§_YXöþ¨þŸ`bþœ`dY`þ¦X\ÿ“áÚ!-8h@21&%BK°PX@kCSCSD@jkSCSDY·)*%(%$+#"&'+&546$327>;.#"4&'32>áfºþûžl¼Od:N¿p{fºžsÈSR d¬gpûAKE“<”Ws¼…HøA<ýqtœt¼„HÌ¡þóÂk10ˆb ³¡ Ãl:6oëbþ꫇ÓIƒ*+N‘Ò„~ÉHü„FM‘Ñÿÿ ÿïö&8 ÿÿ ÿïö&8 ÿÿ ÿïÜ&8 ¨ÿÿ ÿïò&8 ¨ÿÿäö&< 9™.@+[[ C D!+#332#'32>54&+ƒÁÁæˆÉ„AF‡ÈææSV,©«æþð™þø?t¤ed¦xCš,OnB‰šºÿðv®HwK°PXµBµBYK°PX@hSCSD@#hSC CSDY@CB=;%# HH+2#"&'7>3232>54.54>54.#"#4>¡g—b/+@K@+5P]P59d‡Oaž<)(7K5,F18TbT8-CNC-8Y?DoO+³E€´®<]n332#"&'#".54>754&#"#"&/>32>32>5"!4.ŽRg;ý.MiAE\=&/!Wcj4u¿7Wjw;ErS-B“î¬ecAYA/ Tµqx’!6­þ¶{¬l1dQ9cI*¼=`E)üX8#FjH32#".#"32>32#"&'76 *+)<&%Sf:?y²sj¤?/#6M8JrM'*LmDAT8$ 2;ªaZQ 9P0)J÷ ! y O„¶qqÀ‹NE?@ 5dŽX\a3& AHJ:E6 3$7ÿÿJÿòÇ©&HCôÿÿJÿòÇ©&HvôÿÿJÿòÇ™&HÛôÿÿJÿòÇ{&Hjôÿÿÿù†©&ÈCÓÿÿ—(©&ÈvÓÿÿÿÒ6™&ÈÛÒÿÿÿá){&ÈjÓLÿó†4H6@3:0B43@[SD65@>5H6H.,$"+.54?.'.54?7#".54>32.'2>7.#"¡g-e9`´Q§#a{·xbª}H>t¥hd±Au^¸_GsQ.4Kc>KqL'.Pi) H"><0z9 C1|›¹nä VB{²p^§~JVWˆ¾@‡üŒ6m¥o+Q?%2WwDQV-ÿÿ’݉&QâÿÿHÿò©&RCûÿÿHÿò©&RvûÿÿHÿò™&RÛûÿÿHÿò‰&RâûÿÿHÿò{&Rjûd½"€++@([YOSG(((%+!!4>32#".4>32#".d¾üBb!--""--!!--""--!㇦."".-""-ýS."".-""-@ÿ´-I!+5t@43%$ BAK° PX@ jkSCSD@ jkSCSDY@ -,,5-5%%(%'+#"&'+7.54>327>;&#"2>54'=BC}³oLƒ67;C‘BFD~³oO‡8D ZüÉ;´IoLtO(7KsO(4þOFtD¿vxÀˆI" JÄEÂ|wÁˆJ&#[ý± aN86d‘þ$5dZ—`ý·0ÿÿzÿðÅ©&XCõÿÿzÿðÅ©&XvõÿÿzÿðÅ™&XÛõÿÿzÿðÅ{&Xjõÿÿþ©ð©&\vä’þ©Á#q@ BK° PX@!CSCSCD@!CSCSCDY@##(#+3>32#"&'"32654.’²?¤iWŽd6!&'d ]3XeO< †ý}†"–=Å=%*"1* ü?á  ÓBOB:e&Zþ¦™úg '/&+ çG6Q)E\þªèCSŸ@;I BK°PX@1h  [SC SCSD@8hh  [SC SCSDY@EDKJDSES%##.%#' +!32>32#"&54>7&/#".54>754&#"#"&/>322>75z*"1*  ]3Xe'5 (LT_:;gL-B“î¬ecAYA/ TÂvU„Z.þ2/NE?{¬l1,< '/&+ BOB83- ^$9'!BeE ]3XeO<ýZsýP-ýÓ°[*"1* ÓBOB:e&™žþ$˜þž '/&+ JþªÇ<E¶K°PX@ -B@ -BYK°PX@2h  [ SCSC SD@9h h  [ SCSCSDY@>=A@=E>E97+)&$  << +2#"&5467.54>32#!32>3232>"!4.é ]3XeB4g­~FAz°n[šp?ý^0TtHCaF/ 22“R'!1* ½”'"B_ÓBOB5]%HŠÈj¸‡M=s©l*`Ž_/$AgK)¦Xõ@C D+#X²õü õ,™#@  B CR D+%!!54?3©€þ›lüÒ»¢Â%ļþ"£*_… VÌ6KÁ#@  BC D+7#54?™² —²±˜ÁýÂMfCý¹Li Dÿÿ®8ö&1 Ðÿÿ’Ý©&Qv\ÿò'©02@  BK°PX@"YS C S DK°"PX@,YSCQ  C S DK°,PX@6YSCQ  C Q C SDK°0PX@4YSCQ  C Q C SD@2YSCQ  CQ C SDYYYY@-+#!(# +!!!!!5#".54>3254.#"32>'ýP-ýÓ°ü¤Tþ¡Žê¨\\¨êŽ¡þT@w©hh©xAAx©hh©w@™žþ$˜þžðx†kÁ ¡¡ Ãl‡yðý3„Ó”OO”Ó„„Ó“NN“ÓHÿòu0@K—@ . BK° PX@,h  [ S C SD@,h  [ S C SDY@$BA21GFAKBK:81@2@,*"  00+2#!32>32#"&'#".54>32>2654.#""!4.ôRg;ý.MiA=Y@- 3!Wcj4w¿76Á‰d¤u@@w§fƒ¾52»ý§‹ˆ"DhEGhE"‰e=`E*üÿð©&Vv§ÿÿ:ÿðÛÜ&6ÿÿ>ÿð™&VÜ—ÿÿäò&< DÿÿV”ö&= NÿÿFU©&]v´ÿÿV”&=XÿÿFUº&]ßµÿÿV”Ü&=XÿÿFU™&]Ü´jþ ò®#6@3 BYSCTD###"+#5432>7'.=37>3#"!¡YïÖ<-Q?,Y›ÕëÚ.Q@- Bý&͹^9:\DÐI¨Æ¿b:]D¢‘d™ @Bk D' +#"/+3dw€ {ߦ‘~~ ‘d™@Bk D+ +32>?>;#{ € € wߦ™ }  } þøÿÿÏRDq ŒD™ @W D +".5332>532MhA~"9++9"~AiŒ+Ib7!9((9!7bI+´½±º@SD($+#".54>32±#.-""-.#:-""-/##/jkûÞ=K°PX@WSD@[OSGYµ$&($+4>32#".732654&#"j 7H()I8 8I)(H7 d6/-77-/6#*D22D*)D00D),88,-88†þªñ YK°PX@ B@@ B@YK°PX@ SD@jSDY@ +2#"&54>732>à ]3Xe+;"\*"1* ÓBOB;6/ '/&+ ®Y‰QK°.PX@WS D@O[SGY@ +2673#".#"#4>32¡$'l/A(#=60Ho0B'#=6/-*,/O8"X0O9"^‹ç© #@ SD   #++7>3!+7>3­Ç JŠ !!°óV´!©þþ òþþ ò0ÿô¯õ!YµBK°(PX@SCS D@SC CSDY@! 6##++#!#"&'7>3265#54>3¯ˆ²þsy"BB9½ õH$üŽrý‚w‰ K >B|@  œ ¼@MQE+!!œ üà‚œ Î@MQE+!!œ2ú΂:ô'ù³(+.5467rYP7 ô0a0Z¥E"  ,16 D&  ZßFä³(+'.547>54&'&547YO7 ä0`0[¥E"  -16 D&  ZþìFñ³(+%'.547>54&'&547YO7 ñ0`0[¥E"  -16 D&  :ôWù1µ(+.5467.5467rYP7 ÃYP7 ô0a0Z¥E"  ,16 D&  ,0a0Z¥E"  ,16 D&  Zßvä1µ(+'.547>54&'&547%'.547>54&'&547YO7 YO7 ä0`0[¥E"  -16 D&  ,0`0[¥E"  -16 D&  Zþìvñ1µ(+%'.547>54&'&547%'.547>54&'&547YO7 YO7 ñ0`0[¥E"  -16 D&  ,0`0[¥E"  -16 D&  vþšÂ3@0 BCSCD#$&"+4632632>72!#"'!v)+"LPP'(57&N¡E,(þx&75(þw–0 èþ0<þ`ü÷  vþšÂ/G@D %$ B[C SCD/.$$#$&" +4632632>72!!#.'#"'"&=!!v)+"LPP'(57&N¡E,(þxˆ(,E¡N&75(N¢E+)‰þw–0 èþ0<ý¨<0þç0<XÐà·È,K°PX@ SD@OSGY³($+4>32#".Ð;d‡LMˆe;;eˆML‡d;SMˆe;;eˆMM‡d;;d‡XÿñVì';@SD((((($+74>32#".%4>32#".%4>32#".X!..""..!!..""..!ýý!..""..!n."".-""-."".-""-."".-""-Hÿï¹§'1EYm¹K°PX@+[   [SC  S  DK°"PX@/[   [SC C  S D@3[   [ CSC C  S DYY@~|trjh`^VT((%#&((($+#".54>324.#"32>>;+#".54>324.#"32>%#".54>324.#"32>Ã4Wt?DsV00VsDCuU1‹1A%%A00A%%A1… €ûé „54Ws?DsV00VsDCtV0Š1A%%A00A%%A1h4Wt?DsV00VsDCuU1‹1A%%A00A%%A1?T…[00[…TV†\00\†VB\;;\BA[99[} ú„ RT„[00[„TV‡\00\‡VB]::]BAZ99ZAT„[00[„TV‡\00\‡VB]::]BAZ99ZŠÙ¢³(+5Šù: Ÿ Ÿ:… þû þû –墳(+'&54767&'&54?åù: Ÿ  Ÿ :þ{   ÿD#™ @ C D#"+'+>;,L2. M5\ "ÿñr§G[@X 5 Bh  h [   [SC S  DGF@?>=9720"###%$+3>32#".#"!#!!#!32>32#".'#53&45467#"“_¸l†¾F=%>aK’Á #þÓþjÀ’6R<+   KFÐtºˆWކ‡ƒ~ËLdXD &.&ÈÀ7(8ÎÎ % FfqO“Óƒf)@I)™&C@@BhS  CS  D&&!4) +>7>;#7+"'#32'###5à © jn ·  ¸nj ô´~´U/ ý°mKþ°MHþ“P iþçiV~©71@.1BSCS D76***+!>54.#"!"&=!5.54>32!#DQˆc7E{©dd©{E7b‰Qþ$£`›l;c¯òò¯c;m›`¤$¶Ag]k£m88m£k]gAþJ#d®`ˆ®e€ÖšVVšÖ€e®ˆ`®d#\ÿó2¨*>C@@0Bh[SCSD,+64+>,>#'(($+>32#".54>32>54&#"#"&'2>7.#"\'INV3Z”h9I“Ý”V‘h:Lˆ¼phš0Š|*G8*  xBw`G &>W:T‚X- m•WAfF%b™ @ B CR D+3!%!.'`¯Sú¬Mþ€   ™úgœ¿:"";>þ©™ $@!Q CD +##!##5»¸þ ¸»™™ù©Wù©W™Tþ©™&@#BQ CQD+!!!!5467 .5T¯üe=ýÛûQ uý‹ ™™ý;4ý;™A ý ”\ðã@MQE+!!”\ü¤ã‡.×°"@ Bj[ D,# +#"&=!2>7>;#«)O–  °sýÖ•—!)9þe D"8GùP:þ  ';OL@IK-B[  O  SG=<)(GE32>32%2>7.#"!2>54.#"ê8[MAAL\7>qU22Uq>7\LAAM[8>pV22VpýO$>7227>$$?00?$?//?$$>8228>þ";L**L;"0Y|LL|X1";L**L;"1X|LL|Y0’5E''E4/H00H//H00H/4E''E5ÿ–þ¢\©#(@%BSCSD6''"+>32#"#"&'7>32>7v´’&E do‚ AcK#J   :W=%C®¸V  mvûúf’_- L ;]B°~ØÅ7Ã@0!/"BK° PX@+[[ O[ SGK°PX@$[[ WSD@+[[ O[ SGYY@42+)&$77  +2>7#".#"'>322>7#".#"'>326." #p=4ge_-8." #qB5hd_-6." #p=4ge_-8." #qB5hd_Z r/.!(!  m31!)!þ®  q0.!)! m31!(!–»ñ~kK° PX@)^_ ZMQE@'jk ZMQEY@  +!733!!!#7!5!7!–érrñþÐ_þ2wwþôK_þVññ‡È‡üü‡È”Pš!@@MQE+!!”þG.32¹üúüúyz Ï  Ñ z{þ ‡îPô!@ @MQE+5467%67.'%.=!5!ôüú¹)81þGüúJþ…z Ñ  Ï zûÀ‡€ÿ‰ò"@ BMQE+3 #>7 &'€†|†þz|ò þè ½5üËüÌ4ý»&&EF+#&ÿþþ©Á@CD+3#ÁøèË¿!a@ BK°2PX@SCQC D@YSC DY@!!U%+3'.=354>32#"&#"!#!ºp :t­s&O ±¡d²þT] I8]›p> ] “”3ü`ü ò´µBK°&PX@SCQC DK°2PX@"CSCQC D@ YCSC DYY@A!% +3'.=354>32;#.#"!!ºp 4hœhS™Hd²6m(‚Œþþ] I6T—pB úY*•†6ü Ìþ•Šÿ° @SD  +2+h  >1P 2H4ÿÔ Äö @ja  +2#"&'% Ó‹þÙö Ë Ùÿðvò'@OSG((($+#".54>32#".54>32Ì)'')ª((((‚''))''))D'"‘@MQE+!!DÞþ"‘j¢ ’ö @ja #++7>3’þÚŠÓ öØ Ë ÿì xÜ@Bja* +#"&/&'+73x‡  ‚‚  ‡î° __Òÿì xÜ@ Bja,!+#'32>?>;Š°î‡  ‚‚  ‡ Ò^^÷JÜ (@%jOSG   +"&53326533‰ŽsNVVNs÷sr;==;i|¸¬@OSG($+#".54>32¬"-,!!,-"˜,!!,,"",vÍñ-!@[OSG$&($+4>32#".732654&#"v3D&'E55E'&D3Y6/-77-/6{'B//B'&@..@&+99+-88VÒ1@.O[SG +2673#".#"#4>32«#%b*>(#@;4"%d+?'#@:4)%+H5+$+I4N úë +@(OSG   #++7>3!+7>3¨ÇVŽ&!Çó`³( ëÆ µÆ µÒ|¢à @SD  +2+   >9à !;ZBG ¨ÝCð_<õÐÊ“^pÍÓ¡ãÿDþ“¹- ¶þV ÿDÿC¹'-‚®Ú˜ˆ6ˆj$H~R̘X†XJ `ˆd¨^¶d¨Xêÿôˆ<ˆÊˆhˆlˆ(ˆlˆlˆnˆ`ˆ”ø€ø€ˆ”ˆ–ˆî"lVP ®ZZ⮊®l®¼Zè®fÒx<R®0®è®<\ÆÂ<\Â$:œ´ PöêàVXŽîÿìXZˆžf&ö\^˜¦J^HJ¢þ2X’‚üÿȘ¦j’X’XHP’^H&’d>ê,XzüðœFX,XæXXˆt‚®ÚˆŠˆ4ˆ„ˆ,Xæîrf<D¬\žŠˆ”¶d<DfFˆd˜R˜TfÄXz:*"|f„˜xúHž–ff’D,P P P P P P BÿèZZЮЮЮЮfÿÌfšfÿïfÿò*2è®<\<\<\<\<\ˆ~<\´ ´ ´ ´ êÆÂºö\ö\ö\ö\ö\ö\`\¦JJJJJÿù—ÿÒÿáRLX’XHXHXHXHXHˆdX@XzXzXzXzP’P ö\ZZ¦J¤®J¦>,‚6è®X’\ÆHH:d>$:d>êàVœFàVœFàVœFˆjffff f´fjf†ff^Ô0Xœjœ¨:¨Z¨ZØ:ØZØZˆvˆvˆÐ®X HxŠx–fÿDˆ" @ÖVˆ\pX>XTˆ”z.X: ÿ–ˆ°ˆ–ˆ”ˆîˆ€ÿþt¤fÌfÿÔfÿðfDf¢fÿìfÿìff¸fvffNfÒlà#` Ò ÿJÿÿNÿÿJ#ÿÒ$ÿJ&ÿÒ*ÿÒ2ÿÒ4ÿÒ90:0<?0DÿÀFÿ¤Gÿ¤Hÿ¤Rÿ¤Tÿ¤mÿNoÿNyÿN}ÿN‚ÿJƒÿJ„ÿJ…ÿJ†ÿJ‡ÿJˆÿJ‰ÿÒ”ÿÒ•ÿÒ–ÿÒ—ÿÒ˜ÿÒšÿÒŸ¢ÿÀ£ÿÀ¤ÿÀ¥ÿÀ¦ÿÀ§ÿÀ¨ÿÀ©ÿ¤ªÿ¤«ÿ¤¬ÿ¤­ÿ¤²ÿ¤´ÿ¤µÿ¤¶ÿ¤·ÿ¤¸ÿ¤ºÿ¤ÂÿJÃÿÀÄÿÒÅÿ¤Çÿ¤ÍÿÒÎÿ¤ÓåÿNæÿNéÿìÿïÿNòÿNóÿNùÿJ ÿJ ÿ ÿN ÿ ÿJ #ÿÒ $ÿJ &ÿÒ *ÿÒ 2ÿÒ 4ÿÒ 90 :0 < ?0 DÿÀ Fÿ¤ Gÿ¤ Hÿ¤ Rÿ¤ Tÿ¤ mÿN oÿN yÿN }ÿN ‚ÿJ ƒÿJ „ÿJ …ÿJ †ÿJ ‡ÿJ ˆÿJ ‰ÿÒ ”ÿÒ •ÿÒ –ÿÒ —ÿÒ ˜ÿÒ šÿÒ Ÿ ¢ÿÀ £ÿÀ ¤ÿÀ ¥ÿÀ ¦ÿÀ §ÿÀ ¨ÿÀ ©ÿ¤ ªÿ¤ «ÿ¤ ¬ÿ¤ ­ÿ¤ ²ÿ¤ ´ÿ¤ µÿ¤ ¶ÿ¤ ·ÿ¤ ¸ÿ¤ ºÿ¤ ÂÿJ ÃÿÀ ÄÿÒ Åÿ¤ Çÿ¤ ÍÿÒ Îÿ¤ Ó åÿN æÿN éÿ ìÿ ïÿN òÿN óÿN ùÿJ #ÿØ &ÿØ *ÿØ 2ÿØ 4ÿØ Fÿà Gÿà Hÿà Rÿà Tÿà ‰ÿØ ”ÿØ •ÿØ –ÿØ —ÿØ ˜ÿØ šÿØ ©ÿà ªÿà «ÿà ¬ÿà ­ÿà ²ÿà ´ÿà µÿà ¶ÿà ·ÿà ¸ÿà ºÿà ÄÿØ Åÿà Çÿà ÍÿØ Îÿà ÿJ ÿ ÿN ÿ ÿJ #ÿÒ $ÿJ &ÿÒ *ÿÒ 2ÿÒ 4ÿÒ 90 :0 < ?0 DÿÀ Fÿ¤ Gÿ¤ Hÿ¤ Rÿ¤ Tÿ¤ mÿN oÿN yÿN }ÿN ‚ÿJ ƒÿJ „ÿJ …ÿJ †ÿJ ‡ÿJ ˆÿJ ‰ÿÒ ”ÿÒ •ÿÒ –ÿÒ —ÿÒ ˜ÿÒ šÿÒ Ÿ ¢ÿÀ £ÿÀ ¤ÿÀ ¥ÿÀ ¦ÿÀ §ÿÀ ¨ÿÀ ©ÿ¤ ªÿ¤ «ÿ¤ ¬ÿ¤ ­ÿ¤ ²ÿ¤ ´ÿ¤ µÿ¤ ¶ÿ¤ ·ÿ¤ ¸ÿ¤ ºÿ¤ ÂÿJ ÃÿÀ ÄÿÒ Åÿ¤ Çÿ¤ ÍÿÒ Îÿ¤ Ó åÿN æÿN éÿ ìÿ ïÿN òÿN óÿN ùÿJÿ ÿ ÿÿx#ÿÈ&ÿÈ*ÿÈ2ÿÈ4ÿÈ7ÿL9ÿL:ÿ†<ÿh?ÿLYÿ|ZÿÂ\ÿ|lÿmÿxoÿxrÿyÿx|ÿ}ÿx‰ÿÈ”ÿÈ•ÿÈ–ÿÈ—ÿȘÿÈšÿÈŸÿhÄÿÈÍÿÈÓÿhåÿxæÿxçÿèÿêÿëÿïÿxòÿxóÿxöÿÿN ÿÌ ÿN ÿNÿxÿxÿÌ$ÿÌ7ÿL9ÿ:ÿà;ÿÂ<ÿ`=ÿÒ?ÿlÿNrÿN|ÿN‚ÿ̃ÿÌ„ÿÌ…ÿ̆ÿ̇ÿ̈ÿÌŸÿ`ÂÿÌÓÿ`ÔÿÒÖÿÒØÿÒçÿNèÿNéÿxêÿNëÿNìÿxöÿNùÿÌÿ ÿ ÿÿx#ÿÈ&ÿÈ*ÿÈ2ÿÈ4ÿÈ7ÿL9ÿL:ÿ†<ÿh?ÿLYÿ|ZÿÂ\ÿ|lÿmÿxoÿxrÿyÿx|ÿ}ÿx‰ÿÈ”ÿÈ•ÿÈ–ÿÈ—ÿȘÿÈšÿÈŸÿhÄÿÈÍÿÈÓÿhåÿxæÿxçÿèÿêÿëÿïÿxòÿxóÿxöÿ0 ÿx 0 0ÿ@ÿÿ@ÿxÿ¨ÿ¨"0#ÿÌ$ÿx&ÿÌ*ÿÌ-ÿh2ÿÌ4ÿÌDÿŒFÿŒGÿŒHÿŒIÿâJÿxPÿ¨Qÿ¨RÿŒSÿ¨TÿŒUÿ¨Vÿ–WÿÖXÿ¨YÿÐ[ÿÌ\ÿÐ]ÿ®l0mÿoÿr0t:u:wÿ¨yÿ{:|0}ÿ‚ÿxƒÿx„ÿx…ÿx†ÿx‡ÿxˆÿx‰ÿÌ”ÿÌ•ÿÌ–ÿÌ—ÿ̘ÿÌšÿÌ¢ÿŒ£ÿŒ¤ÿŒ¥ÿŒ¦ÿŒ§ÿŒ¨ÿŒ©ÿŒªÿŒ«ÿŒ¬ÿŒ­ÿŒ²ÿŒ³ÿ¨´ÿŒµÿŒ¶ÿŒ·ÿŒ¸ÿŒºÿŒ»ÿ¨¼ÿ¨½ÿ¨¾ÿ¨ÂÿxÃÿŒÄÿÌÅÿŒÇÿŒÌÿ¨ÍÿÌÎÿŒåÿæÿç0è0éÿ@ê0ë0ìÿ@ïÿòÿóÿö0ùÿx#ÿÒ# ÿÖ# ÿÒ# ÿØ# ÿÒ#ÿÈ#ÿÈ#ÿÖ#$ÿÖ#7ÿž#9ÿÌ#;ÿâ#<ÿ°#=ÿº#?ÿÌ#@ÿØ#`ÿØ#lÿÒ#rÿÒ#|ÿÒ#‚ÿÖ#ƒÿÖ#„ÿÖ#…ÿÖ#†ÿÖ#‡ÿÖ#ˆÿÖ#Ÿÿ°#ÂÿÖ#Óÿ°#Ôÿº#Öÿº#Øÿº#çÿÒ#èÿÒ#éÿÈ#êÿÒ#ëÿÒ#ìÿÈ#öÿÒ#ùÿÖ$ÿJ$ ÿJ$ ÿJ$ÿÌ$"ÿÈ$#ÿÖ$&ÿÖ$*ÿÖ$-2$2ÿÖ$4ÿÖ$7ÿ|$8ÿÈ$9ÿx$:ÿ¬$<ÿ\$?ÿx$Yÿ®$\ÿ®$lÿJ$mÿÌ$oÿÌ$rÿJ$tÿH$uÿH$yÿÌ${ÿH$|ÿJ$}ÿÌ$‰ÿÖ$”ÿÖ$•ÿÖ$–ÿÖ$—ÿÖ$˜ÿÖ$šÿÖ$›ÿÈ$œÿÈ$ÿÈ$žÿÈ$Ÿÿ\$ÄÿÖ$ÍÿÖ$Óÿ\$åÿÌ$æÿÌ$çÿJ$èÿJ$êÿJ$ëÿJ$ïÿÌ$òÿÌ$óÿÌ$öÿJ&ÿj&mÿj&oÿj&yÿj&}ÿj&åÿj&æÿj&ïÿj&òÿj&óÿj'ÿÒ' ÿÖ' ÿÒ' ÿØ' ÿÒ'ÿÈ'ÿÈ'ÿÖ'$ÿÖ'7ÿž'9ÿÌ';ÿâ'<ÿ°'=ÿº'?ÿÌ'@ÿØ'`ÿØ'lÿÒ'rÿÒ'|ÿÒ'‚ÿÖ'ƒÿÖ'„ÿÖ'…ÿÖ'†ÿÖ'‡ÿÖ'ˆÿÖ'Ÿÿ°'ÂÿÖ'Óÿ°'Ôÿº'Öÿº'Øÿº'çÿÒ'èÿÒ'éÿÈ'êÿÒ'ëÿÒ'ìÿÈ'öÿÒ'ùÿÖ) ÿ|)ÿL)ÿL)ÿ|)ÿÄ)ÿÄ)")$ÿ|)-ÿ:)Fÿº)Gÿº)Hÿº)PÿÄ)QÿÄ)Rÿº)SÿÄ)Tÿº)UÿÄ)XÿÄ)wÿÄ)‚ÿ|)ƒÿ|)„ÿ|)…ÿ|)†ÿ|)‡ÿ|)ˆÿ|)©ÿº)ªÿº)«ÿº)¬ÿº)­ÿº)²ÿº)³ÿÄ)´ÿº)µÿº)¶ÿº)·ÿº)¸ÿº)ºÿº)»ÿÄ)¼ÿÄ)½ÿÄ)¾ÿÄ)Âÿ|)Åÿº)Çÿº)ÌÿÄ)Îÿº)éÿL)ìÿL)ùÿ|- ÿÈ-ÿÎ-ÿÎ-ÿÈ-$ÿÈ-‚ÿÈ-ƒÿÈ-„ÿÈ-…ÿÈ-†ÿÈ-‡ÿÈ-ˆÿÈ-ÂÿÈ-éÿÎ-ìÿÎ-ùÿÈ.ÿÂ.#ÿâ.&ÿâ.*ÿâ.2ÿâ.4ÿâ.FÿÜ.GÿÜ.HÿÜ.IÿÌ.RÿÜ.TÿÜ.Wÿ®.Yÿ¾.ZÿÈ.\ÿ¾.mÿÂ.oÿÂ.yÿÂ.}ÿÂ.‰ÿâ.”ÿâ.•ÿâ.–ÿâ.—ÿâ.˜ÿâ.šÿâ.©ÿÜ.ªÿÜ.«ÿÜ.¬ÿÜ.­ÿÜ.²ÿÜ.´ÿÜ.µÿÜ.¶ÿÜ.·ÿÜ.¸ÿÜ.ºÿÜ.Äÿâ.ÅÿÜ.ÇÿÜ.Íÿâ.ÎÿÜ.åÿÂ.æÿÂ.ïÿÂ.òÿÂ.óÿÂ/þÞ/ þÞ/ þÞ/6/ÿ:/6/"ÿÎ/#ÿ°/&ÿ°/*ÿ°/2ÿ°/4ÿ°/7ÿT/9ÿJ/:ÿh/<ÿ,/?ÿJ/FÿÜ/GÿÜ/HÿÜ/RÿÜ/TÿÜ/Yÿ”/Zÿ°/\ÿ”/lþÞ/mÿ:/oÿ:/rþÞ/tÿ6/uÿ6/yÿ:/{ÿ6/|þÞ/}ÿ:/‰ÿ°/”ÿ°/•ÿ°/–ÿ°/—ÿ°/˜ÿ°/šÿ°/Ÿÿ,/©ÿÜ/ªÿÜ/«ÿÜ/¬ÿÜ/­ÿÜ/²ÿÜ/´ÿÜ/µÿÜ/¶ÿÜ/·ÿÜ/¸ÿÜ/ºÿÜ/Äÿ°/ÅÿÜ/ÇÿÜ/Íÿ°/ÎÿÜ/Óÿ,/åÿ:/æÿ:/çþÞ/èþÞ/é6/êþÞ/ëþÞ/ì6/ïÿ:/òÿ:/óÿ:/öþÞ2ÿÒ2 ÿÖ2 ÿÒ2 ÿØ2 ÿÒ2ÿÈ2ÿÈ2ÿÖ2$ÿÖ27ÿž29ÿÌ2;ÿâ2<ÿ°2=ÿº2?ÿÌ2@ÿØ2`ÿØ2lÿÒ2rÿÒ2|ÿÒ2‚ÿÖ2ƒÿÖ2„ÿÖ2…ÿÖ2†ÿÖ2‡ÿÖ2ˆÿÖ2Ÿÿ°2ÂÿÖ2Óÿ°2Ôÿº2Öÿº2Øÿº2çÿÒ2èÿÒ2éÿÈ2êÿÒ2ëÿÒ2ìÿÈ2öÿÒ2ùÿÖ3 ÿv3ÿ3ÿ3ÿv3$ÿv3-ÿJ3DÿÎ3Fÿâ3Gÿâ3Hÿâ3Rÿâ3Tÿâ3‚ÿv3ƒÿv3„ÿv3…ÿv3†ÿv3‡ÿv3ˆÿv3¢ÿÎ3£ÿÎ3¤ÿÎ3¥ÿÎ3¦ÿÎ3§ÿÎ3¨ÿÎ3©ÿâ3ªÿâ3«ÿâ3¬ÿâ3­ÿâ3²ÿâ3´ÿâ3µÿâ3¶ÿâ3·ÿâ3¸ÿâ3ºÿâ3Âÿv3ÃÿÎ3Åÿâ3Çÿâ3Îÿâ3éÿ3ìÿ3ùÿv4ÿÒ4 ÿÖ4 ÿÒ4 ÿØ4 ÿÒ4ÿÈ4ÿÈ4ÿÖ4$ÿÖ47ÿž49ÿÌ4;ÿâ4<ÿ°4=ÿº4?ÿÌ4@ÿØ4`ÿØ4lÿÒ4rÿÒ4|ÿÒ4‚ÿÖ4ƒÿÖ4„ÿÖ4…ÿÖ4†ÿÖ4‡ÿÖ4ˆÿÖ4Ÿÿ°4ÂÿÖ4Óÿ°4Ôÿº4Öÿº4Øÿº4çÿÒ4èÿÒ4éÿÈ4êÿÒ4ëÿÒ4ìÿÈ4öÿÒ4ùÿÖ5#ÿÒ5&ÿÒ5*ÿÒ52ÿÒ54ÿÒ57ÿÌ58ÿÖ5‰ÿÒ5”ÿÒ5•ÿÒ5–ÿÒ5—ÿÒ5˜ÿÒ5šÿÒ5›ÿÖ5œÿÖ5ÿÖ5žÿÖ5ÄÿÒ5ÍÿÒ7 ÿ|7ÿL7ÿL7ÿL7ÿ|7ÿ`7ÿ`7#ÿž7$ÿ|7&ÿž7*ÿž7-ÿ872ÿž74ÿž7Dÿ7Fÿ.7Gÿ.7Hÿ.7JÿD7Pÿ`7Qÿ`7Rÿ.7Sÿ`7Tÿ.7Uÿ`7Vÿ^7Xÿ`7YÿL7Zÿt7[ÿp7\ÿL7]ÿˆ7mÿL7oÿL7wÿ`7yÿL7}ÿL7‚ÿ|7ƒÿ|7„ÿ|7…ÿ|7†ÿ|7‡ÿ|7ˆÿ|7‰ÿž7”ÿž7•ÿž7–ÿž7—ÿž7˜ÿž7šÿž7¢ÿ7£ÿ7¤ÿ7¥ÿ7¦ÿ7§ÿ7¨ÿ7©ÿ.7ªÿ.7«ÿ.7¬ÿ.7­ÿ.7²ÿ.7³ÿ`7´ÿ.7µÿ.7¶ÿ.7·ÿ.7¸ÿ.7ºÿ.7»ÿ`7¼ÿ`7½ÿ`7¾ÿ`7Âÿ|7Ãÿ7Äÿž7Åÿ.7Çÿ.7Ìÿ`7Íÿž7Îÿ.7åÿL7æÿL7éÿL7ìÿL7ïÿL7òÿL7óÿL7ùÿ|8 ÿÈ8ÿÎ8ÿÎ8ÿÈ8$ÿÈ8‚ÿÈ8ƒÿÈ8„ÿÈ8…ÿÈ8†ÿÈ8‡ÿÈ8ˆÿÈ8ÂÿÈ8éÿÎ8ìÿÎ8ùÿÈ909 ÿx9 09 09ÿ@9ÿ9ÿ@9ÿx9ÿ¨9ÿ¨9"09#ÿÌ9$ÿx9&ÿÌ9*ÿÌ9-ÿh92ÿÌ94ÿÌ9DÿŒ9FÿŒ9GÿŒ9HÿŒ9Iÿâ9Jÿx9Pÿ¨9Qÿ¨9RÿŒ9Sÿ¨9TÿŒ9Uÿ¨9Vÿ–9WÿÖ9Xÿ¨9YÿÐ9[ÿÌ9\ÿÐ9]ÿ®9l09mÿ9oÿ9r09t:9u:9wÿ¨9yÿ9{:9|09}ÿ9‚ÿx9ƒÿx9„ÿx9…ÿx9†ÿx9‡ÿx9ˆÿx9‰ÿÌ9”ÿÌ9•ÿÌ9–ÿÌ9—ÿÌ9˜ÿÌ9šÿÌ9¢ÿŒ9£ÿŒ9¤ÿŒ9¥ÿŒ9¦ÿŒ9§ÿŒ9¨ÿŒ9©ÿŒ9ªÿŒ9«ÿŒ9¬ÿŒ9­ÿŒ9²ÿŒ9³ÿ¨9´ÿŒ9µÿŒ9¶ÿŒ9·ÿŒ9¸ÿŒ9ºÿŒ9»ÿ¨9¼ÿ¨9½ÿ¨9¾ÿ¨9Âÿx9ÃÿŒ9ÄÿÌ9ÅÿŒ9ÇÿŒ9Ìÿ¨9ÍÿÌ9ÎÿŒ9åÿ9æÿ9ç09è09éÿ@9ê09ë09ìÿ@9ïÿ9òÿ9óÿ9ö09ùÿx:0: ÿ¢: 0: 0:ÿ†:ÿà:ÿ†:ÿ¢:"":$ÿ¢:-ÿš:Dÿ¨:Fÿà:Gÿà:Hÿà:Jÿž:Rÿà:Tÿà:VÿÒ:l0:mÿà:oÿà:r0:t0:u0:yÿà:{0:|0:}ÿà:‚ÿ¢:ƒÿ¢:„ÿ¢:…ÿ¢:†ÿ¢:‡ÿ¢:ˆÿ¢:¢ÿ¨:£ÿ¨:¤ÿ¨:¥ÿ¨:¦ÿ¨:§ÿ¨:¨ÿ¨:©ÿà:ªÿà:«ÿà:¬ÿà:­ÿà:²ÿà:´ÿà:µÿà:¶ÿà:·ÿà:¸ÿà:ºÿà:Âÿ¢:Ãÿ¨:Åÿà:Çÿà:Îÿà:åÿà:æÿà:ç0:è0:éÿ†:ê0:ë0:ìÿ†:ïÿà:òÿà:óÿà:ö0:ùÿ¢;ÿÂ;#ÿâ;&ÿâ;*ÿâ;2ÿâ;4ÿâ;FÿÜ;GÿÜ;HÿÜ;IÿÌ;RÿÜ;TÿÜ;Wÿ®;Yÿ¾;ZÿÈ;\ÿ¾;mÿÂ;oÿÂ;yÿÂ;}ÿÂ;‰ÿâ;”ÿâ;•ÿâ;–ÿâ;—ÿâ;˜ÿâ;šÿâ;©ÿÜ;ªÿÜ;«ÿÜ;¬ÿÜ;­ÿÜ;²ÿÜ;´ÿÜ;µÿÜ;¶ÿÜ;·ÿÜ;¸ÿÜ;ºÿÜ;Äÿâ;ÅÿÜ;ÇÿÜ;Íÿâ;ÎÿÜ;åÿÂ;æÿÂ;ïÿÂ;òÿÂ;óÿÂ<< ÿ\< < <ÿh<ÿ`<ÿh<ÿ\<ÿ†<ÿ†<""<#ÿ°<$ÿ\<&ÿ°<*ÿ°<-ÿ8<2ÿ°<4ÿ°<Dÿ€<Fÿ`<Gÿ`<Hÿ`<JÿT<Pÿ†<Qÿ†<Rÿ`<Sÿ†<Tÿ`<Uÿ†<Vÿ€<Xÿ†<Yÿœ<Zÿ¤<[ÿ|<\ÿœ<l<mÿ`<oÿ`<r<t2<u2<wÿ†<yÿ`<{2<|<}ÿ`<‚ÿ\<ƒÿ\<„ÿ\<…ÿ\<†ÿ\<‡ÿ\<ˆÿ\<‰ÿ°<”ÿ°<•ÿ°<–ÿ°<—ÿ°<˜ÿ°<šÿ°<¢ÿ€<£ÿ€<¤ÿ€<¥ÿ€<¦ÿ€<§ÿ€<¨ÿ€<©ÿ`<ªÿ`<«ÿ`<¬ÿ`<­ÿ`<²ÿ`<³ÿ†<´ÿ`<µÿ`<¶ÿ`<·ÿ`<¸ÿ`<ºÿ`<»ÿ†<¼ÿ†<½ÿ†<¾ÿ†<Âÿ\<Ãÿ€<Äÿ°<Åÿ`<Çÿ`<Ìÿ†<Íÿ°<Îÿ`<åÿ`<æÿ`<ç<è<éÿh<ê<ë<ìÿh<ïÿ`<òÿ`<óÿ`<ö<ùÿ\=ÿº=""=#ÿÆ=&ÿÆ=*ÿÆ=2ÿÆ=4ÿÆ=FÿÚ=GÿÚ=HÿÚ=RÿÚ=TÿÚ=Vÿä=YÿØ=\ÿØ=mÿº=oÿº=yÿº=}ÿº=‰ÿÆ=”ÿÆ=•ÿÆ=–ÿÆ=—ÿÆ=˜ÿÆ=šÿÆ=©ÿÚ=ªÿÚ=«ÿÚ=¬ÿÚ=­ÿÚ=²ÿÚ=´ÿÚ=µÿÚ=¶ÿÚ=·ÿÚ=¸ÿÚ=ºÿÚ=ÄÿÆ=ÅÿÚ=ÇÿÚ=ÍÿÆ=ÎÿÚ=åÿº=æÿº=ïÿº=òÿº=óÿº>#ÿØ>&ÿØ>*ÿØ>2ÿØ>4ÿØ>Fÿà>Gÿà>Hÿà>Rÿà>Tÿà>‰ÿØ>”ÿØ>•ÿØ>–ÿØ>—ÿØ>˜ÿØ>šÿØ>©ÿà>ªÿà>«ÿà>¬ÿà>­ÿà>²ÿà>´ÿà>µÿà>¶ÿà>·ÿà>¸ÿà>ºÿà>ÄÿØ>Åÿà>Çÿà>ÍÿØ>Îÿà?ÿJ? ÿJ? ÿJ?ÿÌ?"ÿÈ?#ÿÖ?&ÿÖ?*ÿÖ?-2?2ÿÖ?4ÿÖ?7ÿ|?8ÿÈ?9ÿx?:ÿ¬?<ÿ\??ÿx?Yÿ®?\ÿ®?lÿJ?mÿÌ?oÿÌ?rÿJ?tÿH?uÿH?yÿÌ?{ÿH?|ÿJ?}ÿÌ?‰ÿÖ?”ÿÖ?•ÿÖ?–ÿÖ?—ÿÖ?˜ÿÖ?šÿÖ?›ÿÈ?œÿÈ?ÿÈ?žÿÈ?Ÿÿ\?ÄÿÖ?ÍÿÖ?Óÿ\?åÿÌ?æÿÌ?çÿJ?èÿJ?êÿJ?ëÿJ?ïÿÌ?òÿÌ?óÿÌ?öÿJDÿ¸D ÿ¸D ÿ¸DYÿàDZÿðD\ÿàDlÿ¸Drÿ¸Dtÿ¸Duÿ¸D{ÿ¸D|ÿ¸Dçÿ¸Dèÿ¸Dêÿ¸Dëÿ¸Döÿ¸Eÿ¤E ÿ¤E ÿàE ÿ¤E9ÿŒE:ÿàE?ÿŒE@ÿàEYÿæE[ÿÄE\ÿæE`ÿàElÿ¤Erÿ¤E|ÿ¤Eçÿ¤Eèÿ¤Eêÿ¤Eëÿ¤Eöÿ¤Hÿ¤H ÿ¤H ÿàH ÿ¤H9ÿŒH:ÿàH?ÿŒH@ÿàHYÿæH[ÿÄH\ÿæH`ÿàHlÿ¤Hrÿ¤H|ÿ¤Hçÿ¤Hèÿ¤Hêÿ¤Hëÿ¤Höÿ¤IDI DI DIÿ~Iÿ~IlDIrDItdIudI{dI|DIçDIèDIéÿ~IêDIëDIìÿ~IöDKÿ¸K ÿ¸K ÿ¸KYÿàKZÿðK\ÿàKlÿ¸Krÿ¸Ktÿ¸Kuÿ¸K{ÿ¸K|ÿ¸Kçÿ¸Kèÿ¸Kêÿ¸Këÿ¸Köÿ¸NFÿÄNGÿÄNHÿÄNRÿÄNTÿÄN©ÿÄNªÿÄN«ÿÄN¬ÿÄN­ÿÄN²ÿÄN´ÿÄNµÿÄN¶ÿÄN·ÿÄN¸ÿÄNºÿÄNÅÿÄNÇÿÄNÎÿÄPÿ¸P ÿ¸P ÿ¸PYÿàPZÿðP\ÿàPlÿ¸Prÿ¸Ptÿ¸Puÿ¸P{ÿ¸P|ÿ¸Pçÿ¸Pèÿ¸Pêÿ¸Pëÿ¸Pöÿ¸Qÿ¸Q ÿ¸Q ÿ¸QYÿàQZÿðQ\ÿàQlÿ¸Qrÿ¸Qtÿ¸Quÿ¸Q{ÿ¸Q|ÿ¸Qçÿ¸Qèÿ¸Qêÿ¸Qëÿ¸Qöÿ¸Rÿ¤R ÿ¤R ÿàR ÿ¤R9ÿŒR:ÿàR?ÿŒR@ÿàRYÿæR[ÿÄR\ÿæR`ÿàRlÿ¤Rrÿ¤R|ÿ¤Rçÿ¤Rèÿ¤Rêÿ¤Rëÿ¤Röÿ¤Sÿ¤S ÿ¤S ÿàS ÿ¤S9ÿŒS:ÿàS?ÿŒS@ÿàSYÿæS[ÿÄS\ÿæS`ÿàSlÿ¤Srÿ¤S|ÿ¤Sçÿ¤Sèÿ¤Sêÿ¤Sëÿ¤Söÿ¤Uÿ|Uÿ|UDÿÚU¢ÿÚU£ÿÚU¤ÿÚU¥ÿÚU¦ÿÚU§ÿÚU¨ÿÚUÃÿÚUéÿ|Uìÿ|Y ÿ®Yÿ|Yÿ|Yÿ®Y$ÿ®YFÿæYGÿæYHÿæYRÿæYTÿæY‚ÿ®Yƒÿ®Y„ÿ®Y…ÿ®Y†ÿ®Y‡ÿ®Yˆÿ®Y©ÿæYªÿæY«ÿæY¬ÿæY­ÿæY²ÿæY´ÿæYµÿæY¶ÿæY·ÿæY¸ÿæYºÿæYÂÿ®YÅÿæYÇÿæYÎÿæYéÿ|Yìÿ|Yùÿ®ZÿÂZÿÂZéÿÂZìÿÂ[FÿÄ[GÿÄ[HÿÄ[RÿÄ[TÿÄ[©ÿÄ[ªÿÄ[«ÿÄ[¬ÿÄ[­ÿÄ[²ÿÄ[´ÿÄ[µÿÄ[¶ÿÄ[·ÿÄ[¸ÿÄ[ºÿÄ[ÅÿÄ[ÇÿÄ[ÎÿÄ\ ÿ®\ÿ|\ÿ|\ÿ®\$ÿ®\Fÿæ\Gÿæ\Hÿæ\Rÿæ\Tÿæ\‚ÿ®\ƒÿ®\„ÿ®\…ÿ®\†ÿ®\‡ÿ®\ˆÿ®\©ÿæ\ªÿæ\«ÿæ\¬ÿæ\­ÿæ\²ÿæ\´ÿæ\µÿæ\¶ÿæ\·ÿæ\¸ÿæ\ºÿæ\Âÿ®\Åÿæ\Çÿæ\Îÿæ\éÿ|\ìÿ|\ùÿ®^#ÿØ^&ÿØ^*ÿØ^2ÿØ^4ÿØ^Fÿà^Gÿà^Hÿà^Rÿà^Tÿà^‰ÿØ^”ÿØ^•ÿØ^–ÿØ^—ÿØ^˜ÿØ^šÿØ^©ÿà^ªÿà^«ÿà^¬ÿà^­ÿà^²ÿà^´ÿà^µÿà^¶ÿà^·ÿà^¸ÿà^ºÿà^ÄÿØ^Åÿà^Çÿà^ÍÿØ^Îÿàl ÿJlÿlÿNlÿlÿJl#ÿÒl$ÿJl&ÿÒl*ÿÒl2ÿÒl4ÿÒl90l:0l<l?0lDÿÀlFÿ¤lGÿ¤lHÿ¤lRÿ¤lTÿ¤lmÿNloÿNlyÿNl}ÿNl‚ÿJlƒÿJl„ÿJl…ÿJl†ÿJl‡ÿJlˆÿJl‰ÿÒl”ÿÒl•ÿÒl–ÿÒl—ÿÒl˜ÿÒlšÿÒlŸl¢ÿÀl£ÿÀl¤ÿÀl¥ÿÀl¦ÿÀl§ÿÀl¨ÿÀl©ÿ¤lªÿ¤l«ÿ¤l¬ÿ¤l­ÿ¤l²ÿ¤l´ÿ¤lµÿ¤l¶ÿ¤l·ÿ¤l¸ÿ¤lºÿ¤lÂÿJlÃÿÀlÄÿÒlÅÿ¤lÇÿ¤lÍÿÒlÎÿ¤lÓlåÿNlæÿNléÿlìÿlïÿNlòÿNlóÿNlùÿJmÿNm ÿÌm ÿNm ÿNmÿxmÿxmÿÌm$ÿÌm7ÿLm9ÿm:ÿàm;ÿÂm<ÿ`m=ÿÒm?ÿmlÿNmrÿNm|ÿNm‚ÿÌmƒÿÌm„ÿÌm…ÿÌm†ÿÌm‡ÿÌmˆÿÌmŸÿ`mÂÿÌmÓÿ`mÔÿÒmÖÿÒmØÿÒmçÿNmèÿNméÿxmêÿNmëÿNmìÿxmöÿNmùÿÌoÿNo ÿÌo ÿNo ÿNoÿxoÿxoÿÌo$ÿÌo7ÿLo9ÿo:ÿào;ÿÂo<ÿ`o=ÿÒo?ÿolÿNorÿNo|ÿNo‚ÿÌoƒÿÌo„ÿÌo…ÿÌo†ÿÌo‡ÿÌoˆÿÌoŸÿ`oÂÿÌoÓÿ`oÔÿÒoÖÿÒoØÿÒoçÿNoèÿNoéÿxoêÿNoëÿNoìÿxoöÿNoùÿÌpÿÒp ÿÖp ÿÒp ÿØp ÿÒpÿÈpÿÈpÿÖp$ÿÖp7ÿžp9ÿÌp;ÿâp<ÿ°p=ÿºp?ÿÌp@ÿØp`ÿØplÿÒprÿÒp|ÿÒp‚ÿÖpƒÿÖp„ÿÖp…ÿÖp†ÿÖp‡ÿÖpˆÿÖpŸÿ°pÂÿÖpÓÿ°pÔÿºpÖÿºpØÿºpçÿÒpèÿÒpéÿÈpêÿÒpëÿÒpìÿÈpöÿÒpùÿÖr ÿJrÿrÿNrÿrÿJr#ÿÒr$ÿJr&ÿÒr*ÿÒr2ÿÒr4ÿÒr90r:0r<r?0rDÿÀrFÿ¤rGÿ¤rHÿ¤rRÿ¤rTÿ¤rmÿNroÿNryÿNr}ÿNr‚ÿJrƒÿJr„ÿJr…ÿJr†ÿJr‡ÿJrˆÿJr‰ÿÒr”ÿÒr•ÿÒr–ÿÒr—ÿÒr˜ÿÒršÿÒrŸr¢ÿÀr£ÿÀr¤ÿÀr¥ÿÀr¦ÿÀr§ÿÀr¨ÿÀr©ÿ¤rªÿ¤r«ÿ¤r¬ÿ¤r­ÿ¤r²ÿ¤r´ÿ¤rµÿ¤r¶ÿ¤r·ÿ¤r¸ÿ¤rºÿ¤rÂÿJrÃÿÀrÄÿÒrÅÿ¤rÇÿ¤rÍÿÒrÎÿ¤rÓråÿNræÿNréÿrìÿrïÿNròÿNróÿNrùÿJt ÿHtÿHt$ÿHt9:t::t<(t?:t‚ÿHtƒÿHt„ÿHt…ÿHt†ÿHt‡ÿHtˆÿHtŸ(tÂÿHtÓ(tùÿHu ÿHuÿHu$ÿHu9:u::u<(u?:u‚ÿHuƒÿHu„ÿHu…ÿHu†ÿHu‡ÿHuˆÿHuŸ(uÂÿHuÓ(uùÿHyÿNy ÿÌy ÿNy ÿNyÿxyÿxyÿÌy$ÿÌy7ÿLy9ÿy:ÿày;ÿÂy<ÿ`y=ÿÒy?ÿylÿNyrÿNy|ÿNy‚ÿÌyƒÿÌy„ÿÌy…ÿÌy†ÿÌy‡ÿÌyˆÿÌyŸÿ`yÂÿÌyÓÿ`yÔÿÒyÖÿÒyØÿÒyçÿNyèÿNyéÿxyêÿNyëÿNyìÿxyöÿNyùÿÌ{ ÿH{ÿH{$ÿH{9:{::{<({?:{‚ÿH{ƒÿH{„ÿH{…ÿH{†ÿH{‡ÿH{ˆÿH{Ÿ({ÂÿH{Ó({ùÿH| ÿJ|ÿ|ÿN|ÿ|ÿJ|#ÿÒ|$ÿJ|&ÿÒ|*ÿÒ|2ÿÒ|4ÿÒ|90|:0|<|?0|DÿÀ|Fÿ¤|Gÿ¤|Hÿ¤|Rÿ¤|Tÿ¤|mÿN|oÿN|yÿN|}ÿN|‚ÿJ|ƒÿJ|„ÿJ|…ÿJ|†ÿJ|‡ÿJ|ˆÿJ|‰ÿÒ|”ÿÒ|•ÿÒ|–ÿÒ|—ÿÒ|˜ÿÒ|šÿÒ|Ÿ|¢ÿÀ|£ÿÀ|¤ÿÀ|¥ÿÀ|¦ÿÀ|§ÿÀ|¨ÿÀ|©ÿ¤|ªÿ¤|«ÿ¤|¬ÿ¤|­ÿ¤|²ÿ¤|´ÿ¤|µÿ¤|¶ÿ¤|·ÿ¤|¸ÿ¤|ºÿ¤|ÂÿJ|ÃÿÀ|ÄÿÒ|Åÿ¤|Çÿ¤|ÍÿÒ|Îÿ¤|Ó|åÿN|æÿN|éÿ|ìÿ|ïÿN|òÿN|óÿN|ùÿJ}ÿN} ÿÌ} ÿN} ÿN}ÿx}ÿx}ÿÌ}$ÿÌ}7ÿL}9ÿ}:ÿà};ÿÂ}<ÿ`}=ÿÒ}?ÿ}lÿN}rÿN}|ÿN}‚ÿÌ}ƒÿÌ}„ÿÌ}…ÿÌ}†ÿÌ}‡ÿÌ}ˆÿÌ}Ÿÿ`}ÂÿÌ}Óÿ`}ÔÿÒ}ÖÿÒ}ØÿÒ}çÿN}èÿN}éÿx}êÿN}ëÿN}ìÿx}öÿN}ùÿÌ‚ÿJ‚ ÿJ‚ ÿJ‚ÿÌ‚"ÿÈ‚#ÿÖ‚&ÿÖ‚*ÿÖ‚-2‚2ÿÖ‚4ÿÖ‚7ÿ|‚8ÿÈ‚9ÿx‚:ÿ¬‚<ÿ\‚?ÿx‚Yÿ®‚\ÿ®‚lÿJ‚mÿÌ‚oÿÌ‚rÿJ‚tÿH‚uÿH‚yÿÌ‚{ÿH‚|ÿJ‚}ÿÌ‚‰ÿÖ‚”ÿÖ‚•ÿÖ‚–ÿÖ‚—ÿÖ‚˜ÿÖ‚šÿÖ‚›ÿÈ‚œÿÈ‚ÿÈ‚žÿÈ‚Ÿÿ\‚ÄÿÖ‚ÍÿÖ‚Óÿ\‚åÿÌ‚æÿÌ‚çÿJ‚èÿJ‚êÿJ‚ëÿJ‚ïÿÌ‚òÿÌ‚óÿÌ‚öÿJƒÿJƒ ÿJƒ ÿJƒÿ̃"ÿȃ#ÿÖƒ&ÿÖƒ*ÿÖƒ-2ƒ2ÿÖƒ4ÿÖƒ7ÿ|ƒ8ÿȃ9ÿxƒ:ÿ¬ƒ<ÿ\ƒ?ÿxƒYÿ®ƒ\ÿ®ƒlÿJƒmÿ̃oÿ̃rÿJƒtÿHƒuÿHƒyÿ̃{ÿHƒ|ÿJƒ}ÿ̃‰ÿÖƒ”ÿÖƒ•ÿÖƒ–ÿÖƒ—ÿÖƒ˜ÿÖƒšÿÖƒ›ÿȃœÿȃÿȃžÿȃŸÿ\ƒÄÿÖƒÍÿÖƒÓÿ\ƒåÿ̃æÿ̃çÿJƒèÿJƒêÿJƒëÿJƒïÿ̃òÿ̃óÿ̃öÿJ„ÿJ„ ÿJ„ ÿJ„ÿÌ„"ÿÈ„#ÿÖ„&ÿÖ„*ÿÖ„-2„2ÿÖ„4ÿÖ„7ÿ|„8ÿÈ„9ÿx„:ÿ¬„<ÿ\„?ÿx„Yÿ®„\ÿ®„lÿJ„mÿÌ„oÿÌ„rÿJ„tÿH„uÿH„yÿÌ„{ÿH„|ÿJ„}ÿÌ„‰ÿÖ„”ÿÖ„•ÿÖ„–ÿÖ„—ÿÖ„˜ÿÖ„šÿÖ„›ÿÈ„œÿÈ„ÿÈ„žÿÈ„Ÿÿ\„ÄÿÖ„ÍÿÖ„Óÿ\„åÿÌ„æÿÌ„çÿJ„èÿJ„êÿJ„ëÿJ„ïÿÌ„òÿÌ„óÿÌ„öÿJ…ÿJ… ÿJ… ÿJ…ÿÌ…"ÿÈ…#ÿÖ…&ÿÖ…*ÿÖ…-2…2ÿÖ…4ÿÖ…7ÿ|…8ÿÈ…9ÿx…:ÿ¬…<ÿ\…?ÿx…Yÿ®…\ÿ®…lÿJ…mÿÌ…oÿÌ…rÿJ…tÿH…uÿH…yÿÌ…{ÿH…|ÿJ…}ÿÌ…‰ÿÖ…”ÿÖ…•ÿÖ…–ÿÖ…—ÿÖ…˜ÿÖ…šÿÖ…›ÿÈ…œÿÈ…ÿÈ…žÿÈ…Ÿÿ\…ÄÿÖ…ÍÿÖ…Óÿ\…åÿÌ…æÿÌ…çÿJ…èÿJ…êÿJ…ëÿJ…ïÿÌ…òÿÌ…óÿÌ…öÿJ†ÿJ† ÿJ† ÿJ†ÿ̆"ÿȆ#ÿÖ†&ÿÖ†*ÿÖ†-2†2ÿÖ†4ÿÖ†7ÿ|†8ÿȆ9ÿx†:ÿ¬†<ÿ\†?ÿx†Yÿ®†\ÿ®†lÿJ†mÿ̆oÿ̆rÿJ†tÿH†uÿH†yÿ̆{ÿH†|ÿJ†}ÿ̆‰ÿÖ†”ÿÖ†•ÿÖ†–ÿÖ†—ÿÖ†˜ÿÖ†šÿÖ†›ÿȆœÿȆÿȆžÿȆŸÿ\†ÄÿÖ†ÍÿÖ†Óÿ\†åÿ̆æÿ̆çÿJ†èÿJ†êÿJ†ëÿJ†ïÿ̆òÿ̆óÿ̆öÿJ‡ÿJ‡ ÿJ‡ ÿJ‡ÿ̇"ÿȇ#ÿÖ‡&ÿÖ‡*ÿÖ‡-2‡2ÿÖ‡4ÿÖ‡7ÿ|‡8ÿȇ9ÿx‡:ÿ¬‡<ÿ\‡?ÿx‡Yÿ®‡\ÿ®‡lÿJ‡mÿ̇oÿ̇rÿJ‡tÿH‡uÿH‡yÿ̇{ÿH‡|ÿJ‡}ÿ̇‰ÿÖ‡”ÿÖ‡•ÿÖ‡–ÿÖ‡—ÿÖ‡˜ÿÖ‡šÿÖ‡›ÿȇœÿȇÿȇžÿȇŸÿ\‡ÄÿÖ‡ÍÿÖ‡Óÿ\‡åÿ̇æÿ̇çÿJ‡èÿJ‡êÿJ‡ëÿJ‡ïÿ̇òÿ̇óÿ̇öÿJ‰ÿj‰mÿj‰oÿj‰yÿj‰}ÿj‰åÿj‰æÿj‰ïÿj‰òÿj‰óÿj’ÿÒ’ ÿÖ’ ÿÒ’ ÿØ’ ÿÒ’ÿÈ’ÿÈ’ÿÖ’$ÿÖ’7ÿž’9ÿÌ’;ÿâ’<ÿ°’=ÿº’?ÿÌ’@ÿØ’`ÿØ’lÿÒ’rÿÒ’|ÿÒ’‚ÿÖ’ƒÿÖ’„ÿÖ’…ÿÖ’†ÿÖ’‡ÿÖ’ˆÿÖ’Ÿÿ°’ÂÿÖ’Óÿ°’Ôÿº’Öÿº’Øÿº’çÿÒ’èÿÒ’éÿÈ’êÿÒ’ëÿÒ’ìÿÈ’öÿÒ’ùÿÖ”ÿÒ” ÿÖ” ÿÒ” ÿØ” ÿÒ”ÿÈ”ÿÈ”ÿÖ”$ÿÖ”7ÿž”9ÿÌ”;ÿâ”<ÿ°”=ÿº”?ÿÌ”@ÿØ”`ÿØ”lÿÒ”rÿÒ”|ÿÒ”‚ÿÖ”ƒÿÖ”„ÿÖ”…ÿÖ”†ÿÖ”‡ÿÖ”ˆÿÖ”Ÿÿ°”ÂÿÖ”Óÿ°”Ôÿº”Öÿº”Øÿº”çÿÒ”èÿÒ”éÿÈ”êÿÒ”ëÿÒ”ìÿÈ”öÿÒ”ùÿÖ•ÿÒ• ÿÖ• ÿÒ• ÿØ• ÿÒ•ÿÈ•ÿÈ•ÿÖ•$ÿÖ•7ÿž•9ÿÌ•;ÿâ•<ÿ°•=ÿº•?ÿÌ•@ÿØ•`ÿØ•lÿÒ•rÿÒ•|ÿÒ•‚ÿÖ•ƒÿÖ•„ÿÖ•…ÿÖ•†ÿÖ•‡ÿÖ•ˆÿÖ•Ÿÿ°•ÂÿÖ•Óÿ°•Ôÿº•Öÿº•Øÿº•çÿÒ•èÿÒ•éÿÈ•êÿÒ•ëÿÒ•ìÿÈ•öÿÒ•ùÿÖ–ÿÒ– ÿÖ– ÿÒ– ÿØ– ÿÒ–ÿÈ–ÿÈ–ÿÖ–$ÿÖ–7ÿž–9ÿÌ–;ÿâ–<ÿ°–=ÿº–?ÿÌ–@ÿØ–`ÿØ–lÿÒ–rÿÒ–|ÿÒ–‚ÿÖ–ƒÿÖ–„ÿÖ–…ÿÖ–†ÿÖ–‡ÿÖ–ˆÿÖ–Ÿÿ°–ÂÿÖ–Óÿ°–Ôÿº–Öÿº–Øÿº–çÿÒ–èÿÒ–éÿÈ–êÿÒ–ëÿÒ–ìÿÈ–öÿÒ–ùÿÖ—ÿÒ— ÿÖ— ÿÒ— ÿØ— ÿÒ—ÿÈ—ÿÈ—ÿÖ—$ÿÖ—7ÿž—9ÿÌ—;ÿâ—<ÿ°—=ÿº—?ÿÌ—@ÿØ—`ÿØ—lÿÒ—rÿÒ—|ÿÒ—‚ÿÖ—ƒÿÖ—„ÿÖ—…ÿÖ—†ÿÖ—‡ÿÖ—ˆÿÖ—Ÿÿ°—ÂÿÖ—Óÿ°—Ôÿº—Öÿº—Øÿº—çÿÒ—èÿÒ—éÿÈ—êÿÒ—ëÿÒ—ìÿÈ—öÿÒ—ùÿÖ˜ÿÒ˜ ÿÖ˜ ÿÒ˜ ÿؘ ÿÒ˜ÿȘÿȘÿÖ˜$ÿÖ˜7ÿž˜9ÿ̘;ÿâ˜<ÿ°˜=ÿº˜?ÿ̘@ÿؘ`ÿؘlÿÒ˜rÿÒ˜|ÿÒ˜‚ÿÖ˜ƒÿÖ˜„ÿÖ˜…ÿÖ˜†ÿÖ˜‡ÿÖ˜ˆÿÖ˜Ÿÿ°˜ÂÿÖ˜Óÿ°˜Ôÿº˜Öÿº˜Øÿº˜çÿÒ˜èÿÒ˜éÿȘêÿÒ˜ëÿÒ˜ìÿȘöÿÒ˜ùÿÖ› ÿÈ›ÿΛÿΛÿÈ›$ÿÈ›‚ÿÈ›ƒÿÈ›„ÿÈ›…ÿÈ›†ÿÈ›‡ÿÈ›ˆÿÈ›ÂÿÈ›éÿΛìÿΛùÿÈœ ÿÈœÿΜÿΜÿÈœ$ÿÈœ‚ÿÈœƒÿÈœ„ÿÈœ…ÿÈœ†ÿÈœ‡ÿÈœˆÿÈœÂÿÈœéÿΜìÿΜùÿÈ ÿÈÿÎÿÎÿÈ$ÿÈ‚ÿȃÿÈ„ÿÈ…ÿȆÿȇÿȈÿÈÂÿÈéÿÎìÿÎùÿÈž ÿÈžÿΞÿΞÿÈž$ÿÈž‚ÿÈžƒÿÈž„ÿÈž…ÿÈž†ÿÈž‡ÿÈžˆÿÈžÂÿÈžéÿΞìÿΞùÿÈŸŸ ÿ\Ÿ Ÿ ŸÿhŸÿ`ŸÿhŸÿ\Ÿÿ†Ÿÿ†Ÿ""Ÿ#ÿ°Ÿ$ÿ\Ÿ&ÿ°Ÿ*ÿ°Ÿ-ÿ8Ÿ2ÿ°Ÿ4ÿ°ŸDÿ€ŸFÿ`ŸGÿ`ŸHÿ`ŸJÿTŸPÿ†ŸQÿ†ŸRÿ`ŸSÿ†ŸTÿ`ŸUÿ†ŸVÿ€ŸXÿ†ŸYÿœŸZÿ¤Ÿ[ÿ|Ÿ\ÿœŸlŸmÿ`Ÿoÿ`ŸrŸt2Ÿu2Ÿwÿ†Ÿyÿ`Ÿ{2Ÿ|Ÿ}ÿ`Ÿ‚ÿ\Ÿƒÿ\Ÿ„ÿ\Ÿ…ÿ\Ÿ†ÿ\Ÿ‡ÿ\Ÿˆÿ\Ÿ‰ÿ°Ÿ”ÿ°Ÿ•ÿ°Ÿ–ÿ°Ÿ—ÿ°Ÿ˜ÿ°Ÿšÿ°Ÿ¢ÿ€Ÿ£ÿ€Ÿ¤ÿ€Ÿ¥ÿ€Ÿ¦ÿ€Ÿ§ÿ€Ÿ¨ÿ€Ÿ©ÿ`Ÿªÿ`Ÿ«ÿ`Ÿ¬ÿ`Ÿ­ÿ`Ÿ²ÿ`Ÿ³ÿ†Ÿ´ÿ`Ÿµÿ`Ÿ¶ÿ`Ÿ·ÿ`Ÿ¸ÿ`Ÿºÿ`Ÿ»ÿ†Ÿ¼ÿ†Ÿ½ÿ†Ÿ¾ÿ†ŸÂÿ\ŸÃÿ€ŸÄÿ°ŸÅÿ`ŸÇÿ`ŸÌÿ†ŸÍÿ°ŸÎÿ`Ÿåÿ`Ÿæÿ`ŸçŸèŸéÿhŸêŸëŸìÿhŸïÿ`Ÿòÿ`Ÿóÿ`ŸöŸùÿ\ ÿÒ  ÿÖ  ÿÒ  ÿØ  ÿÒ ÿÈ ÿÈ ÿÖ $ÿÖ 7ÿž 9ÿÌ ;ÿâ <ÿ° =ÿº ?ÿÌ @ÿØ `ÿØ lÿÒ rÿÒ |ÿÒ ‚ÿÖ ƒÿÖ „ÿÖ …ÿÖ †ÿÖ ‡ÿÖ ˆÿÖ Ÿÿ° ÂÿÖ Óÿ° Ôÿº Öÿº Øÿº çÿÒ èÿÒ éÿÈ êÿÒ ëÿÒ ìÿÈ öÿÒ ùÿÖ¢ÿ¸¢ ÿ¸¢ ÿ¸¢Yÿà¢Zÿð¢\ÿà¢lÿ¸¢rÿ¸¢tÿ¸¢uÿ¸¢{ÿ¸¢|ÿ¸¢çÿ¸¢èÿ¸¢êÿ¸¢ëÿ¸¢öÿ¸£ÿ¸£ ÿ¸£ ÿ¸£Yÿà£Zÿð£\ÿà£lÿ¸£rÿ¸£tÿ¸£uÿ¸£{ÿ¸£|ÿ¸£çÿ¸£èÿ¸£êÿ¸£ëÿ¸£öÿ¸¤ÿ¸¤ ÿ¸¤ ÿ¸¤Yÿà¤Zÿð¤\ÿà¤lÿ¸¤rÿ¸¤tÿ¸¤uÿ¸¤{ÿ¸¤|ÿ¸¤çÿ¸¤èÿ¸¤êÿ¸¤ëÿ¸¤öÿ¸¥ÿ¸¥ ÿ¸¥ ÿ¸¥Yÿà¥Zÿð¥\ÿà¥lÿ¸¥rÿ¸¥tÿ¸¥uÿ¸¥{ÿ¸¥|ÿ¸¥çÿ¸¥èÿ¸¥êÿ¸¥ëÿ¸¥öÿ¸¦ÿ¸¦ ÿ¸¦ ÿ¸¦Yÿà¦Zÿð¦\ÿà¦lÿ¸¦rÿ¸¦tÿ¸¦uÿ¸¦{ÿ¸¦|ÿ¸¦çÿ¸¦èÿ¸¦êÿ¸¦ëÿ¸¦öÿ¸§ÿ¸§ ÿ¸§ ÿ¸§Yÿà§Zÿð§\ÿà§lÿ¸§rÿ¸§tÿ¸§uÿ¸§{ÿ¸§|ÿ¸§çÿ¸§èÿ¸§êÿ¸§ëÿ¸§öÿ¸¨ÿ¤¨ ÿ¤¨ ÿਠÿ¤¨9ÿŒ¨:ÿà¨?ÿŒ¨@ÿà¨Yÿæ¨[ÿĨ\ÿæ¨`ÿà¨lÿ¤¨rÿ¤¨|ÿ¤¨çÿ¤¨èÿ¤¨êÿ¤¨ëÿ¤¨öÿ¤ªÿ¤ª ÿ¤ª ÿઠÿ¤ª9ÿŒª:ÿàª?ÿŒª@ÿàªYÿæª[ÿĪ\ÿæª`ÿàªlÿ¤ªrÿ¤ª|ÿ¤ªçÿ¤ªèÿ¤ªêÿ¤ªëÿ¤ªöÿ¤«ÿ¤« ÿ¤« ÿà« ÿ¤«9ÿŒ«:ÿà«?ÿŒ«@ÿà«Yÿæ«[ÿÄ«\ÿæ«`ÿà«lÿ¤«rÿ¤«|ÿ¤«çÿ¤«èÿ¤«êÿ¤«ëÿ¤«öÿ¤¬ÿ¤¬ ÿ¤¬ ÿଠÿ¤¬9ÿŒ¬:ÿà¬?ÿŒ¬@ÿà¬Yÿæ¬[ÿĬ\ÿæ¬`ÿà¬lÿ¤¬rÿ¤¬|ÿ¤¬çÿ¤¬èÿ¤¬êÿ¤¬ëÿ¤¬öÿ¤­ÿ¤­ ÿ¤­ ÿà­ ÿ¤­9ÿŒ­:ÿà­?ÿŒ­@ÿà­Yÿæ­[ÿÄ­\ÿæ­`ÿà­lÿ¤­rÿ¤­|ÿ¤­çÿ¤­èÿ¤­êÿ¤­ëÿ¤­öÿ¤³ÿ¸³ ÿ¸³ ÿ¸³Yÿà³Zÿð³\ÿà³lÿ¸³rÿ¸³tÿ¸³uÿ¸³{ÿ¸³|ÿ¸³çÿ¸³èÿ¸³êÿ¸³ëÿ¸³öÿ¸´ÿ¤´ ÿ¤´ ÿà´ ÿ¤´9ÿŒ´:ÿà´?ÿŒ´@ÿà´Yÿæ´[ÿÄ´\ÿæ´`ÿà´lÿ¤´rÿ¤´|ÿ¤´çÿ¤´èÿ¤´êÿ¤´ëÿ¤´öÿ¤µÿ¤µ ÿ¤µ ÿൠÿ¤µ9ÿŒµ:ÿàµ?ÿŒµ@ÿàµYÿæµ[ÿĵ\ÿæµ`ÿàµlÿ¤µrÿ¤µ|ÿ¤µçÿ¤µèÿ¤µêÿ¤µëÿ¤µöÿ¤¶ÿ¤¶ ÿ¤¶ ÿà¶ ÿ¤¶9ÿŒ¶:ÿà¶?ÿŒ¶@ÿà¶Yÿæ¶[ÿĶ\ÿæ¶`ÿà¶lÿ¤¶rÿ¤¶|ÿ¤¶çÿ¤¶èÿ¤¶êÿ¤¶ëÿ¤¶öÿ¤·ÿ¤· ÿ¤· ÿà· ÿ¤·9ÿŒ·:ÿà·?ÿŒ·@ÿà·Yÿæ·[ÿÄ·\ÿæ·`ÿà·lÿ¤·rÿ¤·|ÿ¤·çÿ¤·èÿ¤·êÿ¤·ëÿ¤·öÿ¤¸ÿ¤¸ ÿ¤¸ ÿภÿ¤¸9ÿŒ¸:ÿà¸?ÿŒ¸@ÿà¸Yÿæ¸[ÿĸ\ÿæ¸`ÿà¸lÿ¤¸rÿ¤¸|ÿ¤¸çÿ¤¸èÿ¤¸êÿ¤¸ëÿ¤¸öÿ¤ºÿ¤º ÿ¤º ÿຠÿ¤º9ÿŒº:ÿàº?ÿŒº@ÿàºYÿæº[ÿĺ\ÿæº`ÿàºlÿ¤ºrÿ¤º|ÿ¤ºçÿ¤ºèÿ¤ºêÿ¤ºëÿ¤ºöÿ¤Àÿ¤À ÿ¤À ÿàÀ ÿ¤À9ÿŒÀ:ÿàÀ?ÿŒÀ@ÿàÀYÿæÀ[ÿÄÀ\ÿæÀ`ÿàÀlÿ¤Àrÿ¤À|ÿ¤Àçÿ¤Àèÿ¤Àêÿ¤Àëÿ¤Àöÿ¤ÂÿJ ÿJ ÿJÂÿÌÂ"ÿÈÂ#ÿÖÂ&ÿÖÂ*ÿÖÂ-2Â2ÿÖÂ4ÿÖÂ7ÿ|Â8ÿÈÂ9ÿxÂ:ÿ¬Â<ÿ\Â?ÿxÂYÿ®Â\ÿ®ÂlÿJÂmÿÌÂoÿÌÂrÿJÂtÿHÂuÿHÂyÿÌÂ{ÿHÂ|ÿJÂ}ÿ̉ÿÖ”ÿÖ•ÿÖ–ÿÖ—ÿÖ˜ÿÖšÿÖ›ÿÈœÿÈÂÿÈžÿÈŸÿ\ÂÄÿÖÂÍÿÖÂÓÿ\ÂåÿÌÂæÿÌÂçÿJÂèÿJÂêÿJÂëÿJÂïÿÌÂòÿÌÂóÿÌÂöÿJÃÿ¸Ã ÿ¸Ã ÿ¸ÃYÿàÃZÿðÃ\ÿàÃlÿ¸Ãrÿ¸Ãtÿ¸Ãuÿ¸Ã{ÿ¸Ã|ÿ¸Ãçÿ¸Ãèÿ¸Ãêÿ¸Ãëÿ¸Ãöÿ¸ÄÿjÄmÿjÄoÿjÄyÿjÄ}ÿjÄåÿjÄæÿjÄïÿjÄòÿjÄóÿjÇÿ¤Ç ÿ¤Ç ÿàÇ ÿ¤Ç9ÿŒÇ:ÿàÇ?ÿŒÇ@ÿàÇYÿæÇ[ÿÄÇ\ÿæÇ`ÿàÇlÿ¤Çrÿ¤Ç|ÿ¤Ççÿ¤Çèÿ¤Çêÿ¤Çëÿ¤Çöÿ¤ÉÿtÉ ÿtÉ ÿtÉÿ€É9ÿ^É:ÿ†É<ÿhÉ?ÿ^ÉYÿÈÉ\ÿÈÉlÿtÉmÿ€Éoÿ€ÉrÿtÉtÿ|Éuÿ|Éyÿ€É{ÿ|É|ÿtÉ}ÿ€ÉŸÿhÉÓÿhÉåÿ€Éæÿ€ÉçÿtÉèÿtÉêÿtÉëÿtÉïÿ€Éòÿ€Éóÿ€ÉöÿtÌÿ¸Ì ÿ¸Ì ÿ¸ÌYÿàÌZÿðÌ\ÿàÌlÿ¸Ìrÿ¸Ìtÿ¸Ìuÿ¸Ì{ÿ¸Ì|ÿ¸Ìçÿ¸Ìèÿ¸Ìêÿ¸Ìëÿ¸Ìöÿ¸Îÿ¤Î ÿ¤Î ÿàÎ ÿ¤Î9ÿŒÎ:ÿàÎ?ÿŒÎ@ÿàÎYÿæÎ[ÿÄÎ\ÿæÎ`ÿàÎlÿ¤Îrÿ¤Î|ÿ¤Îçÿ¤Îèÿ¤Îêÿ¤Îëÿ¤Îöÿ¤ÓÓ ÿ\Ó Ó ÓÿhÓÿ`ÓÿhÓÿ\Óÿ†Óÿ†Ó""Ó#ÿ°Ó$ÿ\Ó&ÿ°Ó*ÿ°Ó-ÿ8Ó2ÿ°Ó4ÿ°ÓDÿ€ÓFÿ`ÓGÿ`ÓHÿ`ÓJÿTÓPÿ†ÓQÿ†ÓRÿ`ÓSÿ†ÓTÿ`ÓUÿ†ÓVÿ€ÓXÿ†ÓYÿœÓZÿ¤Ó[ÿ|Ó\ÿœÓlÓmÿ`Óoÿ`ÓrÓt2Óu2Ówÿ†Óyÿ`Ó{2Ó|Ó}ÿ`Ó‚ÿ\Óƒÿ\Ó„ÿ\Ó…ÿ\Ó†ÿ\Ó‡ÿ\Óˆÿ\Ó‰ÿ°Ó”ÿ°Ó•ÿ°Ó–ÿ°Ó—ÿ°Ó˜ÿ°Óšÿ°Ó¢ÿ€Ó£ÿ€Ó¤ÿ€Ó¥ÿ€Ó¦ÿ€Ó§ÿ€Ó¨ÿ€Ó©ÿ`Óªÿ`Ó«ÿ`Ó¬ÿ`Ó­ÿ`Ó²ÿ`Ó³ÿ†Ó´ÿ`Óµÿ`Ó¶ÿ`Ó·ÿ`Ó¸ÿ`Óºÿ`Ó»ÿ†Ó¼ÿ†Ó½ÿ†Ó¾ÿ†ÓÂÿ\ÓÃÿ€ÓÄÿ°ÓÅÿ`ÓÇÿ`ÓÌÿ†ÓÍÿ°ÓÎÿ`Óåÿ`Óæÿ`ÓçÓèÓéÿhÓêÓëÓìÿhÓïÿ`Óòÿ`Óóÿ`ÓöÓùÿ\ÔÿºÔ""Ô#ÿÆÔ&ÿÆÔ*ÿÆÔ2ÿÆÔ4ÿÆÔFÿÚÔGÿÚÔHÿÚÔRÿÚÔTÿÚÔVÿäÔYÿØÔ\ÿØÔmÿºÔoÿºÔyÿºÔ}ÿºÔ‰ÿÆÔ”ÿÆÔ•ÿÆÔ–ÿÆÔ—ÿÆÔ˜ÿÆÔšÿÆÔ©ÿÚÔªÿÚÔ«ÿÚÔ¬ÿÚÔ­ÿÚÔ²ÿÚÔ´ÿÚÔµÿÚÔ¶ÿÚÔ·ÿÚÔ¸ÿÚÔºÿÚÔÄÿÆÔÅÿÚÔÇÿÚÔÍÿÆÔÎÿÚÔåÿºÔæÿºÔïÿºÔòÿºÔóÿºÖÿºÖ""Ö#ÿÆÖ&ÿÆÖ*ÿÆÖ2ÿÆÖ4ÿÆÖFÿÚÖGÿÚÖHÿÚÖRÿÚÖTÿÚÖVÿäÖYÿØÖ\ÿØÖmÿºÖoÿºÖyÿºÖ}ÿºÖ‰ÿÆÖ”ÿÆÖ•ÿÆÖ–ÿÆÖ—ÿÆÖ˜ÿÆÖšÿÆÖ©ÿÚÖªÿÚÖ«ÿÚÖ¬ÿÚÖ­ÿÚÖ²ÿÚÖ´ÿÚÖµÿÚÖ¶ÿÚÖ·ÿÚÖ¸ÿÚÖºÿÚÖÄÿÆÖÅÿÚÖÇÿÚÖÍÿÆÖÎÿÚÖåÿºÖæÿºÖïÿºÖòÿºÖóÿºØÿºØ""Ø#ÿÆØ&ÿÆØ*ÿÆØ2ÿÆØ4ÿÆØFÿÚØGÿÚØHÿÚØRÿÚØTÿÚØVÿäØYÿØØ\ÿØØmÿºØoÿºØyÿºØ}ÿºØ‰ÿÆØ”ÿÆØ•ÿÆØ–ÿÆØ—ÿÆØ˜ÿÆØšÿÆØ©ÿÚØªÿÚØ«ÿÚØ¬ÿÚØ­ÿÚØ²ÿÚØ´ÿÚØµÿÚØ¶ÿÚØ·ÿÚØ¸ÿÚØºÿÚØÄÿÆØÅÿÚØÇÿÚØÍÿÆØÎÿÚØåÿºØæÿºØïÿºØòÿºØóÿºåÿNå ÿÌå ÿNå ÿNåÿxåÿxåÿÌå$ÿÌå7ÿLå9ÿå:ÿàå;ÿÂå<ÿ`å=ÿÒå?ÿålÿNårÿNå|ÿNå‚ÿÌåƒÿÌå„ÿÌå…ÿÌå†ÿÌå‡ÿÌåˆÿÌåŸÿ`åÂÿÌåÓÿ`åÔÿÒåÖÿÒåØÿÒåçÿNåèÿNåéÿxåêÿNåëÿNåìÿxåöÿNåùÿÌæÿNæ ÿÌæ ÿNæ ÿNæÿxæÿxæÿÌæ$ÿÌæ7ÿLæ9ÿæ:ÿàæ;ÿÂæ<ÿ`æ=ÿÒæ?ÿælÿNærÿNæ|ÿNæ‚ÿÌæƒÿÌæ„ÿÌæ…ÿÌæ†ÿÌæ‡ÿÌæˆÿÌæŸÿ`æÂÿÌæÓÿ`æÔÿÒæÖÿÒæØÿÒæçÿNæèÿNæéÿxæêÿNæëÿNæìÿxæöÿNæùÿÌç ÿJçÿçÿNçÿçÿJç#ÿÒç$ÿJç&ÿÒç*ÿÒç2ÿÒç4ÿÒç90ç:0ç<ç?0çDÿÀçFÿ¤çGÿ¤çHÿ¤çRÿ¤çTÿ¤çmÿNçoÿNçyÿNç}ÿNç‚ÿJçƒÿJç„ÿJç…ÿJç†ÿJç‡ÿJçˆÿJç‰ÿÒç”ÿÒç•ÿÒç–ÿÒç—ÿÒç˜ÿÒçšÿÒçŸç¢ÿÀç£ÿÀç¤ÿÀç¥ÿÀç¦ÿÀç§ÿÀç¨ÿÀç©ÿ¤çªÿ¤ç«ÿ¤ç¬ÿ¤ç­ÿ¤ç²ÿ¤ç´ÿ¤çµÿ¤ç¶ÿ¤ç·ÿ¤ç¸ÿ¤çºÿ¤çÂÿJçÃÿÀçÄÿÒçÅÿ¤çÇÿ¤çÍÿÒçÎÿ¤çÓçåÿNçæÿNçéÿçìÿçïÿNçòÿNçóÿNçùÿJè ÿJèÿèÿNèÿèÿJè#ÿÒè$ÿJè&ÿÒè*ÿÒè2ÿÒè4ÿÒè90è:0è<è?0èDÿÀèFÿ¤èGÿ¤èHÿ¤èRÿ¤èTÿ¤èmÿNèoÿNèyÿNè}ÿNè‚ÿJèƒÿJè„ÿJè…ÿJè†ÿJè‡ÿJèˆÿJè‰ÿÒè”ÿÒè•ÿÒè–ÿÒè—ÿÒè˜ÿÒèšÿÒèŸè¢ÿÀè£ÿÀè¤ÿÀè¥ÿÀè¦ÿÀè§ÿÀè¨ÿÀè©ÿ¤èªÿ¤è«ÿ¤è¬ÿ¤è­ÿ¤è²ÿ¤è´ÿ¤èµÿ¤è¶ÿ¤è·ÿ¤è¸ÿ¤èºÿ¤èÂÿJèÃÿÀèÄÿÒèÅÿ¤èÇÿ¤èÍÿÒèÎÿ¤èÓèåÿNèæÿNèéÿèìÿèïÿNèòÿNèóÿNèùÿJéÿé ÿé ÿéÿxé#ÿÈé&ÿÈé*ÿÈé2ÿÈé4ÿÈé7ÿLé9ÿLé:ÿ†é<ÿhé?ÿLéYÿ|éZÿÂé\ÿ|élÿémÿxéoÿxérÿéyÿxé|ÿé}ÿxé‰ÿÈé”ÿÈé•ÿÈé–ÿÈé—ÿÈé˜ÿÈéšÿÈéŸÿhéÄÿÈéÍÿÈéÓÿhéåÿxéæÿxéçÿéèÿéêÿéëÿéïÿxéòÿxéóÿxéöÿê ÿJêÿêÿNêÿêÿJê#ÿÒê$ÿJê&ÿÒê*ÿÒê2ÿÒê4ÿÒê90ê:0ê<ê?0êDÿÀêFÿ¤êGÿ¤êHÿ¤êRÿ¤êTÿ¤êmÿNêoÿNêyÿNê}ÿNê‚ÿJêƒÿJê„ÿJê…ÿJê†ÿJê‡ÿJêˆÿJê‰ÿÒê”ÿÒê•ÿÒê–ÿÒê—ÿÒê˜ÿÒêšÿÒêŸê¢ÿÀê£ÿÀê¤ÿÀê¥ÿÀê¦ÿÀê§ÿÀê¨ÿÀê©ÿ¤êªÿ¤ê«ÿ¤ê¬ÿ¤ê­ÿ¤ê²ÿ¤ê´ÿ¤êµÿ¤ê¶ÿ¤ê·ÿ¤ê¸ÿ¤êºÿ¤êÂÿJêÃÿÀêÄÿÒêÅÿ¤êÇÿ¤êÍÿÒêÎÿ¤êÓêåÿNêæÿNêéÿêìÿêïÿNêòÿNêóÿNêùÿJë ÿJëÿëÿNëÿëÿJë#ÿÒë$ÿJë&ÿÒë*ÿÒë2ÿÒë4ÿÒë90ë:0ë<ë?0ëDÿÀëFÿ¤ëGÿ¤ëHÿ¤ëRÿ¤ëTÿ¤ëmÿNëoÿNëyÿNë}ÿNë‚ÿJëƒÿJë„ÿJë…ÿJë†ÿJë‡ÿJëˆÿJë‰ÿÒë”ÿÒë•ÿÒë–ÿÒë—ÿÒë˜ÿÒëšÿÒëŸë¢ÿÀë£ÿÀë¤ÿÀë¥ÿÀë¦ÿÀë§ÿÀë¨ÿÀë©ÿ¤ëªÿ¤ë«ÿ¤ë¬ÿ¤ë­ÿ¤ë²ÿ¤ë´ÿ¤ëµÿ¤ë¶ÿ¤ë·ÿ¤ë¸ÿ¤ëºÿ¤ëÂÿJëÃÿÀëÄÿÒëÅÿ¤ëÇÿ¤ëÍÿÒëÎÿ¤ëÓëåÿNëæÿNëéÿëìÿëïÿNëòÿNëóÿNëùÿJìÿì ÿì ÿìÿxì#ÿÈì&ÿÈì*ÿÈì2ÿÈì4ÿÈì7ÿLì9ÿLì:ÿ†ì<ÿhì?ÿLìYÿ|ìZÿÂì\ÿ|ìlÿìmÿxìoÿxìrÿìyÿxì|ÿì}ÿxì‰ÿÈì”ÿÈì•ÿÈì–ÿÈì—ÿÈì˜ÿÈìšÿÈìŸÿhìÄÿÈìÍÿÈìÓÿhìåÿxìæÿxìçÿìèÿìêÿìëÿìïÿxìòÿxìóÿxìöÿïÿNï ÿÌï ÿNï ÿNïÿxïÿxïÿÌï$ÿÌï7ÿLï9ÿï:ÿàï;ÿÂï<ÿ`ï=ÿÒï?ÿïlÿNïrÿNï|ÿNï‚ÿÌïƒÿÌï„ÿÌï…ÿÌï†ÿÌï‡ÿÌïˆÿÌïŸÿ`ïÂÿÌïÓÿ`ïÔÿÒïÖÿÒïØÿÒïçÿNïèÿNïéÿxïêÿNïëÿNïìÿxïöÿNïùÿÌòÿNò ÿÌò ÿNò ÿNòÿxòÿxòÿÌò$ÿÌò7ÿLò9ÿò:ÿàò;ÿÂò<ÿ`ò=ÿÒò?ÿòlÿNòrÿNò|ÿNò‚ÿÌòƒÿÌò„ÿÌò…ÿÌò†ÿÌò‡ÿÌòˆÿÌòŸÿ`òÂÿÌòÓÿ`òÔÿÒòÖÿÒòØÿÒòçÿNòèÿNòéÿxòêÿNòëÿNòìÿxòöÿNòùÿÌóÿNó ÿÌó ÿNó ÿNóÿxóÿxóÿÌó$ÿÌó7ÿLó9ÿó:ÿàó;ÿÂó<ÿ`ó=ÿÒó?ÿólÿNórÿNó|ÿNó‚ÿÌóƒÿÌó„ÿÌó…ÿÌó†ÿÌó‡ÿÌóˆÿÌóŸÿ`óÂÿÌóÓÿ`óÔÿÒóÖÿÒóØÿÒóçÿNóèÿNóéÿxóêÿNóëÿNóìÿxóöÿNóùÿÌö ÿJöÿöÿNöÿöÿJö#ÿÒö$ÿJö&ÿÒö*ÿÒö2ÿÒö4ÿÒö90ö:0ö<ö?0öDÿÀöFÿ¤öGÿ¤öHÿ¤öRÿ¤öTÿ¤ömÿNöoÿNöyÿNö}ÿNö‚ÿJöƒÿJö„ÿJö…ÿJö†ÿJö‡ÿJöˆÿJö‰ÿÒö”ÿÒö•ÿÒö–ÿÒö—ÿÒö˜ÿÒöšÿÒöŸö¢ÿÀö£ÿÀö¤ÿÀö¥ÿÀö¦ÿÀö§ÿÀö¨ÿÀö©ÿ¤öªÿ¤ö«ÿ¤ö¬ÿ¤ö­ÿ¤ö²ÿ¤ö´ÿ¤öµÿ¤ö¶ÿ¤ö·ÿ¤ö¸ÿ¤öºÿ¤öÂÿJöÃÿÀöÄÿÒöÅÿ¤öÇÿ¤öÍÿÒöÎÿ¤öÓöåÿNöæÿNöéÿöìÿöïÿNöòÿNöóÿNöùÿJùÿJù ÿJù ÿJùÿÌù"ÿÈù#ÿÖù&ÿÖù*ÿÖù-2ù2ÿÖù4ÿÖù7ÿ|ù8ÿÈù9ÿxù:ÿ¬ù<ÿ\ù?ÿxùYÿ®ù\ÿ®ùlÿJùmÿÌùoÿÌùrÿJùtÿHùuÿHùyÿÌù{ÿHù|ÿJù}ÿÌù‰ÿÖù”ÿÖù•ÿÖù–ÿÖù—ÿÖù˜ÿÖùšÿÖù›ÿÈùœÿÈùÿÈùžÿÈùŸÿ\ùÄÿÖùÍÿÖùÓÿ\ùåÿÌùæÿÌùçÿJùèÿJùêÿJùëÿJùïÿÌùòÿÌùóÿÌùöÿJ€€€€ÈŽDÌô(\Îþ8R|šè ŠZÀ RØ @ ˜  * N t ä ž Ú : ¢ à  : ¬ Ô ê:ˆ¤ô*z¸>&`’ê.d’¾Þ8TvˆúlìHPœTnÖ"€”ð`ÎN¬ð*ZÈÞN––ÞŽ  z Ò ô!!ä"°# #j#š#´$n$ˆ$Ð% %l%ì&&X&Ž&º'('l'²'ö(l))Ì*>*J*V*b*n*z*†*Ô+Ø+ä+ð+ü,,, ,,,8,†,’,ž,ª,¶,Â,Î,ô-†-’-ž-ª-¶-Â.. .¬.¸.Ä.Ð.Ü.è/ä0¤0°0¼0È0Ô0à0ì0ø11ˆ1”1 1¬1¸1Ä1Ð2(2¶2Â2Î2Ú2æ2ò3d3p44Æ4Ò4Þ5d6"6<6p6¢6®6º7œ8T8`8l8x8„88œ8¨8´8À8Ì8Ø9*9R9€9ˆ9¼9æ:4:Ž:à;;x;’;¬;Ø<<0<~<Î==j=Ø>>p?~?¨?Ð?î@~@ÞADA¾AìBBNBhB¤C8C„D8DDÌEEHE^EÂF8F`F‚FÊFäGG4GbGG¼GüH@HzH¡‚b"/n‰ ‘nŠŠŽ*• ¿(Ë ó0ÿ/ G ÜV 2 2J Â|> X l t T‚ Ö Pî Ö `> 0ž Î ¸ì 0 ¤ d Ô „8 4¼Copyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.LatoRegulartyPolandLukaszDziedzic: Lato Regular: 2013Lato RegularVersion 1.105; Western+Polish opensourceLato-RegularLato is a trademark of tyPoland Lukasz Dziedzic.tyPoland Lukasz DziedzicLukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2010-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLCopyright (c) 2010-2013 by tyPoland Lukasz Dziedzic with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1.LatoRegulartyPolandLukaszDziedzic: Lato Regular: 2013Lato-RegularVersion 1.105; Western+Polish opensourceLato is a trademark of tyPoland Lukasz Dziedzic.tyPoland Lukasz DziedzicLukasz DziedzicLato is a sanserif typeface family designed in the Summer 2010 by Warsaw-based designer Lukasz Dziedzic ("Lato" means "Summer" in Polish). It tries to carefully balance some potentially conflicting priorities: it should seem quite "transparent" when used in body text but would display some original traits when used in larger sizes. The classical proportions, particularly visible in the uppercase, give the letterforms familiar harmony and elegance. At the same time, its sleek sanserif look makes evident the fact that Lato was designed in 2010, even though it does not follow any current trend. The semi-rounded details of the letters give Lato a feeling of warmth, while the strong structure provides stability and seriousness.http://www.typoland.com/http://www.typoland.com/designers/Lukasz_Dziedzic/Copyright (c) 2010-2013 by tyPoland Lukasz Dziedzic (http://www.typoland.com/) with Reserved Font Name "Lato". Licensed under the SIL Open Font License, Version 1.1 (http://scripts.sil.org/OFL).http://scripts.sil.org/OFLÿtx  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a£„…½–膎‹©¤Šƒ“òó—ˆÞñžªõôö¢­ÉÇ®bcdËeÈÊÏÌÍÎéfÓÐѯgð‘ÖÔÕhëí‰jikmln oqprsutvwêxzy{}|¸¡~€ìîºýþ  ×âã  °± äå»æç¦ØáÛÜÝàÙß›²³¶·Ä´µÅ‚‡«Æ¾¿¼ŒŸ˜š™ï¥’œ§”•¹ÀÁ !"#NULLuni00A0uni00ADmacronperiodcenteredAogonekaogonekEogonekeogonekNacutenacuteSacutesacuteZacutezacute Zdotaccent zdotaccentuni02C9EuroDeltauni2669undercommaaccent grave.case dieresis.case macron.case acute.casecircumflex.case caron.case breve.casedotaccent.case ring.case tilde.casehungarumlaut.case caron.saltÿÿ¸‹¸‹™ºõþ©¶þV©ÿðºÿòþ”¶þV°,° `f-°, d °ÀP°&Z°E[X!#!ŠX °PPX!°@Y °8PX!°8YY ° Ead°(PX!° E °0PX!°0Y °ÀPX f ŠŠa ° PX` ° PX!° ` °6PX!°6``YYY°+YY#°PXeYY-°, E °%ad °CPX°#B°#B!!Y°`-°,#!#! d±bB °#B² *! °C Ра+±0%ŠQX`PaRYX#Y! °@SX°+!°@Y#°PXeY-°,°C+²C`B-°,°#B# °#Ba°€b°`°*-°, E °Ec°Eb`D°`-°, E °+#±%` EŠ#a d ° PX!°°0PX° °@YY#°PXeY°%#aDD°`-°,±E°aD-° ,°` ° CJ°PX ° #BY° CJ°RX ° #BY-° , ¸b ¸cŠ#a° C` Š` ° #B#-° ,KTX±DY$° e#x-° ,KQXKSX±DY!Y$°e#x-° ,± CUX± C°aB° +Y°C°%B± %B± %B°# °%PX±C`°%BŠŠ Š#a° *!#°a Š#a° *!±C`°%B°%a° *!Y° CG° CG`°€b °Ec°Eb`±#D°C°>²C`B-°,±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-°,± +-°,°+±ETX° #B `°aµ  BBŠ`± +°m+"Y-°,±+-°,±+-°,±+-°,±+-°,±+-°,±+-° ,±+-°!,±+-°",±+-°#,± +-°$, <°`-°%, `° ` C#°`C°%a°`°$*!-°&,°%+°%*-°', G °Ec°Eb`#a8# ŠUX G °Ec°Eb`#a8!Y-°(,±ETX°°'*°0"Y-°),°+±ETX°°'*°0"Y-°*, 5°`-°+,°Ec°Eb°+°Ec°Eb°+°´D>#8±**-°,, < G °Ec°Eb`°Ca8-°-,.<-°., < G °Ec°Eb`°Ca°Cc8-°/,±% . G°#B°%IŠŠG#G#a Xb!Y°#B².*-°0,°°%°%G#G#a°E+eŠ.# <Š8-°1,°°%°% .G#G#a °#B°E+ °`PX °@QX³  ³&YBB# °C Š#G#G#a#F`°C°€b` °+ ŠŠa °C`d#°CadPX°Ca°C`Y°%°€ba# °&#Fa8#°CF°%°CG#G#a` °C°€b`# °+#°C`°+°%a°%°€b°&a °%`d#°%`dPX!#!Y# °&#Fa8Y-°2,° °& .G#G#a#<8-°3,° °#B F#G°+#a8-°4,°°%°%G#G#a°TX. <#!°%°%G#G#a °%°%G#G#a°%°%I°%a°Ec# Xb!Yc°Eb`#.# <Š8#!Y-°5,° °C .G#G#a `° `f°€b# <Š8-°6,# .F°%FRX ,°1+!# <°#B#8±&+°C.°&+-°?,° G°#B².°,*-°@,° G°#B².°,*-°A,±°-*-°B,°/*-°C,°E# . FŠ#a8±&+-°D,°#B°C+-°E,²<+-°F,²<+-°G,²<+-°H,²<+-°I,²=+-°J,²=+-°K,²=+-°L,²=+-°M,²9+-°N,²9+-°O,²9+-°P,²9+-°Q,²;+-°R,²;+-°S,²;+-°T,²;+-°U,²>+-°V,²>+-°W,²>+-°X,²>+-°Y,²:+-°Z,²:+-°[,²:+-°\,²:+-°],°2+.±&+-°^,°2+°6+-°_,°2+°7+-°`,°°2+°8+-°a,°3+.±&+-°b,°3+°6+-°c,°3+°7+-°d,°3+°8+-°e,°4+.±&+-°f,°4+°6+-°g,°4+°7+-°h,°4+°8+-°i,°5+.±&+-°j,°5+°6+-°k,°5+°7+-°l,°5+°8+-°m,+°e°$Px°0-K¸KRX±ŽY¹c °#D°#p°E °(`f ŠUX°%a°Ec#b°#D² *² *²*Y²( ERD² *±D±$ˆQX°@ˆX±D±&ˆQX¸ˆX±DYYYY¸ÿ…°±Dmcollective-2.12.1/doc/fonts/SourceCodePro-Bold.ttf0000644005276200011600000021304013265671731022015 0ustar jenkinsjenkins€pFFTMf¢h®OS/2säùöx`cmap_…ceXcvt ÿ ± "fpgmYœ7 `sgaspÿÿüglyfêp™e´¸¼headûØuÆü6hheaA¡4$hmtxf¶]õØ~locabk3â <vmaxpS—X nameDèÆØÊp;Xpost6z È3prep~æ† ÔAZ¿¥R_<õèÍáÍáÿÒþ÷”ÎØþïèÿÒÿÄ”:Qc áX¼ŠXKŠX^2    ADBE "îÿØ`“ðŒ èMXÈSF@×µ~@F¶F¸J8R3*#.<>=5¸¶tFbb T7A^l,@J;DkAB&J$F2@8ÉJiV<=HC/7R4HO*L>$H/H/t51>%Fjëi4È`C$ëGpš+FFe¢œFž¥Ú>0¸ÇÄ…<g ÿý7^^^^JJJJB&&&&&T@@@@I=======C7777OOOO/H/////F/>>>>H = = =7C7C7C7CA/^7^7^7^7^7,4,4,4,4@ÿîJOJOJOJOJO;*DLLk>k>k>k !>BHBHBHÿÒ&/&/&/ FtFeFt2525252511@>@>@>@>@>@>8F8F8F:.&/@> =JO&/@>@>@>@>@>,4&/251* H/H467/< 42œ…ÖS¸ÊêÑŠŠà¢Úà™Ê»Ö¯¶‘|ÚŠ¢™ÊpÛ»¯ŠEèô"ʃÅÅÖ¡¢šš€”… s…š–vª·—A/A/,4@H@Hk>7>k>A$BHBHBHFjFjFB2525118F1@P¸ÊÊ4FFˆ¯À˜©¡¥¢±¦¤ÖÇœ˜Äž¥¡¥¢±¦¤ÖÇ”OC[-COX$F˜Äœ¥¡¥¢±¦¤ÖÇŠá„‚Ÿ£×ƒÛ»£„Ä‹‹‹‹‹„‹‹ýÖüà´€4 ~1Ie~€’¡°Üçë7CRTYaeoy‡Žž°³¸¼¿ÌÝã $(.1CIMPRX[œ »!%+;Ico…“—žù    " : D q y ‰ Ž ” ¡ ¤ § ¬ ² µ º""ÿÿ  4Lh€’ ¯Íæê7CPTXaeoy‡Œž°²·»¾ÆØá#&.1CGMORV[œ »  $*6BZl€Ž’—žò    " 9 D p t } ” ¡ ¤ ¦ « ± µ ¹""ÿÿÿ÷ÿåÿÄÿÂÿÀÿ¾ÿ½ÿ¯ÿ­ÿ ÿ“ÿwÿnÿlÿ@ÿ%ÿÿÿ ÿ ÿÿþ÷þîþáþÝþÎþ½þ¼þ¹þ·þ¶þ°þ¥þ¢þ†þ…þƒþþzþsþrþmþkäZäWäTäSäRäOäMä ä ãðã ããŽãŠã€ãzãjãbãRãJãHãEã?âìáÓáÐáÏáÌá¶á­á‚á€á}ázáuáiágáfácá_á]áZàà   !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcˆ‰‹•𠥤¦¨§©«­¬®¯±°²³µ·¶¸º¹¾½¿Àtfgkîz£rmxlŠœuiyn~ª¼ƒep?od„‡™æçëìèé»Ã6ñïð{ê톎…Œ‘’“—˜–žŸõvs}~|‚€w¸,K¸ PX±ŽY¸ÿ…¸D¹ _^-¸, EiD°`-¸,¸*!-¸, F°%FRX#Y Š ŠIdŠ F had°%F hadRX#eŠY/ °SXi °TX!°@Yi °TX!°@eYY:-¸, F°%FRX#ŠY F jad°%F jadRX#ŠY/ý-¸,K °&PXQX°€D°@DY!! E°ÀPX°ÀD!YY-¸, EiD°` E}iD°`-¸,¸*-¸,K °&SX°@°YŠŠ °&SX#!°€ŠŠŠ#Y °&SX#!¸ÀŠŠŠ#Y °&SX#!¸ŠŠŠ#Y °&SX#!¸@ŠŠŠ#Y ¸&SX°%E¸€PX#!¸€#!°%E#!#!Y!YD-¸ ,KSXED!!Y-°+²+²+·% +·.& +²+° E}iDK°`RX°°Y°Žs– ÿH ð B { Œ ½ 6Dº.Øî4j޲ºâül¤øb´ŒÂF¼ÈÔD–Tº  L Š À  V Œ È  > ’ à * r Ø ( ’ ¾ ö,Œè&\z”²Úîötè2ªVt²T’l¶( ìT¢ö0¢^”ôf  Î6 îPnòú‚Š˜®¶,4p®¸ÂÊ>pz‚Œ”¢²ÂÒ $ 0 < H T ` l Æ Ò Þ ê ö!!!!&!2!Œ!˜!¤!°!¼!È!Ô""‚"Ž"š"¦"²"¾##|#ˆ#”# #¬#¸#Ä$~$Š$–$¢$®$º$Æ$Ò$Þ$ê%n%z%†%’%ž%ª%¶%ö&v&‚&Ž&š&¦&²'*'6'B'N'Z'f'Ú(v(‚(Ž(š(¦(²(¾(Ê(Ö(â(î(ö)ˆ)”) )¬)¸)Ä)Ð*4*´*À*Ì*Ø*ä*ð*ü+++ +,+8+D+ª,,&,2,>,J,V,b,Æ-4-@-h-t-€-Œ-˜-è-ô.. ..$.0.<.H.†.Þ.ê.ö////&/˜/¤/°/¼/È/Ô/à0:0Ú0æ0ò0þ1 11"1.1:1F1R1^1j1v1‚1Ž1š1¦1²1¾1Ê1Ö1â1î1ú2222*2333(343@3L3X3d3p3|3ˆ3”4"4z4Ü5B5ª66l6x6„66œ6¨6´6À6Ì6Ø6ä6ð6ü777 7,787D7²8*868B8N8Z8–99€9ö:l:¶;;h;ô>`>ª>è?2?n?Ò@$@,@4@X@|@„@Œ@ @¨@°@¸@Ì@Ô@Ü@ä@ì@ô@üA.A€AÐAâAöBBPBdBB®BâCC@CbC€C¢CÊCðDD6DjD–DÄDöEE0E‚EÜF:F€GG^G´GðHJH”HÖI IDI†I´IÀIÌIØIäIðIüJJJ J,J8JDJRJbJnJzJ†J’JžJªJ¶JÂJÎJÚJæJòKKKK&K2K>KJKVKbKnKzK†K’KžKªK¶KÂKÎKÚKæKòKþL L~LŠL–L¢L®LºLÆLÒLÞLòMM,MPMZMhMvM„MªMÂMÚMöNN4N>NHNRN\NfNpNzN„NÂNÌNÖNàNêNôNþOOOO&O0O:O€P.P´QdQÊR:R¸SJS¬T$TˆTìUUULUpU¬VV@V”VòW WŒWêX X,X>XRXrX®XÂXêYYY¸EX¸/¹>Y¸ ܸÜ01'3#4632#"&è’`49++99++9……þæ¥-77--99ÿÿS/­' ÿ| …FŠ‹¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y»+»+¸¸ ии¸ии¸ии¸иÐ017#537#53733733#3##7##7#JUO[ThTO[ T_UgUÞh »^n^¥¥¥¥^n^»»»nn@ÿ’ é,G¸/¸'/¸&ܹô¸¸ܺ&9¸и¹ôº&9¸&¸)Ð01.#"#5.'732654.546753¸#?)E+AKA+VTb1h&@0U0$&+ALA+XLb4MÁ.+>-?]•’)!d!*>.AV ƒ‚+ ÿÿÿôHˆ'ÿD&ñ€ÿôJ˜ A¸EX¸3/¹3 >Y¸EX¸!/¹!>Y¸EX¸/¹>Y¸!¹ôº3!9º !39¸ ¸+ии3¸ܸ¸ии¹ô¸ ¹;ô¸¸>Ð017327.'>54&#"&'#".54>7.54>32>7¢1( #?-%{, 3(#DF%Y73N5&*<%JS#,=  ·%.C$$#").' /PD3,&; J,×/­ º+01'3#ܪ#Z‘‘íµÿMÚß º+01.5467ˆbqqbRYQ)@-R³W䎎äWAW»v;i`Y+A~ÿM£ß º +01>54&'7~-@)QYRbqqbr+Y`i;v»WAW䎎äW@[6/¸/º9¸¹ô¸и¸ и¸ Ð01?'7737'xa™¢ N ¢™a@tt‡DG+²²+GD,ŽŽF^6 »+¸¸и¸ Ð01#53533##ö°°l°°lh¸¸h¸¶ÿ'­× º +0165#".54632¶‚'A/>>jhƒ-a &/8[Q`„ ÿÿF~¸ÿô ß¸EX¸ /¹ >Y¸Ü0174>32#"&¸++@44@i+  +2CCJÿ`ƸEX¸/¹>Y¸Ü01#3Å{I{ f8ÿô ‡'3K¸EX¸ /¹ >Y¸EX¸/¹>Y¸ ¹ô¸¹ôº(9¸(/¸.Ü01".54>32"32>54."&54632,7Z@##@Z77Z@##@Z7)))) ++ ++ +T|QQzS))SzQQ|T+!1R>>S33S>>R1þá)##))##)R{ =¸EX¸ /¹ >Y¸EX¸/¹>Y¹ô¸Ð¸ ¸йô01%!53#5>73þ9¤ƒ8O#lwwwo[ ýü3‡!C¸EX¸/¹ >Y¸EX¸ /¹ >Y¹ô¸Ðº 9¸¹ô017>54&#"'>32>;!:DpP,62&?O17=%2R;!&AT.?—þ#T9cVL"/1'O& 5J.(SSS(|*ÿô‡-S¸EX¸/¹ >Y¸EX¸*/¹*>Y¹ôº *9¸ /¹ ô¸¹ôº" 901732654.#52654&#"'>32#"&'n O,4C,J8^M2,&@!J/k>4V>"@9,E/1)#.{ Y¸EX¸/¹ >Y¸EX¸ /¹ >Y»+¸¹ô¸¸ и¸ и ¸и¸Ð015467###5!533U$`}P‰þÎ ²PkO6Šp˜˜e~þ.ÿô{$U¸EX¸/¹ >Y¸EX¸!/¹!>Y» +¸!¹ôº!9¸¸и¹ô01732654&#"'!!>32#"&'p I-6EA3A…þú &,O<#)EZ1Ss'ª%2102 *A|w0J35R93&<ÿô!‡2W¸EX¸//¹/ >Y¸EX¸%/¹%>Y»+¸%¹ôº%/9¸¹ ô¸/¹ô01%2>54&#".#">32#".54>32:#4*<$¤< 5)"S$*G5%?R.1\H,.Mb5Df b '1+"&3 /L8!$0J22N8$LwTY‚T)->{3¸EX¸/¹ >Y¸EX¸/¹>Y¸¹ô¸ Ð013>7!5!#Á+D2þÁÝW¸EX¸'/¹' >Y¸EX¸:/¹:>Y¹ô¸'¹ôº 9¸ /¸/и//¸ܸ ¸Ü01732654.'7654&#"4>75.54>32#".¼B1-7)8"#›0.,#1Fâ!+(5 9P/0M65&,  =Z:7X>!³*/'( 2 +2%-$%',ã/& G3(A.-A'-F (3 &@/.@5ÿô‡2W¸EX¸%/¹% >Y¸EX¸//¹/>Y»+º/%9¸¹ô¸%¹ô¸/¹ô01267.#"32>7#".54>32#"&'<$#4c< 5)#S#*H5%?R.1]H+.Lc4Df!V#%3 '1+¾/M8!%0J21O8$LwTY‚T).ÿÿ¸ÿô "'Cÿÿ¶ÿ'­"'Ctöy;»+¸¸и/¹ôº9¸¸и/¹ô01%%t‚þ÷ þ~ú…¦¦…úÿÿF£ò&tbäy;»+¸¸и/¹ôº9¸¸и/¹ô015%5%5äþ~ þ÷‚ú…¦¦…úbÿôñª)*¸EX¸'/¹'>Y» +¸'¸!ܸÜ01&>54&#"'>324632#"&Ú'"* 0R$b<+K7 $(" 9++99++9ÿ!5*"  K(2)=)!0("%+¥-77--99ÿm&„6@?» '+»1+»:+»>+º19¸¹=ô01%#'##".54>754.#"3267#".54>3232675&N <)/"#A\9 2!#F8#$;L)-;**T6Y¸EX¸ /¹ >Y»  +¸¹ô¸ ¸Ð01'.'###3#n    §Å(–˰˜<1n34m1Y¸EX¸/¹>Yº%9¸%/¹ôº %9¸¹ô¸¹&ô0132+2654&+254&+TÌ3W@% *FH&D[5ßÅ60143@v9=@Œ&>/,& JB2H/…+%% •þíY+'«7ÿô9˜9¸EX¸/¹ >Y¸EX¸/¹>Y¸¹ ô¸¹ô014>32.#"3267#".70TrA?a!Q6##:+\J#9QR|?pT1BQX.3 [6N1eo Y`+T}A,Œ 9¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ô¸ ¹ô0132+72>54.+A±GtR--QpD¹©&?--?&Œ&OzTT|Q(w1P;:O/þb^Œ M¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ôº 9¸/¹ô¸ ¹ô01!!3#!!^®þåðð%þHŒ|ƒ{–|lŒ C¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ôº 9¸/¹ô01!!3##l®þåññ“Œ|—|ý,ÿô#˜#M¸EX¸/¹ >Y¸EX¸/¹>Y¸¹ ô¸¹ôº9¸/¹ô014>32.#"32675#53#".,/Qn?B] Q2' 6(MN" _ã!l??mQ.BRW.3 [6N1eo pxþÔ0+T}@Œ I¸EX¸/¹ >Y¸EX¸ /¹ >Yº 9¸ /¹ô¸¸и ¸Ð013353###@“²““²“ŒûûýtþðJŒ A¸EX¸/¹ >Y¸EX¸ /¹ >Y¹ô¸¹ô¸и¸ Ð0173#5!#3!J˜˜Ę˜þ<|”||þl|;ÿôŒ5¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸¹ô0173265#5!#".':B32ÿ’:]C:84»H3D'{þR0U@% )DRŒ k¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Yº9º 9¸ ¸и¸ Ð01333##D•Á¢ÃÖ£ŠL•Œþõ þüþxd¯k Œ+¸EX¸/¹ >Y¸EX¸/¹>Y¹ô013!!k“"þKŒýð|AŒM¸EX¸/¹ >Y¸EX¸/¹>Y»+¸¸и¸ и¹ô¸Ð0133?3#54>7##/##A“B?“w32A21uŒçZZçýtÕOTO³››³OTOÕBŒY¸EX¸/¹ >Y¸EX¸ /¹ >Yº 9¹ô¸¸к 9¸¹ ô¸ ¸Ð0133.=3#'##B–Š8–Š8Œþ»4€9çýtEŽ6|:ç&ÿô2˜5¸EX¸ /¹ >Y¸EX¸/¹>Y¹ô¸ ¹ô01".54>32'2654&#",:aE&&Ea::aE&&Ea:3<<33<< /X~PP}U--V}OP~X/scbnnbcsJ7ŒG¸EX¸/¹ >Y¸EX¸/¹>Yº 9¸ /¸¹ô¸ ¹ô0132+#254&+Jî5]E()F\4[“åw<;RŒ1Q;9T6ÝRh3*Å$ÿND˜ *K¸EX¸/¹ >Y¸EX¸/¹>Y»'+¸¹ô¸¹ô¸¸$Ð01"32654&#".'.54>323267)4::43;;è00PA0Xh&D`;:`D&^QC# nbcsscbnýJ .A&«ˆP}U--V}O©#FHŒS¸EX¸ /¹ >Y¸EX¸ /¹ >Yº 9¸ /¹ô¸ ¹ô¸ ¸и ¸Ð013254&+'##32ÙHw<;HÊ}M“ç4[E(@4“`a0&ýéëëŒ/N:H]þû2ÿô'˜/I¸EX¸/¹ >Y¸EX¸,/¹,>Y¹ôº,9¸¹ôº ,901732654./.54>32.#"#"&'‡%W+3/%T3&#?X5Y¸EX¸/¹>Y¸¹ô¸Ð01#5!##âÃÔ||ýð@ÿôŒ3¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸¸ Ð01332653#"&5@“,//-ŽswxvŒþ`;>>; þo€‡‡€IŒ 3¸EX¸/¹ >Y¸EX¸ /¹ >Y¹ô¸¸ Ð0133>73#œNM–ıŒþÐ6e66e60ýtTŒ!M¸EX¸/¹ >Y¸EX¸ /¹ >Y» +¸ ¹ô¸и¸и ¸Ð0133>?33>73#'.'##–-Y+ŒQš)  '—Œþ¥*R**R*µµ(T**S)[ýtÍAAÍFŒ]¸EX¸/¹ >Y¸EX¸/¹>Yº9º9¸¸ к 9¸¸к90133>?3#'.'##г¥?   :ž³¾¤G  DO={33{þ¼þ¸…33…HŒ@¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Yº901733>?3#âÒžBBšÒ”ܰ&H''H&þPÜ8#Œ =¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸Ð¸¹ô¸Ð017!5!!!82þëÊþÏ5þY¸{YþI|ÉÿhðÄ»+»+01!#3!É'··þÙÄNý@NJÿ`ƸEX¸/¹>Y¸Ü013#J{I{ÆüšiÿhÄ»+»+01#5!!5¶'þÙJÀNü¤NVž &¸EX¸/¹ >Y¸ܺ9¸ Ð013#/##ñv›y.--.yžþt€‡‡€<ÿ\ÿÊ »+01!5þ 6nnÿÿ>~å†=ÿôü)€¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸/¸¹ô¸¹!ôº !9º9¸¹$ô¸¹%ô0174>7.#"'>32#'##".732675=$O|X33$I*43u@hxx &Z0&<*% !:7H*Š*?,"&`&nrþä8')6+RHÿô)½!ƒ¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9º9¸¹ô¸¹ô¸¹ô¸¹!ô013>32#"&'##732654#"H“I$.K3$Y¸EX¸/¹>Y¸¹ ô¸¹ô0174>32&#"3267#".C/Oh9;_ D77@PM>'B<-m3;dK*ø>aB#&['LAAL]&##Ba/ÿô½#ƒ¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Yº9º9¸¹ô¸¹ô¸¹ô¸¹!ô0174>32'53#'##".732675.#"/$;L)+:“x H&/M7—2.*));ø=`C$M©ýC3$$C`?JDÉD7ÿô"ü#C¸EX¸/¹>Y¸EX¸/¹>Y»# +¸¹ô¸¹ ô0174>32!3267#".%4&#"7+H]29X:þ²Q7"=!0*h2:dJ+f.3(> ø=`C$$@W3$ 81X#C`t*4.0RIÉV¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸¹ô¸¸и¹ ô¸ Ð01&#"3###5754>32-170-««“ƒƒ6T:+K B*-sþƒ}m +J7  4ÿ-<üDP²¸EX¸"/¹">Y¸EX¸%/¹%>Y¸EX¸$/¹$>Y¸EX¸@/¹@>Y¸EX¸ /¹ >Y¸@¹ô¸ ¸ ии ¹7ô¸/ܹEôº1E/9¸1¸и%¹&ô¸$¹'ô¸"¹Kô0132654&+"&'475.54675.54>323##"';2#".2654&#"¬C;#xM!&#:M*.$Ée  7K+ $ (,\bg*LnC0S<"é--++># $=& ( 2A,-C- k #+>)  :C'B/0k))())())H½e¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Yº 9¸¹ô¸¹ô013>32#4&#"#H“#)1TM“ )-“½¬_ l^þÎ2.þµO»î;¸EX¸/¹>Y¸EX¸/¹>Y¸¹ô¸¸ܸ Ü01!##%"&54632OZ“Ç +77++77ðþ}¼0))33))0*ÿ<»îA¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸¹ô¸¸ܸÜ01!#"&'73265#%"&54632OZ5YC.K +15'Ç +77++77ðþ.-R>%i 36e¼0))33))0LI½ m¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Yº 9º 9¸ ¸и¹ ô013373#'#L“¶¡¼ËŸ‚I“½þkÈÌþÜÊM}>ÿô ½5¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸¹ô01!3267#"&5#># &!""&^_½ýÿ l o_‡$=ü £¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº 9¸¸ и ¹ô¸ ¹ô¸¹ô¸¹ô0133>32>32#4#"#4#"#$n 1+!- 5*4:Šq Šð>+)("/UKþ¤Q.0þ±Q.þ±Hüe¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Yº9¸¹ô¸¹ô0133>32#4&#"#Hx  S8TN“ )-“ðB .l^þÎ2.þµ/ÿô)ü5¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸¹ô0174>32#".732654&#"/*G[11[G**G[11[G*—33333333ø>aB##Ba>>aB##Ba>ALLAALLHÿH)ü ƒ¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº 9º 9¸¹ô¸¹ô¸ ¹ô¸¹ ô01#33>32#"'732654#"Û“x N(.J4$Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Yº9º9¸¹ô¸¹ô¸¹ô¸¹!ô0174>32373#57#".732675.#"/$;L)*A s“F#/M7—2.*));ø=`C$!3ýX™L $C`?JDÉDt$üT¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¹ ô¸¹ô0133>32.#"#tx %hY¸EX¸,/¹,>Y¹ôº,9¸¹ôº,901732654&'.54>32.#"#"&'w+W23,IY¸EX¸/¹>Y¸¹ô¸¸и¸и¹ ô01#5?33#3267#".5®}„yÒÒ260!Q0;R3}m„„s®9/ j  9P1>ÿô ðe¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¹ô¸¹ô01!#'##"&5332673 x !P6UL“*+“G&-l^2þá2.!F9ð <¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸ /¹ >Y¹ô0133>?3#”N    N¶©ðê&L''L&êþTð!t¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸!/¹!>Y¸EX¸/¹>Yº!9¸¸к90133>?33>?3#'.'## g! †Mª §ðæ%H&&J#¤¤%H&&H%æþ¥#G(D3¥%2ðe¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¸Ð¸¸ иÐ01'33>?3#'.'##Ñ¡ž3    ,š¢¬ž9  1šîP+,PÿñR,+Rÿ>;ð[¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ôº 901326?33>?3#"&'7]+1 ×’S   I‹Ä*6G/&H$ãÕ#K'%K%Õþ /G/pFð =¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸Ð¸¹ô¸Ð017#5!!!Fä¯þü þ)O.sNþÑsjÿhðÄ7+»34+»+»  +º' 9014>54.'5>54.546;#";#"&é 5((5 Z`M)( .66. ()M`Z *''  V  ('+Q=N(N-93  39.L)N=ëÿmî º+013#ë‚‚îüiÿhïÄ7+»5+»+»'(+º ('9012>54&54675.54654.+532+5’' .66. ')M`Z 5))5 Z`MJ)L.93  39-N(N=Q+'(  V  ''*Q=N4ê$ª'»+»+¸¸ и¸Ð01>323267#".#"4 T/.'#&X T/.'#'NC(*)NC(*ÈÿRü¸EX¸/¹>Y¸ ܸÜ01#737#"&54632p’`49++99++9)……¥-77--99`ÿ× $\¸EX¸/¹ >Y»! +» +¸ ¸Ð¸/¸!¸и/¸ ¸ и /¸ ܸ¸и/01#5.54>753&'>77$$%#ÔD"Q1O8 :N/Q'?C$°@--AE__(AX65V?)a]Zþð C&‡*W¸EX¸/¹ >Y¸EX¸/¹>Y» +¸¹ô¸и¹ô¸ ¸ и¸#Ð01%!5>54'#57.54>32.#"3#&þ4BpT "Y#O,07©’||[O6 V(/L5*'P.0$[$4$?4S*»"+» (+01?&5467'76327'#"'32654&#"$Q"PIY0642YIQ"RIZ47.Zi2$$22$$2‰R.A 7RJZZJR-@!7RJ[ [ -55--559{l¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Yº 9¸¸ йô¸иܸйô¸и ¸Ð013>?33#3##5#535#53¶==“£‰ªªª“©©©‰£{…!B !B …þÛF7F““F7F%ëÿmîº+º+013##ë‚‚‚‚îþJlþ:ÆGÿ¬² AO»+%+»>+º.%9º>+9º.9¸¸Ðº8.9¸8¸Ð01654.'7.#"#"&'732654.5467.54632à 3>$ 2=%Ý86(=G=(+) 0H/7j"T3<';E;',&]U:\ Q!&! ' -@-,A&":*)+K3-@/&C(DU)ÿÿp;èØÿ÷E'EM¸EX¸/¹ >Y¸EX¸/¹>Y¹ô¸¹#ô¸-й4ô¸¸Aй:ô014>32#".732>54.#"4>32.#"3267#".-Mf99fM--Mf99fM-@9P11P99P11P980?"'57 %'&!/7!&?.ENzU--UzNN{W..W{N>dH''Hd>=dG&&Gd>,G2= =-3< D3IÿÿšüÅUÿÿ+ß'ïÿ|ï…F^~ »+01!#5!FÌlþ ~þà¸ÿÿF~e7ñË'5>=»+» +»62+¸¸)и2¸/и¸4и1Ð01".54>32'2>54.#"'32#'##72654&++)H66H))H66H)5''5 4''41Y#.*<:N76J,,J66J,,J61(8##8((8##8(ý "M<32'2654&#"-5''55''5%%%%Ž'4 5''5 4'F)! )) !)F6 D¸EX¸/¹>Y»+¸¸ܸ¸и¸и¹ ô¸ Ü01#53533##!!ö°°l°°l°Ìþ4&h¨¨hˆ6hÿÿž¸±ü¸ÿÿ¥¬²ü¸ÿÿÚ>Éå‡>ÿ<Kð's¸'/¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸ ¹ôº 9¸¹ô¸¹ô013326733:7#"&'##"&'#>“#"0” %19 >%%•ðþá6*"+2,`]Tm0.,- Y¸EX¸/¹ >Y013##".54>;|’’4(1WB&&AV1*Œý$;W9?U5ÿÿ¸ì ×øÿÿÇÿ#ƒ™ÿÿĸ€ð¸ÿÿ…üÓU¤ÿÿ<.ß'ðÿ|ð…ÿÿQ|'ÿ‚D&ñÿÿG|'ÿ‚D&ñ€ÿÿRˆ'ÿD&ñ€gÿFöü)*¸EX¸'/¹'>Y» +¸'¸!ܸÜ01%3267#".54>'7#"&54632'"* 0Q$b<+K7 $(" 8,+88+,8ñ"4*" ! J)2(>)!0("%+¥-77--99ÿÿ O6&&#ÿÿ O6&&$ÿÿ O6&&%ÿÿ OG&&&ÿÿ OH&&*ÿÿ O}&&,ÿýSŒf¸EX¸/¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Y» +»+¸¹ô¸¹ô¸¸Ð01#!5##!#3#7  pþäv-—Ñ|‘vv1]+]‹|••Œ||—ÿÿ7ÿ#9˜&(/0ÿÿ^6&*#ÿÿ^6&*$ÿÿ^6&*%ÿÿ^H&**ÿÿJ6&.#ÿÿJ6&.$ÿÿJ6&.%ÿÿJH&.*2Œ!S¸EX¸/¹ >Y¸EX¸/¹>Y»+¸¹ô¸¹ô¸¸и¸Ð01#5732+72>54.+3#GEE²FtR--PqCº©&?--?&kk-B&OzTT|Q(w1P;:O/¡G¶ÿÿBG&3&ÿÿ&ÿô26&4#ÿÿ&ÿô26&4$ÿÿ&ÿô26&4%ÿÿ&ÿô2G&4&ÿÿ&ÿô2H&4*Tp# )º +º 9¸ ¸и ¸иÐ01?'77'TŽŽIIŽŽIºJJJÿ×Cµ*}¸EX¸&/¹& >Y¸EX¸/¹>Yº&9¸¸Ð¸¹ôº&9¸¸ и&¹ ô¸¸и¸и¸и ¸(Ð01732654&/&#"#"''7.54>327í%3<&)3<<&Ea:R;3N@&Ea:U?5N’sc!l$nb,$'*kBP~X/,I2]+rFP}U-0M2ÿÿ@ÿô6&:#ÿÿ@ÿô6&:$ÿÿ@ÿô6&:%ÿÿ@ÿôH&:*ÿÿH6&>$I6Œ9¸EX¸/¹ >Y¸EX¸/¹>Y» +»+01332+#7254&+I“[5]E()F\4[“åw<;RŒc1P;:S6{ðh3)Ä=ÿô;É9Z¸EX¸/¹>Y¸EX¸9/¹9>Y¸EX¸/¹>Y¹"ôº"9¸¹4ôº%49014>32#"&'732654.54>54&#"#=8V90H.")",A+*?"1*")")&“ï.P;!/= $1'!%3$"<,d ".!,(+#<3þÿÿ=ÿôå&F†ÿÿ=ÿôå&F‡ÿÿ=ÿôå&Fˆÿÿ=ÿôÎ&F‰ÿÿ=ÿôØ&Fÿÿ=ÿôð&FÿôQü09E¥¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸&/¹&>Y¸EX¸,/¹,>Y»1+º ,9¸/¸ ¹ôº& 9¸&¹ôº)& 9¸¹6ô¸,¹<ô¸¹Cô017467.#"'>32>32#3267#"&'#".%4.#"3267./or94*P+,7:,&:'ã0#%2H 6G'C""3#Ì %Ñ.!1+ŠMS#$`(!%$&AW1" 33 ^*#)$(7¼$14‘0' )ÿÿCÿ#$ü&H™)ÿÿ7ÿô"å&J† ÿÿ7ÿô"å&J‡ ÿÿ7ÿô"å&Jˆ ÿÿ7ÿô"Ø&J ÿÿO«å&õ†-ÿÿOöå&õ‡-ÿÿOûå&õˆ-ÿÿOØ&õ-/ÿô&ä4k¸EX¸//¹/>Y¸EX¸/¹>Y»# +¸¹ôº&/9¸&¹ôº/9¸¸)и¸2и,Ð01%265<'.#"#".54>32.''7&'77,4=8 6? ,ƒ@O#A_<3ZC($>O,$B1#•&x+3@&I"ˆ&kLK :<-ó<¦rY76T:-I KA<Y)EAÿÿHÎ&S‰ÿÿ/ÿô)å&T†ÿÿ/ÿô)å&T‡ÿÿ/ÿô)å&Tˆÿÿ/ÿô)Î&T‰ÿÿ/ÿô)Ø&TFIK %»+¸¸Ü¸ܸ¸ܸÜ01"&546324632#"&!!,#..##..t.##..##.•Ìþ4±+""++""+þå"++""++ h/ÿç) *}¸EX¸&/¹&>Y¸EX¸/¹>Yº&9¸¸Ð¸¹ôº&9¸¸ и&¹ ô¸¸и¸и¸и ¸(Ð01732654&/&#"%#"''7.54>327ð"33$"33!*G[1L?,7.!*G[1L>-7{QAAQA#ì!X7>aB#(5*8 X7>aB#'6+ÿÿ>ÿô å&Z†ýÿÿ>ÿô å&Z‡ýÿÿ>ÿô å&Zˆýÿÿ>ÿô Ø&Zýÿÿÿ>;å&^‡HÿH)½$ƒ¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9º9¸¹ô¸¹ô¸¹"ô¸¹$ô01#3>32#"&'532>54&#"Û““G&.J3%;Ø&^ÿÿ O-&&'ÿÿ=ÿô­&FŠÿÿ O6&&(ÿÿ=ÿôÚ&F‹ ÿ!eŒ (e¸EX¸/¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y»% +»+¸¹ô01'.'##"&54>7#'##33267n   €;0B ('Å(–˰Ë!  <1n34m1<þ61-)!››Œýt =ÿ.+ü1=…¸EX¸/¹>Y¸EX¸-/¹->Y¸EX¸/¹>Y¸EX¸ /¹ >Yº-9¸/¸¹ô¸ ¹ô¸¸(и-¹5ô¸¹9ô0174>7.#"'>323267#"&54>7'##".732675=$O|X33$I*43u@hx1' :-= &Z0&<*% !:7H*Š*?,"&`&nrþä0B.-%:')6+Rÿÿ7ÿô96&($6ÿÿCÿô$å&H‡)ÿÿ7ÿô96&(%6ÿÿCÿô$å&Hˆ)ÿÿ7ÿô9W&()6ÿÿCÿô$î&HŒ)ÿÿ7ÿô96&(.6ÿÿCÿô$å&H‘)ÿÿA,6&).þÿÿÿôx&Ië8ÿÿ2Œ”/ÿôR½ *¥¸EX¸/¹>Y¸EX¸'/¹'>Y¸EX¸/¹>Y¸EX¸/¹>Y» )+º!9¸!¹ô¸¹ô¸¹ôº9¸¹ ô¸ ¸ и ¸$и)¸%Ð01&#"3267#'##".54>32'5#53533}'**<2.*ÕBx H&/M7$;L)+:››“BO!>BE?˜ýÓ3$"A\;:]A"M,GJJÿÿ^-&*'ÿÿ7ÿô"­&JŠ ÿÿ^6&*(ÿÿ7ÿô"Ú&J‹ ÿÿ^W&*)ÿÿ7ÿô"î&JŒ ^ÿ!*Œ#^¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸"/¹">Y»+»+¸¹ô¸"¹ô01!!3#!#3267#"&54>7!^®þåðð% !;0C þÀŒ|ƒ{–|  J1-(!7ÿ."ü18^¸EX¸"/¹">Y¸EX¸/¹>Y¸EX¸/¹>Y»8*+¸¹ô¸¹.ô¸"¹5ô01%3267#"&5467#".54>32!3267'4&#"!+  :-;% :dJ++H]29X:þ²Q7"=!A.3(> .$B.-%:#C`>=`C$$@W3$ 81¨*4.0ÿÿ^6&*.ÿÿ7ÿô"å&J‘ ÿÿ,ÿô#6&,%ÿÿ4ÿ-<å&Lˆÿÿ,ÿô#6&,(ÿÿ4ÿ-<Ú&L‹ÿÿ,ÿô#W&,)ÿÿ4ÿ-<î&LŒÿÿ,ÿ#˜&,˜"ÿÿ4ÿ-<Ü&L9ÿÿ@6&-%ÿÿÿîf&M%ÿj0UŒƒ¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸¸ܹô¸ܸ¸и¹ô¸¸ и¸и¸Ð01#37#####57533533…²²Ð=“²“==“²“=æUUþþðæA`___½‡¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y»+º9¸¹ô¸¹ô¸¸и¸и¸Ð01>32#4&#"##57533#Ó#)1TM“ )-“BB“¶¶ž l^þâ 2.þÉ,AKJG/ÿÿJG&.&ÿÿOÎ&õ‰-ÿÿJ-&.'ÿÿOã­&õŠ-ÿÿJ6&.(ÿÿOìÚ&õ‹-Jÿ!Œ#`¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸"/¹">Y»+¸"¹ô¸¹ô¸и¸Ð0173#5!#3#3267#".54>7#J˜˜Ę˜˜)" ";) À|”||þl|/ J #(!Oÿ.Îî&g¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ô¸¹ ô¸¸ܸ!Ü01#5!3267#"&54>7#"&54632ÇZ( :-=  *C+77++77}sþ/B.-%90))33))0ÿÿJW&.)O©ð/¸EX¸/¹>Y¸EX¸/¹>Y¸¹ô01!##OZ“Çðþ}ÿÿ;ÿô6&/%ÿÿ*ÿ<ûå&\ˆ-ÿÿDÿRŒ&0˜ÿÿLÿI½&P˜LIð m¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Yº 9º 9¸ ¸и¹ ô013?3#'#L“·¡¼ËŸJ“ðÅÄÌþÜÃH{ÿÿk 6&1$˜ÿÿ>ÿô m&Q$Þ7ÿÿkÿ Œ&1˜ ÿÿ>ÿ ½&Q˜>ÿÿk Ý&18€ÿÛÿÿ>ÿô &Q8œÿÿk'Œ&1Œ™þÐÿÿ ÿô9½&QÏŒ«þÎ! Œ I¸EX¸/¹ >Y¸EX¸/¹>Y¹ôº9¸¸и¸ и Ð01%!55737 þKJJ“ÎÎ||Æ)r)Tþôqrq’>ÿô ½W¸EX¸/¹>Y¸EX¸/¹>Yº 9¸ ¸ и¹ ô¸ ¸ии¹ô01%#"&=575#5!73267 ""&^_qq#”” & o_R?u@¿têPuQ¡ ÿÿB6&3$ÿÿHå&S‡ÿÿBÿŒ&3˜ÿÿHÿü&S˜ÿÿB6&3.ÿÿHå&S‘ÿÒ„¹&m¸EX¸/¹>Y¸EX¸$/¹$>Y¸EX¸#/¹#>Y¸EX¸/¹>Yº +¸¹ôº&#9¸&¹!ô0167#"&54632%>32#4&#"#3.i )25*9=WXfG2JE“!“x p/[2*.7UKR}(Ž-k]þÌ!1-þ³ð@ÿÿ&ÿô2-&4'ÿÿ/ÿô)­&TŠÿÿ&ÿô26&4(ÿÿ/ÿô)Ú&T‹ÿÿ&ÿô26&4-ÿÿ/ÿô)Ô&TSŒ!O¸EX¸/¹ >Y¸EX¸/¹>Y»  +¸¹ô¸¹ ô¸и¸Ð014>3!#3#3!".7;#"*Je;" yyªþÌ:bH(—((ITzO&{„{–|(Q|T;Q1ž/N ÿôPü 4=¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸*/¹*>Y¸EX¸0/¹0>Y»5+¸0¹ô¸¹ ôº*9¸*¹#ôº-*9¸¹:ô01732654&#"4>32>32#3267#"&'#".%4.#"  „3D&+<>%%:'Þ/#"2F'C:-&C2Ð "øAMMAAMMA>aB#/'*,&AW133 ^(%&'#Bam$14ÿÿFH6&7$ÿÿt$å&W‡'ÿÿFÿHŒ&7˜ÿÿeÿ$ü&W˜ ÿÿFH6&7.ÿÿt$å&W‘'ÿÿ2ÿô'6&8$ÿÿ5ÿôå&X‡ ÿÿ2ÿô'6&8%ÿÿ5ÿôå&Xˆ ÿÿ2ÿ#'˜&8/ÿÿ5ÿ#ü&X™ ÿÿ2ÿô'6&8.ÿÿ5ÿôå&X‘ ÿÿÿ#9Œ&9/ÿÿ1ÿ#&t&Y™Iÿÿ96&9.ÿÿ1ÿô&&Y8„ÿÿ@ÿôG&:&ÿÿ>ÿô Î&Z‰ýÿÿ@ÿô-&:'ÿÿ>ÿô ­&ZŠýÿÿ@ÿô6&:(ÿÿ>ÿô Ú&Z‹ýÿÿ@ÿô}&:,ÿÿ>ÿô ð&Zýÿÿ@ÿô6&:-ÿÿ>ÿôÔ&Zý@ÿ!Œ(R¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸%/¹%>Y»+¸%¹ô¸%¸Ð013326533267#"&54>7.5@“,./.Ž>>$ ";0CjhŒþ`;>>; þo^w  J1-&ˆv>ÿ.!ð(ˆ¸EX¸/¹>Y¸EX¸'/¹'>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸ ¹ô¸¸к9¸¹#ô¸¹&ô01!3267#"&54>7'##"&5332673 2& :-= !P6UL“*+“/B.-%I&-l^2þá2.!FÿÿT6&<%ÿÿTå&\ˆÿÿH6&>%ÿÿÿ>;å&^ˆÿÿHH&>*ÿÿ8#6&?$ÿÿFå&_‡ÿÿ8#W&?)ÿÿFî&_Œÿÿ8#6&?.ÿÿFå&_‘ÿô)½ )¥¸EX¸/¹>Y¸EX¸#/¹#>Y¸EX¸/¹>Y¸EX¸/¹>Y»"+º9¸¹ô¸¹ô¸¹ôº 9¸ ¹ ô¸"¸!и"¸&и¸'Ð01732654#"'>32#"&'###57533#Û-)8[/-I$.K3$`B#" 6,AKJG/:ÿô0˜"C¸EX¸ /¹ >Y¸EX¸/¹>Y»+¸¹ô¸ ¹ô013267>32#".547!.#"Ð5*'7 þá"[?7[B$%B]88\B$`;6#2KKLKG )-W}PP~W.-V}OSU.ÿ &˜+B¸EX¸&/¹& >Y»+» +¸&¹ô¸ ¸и¸ Ð01.#"3##"&'7>?#5737>32 &'s-G6!2 WK2I4 +#7k½-N:! l 6+’e+7N2&ÿôS ,G¸EX¸$/¹$ >Y¸EX¸/¹>Y¹ô¸$¹ôº$9¸¸&Ð01%2654&#"#".54>32>54&',3<<33<<= >0$)&Ea::aE&&Ea:B8 sscbnnbcs)3< +RP~X//X~PP}U- /ÿôI~ -G¸EX¸$/¹$>Y¸EX¸/¹>Y¹ô¸$¹ôº$9¸¸'Ð01%2654&#"#".54>32>54&',33333332 <."(*G[11[G**G[15$ kLAALLAAL)3: !_=>aB##Ba>>aB# @ÿô”$"J¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ô¸ ¹ô01#"&5332653>54&'w !.swxv“,//-F $)+þ¥€‡‡€‘þ`;>>;   >ÿônˆ#a¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Yº 9¹ô¸ ¹ô01#'##"&5332673>54&'Q 8)x !P6UL“*+1 ˆ)19 þ>G&-l^2þá2.!F ÿÿ O6&&.ÿÿ=ÿôå&F‘ÿÿJ6&..ÿÿOûå&õ‘-ÿÿ&ÿô26&4.ÿÿ/ÿô)å&T‘ÿÿ@ÿô6&:.ÿÿ>ÿô å&Z‘ýÿÿ@ÿô­&:1ÿÿ>ÿô 9&Z0ýÿÿ@ÿôÎ&:3ÿÿ>ÿô P&Z2ýÿÿ@ÿôÎ&:5ÿÿ>ÿô P&Z4ýÿÿ@ÿôÎ&:7ÿÿ>ÿô P&Z6ýÿÿ,ÿô#6&,.ÿÿ4ÿ-<å&L‘&ÿ!2˜&2K¸EX¸/¹ >Y¸EX¸ /¹ >Y»#+¸ ¸и ¹'ô¸¹-ô01#"&5467.54>323267'2654&#"®;0C(Y¸EX¸/¹>Y¸EX¸$/¹$>Y¸¸и$¹ô¸¹*ô¸ ¹0ô01467.54>323267#"&32654&#"Ö%-Q<$*F[21\F*.@'$ :-=33333333w&8(BZ9>aB##Ba>2L<-.B.œALLAALLÿÿ2ÿ'˜&8˜ ÿÿ5ÿü&X˜ÿÿÿ9Œ&9˜ÿÿ1ÿ&t&Y˜D*ÿ<©ð5¸EX¸/¹>Y¸EX¸/¹>Y¹ô¸¹ô01!#"&'73265#OZ5YC.K +15'Çðþ.-R>%i 36e 0Œ ,c¸EX¸$/¹$ >Y¸EX¸/¹>Y¹ô¸ иии и$¹ôº 9¸ ¸ и¸"и!Ð01%2654&+3#3254&++5#5732)<>==Bjj5g336»FH'C\5áKKÎ3W?$49i644/@GF$Q$ ¸OE4K1¯A–%<,)JHÿô"ü)v¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸/¸¹ôº9¸¹!ô¸¹$ô¸¹%ô013267#"&533>324&#">"$O|X33$I*43u@hxx &Z0%=*% !:7H*g*?-"&a&nr8')6+R/ÿôü #¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¹ôº9¸¹ôº9¸¹ô¸¹ ô01732675.#"4>32373#'##".Æ2.*));—$;N*&C sx K$/M7úJDÉDH=`C$!3þ3$$C`Hÿô)ü "ƒ¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ôº9¸¹ôº9¸¹ô¸¹ ô01%4#"3267#"&'##33>32’^+,):—$Y¸EX¸/¹>Y¹ ô¸¹ô01%#"&'732654&#"'>32,Kg:5i+<=%AQI< :D h?9cK+ø>aB##&]LAAL[&#Ba6ÿô!ü#C¸EX¸/¹>Y¸EX¸/¹>Y»+¸¹ ô¸¹ ô01%#"&'73267!.54>32.#"!+Jd:2h*0!="7N þ² >[;0ZD)• 7*25ø>`C#X18 $3W@$$C`0.4*7ÿô"ü#C¸EX¸/¹>Y¸EX¸/¹>Y»#+¸¹ô¸¹ ô017467!.#"'>32#".732677O A6"B1+g2;aE&)F]3:X<†12+;ä$ 61\$C`=Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸¹ôº9º9¸ ¹!ô¸¹$ô¸¹%ô¸¹(ô01326?#".54>32373#"&'2675.#"#M79I#/M7#;N+N9 s‚€*j,Ø+*$6,/#@$AY69]A$?3þ eo°!0>B<ÿHða¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº 9¹ô¸¹ô01#57#"&5332673“S8TM“ )-“¸—_-l^2þá2.K ÿô9𠥸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Yº9º  9¸ ¹ô¸ ¹ô¸¹ô¸¹ô01!#'##"&'#"&533273326739n 1, - 5*4:Šq Š>+)(!0UK\þ¯.0Oþ¯.O4ÿôäðT¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Yº9¸¹ ô¸¹ô01!#'##"&'732673äx %hY¸EX¸/¹>Y¹ô¸Ð¸¸и¹ ô01%3##5#5354&#"'>32ª}„yÒÒ260!Q0;R3ss„„s®9/ j 9Q19ð @¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸ /¹ >Yº 901!#'.'##39”N    N¶©ê&L''L&êðTð!i¸ /¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº 9º9º901!#'.'##'.'##33>?3T g! †Mª §æ%H&&J#¤¤%H&&H%æð¥#G(D3¥9ÉR¸EX¸/¹>Y¸EX¸/¹>Y¸EX¸/¹>Y¸¹ôº 9¸ ¸Ð01&#"#'.'##>32ü). ×’S   I‹Ì)5E.$O(-þÕ#K'%K%Õ .H1pÿH ð c¸EX¸/¹>Y¸EX¸ /¹ >Y¸EX¸/¹>Y¸EX¸/¹>Yº9º 9¸ ¸Ð01###73753 “¶¡¼ËŸJ“¸}ÅÌ$ÃH{œÕÒ:¸/¸ /¸EX¸/¹>Y»+º9¸¹ô013>32#54&#"#œh5$92hhÒp<H>˾Û…„‘ö4¸EX¸/¹ >Y»+¸¹ô¸¸ܸÜ013#"&'7326=#7"&54632å$<.0 "~°&&''MþÎ7) I #å~!##!ÖïU6¸/¸EX¸/¹ >Y» +º9¸¹ô0133>32.#"#ÖV@#5hMH)'V",¨SâM!Z¸/¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸/¹ >Y¸¹ô¸и ¹ô¸¸ Ð0133>?33>?3#'.'##SfG^1xwM”02dd00”þ·f/,#f…ÖMA¸EX¸/¹ >Y¸EX¸ /¹ >Y»+º9¸¸Ð017326?33>?3#"&'7® ‚h/  (ct ". Ú?ˆ./ˆþ· 0Nÿÿ¸"¹èÿÿÊ"Ÿ¹éêü‰ð º +º+0126548'ê2$$2QNNQ2&&6E88EÑüpð º+º +01"3&546p2%%2QNNQº&&6E88EÿÿŠ>ÎåˆÿÿŠ>Îå‘àyxä º+013#à˜Zäþ•ÿÿ¢P¶­ŠÿÿÚ>Éå‡ÿÿ>~å†àþ÷xb º+01#3x˜Zþ÷kÿÿ™>¿Ú‹ÿÿÊ9ŽîŒÿÿ»$ðÿÿÖÿ.¥šÿÿ?×Ήÿÿ¯=Ô¶üÒÒ(¸EX¸/¹>Y» +¸¹ô0133267#"&=#¶¬ %D|ÚMK¸/¸/¸EX¸/¹ >Y¸EX¸ /¹ >Yº 9¸¸Ð¸¸ иÐ01'33>?3#'&'##ëholioo"l¯ž33ªŸ33>~å º+013#–Ykå§Ú>Éå º+01#73EkY–>§Š>Îå»+¸¸ܸÐ013#'##î|dk55kå§__?×Î'»+¸¸ܹô¸ и¸Ð01>3232673#".#"# >. K >. K?IFHG¢P¶­ »+01!!¢þì­]™>¿Ú» +¸ ¸ ܸÐ01".'332673,%7$RR$7>+9 ++ 9+Ê9Žî »+01"&54632,+77++7790))33))0p;èØ »+¸¸ и¸Ð01"&546323"&54632¿#,,##++·#++##,,;-"!--!"--"!--!"-Û-”ð»+¸¸ܹô01>54&'7þ$+\L&3g J1(% »$ð º +¸ ¸ܸÜ01"&54632'2654&#",3>>33>>3$7//77//77¯=Ô»+¸¸и¸Ð013#73#óu_Zïu_[Ô———Š>Îå»+¸¸ܸÐ013373#Šk55kd|å``§E=©Ô»+¸¸и¸Ð01#'3#'3©Z_uf[_u=———è aô» +¸¸Ü01632#"5467a8 B03Ç'S*Cômñ» +¸¸Ü0167#"&54632ô8B03J'S*C"«×~»+¸¸Ü01>54&'7"% e .>#æ ,)!0ÊÿŽÿ¸ »+01"&54632,+77++77ý0))33))0ƒÿ(Õÿ´ »+¸¸ и¸Ð01"&546323"&54632É''''§''''Ø())(())(ÅÿÿÖ»+¸¸ ܹô01'>54&'7D50@$ $/*'&% ;6Åÿ#º+¸¸и ܹ ô01%3'>54&'V 0@$ $/"/#% ; Öÿ.¥¸EX¸/¹>Y» +014>733267#"&Ö S6 :-=w&*-B.¡ÿ.·ÿ¹ » +01".'332673,#3"RR"3Ò&3""3&¢ÿC¶ÿ¡ »+01!5!¶þì½^šüÅU"9¸/»+» +» +º 9¸¹ô01467&#"'>32#'##"&732675š\g5.%!L(AJU309d 6)_5: +CINº!:2 5šüÞÒT¸/¸EX¸/¹>Y»+»+º9º9¸¹ô¸¹ô013>32#"'##732654#"šh/?G(30) Qh  #8Òp1\K*C-+#_ *2U€üÄÒ!T¸ /¸EX¸ /¹ >Y»+»+º9º9¸¹ô¸¹ô014>32'53#'##"&732675.#"€(3%gT0@Lk $¨(A,1oþ2 [R/* ‚ *”üÑU"!»+»+» +014>32#327#".74&#"”/; &9%Õ2"&'#D&@0å '¨(@-+:!#=,@L! …zäUBNd¸EX¸#/¹# >Y»>+»!I+»8 +»C.+º 89¸¸ и.¸1ии#¹&ô01732654&+"&'475.54675.54>323##"&';2#".72654&#"Ú,%&1( U4'4‡A&2  Y¸EX¸/¹ >Yº 9¸ ¹ôº 9¸ ¸Ð013373#'# hrq|…pQ-hÒþþ}ˆÁ},QsßUT¸/¸/¸/¸EX¸/¹ >Y» +»+º9¸¸и¹ô¸Ð0133>32>32#54#"#54#"#sN . $#'c P bM)6 :1æßÛßÛ…üÓU»+»+014>32#".732654&#"…/Y» +»+º 9º 9¸¹ô¸¹ô01%#33>32#"&'732654#"hU3?F(3'  #8ë_Á \K*C-E *2U–üÓ£I¸EX¸/¹ >Y¸EX¸/¹ >Y» +¸¹ô¸¸и¸ Ð01#5?33#327#".5ÛEJ W #3(8"üMVVQn$ J &5!üÈMA¸EX¸ /¹ >Y¸EX¸/¹ >Y»+º9¸¸Ð01#'##"&=3326753ÈU5#92hh-H=Ì¿ ØvÜM 3¸ /¸EX¸ /¹ >Y¸EX¸/¹ >Y¸ ¹ô0133>?3#vh0  0cuxM—22—þ·ªüÒU»+» +014>32&#"3267#".ª3C%"50'//$ )@&@0¨)@-@0*)1 A,@·üÚ8¸ /¸EX¸/¹ >Y»+¸¹ô¸ и¸Ð01.#"3##5#5754>32è#Y¸ ¹ô¸Ð¸¹ô¸Ð017#5!3!—§“#§­þÃ;ÁQ7ÁQÿÿAÿ,Œ&)–÷ÿÿ/ÿ½&I– ÿÿAÿC,Œ&)œ÷ÿÿ/ÿC½&Iœ ÿÿ,ÿô#-&,'ÿÿ4ÿ-<­&LŠÿÿ@ÿŒ&-–ÿÿHÿ½&M–ÿÿ@ÿ.Œ&-›ÿÿHÿ.½&M›ÿÿkÿ Œ&1– ÿÿ>ÿ ½&Q–>ÿÿ7ÿ -&1&'˜– ÿÿ>ÿ &Q'ŠÿäÔ–>ÿÿkÿC Œ&1œ ÿÿ>ÿC ½&Qœ>ÿÿAÿŒ&2–ÿÿ$ÿ=ü&R–ÿÿBW&3)ÿÿHî&SŒÿÿBÿŒ&3–ÿÿHÿü&S–ÿÿBÿCŒ&3œÿÿHÿCü&SœÿÿFÿHŒ&7–ÿÿjÿ$ü&W– ÿÿFÿH-&7&'–ÿÿjÿ$­&W&Š'– ÿÿFÿCHŒ&7œÿÿBÿC$ü&Wœ ÿÿ2ÿô'W&8)ÿÿ5ÿôî&XŒ ÿÿ2ÿ'˜&8– ÿÿ5ÿü&X–ÿÿÿ9Œ&9–ÿÿ1ÿ&t&Y–DÿÿÿC9Œ&9œÿÿ1ÿC&t&YœDÿÿT6&<#ÿÿTå&\†ÿÿT6&<$ÿÿTå&\‡ÿÿTH&<*ÿÿTØ&\ÿÿHW&>)ÿÿÿ>;î&^Œÿÿ8ÿ#Œ&?– ÿÿFÿð&_–ÿÿ1ÿô&d&YÿÞŒ@ÿô5˜*h¸EX¸/¹ >Y¸EX¸)/¹)>Y¸EX¸/¹>Y»! +º ! 9¸¹ô¸¹%ôº%9014>32#"&'732654&/7.#"#@:[@WqH*!+@+8LL 33 K%-(—º.Q<#eRz +9%(F3&Y( 3V}$=>þZÿÿH6&>#ÿÿÿ>;å&^†ÿÿÿHŒ&>–ÿÿÿ Hð&^–ºÿÿHv&>+ÿÿÿ>;ð&^ŽÿÿHG&>&ÿÿÿ>;Î&^‰PÇ6 »+01!!P¸þH6oÇD6 »+01!!0ýÐ6o¸"¹ º +01632#"&546757 (36*8=WXkD01*.7TLR}(Ê"Ÿ¹ º +0167#"&54632Êi )36*932,#<,,<##<,,>³’š7¬¬7À©ß º+01%'7'?>««>ü¬7š’š7Gp º+01?'7º3¢ëº3¢e­/Ç ®0Çÿÿ˜¬Àü¸©ö*¸/¸EX¸/¹ >Y¹ô¸¸ܸ Ü013#5#7"&54632©åh}°&&''Mþ·ø~!##!ÿÿ¡¸Òð¸ÿÿ¥¬´ð¸ÿÿ¢¬µü¸ÿÿ±¸¹ð¸ÿÿ¦¬²ü¸ÿÿ¤¬·ü ¸ÿÿÖ_‘\!¸ÿÿÇ_‚\"¸œÕU:¸/¸ /¸EX¸/¹ >Y»+º9¸¹ô0133>32#54&#"#œU5%83hhM*H>˾Ûÿÿ˜ÿKÀ›ÿWÿÿÄÿW€ÿWÿÿžÿW±›ÿWÿÿ¥ÿK²›ÿWÿÿ¡ÿWÒÿWÿÿ¥ÿK´ÿWÿÿ¢ÿKµ›ÿWÿÿ±ÿW¹ÿWÿÿ¦ÿK²›ÿWÿÿ¤ÿK·› ÿWÿÿÖþþ‘û!ÿWÿÿÇþþ‚û"ÿW”üÎU!!»+»+»+014673.#"'>32#".73267”Ò)!&#@ L]-; &9&X %›# ?\Q(?-+: ! Oÿ’3é 4±¸EX¸$/¹$ >Y¸EX¸/¹>Y¸йô¸¹ô¸$¹ô¸и¸и¸ и¸и¸ܸи¸и$¸!и$¸#ܸ¹(ô¸#¸)и(¸+и¸1и¸2и2/01%&+#7&'#7.54>?33273&'672  2 4&($-&Z6 2  2KY"?X7 2  2 2Q02%vªeKf7R).cciy ™vFqT4 gbdq O þh +C&‡1}¸EX¸/¹ >Y¸EX¸/¹>Y» +»$'+¸¹ô¸ ¸и/¸'¸ и$¸и/¸¹ô¸ ¸*и¸,Ð01%!5>7#573.'#57&54>32.#"3#3#&þ-?pO[C"Y#O,07µ¢•’||[A,E  E/L5*'P.0 K  J)<{ +á¸EX¸#/¹# >Y¸EX¸'/¹' >Y¸EX¸/¹>Y¸EX¸/¹>Y»+»&+¸¹ô¸¸к #9¸¸ и&¸ и#¹ô¸¸и¸и¸и¸и¸и¸и/¸¸и&¸!и!/¸&¸)Ð01#3#3'3'#'3'#3##'##5#575#57533533‘;/!*á;/!*GEEˆYKjFFFFˆYKjEV0:¹0:~¸0:ìììì404ëëëë:;{ !c¸EX¸/¹ >Y¸EX¸/¹>Y»+»+¸¹ ô¸¸ и¸и¸и¸ Ð01267#53.##+##575323,6 Ž 6,5=+?P,,‹DD·,P?*>3&#IìH&"£+>)×|U¥'?-[2‹-V¸EX¸/¹>Y»"+¸¹ô¹ô¹ ô¸"¸ܸ'и¹-ô¸)и-¸,Ü017!!.#"3267#'##"&54>32'5#5353h£þ] "2*##ÁHd ;#N]2? %/ššyJJh +&0,aDþn$b\*B/CJ11-ÿôG‡1m¸EX¸/¹ >Y¸EX¸/¹>Yº($+¸(¹+ô¸и(¸ и$¸и$¹!ô¸и¹ô¸¹.ô01%#".'#57&45<7#57>32.#"3!3#3267G)d=3XG2 @77@–l3_%Q4 3BýþùÞÒB1#6P-/9U8E  Dpz(&O>9JK7:;{ 5¸EX¸./¹. >Y¸EX¸$/¹$>Y» "+¸.¹ô¸йô¸Ü¹ ô¸¸и¸и ¸и ¸&и¸)и¸*и¸-и¸4Ð016454&'#27#53&##3#+##575#575323w’">~}@5;;F .Y¸EX¸/¹>Y»&+¸¸и¸и¹ô¸¹"ô013#5.54>753.#"3275#KÎI)b2S<"!Y¸EX¸/¹ >Y¸EX¸ /¹ >Y¸EX¸ /¹ >Y¸¹ô¸ ¹ô¸ ¸ ܸ¸ܸ¸!и¸"Ð01#5.54>753.'67C012/ð!O/Q5ZA$#@Z7Q*NQ"+#jTUj$-ed0RrJGrT4][' O þZ (X {p¸EX¸/¹ >Y¸EX¸/¹ >Y¸EX¸ /¹ >Y¸¹ô¸иии¹ô¸и¸й ô¸ и /01#3##'#5327#573.+5! •%LJI6»£¤VJtÖQƒ E8J³12J@QàÉuGDu$ÿô'{#]¸EX¸/¹ >Y¸EX¸/¹>Y¹ôº9¸¸"ии¸иии¸иÐ01%>54&'7'5575575377 9,y;o dUUUU“    }",  DiE"è)Q)7)Q)Æ~MQM7MQM§F~ »+01!!FÌþ4~hÿÿGpñ˜ÿôÀD(¸EX¸/¹>Y» +¸¹ô01".54>32'2654&#", 6((6 6((6  ,?''=++=''?,O(33$$33(Ä€8 ¸EX¸ /¹ >Y»+01%#5>73#Q SkÔ@ þÈœ¯D,¸EX¸/¹>Y»+¸¹ô¸Ð017>54&#"'>323!«5'>@)Y»+º +¸'¹ôº 901732654춮&#"'>32#"&'Î-#& "*D)+ #$1F#` 7  > !! &%¡Ò88¸EX¸ /¹ >Y»+» +¸¸Ð¸¸ Ð01%57###5#5733A"!Ò3^ x†3c>;B==3ȹ¥ÿô´8 D¸EX¸/¹>Y»+» +¸¹ôº 9¸¸ Ð01732654&#"'73#>32#"&'Î++Õ… 2;$1"D#` ¡X#3.(¢ÿôµD &F¸EX¸/¹>Y»# +»+¸¹ôº#9¸¹ô01%2654#"7.#">32#"&54>323&d &"'.6!.HN)=) 6C› ."0.)]J"=.±¹8&¸EX¸/¹>Y»+¸¸ Ð013>7#5!#é  ’# p"854W8 ;Y»#+¸4¹ôº49º#901%32654&'7654&#"4675.54>32#"& 5 &"-6H  #1Y»+»+º$9¸¹ô¸$¹ ô01%3267.#"3267#"&54>32#"&'%  #3 &!!.-6!.#8'*=(!6Ô œ -!//),>%!=/Öÿ§‘¤ º +017467.Ö93O)**)O39¦Ow800g78g008wÇÿ§‚¤ º +01%'>54&'7‚93O)**)O39¦Pw800g87g008wоw6 º+013#ŠœQu6xá¾Î6 º+01#73VuQœ¾x„¾Ô6»+¸¸ܸÐ0173#'#„``q55¾xx==‚¿ÖG+»+¸¸ܸ¸и¹ ô¸Ð01".#"#>3232673k" K<'" K<¿ECECŸÐ¹- »+01!!Ÿþæ-]£¾µ6 »+¸¸ܸ Ð01"&'332673,BAQQA¾B66B×½W »+01"&54632,%00%%00½+""++""+ƒ¼ÕH »+¸¸ и¸Ð01"&546323"&54632É''''§''''¼((((((((Û³”vº+¸¸ܹô01>54&'7þ$+\L&3í I1(%»¹} º +¸ ¸ܸÜ01"&54632'2654&#",3>>33>>3¹5--55--57£¾6»+¸¸и¸Ð017373#£?}Y}Zc¾xxxx„¾Ô6»+¸¸ܸÐ01#'337Ô``q556xx==Äÿ#€º+¸¸и ܹ ô01%3'>54&'V!1@# #/"/#% ; ‹;Í9 +»+¸¸ и¸и¸ܹô01"&546323"&54632%!!É####«####þøþì;#$$##$$#þE‹¼Í­+» +¸ ¸ܹô¸¸и ¸Ð01!!"&546323"&54632Ÿþæ*####«####­E¬$##$$##$‹;ÍP%º+» +¸¸и ¸Ð013#"&546323"&54632;~s^####«####Po¦#$$##$$#‹¼ÍÎ)» +¸ ¸ܸܸ¸и ¸Ð013#"&546323"&546322švu####«####Îxš$##$$##$‹;ÍP+»+¸¸ܹô¸¸и¸Ð013373#"&546323"&54632˜d..dV|%####«####P::o¦#$$##$$#„¼ÔÎ +»+¸¸ и¸и¸ܹô01"&546323"&54632#'337É####«####*``q55¼$##$$##$xx<<‹;ÍP)» +¸ ¸ܸܸ¸и ¸Ð01#'3"&54632#"&54632p^s~r####á####áoþë#$$##$$#‹¼ÍÎ)» +¸ ¸Ü¸ܸ¸и ¸Ð01#'3"&54632#"&54632wuvši####á####Vxþî$##$$##$ý$[ »+01'3^$ÞE™Ö!“Ü»+¸¸ ܹô01.54>7OC61@$ $/+!'&&; 6>.e#ÌA³`ð‡ ¼ ý Û'Ï$9õ , E u F„ (ð ‚/ $õ À. 4Q ¢ 2É #¶ H9«Typographic alternatesTypographic alternatesSource Code ProSource Code ProBoldBold1.017;ADBE;SourceCodePro-Bold;ADOBE1.017;ADBE;SourceCodePro-Bold;ADOBESource Code Pro BoldSource Code Pro BoldVersion 1.017;PS Version 1.000;hotconv 1.0.70;makeotf.lib2.5.5900Version 1.017;PS Version 1.000;hotconv 1.0.70;makeotf.lib2.5.5900SourceCodePro-BoldSourceCodePro-BoldSource is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.Adobe Systems IncorporatedAdobe Systems IncorporatedPaul D. HuntPaul D. Hunthttp://www.adobe.com/typehttp://www.adobe.com/typeCopyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries. This Font Software is licensed under the SIL Open Font License, Version 1.1. This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL ----------------------------------------------------------- SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ----------------------------------------------------------- PREAMBLE The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. DEFINITIONS "Font Software" refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. "Reserved Font Name" refers to any names specified as such after the copyright statement(s). "Original Version" refers to the collection of Font Software components as distributed by the Copyright Holder(s). "Modified Version" refers to any derivative made by adding to, deleting, or substituting -- in part or in whole -- any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. "Author" refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. PERMISSION & CONDITIONS Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. TERMINATION This license becomes null and void if any of the above conditions are not met. DISCLAIMER THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. http://www.adobe.com/type/legal.htmlhttp://www.adobe.com/type/legal.htmlÿµ2:  !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`a£„…½–膎‹©¤ŠÚƒ“ˆÃÞ žªõôö¢­ÉÇ®bcdËeÈÊÏÌÍÎéfÓÐѯgð‘ÖÔÕhëí‰jikmln oqprsutvwêxzy{}|¸¡~€ìîº    ýþÿ !"øù#$%&'()*+,-./012ú×3456789:;<=>?âã@ABCDEFGHIJKL°±MNOPQRSTUVWXäåYZ[\]^_`abcdefghijkl»mnopæçqrstuvwxyz{|}~€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§¨©Øáª«¬­®ÛÜÝàÙ߯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ     ²³¶·Ä´µÅ‡¾¿¼ !"#$%&'()*+,-./01234ï56789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXNULLCRuni00A0uni00ADtwo.sups three.supsuni00B5one.supsAmacronamacronAbreveabreveAogonekaogonek Ccircumflex ccircumflex Cdotaccent cdotaccentDcarondcaronDcroatEmacronemacronEbreveebreve Edotaccent edotaccentEogonekeogonekEcaronecaron Gcircumflex gcircumflex Gdotaccent gdotaccentuni0122uni0123 Hcircumflex hcircumflexHbarhbarItildeitildeImacronimacronuni012Cuni012DIogonekiogonek Jcircumflex jcircumflexuni0136uni0137 kgreenlandicLacutelacuteuni013Buni013CLcaronlcaronLdotldotNacutenacuteuni0145uni0146Ncaronncaron napostropheOmacronomacronuni014Euni014F Ohungarumlaut ohungarumlautRacuteracuteuni0156uni0157RcaronrcaronSacutesacute Scircumflex scircumflexuni015Euni015Funi0162uni0163TcarontcaronUtildeutildeUmacronumacronUbreveubreveUringuring Uhungarumlaut uhungarumlautUogonekuogonek Wcircumflex wcircumflex Ycircumflex ycircumflexZacutezacute Zdotaccent zdotaccentuni0180uni018Funi0192OhornohornUhornuhornuni01CDuni01CEuni01CFuni01D0uni01D1uni01D2uni01D3uni01D4uni01D5uni01D6uni01D7uni01D8uni01D9uni01DAuni01DBuni01DCGcarongcaronuni01EAuni01EBuni0218uni0219uni021Auni021Buni0237uni0243uni0250uni0251uni0252uni0254uni0258uni0259uni0261uni0265uni026Funi0279uni0287uni028Cuni028Duni028Euni029Eh.supsj.supsr.supsw.supsy.supsuni02BBuni02BCuni02BEuni02BFuni02C8uni02C9uni02CAuni02CBuni02CCl.supss.supsx.supsuni0300uni0301uni0302uni0303uni0304uni0306uni0307uni0308uni0309uni030Auni030Buni030Cuni030Funi0312uni0313uni031Buni0323uni0324uni0326uni0327uni0328uni032Euni0331a.supsb.supsd.supse.supsg.supsk.supsm.supso.supsp.supst.supsu.supsv.supsc.supsf.supsz.supsuni1E0Cuni1E0Duni1E0Euni1E0Funi1E20uni1E21uni1E24uni1E25uni1E2Auni1E2Buni1E36uni1E37uni1E38uni1E39uni1E3Auni1E3Buni1E42uni1E43uni1E44uni1E45uni1E46uni1E47uni1E48uni1E49uni1E5Auni1E5Buni1E5Cuni1E5Duni1E5Euni1E5Funi1E60uni1E61uni1E62uni1E63uni1E6Cuni1E6Duni1E6Euni1E6FWgravewgraveWacutewacute Wdieresis wdieresisuni1E8Euni1E8Funi1E92uni1E93uni1E97uni1E9EYgraveygraveuni1EF4uni1EF5uni1EF6uni1EF7uni1EF8uni1EF9 zero.supsi.sups four.sups five.supssix.sups seven.sups eight.sups nine.supsparenleft.supsparenright.supsn.sups zero.subsone.substwo.subs three.subs four.subs five.subssix.subs seven.subs eight.subs nine.subsparenleft.subsparenright.subs uni0259.sups colonmonetarylirauni20A6pesetadongEurouni20B1uni20B2uni20B5uni20B9uni20BAuni2215 zero.dnomone.dnomtwo.dnom three.dnom four.dnom five.dnomsix.dnom seven.dnom eight.dnom nine.dnomparenleft.dnomparenright.dnom uni0300.cap uni0301.cap uni0302.cap uni0303.cap uni0304.cap uni0306.cap uni0307.cap uni0308.cap uni0309.cap uni030A.cap uni030B.cap uni030C.cap uni0327.cap uni03080304uni03080304.cap uni03080301uni03080301.cap uni0308030Cuni0308030C.cap uni03080300uni03080300.cap uni030C.a uni0326.aÿÿÌm±UÍ wÍámcollective-2.12.1/ext/0000755005276200011600000000000013265671736014613 5ustar jenkinsjenkinsmcollective-2.12.1/ext/help-templates/0000755005276200011600000000000013265671736017537 5ustar jenkinsjenkinsmcollective-2.12.1/ext/help-templates/README0000644005276200011600000000007613265671724020417 0ustar jenkinsjenkinsA number of templates for the SimpleRPC DDL based help system mcollective-2.12.1/ext/help-templates/rpc-help-markdown.erb0000644005276200011600000000302013265671724023553 0ustar jenkinsjenkins<%= 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.12.1/ext/vim/0000755005276200011600000000000013265671736015406 5ustar jenkinsjenkinsmcollective-2.12.1/ext/vim/mcollective_ddl.snippets0000644005276200011600000000500313265671724022321 0ustar jenkinsjenkins# 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.12.1/ext/vim/_.snippets0000644005276200011600000000025313265671724017410 0ustar jenkinsjenkinssnippet mcagent module MCollective module Agent class ${1:Agentname} 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.12.1/ext/mc-irb0000755005276200011600000001460013265671724015710 0ustar jenkinsjenkins#!/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.12.1/ext/project_data.yaml0000644005276200011600000000253113265671724020134 0ustar jenkinsjenkins--- 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.' # 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/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.12.1/ext/build_defaults.yaml0000644005276200011600000000122613265671724020463 0ustar jenkinsjenkins--- packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master' packaging_repo: 'packaging' #default_cow: 'base-squeeze-i386.cow' #cows: 'base-precise-i386.cow base-trusty-i386.cow' pbuild_conf: '/etc/pbuilderrc' packager: 'puppetlabs' gpg_name: 'info@puppetlabs.com' gpg_key: '7F438280EF8D349F' sign_tar: FALSE # a space separated list of mock configs #final_mocks: 'pl-el-6-i386 pl-el-7-x86_64' 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.12.1/ext/packaging.rake0000644005276200011600000000233713265671724017405 0ustar jenkinsjenkinsbuild_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.12.1/ext/mc-rpc-restserver.rb0000755005276200011600000000153213265671724020524 0ustar jenkinsjenkins#!/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.12.1/ext/stompclient0000755005276200011600000001001713265671724017076 0ustar jenkinsjenkins#!/usr/bin/env ruby # == Synopsis # # stompclient: Generic client to consume and produce STOMP queues and topics, tested against # Apache Active MQ # # == Description # A simple client that can connect to an STOMP server, subscribe to topics and queues and also # send to topics and queues. # # == Usage # stompclient [OPTIONS] # # --help, -h: # Show Help # # --server, -s # The server to connect to, can also be set in STOMP_SERVER environment variable # # --port, -p # The port to connect to, default to 6163 # # --user, -u # The user to connect as, can also be set in STOMP_USER environment variable # # --password, -P # The password to use, can also be set in STOMP_PASSWORD environment variable # # When connected to a server, use the 'help' command to see further information about # using the client, common commands that can be issued are: # # - subscribe /topic/foo: Subscribes to topic 'foo' # - /topic/foo bar: Sends 'bar' to the topic 'foo' # - details: Toggle the display or timestamp and topic or queue information for each message # # # == Changelog # - 20 December 2009 Include into MCollective # - 17 March 2009 Initial release # # R.I.Pienaar 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| loop do 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.12.1/ext/aio/0000755005276200011600000000000013265671736015363 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/solaris/0000755005276200011600000000000013265671736017037 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/solaris/smf/0000755005276200011600000000000013265671736017624 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/solaris/smf/mcollective.xml0000644005276200011600000000330513265671724022652 0ustar jenkinsjenkins mcollective-2.12.1/ext/aio/common/0000755005276200011600000000000013265671736016653 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/common/client.cfg.dist0000644005276200011600000000103413265671724021547 0ustar jenkinsjenkinsmain_collective = mcollective collectives = mcollective libdir = /opt/puppetlabs/mcollective/plugins # consult the "classic" libdirs too libdir = /usr/share/mcollective/plugins 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 connection_timeout = 3 mcollective-2.12.1/ext/aio/common/server.cfg.dist0000644005276200011600000000120613265671724021600 0ustar jenkinsjenkinsmain_collective = mcollective collectives = mcollective libdir = /opt/puppetlabs/mcollective/plugins # consult the "classic" libdirs too libdir = /usr/share/mcollective/plugins libdir = /usr/libexec/mcollective logfile = /var/log/puppetlabs/mcollective/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/puppetlabs/mcollective/facts.yaml mcollective-2.12.1/ext/aio/debian/0000755005276200011600000000000013265671736016605 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/debian/mcollective.default0000644005276200011600000000005213265671724022453 0ustar jenkinsjenkinsSTART=true DAEMON_OPTS="--pid ${PIDFILE}" mcollective-2.12.1/ext/aio/debian/mcollective.init0000755005276200011600000000511013265671724021775 0ustar jenkinsjenkins#!/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 piddir="/var/run/puppetlabs" pidfile="${piddir}/mcollectived.pid" name="mcollective" mcollectived=/opt/puppetlabs/puppet/bin/mcollectived daemonopts="--pid=${pidfile} --config=/etc/puppetlabs/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 mkdir -p ${piddir} # start the program start-stop-daemon --start --pidfile ${pidfile} --oknodo --quiet --startas ${mcollectived} -- ${daemonopts} --daemonize [ $? = 0 ] && { exit 0 ; } || { exit 1 ; } log_success_msg "mcollective started" ;; stop) echo "Stopping daemon: " $name start-stop-daemon --stop --retry 5 --signal "TERM" --oknodo --quiet --pidfile ${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) # status prints a horrible error message when it fails. We # don't want to scare the user away. if $0 status >/dev/null 2>&1; then $0 restart 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.12.1/ext/aio/osx/0000755005276200011600000000000013265671736016174 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/osx/mcollective.plist0000644005276200011600000000224313265671724021555 0ustar jenkinsjenkins EnvironmentVariables PATH /opt/puppetlabs/puppet/bin:/usr/bin:/bin:/usr/sbin:/sbin LANG en_US.UTF-8 Label mcollective KeepAlive ProgramArguments /opt/puppetlabs/puppet/bin/mcollectived --no-daemonize --config=/etc/puppetlabs/mcollective/server.cfg --pidfile=/var/run/puppetlabs/mcollective.pid RunAtLoad StandardErrorPath /var/log/puppetlabs/mcollective/mcollective.log StandardOutPath /var/log/puppetlabs/mcollective/mcollective.log mcollective-2.12.1/ext/aio/README.md0000644005276200011600000000030013265671724016630 0ustar jenkinsjenkinsAll-In-One Agent Support files Here you'll find the init scripts and supplemental files used by the Puppetlabs All-In-One Agent packages built with https://github.com/puppetlabs/puppet-agent mcollective-2.12.1/ext/aio/suse/0000755005276200011600000000000013265671736016342 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/suse/mcollective.init0000755005276200011600000000772713265671724021552 0ustar jenkinsjenkins#!/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: 3 5 # Default-Stop: 0 1 2 6 # Short-Description: Start daemon at boot time # Description: Enable service provided by daemon. ### END INIT INFO # Shell functions sourced from /etc/rc.status: # rc_check check and set local and overall rc status # rc_status check and set local and overall rc status # rc_status -v ditto but be verbose in local rc status # rc_status -v -r ditto and clear the local rc status # rc_failed set local and overall rc status to failed # rc_reset clear local rc status (overall remains) # rc_exit exit appropriate to overall rc status [ -f /etc/rc.status ] && . /etc/rc.status [ -f /etc/sysconfig/mcollective ] && . /etc/sysconfig/mcollective desc=${DESC:-mcollective daemon} daemon=${DAEMON:-/opt/puppetlabs/puppet/bin/mcollectived} name=${NAME:-mcollectived} piddir=${PIDDIR:-/var/run/puppetlabs} pidfile=${PIDFILE:-${piddir}/mcollectived.pid} daemon_opts=${DAEMON_OPTS:---pid ${pidfile}} # First reset status of this service rc_reset # Return values acc. to LSB for all commands but status: # 0 - success # 1 - misc error # 2 - invalid or excess args # 3 - unimplemented feature (e.g. reload) # 4 - insufficient privilege # 5 - program not installed # 6 - program not configured # # Note that starting an already running service, stopping # or restarting a not-running service as well as the restart # with force-reload (in case signalling is not supported) are # considered a success. case "$1" in start) echo -n "Starting ${desc}: " ## Start daemon with startproc(8). If this fails ## the echo return value is set appropriate. # startproc should return 0, even if service is # already running to match LSB spec. mkdir -p ${piddir} startproc -p $pidfile $daemon $daemon_opts # Remember status and be verbose rc_status -v ;; stop) echo -n "Stopping ${desc}: " ## Stop daemon with killproc(8) and if this fails ## set echo the echo return value. killproc -p $pidfile $daemon && rm -f ${pidfile} # Remember status and be verbose rc_status -v ;; try-restart|condrestart|force-reload) ## Stop the service and if this succeeds (i.e. the ## service was running before), start it again. $0 status &> /dev/null if test $? = 0; then $0 restart else rc_reset # Not running is not a failure. fi # Remember status and be quiet rc_status ;; restart) ## Stop the service and regardless of whether it was ## running or not, start it again. $0 stop sleep 1 $0 start # Remember status and be quiet rc_status ;; status) echo -n "Checking ${desc}: " ## Check status with checkproc(8), if process is running ## checkproc will return with exit status 0. # Status has a slightly different for the status command: # 0 - service running # 1 - service dead, but /var/run/ pid file exists # 2 - service dead, but /var/lock/ lock file exists # 3 - service not running # NOTE: checkproc returns LSB compliant status values. checkproc -p ${pidfile} ${daemon} rc_status -v ;; *) echo "Usage: $0 {start|stop|status|try-restart|condrestart|restart|force-reload}" exit 1 ;; esac rc_exit mcollective-2.12.1/ext/aio/redhat/0000755005276200011600000000000013265671736016632 5ustar jenkinsjenkinsmcollective-2.12.1/ext/aio/redhat/mcollective-systemd.logrotate0000644005276200011600000000027413265671724024550 0ustar jenkinsjenkins/var/log/puppetlabs/mcollective/mcollective.log { missingok notifempty sharedscripts postrotate systemctl restart mcollective >/dev/null 2>&1 || true endscript } mcollective-2.12.1/ext/aio/redhat/mcollective-sysv.logrotate0000644005276200011600000000030013265671724024052 0ustar jenkinsjenkins/var/log/puppetlabs/mcollective/mcollective.log { missingok notifempty sharedscripts postrotate /etc/init.d/mcollective restart >/dev/null 2>&1 || true endscript } mcollective-2.12.1/ext/aio/redhat/mcollective.init0000755005276200011600000000633113265671724022030 0ustar jenkinsjenkins#!/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="/opt/puppetlabs/puppet/bin/mcollectived" piddir="/var/run/puppetlabs" pidfile="${piddir}/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: " mkdir -p ${piddir} # Only try to start if not already started if ! rh_status_q; then daemon ${daemonopts} ${mcollectived} --pid=${pidfile} --config="/etc/puppetlabs/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.12.1/ext/aio/redhat/mcollective.sysconfig0000644005276200011600000000040713265671724023064 0ustar jenkinsjenkins# Configuration file for mcollectived # Full path to the mcollectived binary #DAEMON=/opt/puppetlabs/agent/bin/mcollectived # Location of PID file #PIDFILE=/var/run/puppetlabs/agent/mcollectived.pid # Any other parameters to pass to mcollectived #DAEMON_OPTS= mcollective-2.12.1/ext/aio/redhat/mcollective.service0000644005276200011600000000157213265671724022524 0ustar jenkinsjenkins# # Local settings can be configured without being overwritten by package upgrades, for example # if you want to increase mcollective open-files-limit to 10000, # you need to increase systemd's LimitNOFILE setting, so create a file named # "/etc/systemd/system/mcollective.service.d/limits.conf" containing: # [Service] # LimitNOFILE=10000 # You can confirm it worked by running systemctl daemon-reload # then running systemctl show mcollective | grep LimitNOFILE # [Unit] Description=The Marionette Collective After=network.target [Service] Type=forking StandardOutput=syslog StandardError=syslog ExecStart=/opt/puppetlabs/puppet/bin/mcollectived --config=/etc/puppetlabs/mcollective/server.cfg --pidfile=/var/run/puppetlabs/mcollective.pid --daemonize ExecReload=/bin/kill -USR1 $MAINPID PIDFile=/var/run/puppetlabs/mcollective.pid KillMode=process [Install] WantedBy=multi-user.target mcollective-2.12.1/ext/debian/0000755005276200011600000000000013265671736016035 5ustar jenkinsjenkinsmcollective-2.12.1/ext/debian/compat0000644005276200011600000000000213265671724017230 0ustar jenkinsjenkins7 mcollective-2.12.1/ext/debian/source/0000755005276200011600000000000013265671736017335 5ustar jenkinsjenkinsmcollective-2.12.1/ext/debian/source/format0000644005276200011600000000001413265671724020540 0ustar jenkinsjenkins3.0 (quilt) mcollective-2.12.1/ext/debian/rules0000755005276200011600000000107513265671724017115 0ustar jenkinsjenkins#!/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.12.1/ext/debian/control0000644005276200011600000000312413265671724017435 0ustar jenkinsjenkinsSource: mcollective Section: utils Priority: extra Maintainer: Riccardo Setti Build-Depends: debhelper (>= 7), quilt, cdbs, ruby Standards-Version: 3.8.0 Homepage: https://docs.puppetlabs.com/mcollective/ 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, libjson-ruby | ruby-json 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.12.1/ext/debian/changelog0000644005276200011600000000031613265671736017707 0ustar jenkinsjenkinsmcollective (2.12.1-1puppetlabs1) unstable sid squeeze wheezy precise; urgency=low * Update to version 2.12.1-1puppetlabs1 -- Puppet Labs Release Wed, 18 Apr 2018 09:35:42 -0700 mcollective-2.12.1/ext/debian/mcollective-doc.install0000644005276200011600000000007313265671724022473 0ustar jenkinsjenkinsusr/share/doc/mcollective/* usr/share/doc/mcollective-doc/ mcollective-2.12.1/ext/debian/mcollective-client.install0000644005276200011600000000010013265671724023173 0ustar jenkinsjenkinsusr/bin/mco usr/bin/ etc/mcollective/client.cfg etc/mcollective mcollective-2.12.1/ext/debian/mcollective.dirs0000644005276200011600000000006513265671724021224 0ustar jenkinsjenkinsetc/mcollective/ssl/clients etc/mcollective/plugin.d mcollective-2.12.1/ext/debian/mcollective-common.install0000644005276200011600000000013313265671724023213 0ustar jenkinsjenkinsusr/lib/ruby/vendor_ruby/* usr/lib/ruby/vendor_ruby/ etc/mcollective/*.erb etc/mcollective mcollective-2.12.1/ext/debian/mcollective.init0000755005276200011600000000462713265671724021241 0ustar jenkinsjenkins#!/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.12.1/ext/debian/copyright0000644005276200011600000000167613265671724017777 0ustar jenkinsjenkinsThis 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.12.1/ext/debian/patches/0000755005276200011600000000000013265671736017464 5ustar jenkinsjenkinsmcollective-2.12.1/ext/debian/patches/pluginsdir.patch0000755005276200011600000000125413265671724022667 0ustar jenkinsjenkinsdiff --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.12.1/ext/debian/patches/series0000644005276200011600000000002113265671724020667 0ustar jenkinsjenkinspluginsdir.patch mcollective-2.12.1/ext/debian/mcollective.install0000644005276200011600000000011413265671724021724 0ustar jenkinsjenkinsusr/sbin/mcollectived etc/mcollective/facts.yaml etc/mcollective/server.cfg mcollective-2.12.1/ext/Makefile0000644005276200011600000000211013265671724016242 0ustar jenkinsjenkins#!/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.12.1/ext/perl/0000755005276200011600000000000013265671736015555 5ustar jenkinsjenkinsmcollective-2.12.1/ext/perl/mc-find-hosts.pl0000644005276200011600000000450113265671724020562 0ustar jenkinsjenkins#!/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.12.1/mcollective.init0000755005276200011600000000555713265671724017222 0ustar jenkinsjenkins#!/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.12.1/etc/0000755005276200011600000000000013265671736014566 5ustar jenkinsjenkinsmcollective-2.12.1/etc/metadata-help.erb0000644005276200011600000000067213265671724017770 0ustar jenkinsjenkins<%= 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.12.1/etc/data-help.erb0000644005276200011600000000221013265671724017107 0ustar jenkinsjenkins<%= 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.12.1/etc/facts.yaml.dist0000644005276200011600000000002313265671724017504 0ustar jenkinsjenkins--- mcollective: 1 mcollective-2.12.1/etc/rpc-help.erb0000644005276200011600000000323613265671724016773 0ustar jenkinsjenkins<%= 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.12.1/etc/client.cfg.dist0000644005276200011600000000071613265671724017470 0ustar jenkinsjenkinsmain_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.12.1/etc/discovery-help.erb0000644005276200011600000000067713265671724020224 0ustar jenkinsjenkins<%= 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.12.1/etc/ssl/0000755005276200011600000000000013265671736015367 5ustar jenkinsjenkinsmcollective-2.12.1/etc/ssl/PLACEHOLDER0000644005276200011600000000000013265671724017017 0ustar jenkinsjenkinsmcollective-2.12.1/etc/ssl/clients/0000755005276200011600000000000013265671736017030 5ustar jenkinsjenkinsmcollective-2.12.1/etc/ssl/clients/PLACEHOLDER0000644005276200011600000000000013265671724020460 0ustar jenkinsjenkinsmcollective-2.12.1/etc/server.cfg.dist0000644005276200011600000000075113265671724017517 0ustar jenkinsjenkinsmain_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.12.1/COPYING0000644005276200011600000002612413265671724015050 0ustar jenkinsjenkins 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.