Class: MCollective::DDL::AgentDDL
- Defined in:
- lib/mcollective/ddl/agentddl.rb
Overview
A DDL class specific to agent plugins.
A full DDL can be seen below with all the possible bells and whistles present.
metadata :name => “Utilities and Helpers for SimpleRPC Agents”,
:description => "General helpful actions that expose stats and internals to SimpleRPC clients",
:author => "R.I.Pienaar <[email protected]>",
:license => "Apache License, Version 2.0",
:version => "1.0",
:url => "https://docs.puppetlabs.com/mcollective/",
:timeout => 10
action “get_fact”, :description => “Retrieve a single fact from the fact store” do
display :always
input :fact,
:prompt => "The name of the fact",
:description => "The fact to retrieve",
:type => :string,
:validation => '^[\w\-\.]+$',
:optional => false,
:maxlength => 40,
:default => "fqdn"
output :fact,
:description => "The name of the fact being returned",
:display_as => "Fact"
output :value,
:description => "The value of the fact",
:display_as => "Value",
:default => ""
summarize do
aggregate summary(:value)
end
end
Constant Summary
Constants inherited from Base
Instance Attribute Summary
Attributes inherited from Base
#entities, #meta, #pluginname, #plugintype, #requirements, #usage
Instance Method Summary collapse
-
#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_interface(name) ⇒ Object
Returns the interface for a specific action.
-
#actions ⇒ Object
Returns an array of actions this agent support.
-
#aggregate(function, format = {:format => nil}) ⇒ Object
Sets the aggregate array for the given action.
- #client_activated? ⇒ Boolean
-
#display(pref) ⇒ Object
Sets the display preference to either :ok, :failed, :flatten or :always operates on action level.
-
#initialize(plugin, plugintype = :agent, loadddl = true) ⇒ AgentDDL
constructor
A new instance of AgentDDL.
- #input(argument, properties) ⇒ Object
-
#is_function?(method_name) ⇒ Boolean
Checks if a method name matches a aggregate plugin.
-
#method_missing(name, *args, &block) ⇒ Object
If the method name matches a # aggregate function, we return the function with args as a hash.
- #respond_to_missing?(method) ⇒ Boolean
-
#set_default_input_arguments(action, arguments) ⇒ Object
For a given action and arguments look up the DDL interface to that action and if any arguments in the DDL have a :default value assign that to any input that does not have an argument in the input arguments.
-
#summarize(&block) ⇒ Object
Calls the summarize block defined in the ddl.
-
#symbolize_basic_input_arguments(input, arguments) ⇒ Object
Creates a new set of arguments with string arguments mapped to symbol ones.
-
#validate_rpc_request(action, arguments) ⇒ Object
Helper to use the DDL to figure out if the remote call to an agent should be allowed based on action name and inputs.
Methods inherited from Base
#findddlfile, #help, #loadddlfile, #metadata, #output, #requires, #template_for_plugintype, #validate_input_argument, #validate_requirements
Constructor Details
#initialize(plugin, plugintype = :agent, loadddl = true) ⇒ AgentDDL
Returns a new instance of AgentDDL.
41 42 43 44 45 |
# File 'lib/mcollective/ddl/agentddl.rb', line 41 def initialize(plugin, plugintype=:agent, loadddl=true) @process_aggregate_functions = nil super end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, &block) ⇒ Object
If the method name matches a # aggregate function, we return the function with args as a hash. This will only be active if the @process_aggregate_functions is set to true which only happens in the #summarize block
139 140 141 142 143 |
# File 'lib/mcollective/ddl/agentddl.rb', line 139 def method_missing(name, *args, &block) super unless @process_aggregate_functions || is_function?(name) {:function => name, :args => args} 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
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/mcollective/ddl/agentddl.rb', line 116 def action(name, input, &block) raise "Action needs a :description property" unless input.include?(:description) unless @entities.include?(name) @entities[name] = {} @entities[name][:action] = name @entities[name][:input] = {} @entities[name][:output] = {} @entities[name][:display] = :failed @entities[name][:description] = input[:description] end # if a block is passed it might be creating input methods, call it # we set @current_entity so the input block can know what its talking # to, this is probably an epic hack, need to improve. @current_entity = name yield if block_given? @current_entity = nil end |
#action_interface(name) ⇒ Object
Returns the interface for a specific action
237 238 239 |
# File 'lib/mcollective/ddl/agentddl.rb', line 237 def action_interface(name) @entities[name] || {} end |
#actions ⇒ Object
Returns an array of actions this agent support
242 243 244 |
# File 'lib/mcollective/ddl/agentddl.rb', line 242 def actions @entities.keys end |
#aggregate(function, format = {:format => nil}) ⇒ Object
Sets the aggregate array for the given action
74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/mcollective/ddl/agentddl.rb', line 74 def aggregate(function, format={:format => nil}) raise(DDLValidationError, "Formats supplied to aggregation functions should be a hash") unless format.is_a?(Hash) raise(DDLValidationError, "Formats supplied to aggregation functions must have a :format key") unless format.keys.include?(:format) raise(DDLValidationError, "Functions supplied to aggregate should be a hash") unless function.is_a?(Hash) unless function.keys.include?(:args) && function[:args] raise DDLValidationError, "aggregate method for action '%s' missing a function parameter" % entities[@current_entity][:action] end entities[@current_entity][:aggregate] ||= [] entities[@current_entity][:aggregate] << (format[:format].nil? ? function : function.merge(format)) end |
#client_activated? ⇒ Boolean
47 48 49 |
# File 'lib/mcollective/ddl/agentddl.rb', line 47 def client_activated? Util.str_to_bool(@config.pluginconf.fetch("%s.activate_client" % @pluginname, ACTIVATION_DEFAULT.to_s)) end |
#display(pref) ⇒ Object
Sets the display preference to either :ok, :failed, :flatten or :always operates on action level
89 90 91 92 93 94 95 96 97 |
# File 'lib/mcollective/ddl/agentddl.rb', line 89 def display(pref) Log.warn("The display option :flatten is being deprecated and will be removed in the next minor release.") if pref == :flatten # defaults to old behavior, complain if its supplied and invalid raise "Display preference #{pref} is not valid, should be :ok, :failed, :flatten or :always" unless [:ok, :failed, :flatten, :always].include?(pref) action = @current_entity @entities[action][:display] = pref end |
#input(argument, properties) ⇒ Object
51 52 53 54 55 |
# File 'lib/mcollective/ddl/agentddl.rb', line 51 def input(argument, properties) raise "Input needs a :optional property" unless properties.include?(:optional) super end |
#is_function?(method_name) ⇒ Boolean
Checks if a method name matches a aggregate plugin. This is used by method missing so that we dont greedily assume that every method_missing call in an agent ddl has hit a aggregate function.
152 153 154 |
# File 'lib/mcollective/ddl/agentddl.rb', line 152 def is_function?(method_name) PluginManager.find("aggregate").include?(method_name.to_s) end |
#respond_to_missing?(method) ⇒ Boolean
145 146 147 |
# File 'lib/mcollective/ddl/agentddl.rb', line 145 def respond_to_missing?(method, *) @process_aggregate_functions || is_function?(name) || super end |
#set_default_input_arguments(action, arguments) ⇒ Object
For a given action and arguments look up the DDL interface to that action and if any arguments in the DDL have a :default value assign that to any input that does not have an argument in the input arguments
This is intended to only be called on clients and not on servers as the clients should never be able to publish non compliant requests and the servers should really not tamper with incoming requests since doing so might raise validation errors that were not raised on the client breaking our fail-fast approach to input validation
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 |
# File 'lib/mcollective/ddl/agentddl.rb', line 165 def set_default_input_arguments(action, arguments) input = action_interface(action)[:input] return unless input input.each_key do |key| if key.is_a?(Symbol) && arguments.include?(key.to_s) && !input.include?(key.to_s) compat_arg = key.to_s else compat_arg = key end if !arguments.include?(compat_arg) && !input[key][:default].nil? && !input[key][:optional] Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]]) arguments[compat_arg] = input[key][:default] end end end |
#summarize(&block) ⇒ Object
Calls the summarize block defined in the ddl. Block will not be called if the ddl is getting processed on the server side. This means that aggregate plugins only have to be present on the client side.
The @process_aggregate_functions variable is used by the method_missing block to determine if it should kick in, this way we very tightly control where we activate the method_missing behavior turning it into a noop otherwise to maximise the chance of providing good user feedback
65 66 67 68 69 70 71 |
# File 'lib/mcollective/ddl/agentddl.rb', line 65 def summarize(&block) unless @config.mode == :server @process_aggregate_functions = true yield @process_aggregate_functions = nil end end |
#symbolize_basic_input_arguments(input, arguments) ⇒ Object
Creates a new set of arguments with string arguments mapped to symbol ones
This is to assist with moving to a JSON pure world where requests might come in from REST or other languages, those languages and indeed JSON itself does not support symbols.
It ensures a backward compatible mode where for rpcutil both of these requests are equivelant
c.get_fact(:fact => "cluster")
c.get_fact("fact" => "cluster")
The case where both :fact and “fact” is in the DDL cannot be handled correctly and this code will assume the caller means “fact” in that case. There’s no way to represent such a request in JSON and just in general sounds like a bad idea, so a warning is logged which would in default client configuration appear on the clients display
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 |
# File 'lib/mcollective/ddl/agentddl.rb', line 201 def symbolize_basic_input_arguments(input, arguments) warned = false Hash[arguments.map do |key, value| if input.include?(key.intern) && input.include?(key.to_s) && !warned Log.warn("String and Symbol versions of input %s found in the DDL for %s, ensure your DDL keys are unique." % [key, @pluginname]) warned = true end if key.is_a?(String) && input.include?(key.intern) && !input.include?(key) [key.intern, value] else [key, value] end end] end |
#validate_rpc_request(action, arguments) ⇒ Object
Helper to use the DDL to figure out if the remote call to an agent should be allowed based on action name and inputs.
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 |
# File 'lib/mcollective/ddl/agentddl.rb', line 220 def validate_rpc_request(action, arguments) # is the action known? raise DDLValidationError, "Attempted to call action #{action} for #{@pluginname} but it's not declared in the DDL" unless actions.include?(action) input = action_interface(action)[:input] || {} compatible_args = symbolize_basic_input_arguments(input, arguments) input.each_key do |key| raise DDLValidationError, "Action #{action} needs a #{key} argument" if !input[key][:optional] && !compatible_args.include?(key) validate_input_argument(input, key, compatible_args[key]) if compatible_args.include?(key) end true end |