X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;f=lib%2Fmcollective%2Fddl%2Fagentddl.rb;fp=lib%2Fmcollective%2Fddl%2Fagentddl.rb;h=5952ec05c656160bf82c4f09270eb37511fc7e92;hb=b87d2f4e68281062df1913440ca5753ae63314a9;hp=0000000000000000000000000000000000000000;hpb=ab0ea530b8ac956091f17b104ab2311336cfc250;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/ddl/agentddl.rb b/lib/mcollective/ddl/agentddl.rb new file mode 100644 index 0000000..5952ec0 --- /dev/null +++ b/lib/mcollective/ddl/agentddl.rb @@ -0,0 +1,208 @@ +module MCollective + module DDL + # 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 ", + # :license => "Apache License, Version 2.0", + # :version => "1.0", + # :url => "http://marionette-collective.org/", + # :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 + class AgentDDL nil}) + DDL.validation_fail!(:PLMC28, "Formats supplied to aggregation functions should be a hash", :error) unless format.is_a?(Hash) + DDL.validation_fail!(:PLMC27, "Formats supplied to aggregation functions must have a :format key", :error) unless format.keys.include?(:format) + DDL.validation_fail!(:PLMC26, "Functions supplied to aggregate should be a hash", :error) unless function.is_a?(Hash) + + unless (function.keys.include?(:args)) && function[:args] + DDL.validation_fail!(:PLMC25, "aggregate method for action '%{action}' missing a function parameter", :error, :action => entities[@current_entity][:action]) + end + + entities[@current_entity][:aggregate] ||= [] + entities[@current_entity][:aggregate] << (format[:format].nil? ? function : function.merge(format)) + end + + # Sets the display preference to either :ok, :failed, :flatten or :always + # operates on action level + 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_entity + @entities[action][:display] = pref + 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 + 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 + block.call if block_given? + @current_entity = nil + end + + # 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 + def method_missing(name, *args, &block) + unless @process_aggregate_functions || is_function?(name) + raise NoMethodError, "undefined local variable or method `#{name}'", caller + end + + return {:function => name, :args => args} + end + + # 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. + def is_function?(method_name) + PluginManager.find("aggregate").include?(method_name.to_s) + end + + # 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 + def set_default_input_arguments(action, arguments) + input = action_interface(action)[:input] + + return unless input + + input.keys.each do |key| + if !arguments.include?(key) && !input[key][:default].nil? && !input[key][:optional] + Log.debug("Setting default value for input '%s' to '%s'" % [key, input[key][:default]]) + arguments[key] = input[key][:default] + end + end + end + + # Helper to use the DDL to figure out if the remote call to an + # agent should be allowed based on action name and inputs. + def validate_rpc_request(action, arguments) + # is the action known? + unless actions.include?(action) + DDL.validation_fail!(:PLMC29, "Attempted to call action %{action} for %{plugin} but it's not declared in the DDL", :debug, :action => action, :plugin => @pluginname) + end + + input = action_interface(action)[:input] || {} + + input.keys.each do |key| + unless input[key][:optional] + unless arguments.keys.include?(key) + DDL.validation_fail!(:PLMC30, "Action '%{action}' needs a '%{key}' argument", :debug, :action => action, :key => key) + end + end + + if arguments.keys.include?(key) + validate_input_argument(input, key, arguments[key]) + end + end + + true + end + + # Returns the interface for a specific action + def action_interface(name) + @entities[name] || {} + end + + # Returns an array of actions this agent support + def actions + @entities.keys + end + end + end +end