Updated mcollective.init according to OSCI-658
[packages/precise/mcollective.git] / lib / mcollective / ddl / base.rb
diff --git a/lib/mcollective/ddl/base.rb b/lib/mcollective/ddl/base.rb
new file mode 100644 (file)
index 0000000..d526755
--- /dev/null
@@ -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