| 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
| meta | [R] |
# File lib/mcollective/rpc/ddl.rb, line 39
39: def initialize(agent)
40: @actions = {}
41: @meta = {}
42: @config = MCollective::Config.instance
43: @agent = agent
44:
45: if ddlfile = findddlfile(agent)
46: instance_eval(File.read(ddlfile))
47: else
48: raise("Can't find DDL for agent '#{agent}'")
49: end
50: end
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/rpc/ddl.rb, line 89
89: def action(name, input, &block)
90: raise "Action needs a :description" unless input.include?(:description)
91:
92: unless @actions.include?(name)
93: @actions[name] = {}
94: @actions[name][:action] = name
95: @actions[name][:input] = {}
96: @actions[name][:output] = {}
97: @actions[name][:display] = :failed
98: @actions[name][:description] = input[:description]
99: end
100:
101: # if a block is passed it might be creating input methods, call it
102: # we set @current_action so the input block can know what its talking
103: # to, this is probably an epic hack, need to improve.
104: @current_action = name
105: block.call if block_given?
106: @current_action = nil
107: end
Sets the display preference to either :ok, :failed, :flatten or :always operates on action level
# File lib/mcollective/rpc/ddl.rb, line 157
157: def display(pref)
158: # defaults to old behavior, complain if its supplied and invalid
159: unless [:ok, :failed, :always].include?(pref)
160: raise "Display preference #{pref} :ok, :failed, :flatten or :always"
161: end
162:
163: action = @current_action
164: @actions[action][:display] = pref
165: end
# File lib/mcollective/rpc/ddl.rb, line 52
52: def findddlfile(agent)
53: @config.libdir.each do |libdir|
54: ddlfile = "#{libdir}/mcollective/agent/#{agent}.ddl"
55: if File.exist?(ddlfile)
56: Log.debug("Found #{agent} ddl at #{ddlfile}")
57: return ddlfile
58: end
59: end
60: return false
61: end
Generates help using the template based on the data created with metadata and input
# File lib/mcollective/rpc/ddl.rb, line 169
169: def help(template)
170: template = IO.readlines(template).join
171: meta = @meta
172: actions = @actions
173:
174: erb = ERB.new(template, 0, '%')
175: erb.result(binding)
176: end
Registers an input argument for a given action
See the documentation for action for how to use this
# File lib/mcollective/rpc/ddl.rb, line 112
112: def input(argument, properties)
113: raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
114:
115: action = @current_action
116:
117: [:prompt, :description, :type, :optional].each do |arg|
118: raise "Input needs a :#{arg}" unless properties.include?(arg)
119: end
120:
121: @actions[action][:input][argument] = {:prompt => properties[:prompt],
122: :description => properties[:description],
123: :type => properties[:type],
124: :optional => properties[:optional]}
125:
126: case properties[:type]
127: when :string
128: raise "Input type :string needs a :validation" unless properties.include?(:validation)
129: raise "String inputs need a :maxlength" unless properties.include?(:validation)
130:
131: @actions[action][:input][argument][:validation] = properties[:validation]
132: @actions[action][:input][argument][:maxlength] = properties[:maxlength]
133:
134: when :list
135: raise "Input type :list needs a :list argument" unless properties.include?(:list)
136:
137: @actions[action][:input][argument][:list] = properties[:list]
138: end
139: end
Registers meta data for the introspection hash
# File lib/mcollective/rpc/ddl.rb, line 64
64: def metadata(meta)
65: [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
66: raise "Metadata needs a :#{arg}" unless meta.include?(arg)
67: end
68:
69: @meta = meta
70: end
Registers an output argument for a given action
See the documentation for action for how to use this
# File lib/mcollective/rpc/ddl.rb, line 144
144: def output(argument, properties)
145: raise "Cannot figure out what action input #{argument} belongs to" unless @current_action
146: raise "Output #{argument} needs a description" unless properties.include?(:description)
147: raise "Output #{argument} needs a description" unless properties.include?(:display_as)
148:
149: action = @current_action
150:
151: @actions[action][:output][argument] = {:description => properties[:description],
152: :display_as => properties[:display_as]}
153: end
Helper to use the DDL to figure out if the remote call should be allowed based on action name and inputs.
# File lib/mcollective/rpc/ddl.rb, line 190
190: def validate_request(action, arguments)
191: # is the action known?
192: unless actions.include?(action)
193: raise DDLValidationError, "Attempted to call action #{action} for #{@agent} but it's not declared in the DDL"
194: end
195:
196: input = action_interface(action)[:input]
197:
198: input.keys.each do |key|
199: unless input[key][:optional]
200: unless arguments.keys.include?(key)
201: raise DDLValidationError, "Action #{action} needs a #{key} argument"
202: end
203: end
204:
205: # validate strings, lists and booleans, we'll add more types of validators when
206: # all the use cases are clear
207: #
208: # only does validation for arguments actually given, since some might
209: # be optional. We validate the presense of the argument earlier so
210: # this is a safe assumption, just to skip them.
211: #
212: # :string can have maxlength and regex. A maxlength of 0 will bypasss checks
213: # :list has a array of valid values
214: if arguments.keys.include?(key)
215: case input[key][:type]
216: when :string
217: raise DDLValidationError, "Input #{key} should be a string" unless arguments[key].is_a?(String)
218:
219: if input[key][:maxlength].to_i > 0
220: if arguments[key].size > input[key][:maxlength].to_i
221: raise DDLValidationError, "Input #{key} is longer than #{input[key][:maxlength]}"
222: end
223: end
224:
225: unless arguments[key].match(Regexp.new(input[key][:validation]))
226: raise DDLValidationError, "Input #{key} does not match validation regex #{input[key][:validation]}"
227: end
228:
229: when :list
230: unless input[key][:list].include?(arguments[key])
231: raise DDLValidationError, "Input #{key} doesn't match list #{input[key][:list].join(', ')}"
232: end
233:
234: when :boolean
235: unless [TrueClass, FalseClass].include?(arguments[key].class)
236: raise DDLValidationError, "Input #{key} should be a boolean"
237: end
238: end
239: end
240: end
241: end