d526755ace6e4f6b26f56753798fe32b8e0e2b10
[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 relative to
42       # helptemplatedir configuration parameter
43       def help(template=nil)
44         template = template_for_plugintype unless template
45         template = File.join(@config.helptemplatedir, template) unless template.start_with?(File::SEPARATOR)
46
47         template = File.read(template)
48         meta = @meta
49         entities = @entities
50
51         unless template == "metadata-help.erb"
52           metadata_template = File.join(@config.helptemplatedir, "metadata-help.erb")
53           metadata_template = File.read(metadata_template)
54           metastring = ERB.new(metadata_template, 0, '%')
55           metastring = metastring.result(binding)
56         end
57
58         erb = ERB.new(template, 0, '%')
59         erb.result(binding)
60       end
61
62       def usage(usage_text)
63         @usage = usage_text
64       end
65
66       def template_for_plugintype
67         case @plugintype
68         when :agent
69           return "rpc-help.erb"
70         else
71           if File.exists?(File.join(@config.helptemplatedir,"#{@plugintype}-help.erb"))
72             return "#{@plugintype}-help.erb"
73           else
74             # Default help template gets loaded if plugintype-help does not exist.
75             return "metadata-help.erb"
76           end
77         end
78       end
79
80       def loadddlfile
81         if ddlfile = findddlfile
82           instance_eval(File.read(ddlfile), ddlfile, 1)
83         else
84           raise_code(:PLMC18, "Can't find DDL for %{type} plugin '%{name}'", :debug, :type => @plugintype, :name => @pluginname)
85         end
86       end
87
88       def findddlfile(ddlname=nil, ddltype=nil)
89         ddlname = @pluginname unless ddlname
90         ddltype = @plugintype unless ddltype
91
92         @config.libdir.each do |libdir|
93           ddlfile = File.join([libdir, "mcollective", ddltype.to_s, "#{ddlname}.ddl"])
94           if File.exist?(ddlfile)
95             log_code(:PLMC18, "Found %{ddlname} ddl at %{ddlfile}", :debug, :ddlname => ddlname, :ddlfile => ddlfile)
96             return ddlfile
97           end
98         end
99         return false
100       end
101
102       def validate_requirements
103         if requirement = @requirements[:mcollective]
104           if Util.mcollective_version == "@DEVELOPMENT_VERSION@"
105             log_code(:PLMC19, "DDL requirements validation being skipped in development", :warn)
106             return true
107           end
108
109           if Util.versioncmp(Util.mcollective_version, requirement) < 0
110             DDL.validation_fail!(:PLMC20, "%{type} plugin '%{name}' requires MCollective version %{requirement} or newer", :debug, :type => @plugintype.to_s.capitalize, :name => @pluginname, :requirement => requirement)
111           end
112         end
113
114         true
115       end
116
117       # validate strings, lists and booleans, we'll add more types of validators when
118       # all the use cases are clear
119       #
120       # only does validation for arguments actually given, since some might
121       # be optional.  We validate the presense of the argument earlier so
122       # this is a safe assumption, just to skip them.
123       #
124       # :string can have maxlength and regex.  A maxlength of 0 will bypasss checks
125       # :list has a array of valid values
126       def validate_input_argument(input, key, argument)
127         Validator.load_validators
128
129         case input[key][:type]
130         when :string
131           Validator.validate(argument, :string)
132
133           Validator.length(argument, input[key][:maxlength].to_i)
134
135           Validator.validate(argument, input[key][:validation])
136
137         when :list
138           Validator.validate(argument, input[key][:list])
139
140         else
141           Validator.validate(argument, input[key][:type])
142         end
143
144         return true
145       rescue => e
146         DDL.validation_fail!(:PLMC21, "Cannot validate input '%{input}': %{error}", :debug, :input => key, :error => e.to_s)
147       end
148
149       # Registers an input argument for a given action
150       #
151       # See the documentation for action for how to use this
152       def input(argument, properties)
153         raise_code(:PLMC22, "Cannot determine what entity input '%{entity}' belongs to", :error, :entity => @current_entity) unless @current_entity
154
155         entity = @current_entity
156
157         [:prompt, :description, :type].each do |arg|
158           raise_code(:PLMC23, "Input needs a :%{property} property", :debug, :property => arg) unless properties.include?(arg)
159         end
160
161         @entities[entity][:input][argument] = {:prompt => properties[:prompt],
162                                                :description => properties[:description],
163                                                :type => properties[:type],
164                                                :default => properties[:default],
165                                                :optional => properties[:optional]}
166
167         case properties[:type]
168           when :string
169             raise "Input type :string needs a :validation argument" unless properties.include?(:validation)
170             raise "Input type :string needs a :maxlength argument" unless properties.include?(:maxlength)
171
172             @entities[entity][:input][argument][:validation] = properties[:validation]
173             @entities[entity][:input][argument][:maxlength] = properties[:maxlength]
174
175           when :list
176             raise "Input type :list needs a :list argument" unless properties.include?(:list)
177
178             @entities[entity][:input][argument][:list] = properties[:list]
179         end
180       end
181
182       # Registers an output argument for a given action
183       #
184       # See the documentation for action for how to use this
185       def output(argument, properties)
186         raise "Cannot figure out what action input #{argument} belongs to" unless @current_entity
187         raise "Output #{argument} needs a description argument" unless properties.include?(:description)
188         raise "Output #{argument} needs a display_as argument" unless properties.include?(:display_as)
189
190         action = @current_entity
191
192         @entities[action][:output][argument] = {:description => properties[:description],
193                                                 :display_as  => properties[:display_as],
194                                                 :default     => properties[:default]}
195       end
196
197       def requires(requirement)
198         raise "Requirement should be a hash in the form :item => 'requirement'" unless requirement.is_a?(Hash)
199
200         valid_requirements = [:mcollective]
201
202         requirement.keys.each do |key|
203           unless valid_requirements.include?(key)
204             raise "Requirement %s is not a valid requirement, only %s is supported" % [key, valid_requirements.join(", ")]
205           end
206
207           @requirements[key] = requirement[key]
208         end
209
210         validate_requirements
211       end
212
213       # Registers meta data for the introspection hash
214       def metadata(meta)
215         [:name, :description, :author, :license, :version, :url, :timeout].each do |arg|
216           raise "Metadata needs a :#{arg} property" unless meta.include?(arg)
217         end
218
219         @meta = meta
220       end
221     end
222   end
223 end