Update version according to OSCI-856
[packages/precise/mcollective.git] / lib / mcollective / ddl / base.rb
1 module MCollective
2   module DDL
3     # The base class for all kinds of DDL files.  DDL files when
4     # run gets parsed and builds up a hash of the basic primitive
5     # types, ideally restricted so it can be converted to JSON though
6     # today there are some Ruby Symbols in them which might be fixed
7     # laster on.
8     #
9     # The Hash being built should be stored in @entities, the format
10     # is generally not prescribed but there's a definite feel to how
11     # DDL files look so study the agent and discovery ones to see how
12     # the structure applies to very different use cases.
13     #
14     # For every plugin type you should have a single word name - that
15     # corresponds to the directory in the libdir where these plugins
16     # live.  If you need anything above and beyond 'metadata' in your
17     # plugin DDL then add a PlugintypeDDL class here and add your
18     # specific behaviors to those.
19     class Base
20       include Translatable
21
22       attr_reader :meta, :entities, :pluginname, :plugintype, :usage, :requirements
23
24       def initialize(plugin, plugintype=:agent, loadddl=true)
25         @entities = {}
26         @meta = {}
27         @usage = ""
28         @config = Config.instance
29         @pluginname = plugin
30         @plugintype = plugintype.to_sym
31         @requirements = {}
32
33         loadddlfile if loadddl
34       end
35
36       # Generates help using the template based on the data
37       # created with metadata and input.
38       #
39       # If no template name is provided one will be chosen based
40       # on the plugin type.  If the provided template path is
41       # not absolute then the template will be loaded either from
42       # the config dir and if that does not exist, default to
43       # /etc/mcollective
44       def help(template=nil)
45         template = template_for_plugintype unless template
46         template = Util.templatepath(template) unless Util.absolute_path?(template)
47
48         template = File.read(template)
49         meta = @meta
50         entities = @entities
51
52         unless template == "metadata-help.erb"
53           metadata_template = Util.templatepath("metadata-help.erb")
54           metadata_template = File.read(metadata_template)
55           metastring = ERB.new(metadata_template, 0, '%')
56           metastring = metastring.result(binding)
57         end
58
59         erb = ERB.new(template, 0, '%')
60         erb.result(binding)
61       end
62
63       def usage(usage_text)
64         @usage = usage_text
65       end
66
67       def template_for_plugintype
68         case @plugintype
69         when :agent
70           return "rpc-help.erb"
71         else
72           if File.exists?(Util.templatepath("#{@plugintype}-help.erb"))
73             return "#{@plugintype}-help.erb"
74           else
75             # Default help template gets loaded if plugintype-help does not exist.
76             return "metadata-help.erb"
77           end
78         end
79       end
80
81       def loadddlfile
82         if ddlfile = findddlfile
83           instance_eval(File.read(ddlfile), ddlfile, 1)
84         else
85           raise_code(:PLMC40, "Can't find DDL for %{type} plugin '%{name}'", :debug, :type => @plugintype, :name => @pluginname)
86         end
87       end
88
89       def findddlfile(ddlname=nil, ddltype=nil)
90         ddlname = @pluginname unless ddlname
91         ddltype = @plugintype unless ddltype
92
93         @config.libdir.each do |libdir|
94           ddlfile = File.join([libdir, "mcollective", ddltype.to_s, "#{ddlname}.ddl"])
95           if File.exist?(ddlfile)
96             log_code(:PLMC18, "Found %{ddlname} ddl at %{ddlfile}", :debug, :ddlname => ddlname, :ddlfile => ddlfile)
97             return ddlfile
98           end
99         end
100         return false
101       end
102
103       def validate_requirements
104         if requirement = @requirements[:mcollective]
105           if Util.mcollective_version == "@DEVELOPMENT_VERSION@"
106             log_code(:PLMC19, "DDL requirements validation being skipped in development", :warn)
107             return true
108           end
109
110           if Util.versioncmp(Util.mcollective_version, requirement) < 0
111             DDL.validation_fail!(:PLMC20, "%{type} plugin '%{name}' requires MCollective version %{requirement} or newer", :debug, :type => @plugintype.to_s.capitalize, :name => @pluginname, :requirement => requirement)
112           end
113         end
114
115         true
116       end
117
118       # validate strings, lists and booleans, we'll add more types of validators when
119       # all the use cases are clear
120       #
121       # only does validation for arguments actually given, since some might
122       # be optional.  We validate the presense of the argument earlier so
123       # this is a safe assumption, just to skip them.
124       #
125       # :string can have maxlength and regex.  A maxlength of 0 will bypasss checks
126       # :list has a array of valid values
127       def validate_input_argument(input, key, argument)
128         Validator.load_validators
129
130         case input[key][:type]
131         when :string
132           Validator.validate(argument, :string)
133
134           Validator.length(argument, input[key][:maxlength].to_i)
135
136           Validator.validate(argument, input[key][:validation])
137
138         when :list
139           Validator.validate(argument, input[key][:list])
140
141         else
142           Validator.validate(argument, input[key][:type])
143         end
144
145         return true
146       rescue => e
147         DDL.validation_fail!(:PLMC21, "Cannot validate input '%{input}': %{error}", :debug, :input => key, :error => e.to_s)
148       end
149
150       # Registers an input argument for a given action
151       #
152       # See the documentation for action for how to use this
153       def input(argument, properties)
154         raise_code(:PLMC22, "Cannot determine what entity input '%{entity}' belongs to", :error, :entity => @current_entity) unless @current_entity
155
156         entity = @current_entity
157
158         [:prompt, :description, :type].each do |arg|
159           raise_code(:PLMC23, "Input needs a :%{property} property", :debug, :property => arg) unless properties.include?(arg)
160         end
161
162         @entities[entity][:input][argument] = {:prompt => properties[:prompt],
163                                                :description => properties[:description],
164                                                :type => properties[:type],
165                                                :default => properties[:default],
166                                                :optional => properties[:optional]}
167
168         case properties[:type]
169           when :string
170             raise "Input type :string needs a :validation argument" unless properties.include?(:validation)
171             raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength)
172
173             @entities[entity][:input][argument][:validation] = properties[:validation]
174             @entities[entity][:input][argument][:maxlength] = properties[:maxlength]
175
176           when :list
177             raise "Input type :list needs a :list argument" unless properties.include?(:list)
178
179             @entities[entity][:input][argument][:list] = properties[:list]
180         end
181       end
182
183       # Registers an output argument for a given action
184       #
185       # See the documentation for action for how to use this
186       def output(argument, properties)
187         raise "Cannot figure out what action input #{argument} belongs to" unless @current_entity
188         raise "Output #{argument} needs a description argument" unless properties.include?(:description)
189         raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as)
190
191         action = @current_entity
192
193         @entities[action][:output][argument] = {:description => properties[:description],
194                                                 :display_as  => properties[:display_as],
195                                                 :default     => properties[:default]}
196       end
197
198       def requires(requirement)
199         raise "Requirement should be a hash in the form :item => 'requirement'" unless requirement.is_a?(Hash)
200
201         valid_requirements = [:mcollective]
202
203         requirement.keys.each do |key|
204           unless valid_requirements.include?(key)
205             raise "Requirement %s is not a valid requirement, only %s is supported" % [key, valid_requirements.join(", ")]
206           end
207
208           @requirements[key] = requirement[key]
209         end
210
211         validate_requirements
212       end
213
214       # Registers meta data for the introspection hash
215       def metadata(meta)
216         [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
217           raise "Metadata needs a :#{arg} property" unless meta.include?(arg)
218         end
219
220         @meta = meta
221       end
222     end
223   end
224 end