--- /dev/null
+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