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