Updated mcollective.init according to OSCI-658
[packages/precise/mcollective.git] / lib / mcollective / ddl.rb
diff --git a/lib/mcollective/ddl.rb b/lib/mcollective/ddl.rb
new file mode 100644 (file)
index 0000000..dc2410d
--- /dev/null
@@ -0,0 +1,124 @@
+module MCollective
+  # A set of classes that helps create data description language files
+  # for plugins.  You can define meta data, actions, input and output
+  # describing the behavior of your agent or other plugins
+  #
+  # DDL files are used for input validation, constructing outputs,
+  # producing online help, informing the various display routines and
+  # so forth.
+  #
+  # A sample DDL for an agent be seen below, you'd put this in your agent
+  # dir as <agent name>.ddl
+  #
+  #    metadata :name        => "SimpleRPC Service Agent",
+  #             :description => "Agent to manage services using the Puppet service provider",
+  #             :author      => "R.I.Pienaar",
+  #             :license     => "GPLv2",
+  #             :version     => "1.1",
+  #             :url         => "http://mcollective-plugins.googlecode.com/",
+  #             :timeout     => 60
+  #
+  #    action "status", :description => "Gets the status of a service" do
+  #       display :always
+  #
+  #       input :service,
+  #             :prompt      => "Service Name",
+  #             :description => "The service to get the status for",
+  #             :type        => :string,
+  #             :validation  => '^[a-zA-Z\-_\d]+$',
+  #             :optional    => true,
+  #             :maxlength   => 30
+  #
+  #       output :status,
+  #              :description => "The status of service",
+  #              :display_as  => "Service Status"
+  #   end
+  #
+  # There are now many types of DDL and ultimately all pugins should have
+  # DDL files.  The code is organized so that any plugin type will magically
+  # just work - they will be an instane of Base which has #metadata and a few
+  # common cases.
+  #
+  # For plugin types that require more specific behaviors they can just add a
+  # class here that inherits from Base and add their specific behavior.
+  #
+  # Base defines a specific behavior for input, output and metadata which we'd
+  # like to keep standard across plugin types so do not completely override the
+  # behavior of input.  The methods are written that they will gladly store extra
+  # content though so you add, do not remove.  See the AgentDDL class for an example
+  # where agents want a :required argument to be always set.
+  module DDL
+    autoload :Base, "mcollective/ddl/base"
+    autoload :AgentDDL, "mcollective/ddl/agentddl"
+    autoload :DataDDL, "mcollective/ddl/dataddl"
+    autoload :DiscoveryDDL, "mcollective/ddl/discoveryddl"
+
+    extend Translatable
+
+    # There used to be only one big nasty DDL class with a bunch of mashed
+    # together behaviors.  It's been around for ages and we would rather not
+    # ask all the users to change their DDL.new calls to some other factory
+    # method that would have this exact same behavior.
+    #
+    # So we override the behavior of #new which is a hugely sucky thing to do
+    # but ultimately it's what would be least disrupting to code out there
+    # today.  We did though change DDL to a module to make it possibly a
+    # little less suprising, possibly.
+    def self.new(*args, &blk)
+      load_and_cache(*args)
+    end
+
+    def self.load_and_cache(*args)
+      Cache.setup(:ddl, 300)
+
+      plugin = args.first
+      args.size > 1 ? type = args[1].to_s : type = "agent"
+      path = "%s/%s" % [type, plugin]
+
+      begin
+        ddl = Cache.read(:ddl, path)
+      rescue
+        begin
+          klass = DDL.const_get("%sDDL" % type.capitalize)
+        rescue NameError
+          klass = Base
+        end
+
+        ddl = Cache.write(:ddl, path, klass.new(*args))
+      end
+
+      return ddl
+    end
+
+    # As we're taking arguments on the command line we need a
+    # way to input booleans, true on the cli is a string so this
+    # method will take the ddl, find all arguments that are supposed
+    # to be boolean and if they are the strings "true"/"yes" or "false"/"no"
+    # turn them into the matching boolean
+    def self.string_to_boolean(val)
+      return true if ["true", "t", "yes", "y", "1"].include?(val.downcase)
+      return false if ["false", "f", "no", "n", "0"].include?(val.downcase)
+
+      raise_code(:PLMC17, "%{value} does not look like a boolean argument", :debug, :value => val)
+    end
+
+    # a generic string to number function, if a number looks like a float
+    # it turns it into a float else an int.  This is naive but should be sufficient
+    # for numbers typed on the cli in most cases
+    def self.string_to_number(val)
+      return val.to_f if val =~ /^\d+\.\d+$/
+      return val.to_i if val =~ /^\d+$/
+
+      raise_code(:PLMC16, "%{value} does not look like a numeric value", :debug, :value => val)
+    end
+
+    # Various DDL implementations will validate and raise on error, this is a
+    # utility method to correctly setup a DDLValidationError exceptions and raise them
+    def self.validation_fail!(code, default, level, args={})
+      exception = DDLValidationError.new(code, default, level, args)
+      exception.set_backtrace caller
+
+      raise exception
+    end
+  end
+end