3 # A DDL class specific to agent plugins.
5 # A full DDL can be seen below with all the possible bells and whistles present.
7 # metadata :name => "Utilities and Helpers for SimpleRPC Agents",
8 # :description => "General helpful actions that expose stats and internals to SimpleRPC clients",
9 # :author => "R.I.Pienaar <rip@devco.net>",
10 # :license => "Apache License, Version 2.0",
12 # :url => "http://marionette-collective.org/",
15 # action "get_fact", :description => "Retrieve a single fact from the fact store" do
19 # :prompt => "The name of the fact",
20 # :description => "The fact to retrieve",
22 # :validation => '^[\w\-\.]+$',
28 # :description => "The name of the fact being returned",
29 # :display_as => "Fact"
32 # :description => "The value of the fact",
33 # :display_as => "Value",
37 # aggregate summary(:value)
41 def initialize(plugin, plugintype=:agent, loadddl=true)
42 @process_aggregate_functions = nil
47 def input(argument, properties)
48 raise "Input needs a :optional property" unless properties.include?(:optional)
53 # Calls the summarize block defined in the ddl. Block will not be called
54 # if the ddl is getting processed on the server side. This means that
55 # aggregate plugins only have to be present on the client side.
57 # The @process_aggregate_functions variable is used by the method_missing
58 # block to determine if it should kick in, this way we very tightly control
59 # where we activate the method_missing behavior turning it into a noop
60 # otherwise to maximise the chance of providing good user feedback
62 unless @config.mode == :server
63 @process_aggregate_functions = true
65 @process_aggregate_functions = nil
69 # Sets the aggregate array for the given action
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)
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])
79 entities[@current_entity][:aggregate] ||= []
80 entities[@current_entity][:aggregate] << (format[:format].nil? ? function : function.merge(format))
83 # Sets the display preference to either :ok, :failed, :flatten or :always
84 # operates on action level
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"
91 action = @current_entity
92 @entities[action][:display] = pref
95 # Creates the definition for an action, you can nest input definitions inside the
96 # action to attach inputs and validation to the actions
98 # action "status", :description => "Restarts a Service" do
102 # :prompt => "Service Action",
103 # :description => "The action to perform",
106 # :list => ["start", "stop", "restart", "status"]
109 # :description => "The status of the service after the action"
112 def action(name, input, &block)
113 raise "Action needs a :description property" unless input.include?(:description)
115 unless @entities.include?(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]
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
132 # If the method name matches a # aggregate function, we return the function
133 # with args as a hash. This will only be active if the @process_aggregate_functions
134 # is set to true which only happens in the #summarize block
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
140 return {:function => name, :args => args}
143 # Checks if a method name matches a aggregate plugin.
144 # This is used by method missing so that we dont greedily assume that
145 # every method_missing call in an agent ddl has hit a aggregate function.
146 def is_function?(method_name)
147 PluginManager.find("aggregate").include?(method_name.to_s)
150 # For a given action and arguments look up the DDL interface to that action
151 # and if any arguments in the DDL have a :default value assign that to any
152 # input that does not have an argument in the input arguments
154 # This is intended to only be called on clients and not on servers as the
155 # clients should never be able to publish non compliant requests and the
156 # servers should really not tamper with incoming requests since doing so
157 # might raise validation errors that were not raised on the client breaking
158 # our fail-fast approach to input validation
159 def set_default_input_arguments(action, arguments)
160 input = action_interface(action)[:input]
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]
172 # Helper to use the DDL to figure out if the remote call to an
173 # agent should be allowed based on action name and inputs.
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)
180 input = action_interface(action)[:input] || {}
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)
189 if arguments.keys.include?(key)
190 validate_input_argument(input, key, arguments[key])
197 # Returns the interface for a specific action
198 def action_interface(name)
199 @entities[name] || {}
202 # Returns an array of actions this agent support