dc2410dbc49f69a35f68d2b697d5a6cb6d8df085
[packages/precise/mcollective.git] / lib / mcollective / ddl.rb
1 module MCollective
2   # A set of classes that helps create data description language files
3   # for plugins.  You can define meta data, actions, input and output
4   # describing the behavior of your agent or other plugins
5   #
6   # DDL files are used for input validation, constructing outputs,
7   # producing online help, informing the various display routines and
8   # so forth.
9   #
10   # A sample DDL for an agent be seen below, you'd put this in your agent
11   # dir as <agent name>.ddl
12   #
13   #    metadata :name        => "SimpleRPC Service Agent",
14   #             :description => "Agent to manage services using the Puppet service provider",
15   #             :author      => "R.I.Pienaar",
16   #             :license     => "GPLv2",
17   #             :version     => "1.1",
18   #             :url         => "http://mcollective-plugins.googlecode.com/",
19   #             :timeout     => 60
20   #
21   #    action "status", :description => "Gets the status of a service" do
22   #       display :always
23   #
24   #       input :service,
25   #             :prompt      => "Service Name",
26   #             :description => "The service to get the status for",
27   #             :type        => :string,
28   #             :validation  => '^[a-zA-Z\-_\d]+$',
29   #             :optional    => true,
30   #             :maxlength   => 30
31   #
32   #       output :status,
33   #              :description => "The status of service",
34   #              :display_as  => "Service Status"
35   #   end
36   #
37   # There are now many types of DDL and ultimately all pugins should have
38   # DDL files.  The code is organized so that any plugin type will magically
39   # just work - they will be an instane of Base which has #metadata and a few
40   # common cases.
41   #
42   # For plugin types that require more specific behaviors they can just add a
43   # class here that inherits from Base and add their specific behavior.
44   #
45   # Base defines a specific behavior for input, output and metadata which we'd
46   # like to keep standard across plugin types so do not completely override the
47   # behavior of input.  The methods are written that they will gladly store extra
48   # content though so you add, do not remove.  See the AgentDDL class for an example
49   # where agents want a :required argument to be always set.
50   module DDL
51     autoload :Base, "mcollective/ddl/base"
52     autoload :AgentDDL, "mcollective/ddl/agentddl"
53     autoload :DataDDL, "mcollective/ddl/dataddl"
54     autoload :DiscoveryDDL, "mcollective/ddl/discoveryddl"
55
56     extend Translatable
57
58     # There used to be only one big nasty DDL class with a bunch of mashed
59     # together behaviors.  It's been around for ages and we would rather not
60     # ask all the users to change their DDL.new calls to some other factory
61     # method that would have this exact same behavior.
62     #
63     # So we override the behavior of #new which is a hugely sucky thing to do
64     # but ultimately it's what would be least disrupting to code out there
65     # today.  We did though change DDL to a module to make it possibly a
66     # little less suprising, possibly.
67     def self.new(*args, &blk)
68       load_and_cache(*args)
69     end
70
71     def self.load_and_cache(*args)
72       Cache.setup(:ddl, 300)
73
74       plugin = args.first
75       args.size > 1 ? type = args[1].to_s : type = "agent"
76       path = "%s/%s" % [type, plugin]
77
78       begin
79         ddl = Cache.read(:ddl, path)
80       rescue
81         begin
82           klass = DDL.const_get("%sDDL" % type.capitalize)
83         rescue NameError
84           klass = Base
85         end
86
87         ddl = Cache.write(:ddl, path, klass.new(*args))
88       end
89
90       return ddl
91     end
92
93     # As we're taking arguments on the command line we need a
94     # way to input booleans, true on the cli is a string so this
95     # method will take the ddl, find all arguments that are supposed
96     # to be boolean and if they are the strings "true"/"yes" or "false"/"no"
97     # turn them into the matching boolean
98     def self.string_to_boolean(val)
99       return true if ["true", "t", "yes", "y", "1"].include?(val.downcase)
100       return false if ["false", "f", "no", "n", "0"].include?(val.downcase)
101
102       raise_code(:PLMC17, "%{value} does not look like a boolean argument", :debug, :value => val)
103     end
104
105     # a generic string to number function, if a number looks like a float
106     # it turns it into a float else an int.  This is naive but should be sufficient
107     # for numbers typed on the cli in most cases
108     def self.string_to_number(val)
109       return val.to_f if val =~ /^\d+\.\d+$/
110       return val.to_i if val =~ /^\d+$/
111
112       raise_code(:PLMC16, "%{value} does not look like a numeric value", :debug, :value => val)
113     end
114
115     # Various DDL implementations will validate and raise on error, this is a
116     # utility method to correctly setup a DDLValidationError exceptions and raise them
117     def self.validation_fail!(code, default, level, args={})
118       exception = DDLValidationError.new(code, default, level, args)
119       exception.set_backtrace caller
120
121       raise exception
122     end
123   end
124 end