X-Git-Url: https://review.fuel-infra.org/gitweb?a=blobdiff_plain;ds=sidebyside;f=lib%2Fmcollective%2Fddl%2Fbase.rb;fp=lib%2Fmcollective%2Fddl%2Fbase.rb;h=d526755ace6e4f6b26f56753798fe32b8e0e2b10;hb=b87d2f4e68281062df1913440ca5753ae63314a9;hp=0000000000000000000000000000000000000000;hpb=ab0ea530b8ac956091f17b104ab2311336cfc250;p=packages%2Fprecise%2Fmcollective.git diff --git a/lib/mcollective/ddl/base.rb b/lib/mcollective/ddl/base.rb new file mode 100644 index 0000000..d526755 --- /dev/null +++ b/lib/mcollective/ddl/base.rb @@ -0,0 +1,223 @@ +module MCollective + module DDL + # The base class for all kinds of DDL files. DDL files when + # run gets parsed and builds up a hash of the basic primitive + # types, ideally restricted so it can be converted to JSON though + # today there are some Ruby Symbols in them which might be fixed + # laster on. + # + # The Hash being built should be stored in @entities, the format + # is generally not prescribed but there's a definite feel to how + # DDL files look so study the agent and discovery ones to see how + # the structure applies to very different use cases. + # + # For every plugin type you should have a single word name - that + # corresponds to the directory in the libdir where these plugins + # live. If you need anything above and beyond 'metadata' in your + # plugin DDL then add a PlugintypeDDL class here and add your + # specific behaviors to those. + class Base + include Translatable + + attr_reader :meta, :entities, :pluginname, :plugintype, :usage, :requirements + + def initialize(plugin, plugintype=:agent, loadddl=true) + @entities = {} + @meta = {} + @usage = "" + @config = Config.instance + @pluginname = plugin + @plugintype = plugintype.to_sym + @requirements = {} + + loadddlfile if loadddl + end + + # Generates help using the template based on the data + # created with metadata and input. + # + # If no template name is provided one will be chosen based + # on the plugin type. If the provided template path is + # not absolute then the template will be loaded relative to + # helptemplatedir configuration parameter + def help(template=nil) + template = template_for_plugintype unless template + template = File.join(@config.helptemplatedir, template) unless template.start_with?(File::SEPARATOR) + + template = File.read(template) + meta = @meta + entities = @entities + + unless template == "metadata-help.erb" + metadata_template = File.join(@config.helptemplatedir, "metadata-help.erb") + metadata_template = File.read(metadata_template) + metastring = ERB.new(metadata_template, 0, '%') + metastring = metastring.result(binding) + end + + erb = ERB.new(template, 0, '%') + erb.result(binding) + end + + def usage(usage_text) + @usage = usage_text + end + + def template_for_plugintype + case @plugintype + when :agent + return "rpc-help.erb" + else + if File.exists?(File.join(@config.helptemplatedir,"#{@plugintype}-help.erb")) + return "#{@plugintype}-help.erb" + else + # Default help template gets loaded if plugintype-help does not exist. + return "metadata-help.erb" + end + end + end + + def loadddlfile + if ddlfile = findddlfile + instance_eval(File.read(ddlfile), ddlfile, 1) + else + raise_code(:PLMC18, "Can't find DDL for %{type} plugin '%{name}'", :debug, :type => @plugintype, :name => @pluginname) + end + end + + def findddlfile(ddlname=nil, ddltype=nil) + ddlname = @pluginname unless ddlname + ddltype = @plugintype unless ddltype + + @config.libdir.each do |libdir| + ddlfile = File.join([libdir, "mcollective", ddltype.to_s, "#{ddlname}.ddl"]) + if File.exist?(ddlfile) + log_code(:PLMC18, "Found %{ddlname} ddl at %{ddlfile}", :debug, :ddlname => ddlname, :ddlfile => ddlfile) + return ddlfile + end + end + return false + end + + def validate_requirements + if requirement = @requirements[:mcollective] + if Util.mcollective_version == "@DEVELOPMENT_VERSION@" + log_code(:PLMC19, "DDL requirements validation being skipped in development", :warn) + return true + end + + if Util.versioncmp(Util.mcollective_version, requirement) < 0 + DDL.validation_fail!(:PLMC20, "%{type} plugin '%{name}' requires MCollective version %{requirement} or newer", :debug, :type => @plugintype.to_s.capitalize, :name => @pluginname, :requirement => requirement) + end + end + + true + end + + # validate strings, lists and booleans, we'll add more types of validators when + # all the use cases are clear + # + # only does validation for arguments actually given, since some might + # be optional. We validate the presense of the argument earlier so + # this is a safe assumption, just to skip them. + # + # :string can have maxlength and regex. A maxlength of 0 will bypasss checks + # :list has a array of valid values + def validate_input_argument(input, key, argument) + Validator.load_validators + + case input[key][:type] + when :string + Validator.validate(argument, :string) + + Validator.length(argument, input[key][:maxlength].to_i) + + Validator.validate(argument, input[key][:validation]) + + when :list + Validator.validate(argument, input[key][:list]) + + else + Validator.validate(argument, input[key][:type]) + end + + return true + rescue => e + DDL.validation_fail!(:PLMC21, "Cannot validate input '%{input}': %{error}", :debug, :input => key, :error => e.to_s) + end + + # Registers an input argument for a given action + # + # See the documentation for action for how to use this + def input(argument, properties) + raise_code(:PLMC22, "Cannot determine what entity input '%{entity}' belongs to", :error, :entity => @current_entity) unless @current_entity + + entity = @current_entity + + [:prompt, :description, :type].each do |arg| + raise_code(:PLMC23, "Input needs a :%{property} property", :debug, :property => arg) unless properties.include?(arg) + end + + @entities[entity][:input][argument] = {:prompt => properties[:prompt], + :description => properties[:description], + :type => properties[:type], + :default => properties[:default], + :optional => properties[:optional]} + + case properties[:type] + when :string + raise "Input type :string needs a :validation argument" unless properties.include?(:validation) + raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength) + + @entities[entity][:input][argument][:validation] = properties[:validation] + @entities[entity][:input][argument][:maxlength] = properties[:maxlength] + + when :list + raise "Input type :list needs a :list argument" unless properties.include?(:list) + + @entities[entity][:input][argument][:list] = properties[:list] + end + end + + # Registers an output argument for a given action + # + # See the documentation for action for how to use this + def output(argument, properties) + raise "Cannot figure out what action input #{argument} belongs to" unless @current_entity + raise "Output #{argument} needs a description argument" unless properties.include?(:description) + raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as) + + action = @current_entity + + @entities[action][:output][argument] = {:description => properties[:description], + :display_as => properties[:display_as], + :default => properties[:default]} + end + + def requires(requirement) + raise "Requirement should be a hash in the form :item => 'requirement'" unless requirement.is_a?(Hash) + + valid_requirements = [:mcollective] + + requirement.keys.each do |key| + unless valid_requirements.include?(key) + raise "Requirement %s is not a valid requirement, only %s is supported" % [key, valid_requirements.join(", ")] + end + + @requirements[key] = requirement[key] + end + + validate_requirements + end + + # Registers meta data for the introspection hash + def metadata(meta) + [:name, :description, :author, :license, :version, :url, :timeout].each do |arg| + raise "Metadata needs a :#{arg} property" unless meta.include?(arg) + end + + @meta = meta + end + end + end +end