Class: MCollective::RPC::DDL

Inherits:
Object
  • Object
show all
Defined in:
lib/mcollective/rpc/ddl.rb

Overview

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

  :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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(agent, loadddl = true) ⇒ DDL

Returns a new instance of DDL.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/mcollective/rpc/ddl.rb', line 39

def initialize(agent, loadddl=true)
  @actions = {}
  @meta = {}
  @config = MCollective::Config.instance
  @agent = agent

  if loadddl
    if ddlfile = findddlfile(agent)
      instance_eval(File.read(ddlfile))
    else
      raise("Can't find DDL for agent '#{agent}'")
    end
  end
end

Instance Attribute Details

#metaObject (readonly)

Returns the value of attribute meta.



37
38
39
# File 'lib/mcollective/rpc/ddl.rb', line 37

def meta
  @meta
end

Instance Method Details

#action(name, input, &block) ⇒ Object

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


92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/mcollective/rpc/ddl.rb', line 92

def action(name, input, &block)
  raise "Action needs a :description" unless input.include?(:description)

  unless @actions.include?(name)
    @actions[name] = {}
    @actions[name][:action] = name
    @actions[name][:input] = {}
    @actions[name][:output] = {}
    @actions[name][:display] = :failed
    @actions[name][:description] = input[:description]
  end

  # if a block is passed it might be creating input methods, call it
  # we set @current_action so the input block can know what its talking
  # to, this is probably an epic hack, need to improve.
  @current_action = name
  block.call if block_given?
  @current_action = nil
end

#action_interface(name) ⇒ Object

Returns the interface for a specific action



187
188
189
# File 'lib/mcollective/rpc/ddl.rb', line 187

def action_interface(name)
  @actions[name] || {}
end

#actionsObject

Returns an array of actions this agent support



182
183
184
# File 'lib/mcollective/rpc/ddl.rb', line 182

def actions
  @actions.keys
end

#display(pref) ⇒ Object

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



160
161
162
163
164
165
166
167
168
# File 'lib/mcollective/rpc/ddl.rb', line 160

def display(pref)
  # 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_action
  @actions[action][:display] = pref
end

#findddlfile(agent) ⇒ Object



54
55
56
57
58
59
60
61
62
63
64
# File 'lib/mcollective/rpc/ddl.rb', line 54

def findddlfile(agent)
  @config.libdir.each do |libdir|
    ddlfile = File.join([libdir, "mcollective", "agent", "#{agent}.ddl"])

    if File.exist?(ddlfile)
      Log.debug("Found #{agent} ddl at #{ddlfile}")
      return ddlfile
    end
  end
  return false
end

#help(template) ⇒ Object

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



172
173
174
175
176
177
178
179
# File 'lib/mcollective/rpc/ddl.rb', line 172

def help(template)
  template = IO.read(template)
  meta = @meta
  actions = @actions

  erb = ERB.new(template, 0, '%')
  erb.result(binding)
end

#input(argument, properties) ⇒ Object

Registers an input argument for a given action

See the documentation for action for how to use this



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/mcollective/rpc/ddl.rb', line 115

def input(argument, properties)
  raise "Cannot figure out what action input #{argument} belongs to" unless @current_action

  action = @current_action

  [:prompt, :description, :type, :optional].each do |arg|
    raise "Input needs a :#{arg}" unless properties.include?(arg)
  end

  @actions[action][:input][argument] = {:prompt => properties[:prompt],
                                        :description => properties[:description],
                                        :type => properties[:type],
                                        :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)

      @actions[action][:input][argument][:validation] = properties[:validation]
      @actions[action][:input][argument][:maxlength] = properties[:maxlength]

    when :list
      raise "Input type :list needs a :list argument" unless properties.include?(:list)

      @actions[action][:input][argument][:list] = properties[:list]
  end
end

#metadata(meta) ⇒ Object

Registers meta data for the introspection hash



67
68
69
70
71
72
73
# File 'lib/mcollective/rpc/ddl.rb', line 67

def (meta)
  [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
    raise "Metadata needs a :#{arg}" unless meta.include?(arg)
  end

  @meta = meta
end

#output(argument, properties) ⇒ Object

Registers an output argument for a given action

See the documentation for action for how to use this



147
148
149
150
151
152
153
154
155
156
# File 'lib/mcollective/rpc/ddl.rb', line 147

def output(argument, properties)
  raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
  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_action

  @actions[action][:output][argument] = {:description => properties[:description],
                                         :display_as  => properties[:display_as]}
end

#validate_request(action, arguments) ⇒ Object

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



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
# File 'lib/mcollective/rpc/ddl.rb', line 193

def validate_request(action, arguments)
  # is the action known?
  unless actions.include?(action)
    raise DDLValidationError, "Attempted to call action #{action} for #{@agent} but it's not declared in the DDL"
  end

  input = action_interface(action)[:input]

  input.keys.each do |key|
    unless input[key][:optional]
      unless arguments.keys.include?(key)
        raise DDLValidationError, "Action #{action} needs a #{key} argument"
      end
    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
    if arguments.keys.include?(key)
      case input[key][:type]
        when :string
          raise DDLValidationError, "Input #{key} should be a string" unless arguments[key].is_a?(String)

          if input[key][:maxlength].to_i > 0
            if arguments[key].size > input[key][:maxlength].to_i
              raise DDLValidationError, "Input #{key} is longer than #{input[key][:maxlength]} character(s)"
            end
          end

          unless arguments[key].match(Regexp.new(input[key][:validation]))
            raise DDLValidationError, "Input #{key} does not match validation regex #{input[key][:validation]}"
          end

        when :list
          unless input[key][:list].include?(arguments[key])
            raise DDLValidationError, "Input #{key} doesn't match list #{input[key][:list].join(', ')}"
          end

        when :boolean
          unless [TrueClass, FalseClass].include?(arguments[key].class)
            raise DDLValidationError, "Input #{key} should be a boolean"
          end

        when :integer
          raise DDLValidationError, "Input #{key} should be a integer" unless arguments[key].is_a?(Fixnum)

        when :float
          raise DDLValidationError, "Input #{key} should be a floating point number" unless arguments[key].is_a?(Float)

        when :number
          raise DDLValidationError, "Input #{key} should be a number" unless arguments[key].is_a?(Numeric)
      end
    end
  end

  true
end