module MCollective::Util

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

Public Class Methods

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 127
def self.config_file_for_user
    # expand_path is pretty lame, it relies on HOME environment
    # which isnt't always there so just handling all exceptions
    # here as cant find reverting to default
    begin
        config = File.expand_path("~/.mcollective")

        unless File.readable?(config) && File.file?(config)
            config = "/etc/mcollective/client.cfg"
        end
    rescue Exception => e
        config = "/etc/mcollective/client.cfg"
    end

    return config
end
default_options() click to toggle source

Creates a standard options hash

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

Creates an empty filter

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

Checks if the passed in filter is an empty one

# File lib/mcollective/util.rb, line 113
def self.empty_filter?(filter)
    filter == empty_filter || filter == {}
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 49
def self.get_fact(fact)
    Facts.get_fact(fact)
end
has_agent?(agent) click to toggle source

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

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

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

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

    false
end
has_cf_class?(klass) click to toggle source

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

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

# File lib/mcollective/util.rb, line 30
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}")

    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

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

    if operator == '=~'
        # to maintain backward compat we send the value
        # as /.../ which is what 1.0.x needed.  this strips
        # off the /'s wich is what we need here
        if value =~ %r^\/(.+)\/$/
            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 =~ %r^[0-9]+$/ && fact =~ %r^[0-9]+$/
            fact = Integer(fact)
            value = Integer(value)
        elsif value =~ %r^[0-9]+.[0-9]+$/ && fact =~ %r^[0-9]+.[0-9]+$/
            fact = Float(fact)
            value = Float(value)
        end

        return true if eval("fact #{operator} value")
    end

    false
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 100
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 202
def self.loadclass(klass)
    PluginManager.loadclass(klass)
end
make_target(agent, type, collective=nil) click to toggle source

Constructs an array of the full target names based on topicprefix, topicsep and collectives config options.

If given a collective name it will return a single target aimed at just the one collective

# File lib/mcollective/util.rb, line 159
def self.make_target(agent, type, collective=nil)
    config = Config.instance

    raise("Unknown target type #{type}") unless type == :command || type == :reply

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

        ["#{config.topicprefix}#{collective}", agent, type].join(config.topicsep)
    end
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 207
def self.parse_fact_string(fact)
    if fact =~ %r^([^ ]+?)[ ]*=>[ ]*(.+)/
        return {:fact => $1, :value => $2, :operator => '>=' }
    elsif fact =~ %r^([^ ]+?)[ ]*=<[ ]*(.+)/
        return {:fact => $1, :value => $2, :operator => '<=' }
    elsif fact =~ %r^([^ ]+?)[ ]*(<=|>=|<|>|!=|==|=~)[ ]*(.+)/
        return {:fact => $1, :value => $3, :operator => $2 }
    elsif fact =~ %r^(.+?)[ ]*=[ ]*\/(.+)\/$/
        return {:fact => $1, :value => "/#{$2}/", :operator => '=~' }
    elsif fact =~ %r^([^= ]+?)[ ]*=[ ]*(.+)/
        return {:fact => $1, :value => $2, :operator => '==' }
    end

    return false
end
parse_msgtarget(target) click to toggle source

Parse the msgtarget as sent in 1.1.4 and newer to figure out the agent and collective that a request is targeted at

# File lib/mcollective/util.rb, line 244
def self.parse_msgtarget(target)
    sep = Regexp.escape(Config.instance.topicsep)
    prefix = Regexp.escape(Config.instance.topicprefix)
    regex = "#{prefix}(.+?)#{sep}(.+?)#{sep}command"

    if target.match(regex)
        return {:collective => $1, :agent => $2}
    else
        raise "Failed to handle message, could not figure out agent and collective from #{target}"
    end
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 226
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!(%r([^A-Za-z0-9_\-.,:\/@\n])/, "\\\\\\1")

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

    return str
end
subscribe(topics) click to toggle source

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

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

    if topics.is_a?(Array)
        topics.each do |topic|
            connection.subscribe(topic)
        end
    else
        connection.subscribe(topics)
    end
end
unsubscribe(topics) click to toggle source

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

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

    if topics.is_a?(Array)
        topics.each do |topic|
            connection.unsubscribe(topic)
        end
    else
        connection.unsubscribe(topics)
    end
end