class MCollective::DDL::AgentDDL - mcollective version 2.12.5

# 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
    41 def initialize(plugin, plugintype=:agent, loadddl=true)
    42   @process_aggregate_functions = nil
    43
    44   super
    45 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
    116 def action(name, input, &block)
    117   raise "Action needs a :description property" unless input.include?(:description)
    118
    119   unless @entities.include?(name)
    120     @entities[name] = {}
    121     @entities[name][:action] = name
    122     @entities[name][:input] = {}
    123     @entities[name][:output] = {}
    124     @entities[name][:display] = :failed
    125     @entities[name][:description] = input[:description]
    126   end
    127
    128   # if a block is passed it might be creating input methods, call it
    129   # we set @current_entity so the input block can know what its talking
    130   # to, this is probably an epic hack, need to improve.
    131   @current_entity = name
    132   block.call if block_given?
    133   @current_entity = nil
    134 end

action_interface(name) click to toggle source

Returns the interface for a specific action

        # File lib/mcollective/ddl/agentddl.rb
    243 def action_interface(name)
    244   @entities[name] || {}
    245 end

actions() click to toggle source

Returns an array of actions this agent support

        # File lib/mcollective/ddl/agentddl.rb
    248 def actions
    249   @entities.keys
    250 end

aggregate(function, format = {:format => nil}) click to toggle source

Sets the aggregate array for the given action

       # File lib/mcollective/ddl/agentddl.rb
    70 def aggregate(function, format = {:format => nil})
    71   raise(DDLValidationError, "Formats supplied to aggregation functions should be a hash") unless format.is_a?(Hash)
    72   raise(DDLValidationError, "Formats supplied to aggregation functions must have a :format key") unless format.keys.include?(:format)
    73   raise(DDLValidationError, "Functions supplied to aggregate should be a hash") unless function.is_a?(Hash)
    74
    75   unless (function.keys.include?(:args)) && function[:args]
    76     raise DDLValidationError, "aggregate method for action '%s' missing a function parameter" % entities[@current_entity][:action]
    77   end
    78
    79   entities[@current_entity][:aggregate] ||= []
    80   entities[@current_entity][:aggregate] << (format[:format].nil? ? function : function.merge(format))
    81 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
    85 def display(pref)
    86   if pref == :flatten
    87     Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release.")
    88   end
    89
    90   # defaults to old behavior, complain if its supplied and invalid
    91   unless [:ok, :failed, :flatten, :always].include?(pref)
    92     raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always"
    93   end
    94
    95   action = @current_entity
    96   @entities[action][:display] = pref
    97 end

input(argument, properties) click to toggle source
Calls superclass method

       # File lib/mcollective/ddl/agentddl.rb
    47 def input(argument, properties)
    48   raise "Input needs a :optional property" unless properties.include?(:optional)
    49
    50   super
    51 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
    150 def is_function?(method_name)
    151   PluginManager.find("aggregate").include?(method_name.to_s)
    152 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
    139 def method_missing(name, *args, &block)
    140   unless @process_aggregate_functions || is_function?(name)
    141     raise NoMethodError, "undefined local variable or method `#{name}'", caller
    142   end
    143
    144   return {:function => name, :args => args}
    145 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
    163 def set_default_input_arguments(action, arguments)
    164   input = action_interface(action)[:input]
    165
    166   return unless input
    167
    168   input.keys.each do |key|
    169     if key.is_a?(Symbol) && arguments.include?(key.to_s) && !input.include?(key.to_s)
    170       compat_arg = key.to_s
    171     else
    172       compat_arg = key
    173     end
    174
    175     if !arguments.include?(compat_arg) && !input[key][:default].nil? && !input[key][:optional]
    176       Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]])
    177       arguments[compat_arg] = input[key][:default]
    178     end
    179   end
    180 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
    61 def summarize(&block)
    62   unless @config.mode == :server
    63     @process_aggregate_functions = true
    64     block.call
    65     @process_aggregate_functions = nil
    66   end
    67 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
    199 def symbolize_basic_input_arguments(input, arguments)
    200   warned = false
    201
    202   Hash[arguments.map do |key, value|
    203     if input.include?(key.intern) && input.include?(key.to_s) && !warned
    204       Log.warn("String and Symbol versions of input %s found in the DDL for %s, ensure your DDL keys are unique." % [key, @pluginname])
    205       warned = true
    206     end
    207
    208     if key.is_a?(String) && input.include?(key.intern) && !input.include?(key)
    209       [key.intern, value]
    210     else
    211       [key, value]
    212     end
    213   end]
    214 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
    218 def validate_rpc_request(action, arguments)
    219   # is the action known?
    220   unless actions.include?(action)
    221     raise DDLValidationError, "Attempted to call action #{action} for #{@pluginname} but it's not declared in the DDL"
    222   end
    223
    224   input = action_interface(action)[:input] || {}
    225   compatible_args = symbolize_basic_input_arguments(input, arguments)
    226
    227   input.keys.each do |key|
    228     unless input[key][:optional]
    229       unless compatible_args.include?(key)
    230         raise DDLValidationError, "Action #{action} needs a #{key} argument"
    231       end
    232     end
    233
    234     if compatible_args.include?(key)
    235       validate_input_argument(input, key, compatible_args[key])
    236     end
    237   end
    238
    239   true
    240 end