Class MCollective::DDL::AgentDDL
In: lib/mcollective/ddl/agentddl.rb
Parent: Base

A DDL class specific to agent plugins.

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

metadata :name => "Utilities and Helpers for SimpleRPC Agents",

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

action "get_fact", :description => "Retrieve a single fact from the fact store" do

     display :always

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

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

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

    summarize do
        aggregate summary(:value)
    end

end

Methods

Public Class methods

[Source]

    # File lib/mcollective/ddl/agentddl.rb, line 41
41:       def initialize(plugin, plugintype=:agent, loadddl=true)
42:         @process_aggregate_functions = nil
43: 
44:         super
45:       end

Public Instance methods

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

[Source]

     # File lib/mcollective/ddl/agentddl.rb, line 112
112:       def action(name, input, &block)
113:         raise "Action needs a :description property" unless input.include?(:description)
114: 
115:         unless @entities.include?(name)
116:           @entities[name] = {}
117:           @entities[name][:action] = name
118:           @entities[name][:input] = {}
119:           @entities[name][:output] = {}
120:           @entities[name][:display] = :failed
121:           @entities[name][:description] = input[:description]
122:         end
123: 
124:         # if a block is passed it might be creating input methods, call it
125:         # we set @current_entity so the input block can know what its talking
126:         # to, this is probably an epic hack, need to improve.
127:         @current_entity = name
128:         block.call if block_given?
129:         @current_entity = nil
130:       end

Returns the interface for a specific action

[Source]

     # File lib/mcollective/ddl/agentddl.rb, line 198
198:       def action_interface(name)
199:         @entities[name] || {}
200:       end

Returns an array of actions this agent support

[Source]

     # File lib/mcollective/ddl/agentddl.rb, line 203
203:       def actions
204:         @entities.keys
205:       end

Sets the aggregate array for the given action

[Source]

    # File lib/mcollective/ddl/agentddl.rb, line 70
70:       def aggregate(function, format = {:format => nil})
71:         DDL.validation_fail!(:PLMC28, "Formats supplied to aggregation functions should be a hash", :error) unless format.is_a?(Hash)
72:         DDL.validation_fail!(:PLMC27, "Formats supplied to aggregation functions must have a :format key", :error) unless format.keys.include?(:format)
73:         DDL.validation_fail!(:PLMC26, "Functions supplied to aggregate should be a hash", :error) unless function.is_a?(Hash)
74: 
75:         unless (function.keys.include?(:args)) && function[:args]
76:           DDL.validation_fail!(:PLMC25, "aggregate method for action '%{action}' missing a function parameter", :error, :action => 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

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

[Source]

    # File lib/mcollective/ddl/agentddl.rb, line 85
85:       def display(pref)
86:         # defaults to old behavior, complain if its supplied and invalid
87:         unless [:ok, :failed, :flatten, :always].include?(pref)
88:           raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always"
89:         end
90: 
91:         action = @current_entity
92:         @entities[action][:display] = pref
93:       end

[Source]

    # File lib/mcollective/ddl/agentddl.rb, line 47
47:       def input(argument, properties)
48:         raise "Input needs a :optional property" unless properties.include?(:optional)
49: 
50:         super
51:       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.

[Source]

     # File lib/mcollective/ddl/agentddl.rb, line 146
146:       def is_function?(method_name)
147:         PluginManager.find("aggregate").include?(method_name.to_s)
148:       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

[Source]

     # File lib/mcollective/ddl/agentddl.rb, line 135
135:       def method_missing(name, *args, &block)
136:         unless @process_aggregate_functions || is_function?(name)
137:           raise NoMethodError, "undefined local variable or method `#{name}'", caller
138:         end
139: 
140:         return {:function => name, :args => args}
141:       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

[Source]

     # File lib/mcollective/ddl/agentddl.rb, line 159
159:       def set_default_input_arguments(action, arguments)
160:         input = action_interface(action)[:input]
161: 
162:         return unless input
163: 
164:         input.keys.each do |key|
165:           if !arguments.include?(key) && !input[key][:default].nil? && !input[key][:optional]
166:             Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]])
167:             arguments[key] = input[key][:default]
168:           end
169:         end
170:       end

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

[Source]

    # File lib/mcollective/ddl/agentddl.rb, line 61
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

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

[Source]

     # File lib/mcollective/ddl/agentddl.rb, line 174
174:       def validate_rpc_request(action, arguments)
175:         # is the action known?
176:         unless actions.include?(action)
177:           DDL.validation_fail!(:PLMC29, "Attempted to call action %{action} for %{plugin} but it's not declared in the DDL", :debug, :action => action, :plugin => @pluginname)
178:         end
179: 
180:         input = action_interface(action)[:input] || {}
181: 
182:         input.keys.each do |key|
183:           unless input[key][:optional]
184:             unless arguments.keys.include?(key)
185:               DDL.validation_fail!(:PLMC30, "Action '%{action}' needs a '%{key}' argument", :debug, :action => action, :key => key)
186:             end
187:           end
188: 
189:           if arguments.keys.include?(key)
190:             validate_input_argument(input, key, arguments[key])
191:           end
192:         end
193: 
194:         true
195:       end

[Validate]