Class MCollective::RPC::DDL
In: lib/mcollective/rpc/ddl.rb
Parent: Object

A class that helps creating data description language files for agents. You can define meta data, actions, input and output describing the behavior of your agent.

Later you can access this information to assist with creating of user interfaces or online help

A sample DDL can 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

Methods

Attributes

meta  [R] 

Public Class methods

[Source]

    # File lib/mcollective/rpc/ddl.rb, line 39
39:       def initialize(agent, loadddl=true)
40:         @actions = {}
41:         @meta = {}
42:         @config = MCollective::Config.instance
43:         @agent = agent
44: 
45:         if loadddl
46:           if ddlfile = findddlfile(agent)
47:             instance_eval(File.read(ddlfile))
48:           else
49:             raise("Can't find DDL for agent '#{agent}'")
50:           end
51:         end
52:       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/rpc/ddl.rb, line 92
 92:       def action(name, input, &block)
 93:         raise "Action needs a :description" unless input.include?(:description)
 94: 
 95:         unless @actions.include?(name)
 96:           @actions[name] = {}
 97:           @actions[name][:action] = name
 98:           @actions[name][:input] = {}
 99:           @actions[name][:output] = {}
100:           @actions[name][:display] = :failed
101:           @actions[name][:description] = input[:description]
102:         end
103: 
104:         # if a block is passed it might be creating input methods, call it
105:         # we set @current_action so the input block can know what its talking
106:         # to, this is probably an epic hack, need to improve.
107:         @current_action = name
108:         block.call if block_given?
109:         @current_action = nil
110:       end

Returns the interface for a specific action

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 187
187:       def action_interface(name)
188:         @actions[name] || {}
189:       end

Returns an array of actions this agent support

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 182
182:       def actions
183:         @actions.keys
184:       end

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

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 160
160:       def display(pref)
161:         # defaults to old behavior, complain if its supplied and invalid
162:         unless [:ok, :failed, :flatten, :always].include?(pref)
163:           raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always"
164:         end
165: 
166:         action = @current_action
167:         @actions[action][:display] = pref
168:       end

[Source]

    # File lib/mcollective/rpc/ddl.rb, line 54
54:       def findddlfile(agent)
55:         @config.libdir.each do |libdir|
56:           ddlfile = File.join([libdir, "mcollective", "agent", "#{agent}.ddl"])
57: 
58:           if File.exist?(ddlfile)
59:             Log.debug("Found #{agent} ddl at #{ddlfile}")
60:             return ddlfile
61:           end
62:         end
63:         return false
64:       end

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

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 172
172:       def help(template)
173:         template = IO.read(template)
174:         meta = @meta
175:         actions = @actions
176: 
177:         erb = ERB.new(template, 0, '%')
178:         erb.result(binding)
179:       end

Registers an input argument for a given action

See the documentation for action for how to use this

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 115
115:       def input(argument, properties)
116:         raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
117: 
118:         action = @current_action
119: 
120:         [:prompt, :description, :type, :optional].each do |arg|
121:           raise "Input needs a :#{arg}" unless properties.include?(arg)
122:         end
123: 
124:         @actions[action][:input][argument] = {:prompt => properties[:prompt],
125:                                               :description => properties[:description],
126:                                               :type => properties[:type],
127:                                               :optional => properties[:optional]}
128: 
129:         case properties[:type]
130:           when :string
131:             raise "Input type :string needs a :validation argument" unless properties.include?(:validation)
132:             raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength)
133: 
134:             @actions[action][:input][argument][:validation] = properties[:validation]
135:             @actions[action][:input][argument][:maxlength] = properties[:maxlength]
136: 
137:           when :list
138:             raise "Input type :list needs a :list argument" unless properties.include?(:list)
139: 
140:             @actions[action][:input][argument][:list] = properties[:list]
141:         end
142:       end

Registers meta data for the introspection hash

[Source]

    # File lib/mcollective/rpc/ddl.rb, line 67
67:       def metadata(meta)
68:         [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
69:           raise "Metadata needs a :#{arg}" unless meta.include?(arg)
70:         end
71: 
72:         @meta = meta
73:       end

Registers an output argument for a given action

See the documentation for action for how to use this

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 147
147:       def output(argument, properties)
148:         raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
149:         raise "Output #{argument} needs a description argument" unless properties.include?(:description)
150:         raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as)
151: 
152:         action = @current_action
153: 
154:         @actions[action][:output][argument] = {:description => properties[:description],
155:                                                :display_as  => properties[:display_as]}
156:       end

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

[Source]

     # File lib/mcollective/rpc/ddl.rb, line 193
193:       def validate_request(action, arguments)
194:         # is the action known?
195:         unless actions.include?(action)
196:           raise DDLValidationError, "Attempted to call action #{action} for #{@agent} but it's not declared in the DDL"
197:         end
198: 
199:         input = action_interface(action)[:input]
200: 
201:         input.keys.each do |key|
202:           unless input[key][:optional]
203:             unless arguments.keys.include?(key)
204:               raise DDLValidationError, "Action #{action} needs a #{key} argument"
205:             end
206:           end
207: 
208:           # validate strings, lists and booleans, we'll add more types of validators when
209:           # all the use cases are clear
210:           #
211:           # only does validation for arguments actually given, since some might
212:           # be optional.  We validate the presense of the argument earlier so
213:           # this is a safe assumption, just to skip them.
214:           #
215:           # :string can have maxlength and regex.  A maxlength of 0 will bypasss checks
216:           # :list has a array of valid values
217:           if arguments.keys.include?(key)
218:             case input[key][:type]
219:               when :string
220:                 raise DDLValidationError, "Input #{key} should be a string" unless arguments[key].is_a?(String)
221: 
222:                 if input[key][:maxlength].to_i > 0
223:                   if arguments[key].size > input[key][:maxlength].to_i
224:                     raise DDLValidationError, "Input #{key} is longer than #{input[key][:maxlength]} character(s)"
225:                   end
226:                 end
227: 
228:                 unless arguments[key].match(Regexp.new(input[key][:validation]))
229:                   raise DDLValidationError, "Input #{key} does not match validation regex #{input[key][:validation]}"
230:                 end
231: 
232:               when :list
233:                 unless input[key][:list].include?(arguments[key])
234:                   raise DDLValidationError, "Input #{key} doesn't match list #{input[key][:list].join(', ')}"
235:                 end
236: 
237:               when :boolean
238:                 unless [TrueClass, FalseClass].include?(arguments[key].class)
239:                   raise DDLValidationError, "Input #{key} should be a boolean"
240:                 end
241: 
242:               when :integer
243:                 raise DDLValidationError, "Input #{key} should be a integer" unless arguments[key].is_a?(Fixnum)
244: 
245:               when :float
246:                 raise DDLValidationError, "Input #{key} should be a floating point number" unless arguments[key].is_a?(Float)
247: 
248:               when :number
249:                 raise DDLValidationError, "Input #{key} should be a number" unless arguments[key].is_a?(Numeric)
250:             end
251:           end
252:         end
253: 
254:         true
255:       end

[Validate]