Update version according to OSCI-883
[packages/precise/mcollective.git] / plugins / mcollective / application / plugin.rb
1 module MCollective
2   class Application::Plugin<Application
3
4     exclude_argument_sections "common", "filter", "rpc"
5
6     description "MCollective Plugin Application"
7     usage <<-END_OF_USAGE
8 mco plugin package [options] <directory>
9        mco plugin info <directory>
10        mco plugin doc <plugin>
11        mco plugin doc <type/plugin>
12        mco plugin generate agent <pluginname> [actions=val,val]
13        mco plugin generate data <pluginname> [outputs=val,val]
14
15           info : Display plugin information including package details.
16        package : Create all available plugin packages.
17            doc : Display documentation for a specific plugin.
18     END_OF_USAGE
19
20     option :pluginname,
21            :description => 'Plugin name',
22            :arguments   => ['-n', '--name NAME'],
23            :type        => String
24
25     option :postinstall,
26            :description => 'Post install script',
27            :arguments   => ['--postinstall POSTINSTALL'],
28            :type        => String
29
30     option :preinstall,
31            :description => 'Pre install script',
32            :arguments   => ['--preinstall PREINSTALL'],
33            :type        => String
34
35     option :revision,
36            :description => 'Revision number',
37            :arguments   => ['--revision REVISION'],
38            :type        => String
39
40     option :iteration,
41            :description => 'DEPRECATED - Use --revision instead',
42            :arguments   => ['--iteration ITERATION'],
43            :type        => String
44
45     option :vendor,
46            :description => 'Vendor name',
47            :arguments   => ['--vendor VENDOR'],
48            :type        => String
49
50     option :pluginpath,
51            :description => 'MCollective plugin path',
52            :arguments   => ['--pluginpath PATH'],
53            :type        => String
54
55     option :mcname,
56            :description => 'MCollective type (mcollective, pe-mcollective) that the packages depend on',
57            :arguments   => ['--mcname NAME'],
58            :type        => String
59
60     option :mcversion,
61            :description => 'Version of MCollective that the packages depend on',
62            :arguments   => ['--mcversion MCVERSION'],
63            :type        => String
64
65     option :dependency,
66            :description => 'Adds a dependency to the plugin',
67            :arguments   => ['--dependency DEPENDENCIES'],
68            :type        => :array
69
70     option :format,
71            :description => 'Package output format. Defaults to rpmpackage or debpackage',
72            :arguments   => ['--format OUTPUTFORMAT'],
73            :type        => String
74
75     option :sign,
76            :description => 'Embed a signature in the package',
77            :arguments   => ['--sign'],
78            :type        => :boolean
79
80     option :rpctemplate,
81            :description => 'Template to use.',
82            :arguments   => ['--template HELPTEMPLATE'],
83            :type        => String
84
85     option :description,
86            :description => 'Plugin description',
87            :arguments   => ['--description DESCRIPTION'],
88            :type        => String
89
90     option :author,
91            :description => 'The author of the plugin',
92            :arguments   => ['--author AUTHOR'],
93            :type        => String
94
95     option :license,
96            :description => 'The license under which the plugin is distributed',
97            :arguments   => ['--license LICENSE'],
98            :type        => String
99
100     option :version,
101            :description => 'The version of the plugin',
102            :arguments   => ['--pluginversion VERSION'],
103            :type        => String
104
105     option :url,
106            :description => 'Url at which information about the plugin can be found',
107            :arguments   => ['--url URL'],
108            :type        => String
109
110     option :timeout,
111            :description => "The plugin's timeout",
112            :arguments   => ['--timeout TIMEOUT'],
113            :type        => Integer
114
115     option :actions,
116            :description => 'Actions to be generated for an Agent Plugin',
117            :arguments   => ['--actions [ACTIONS]'],
118            :type        => Array
119
120     option :outputs,
121            :description => 'Outputs to be generated for an Data Plugin',
122            :arguments   => ['--outputs [OUTPUTS]'],
123            :type        => Array
124
125     option :keep_artifacts,
126            :description => "Don't remove artifacts after building packages",
127            :arguments   => ['--keep-artifacts'],
128            :type        => :boolean
129
130     option :module_template,
131            :description => "Path to the template used by the modulepackager",
132            :arguments   => ['--module-template PATH'],
133            :type        => String
134
135     # Handle alternative format that optparser can't parse.
136     def post_option_parser(configuration)
137       if ARGV.length >= 1
138         configuration[:action] = ARGV.delete_at(0)
139
140         configuration[:target] = ARGV.delete_at(0) || "."
141
142         if configuration[:action] == "generate"
143           unless ARGV[0] && ARGV[0].match(/(actions|outputs)=(.+)/i)
144             unless configuration[:pluginname]
145               configuration[:pluginname] = ARGV.delete_at(0)
146             else
147               ARGV.delete_at(0)
148             end
149           end
150
151           ARGV.each do |argument|
152             if argument.match(/(actions|outputs)=(.+)/i)
153               configuration[$1.downcase.to_sym]= $2.split(",")
154             else
155               raise "Could not parse --arg '#{argument}'"
156             end
157           end
158         end
159       end
160     end
161
162     # Display info about plugin
163     def info_command
164       plugin = prepare_plugin
165       packager = PluginPackager["#{configuration[:format].capitalize}Packager"]
166       packager.new(plugin).package_information
167     end
168
169     # Generate a plugin skeleton
170     def generate_command
171       raise "undefined plugin type. cannot generate plugin. valid types are 'agent' and 'data'" if configuration["target"] == '.'
172
173       unless configuration[:pluginname]
174         puts "No plugin name specified. Using 'new_plugin'"
175         configuration[:pluginname] = "new_plugin"
176       end
177
178       load_plugin_config_values
179
180       case configuration[:target].downcase
181       when 'agent'
182         Generators::AgentGenerator.new(configuration[:pluginname], configuration[:actions], configuration[:pluginname],
183                                        configuration[:description], configuration[:author], configuration[:license],
184                                        configuration[:version], configuration[:url], configuration[:timeout])
185       when 'data'
186         raise "data plugin must have at least one output" unless configuration[:outputs]
187         Generators::DataGenerator.new(configuration[:pluginname], configuration[:outputs], configuration[:pluginname],
188                                        configuration[:description], configuration[:author], configuration[:license],
189                                        configuration[:version], configuration[:url], configuration[:timeout])
190       else
191         raise "invalid plugin type. cannot generate plugin '#{configuration[:target]}'"
192       end
193     end
194
195     # Package plugin
196     def package_command
197       if configuration[:sign] && Config.instance.pluginconf.include?("debian_packager.keyname")
198         configuration[:sign] = Config.instance.pluginconf["debian_packager.keyname"]
199         configuration[:sign] = "\"#{configuration[:sign]}\"" unless configuration[:sign].match(/\".*\"/)
200       end
201
202       plugin = prepare_plugin
203       (configuration[:pluginpath] = configuration[:pluginpath] + "/") if (configuration[:pluginpath] && !configuration[:pluginpath].match(/^.*\/$/))
204       packager = PluginPackager["#{configuration[:format].capitalize}Packager"]
205       packager.new(plugin, configuration[:pluginpath], configuration[:sign],
206                    options[:verbose], configuration[:keep_artifacts],
207                    configuration[:module_template]).create_packages
208     end
209
210     # Agents are just called 'agent' but newer plugin types are
211     # called plugin_plugintype for example facter_facts etc so
212     # this will first try the old way then the new way.
213     def load_plugin_ddl(plugin, type)
214       [plugin, "#{plugin}_#{type}"].each do |p|
215         ddl = DDL.new(p, type, false)
216         if ddl.findddlfile(p, type)
217           ddl.loadddlfile
218           return ddl
219         end
220       end
221
222       return nil
223     end
224
225     # Show application list and plugin help
226     def doc_command
227       known_plugin_types = [["Agents", :agent], ["Aggregate", :aggregate], ["Data Queries", :data], ["Discovery Methods", :discovery], ["Validator Plugins", :validator]]
228
229       if configuration.include?(:target) && configuration[:target] != "."
230         if configuration[:target] =~ /^(.+?)\/(.+)$/
231           ddl = load_plugin_ddl($2.to_sym, $1)
232         else
233           found_plugin_type = nil
234
235           known_plugin_types.each do |plugin_type|
236             PluginManager.find(plugin_type[1], "ddl").each do |ddl|
237               pluginname = ddl.gsub(/_#{plugin_type[1]}$/, "")
238               if pluginname == configuration[:target]
239                 abort "Duplicate plugin name found, please specify a full path like agent/rpcutil" if found_plugin_type
240                 found_plugin_type = plugin_type[1]
241               end
242             end
243           end
244
245           abort "Could not find a plugin named '%s' in any supported plugin type" % configuration[:target] unless found_plugin_type
246           ddl = load_plugin_ddl(configuration[:target], found_plugin_type)
247         end
248
249         if ddl
250           puts ddl.help(configuration[:rpctemplate])
251         else
252           abort "Could not find a '%s' plugin named '%s'" % configuration[:target].split('/')
253         end
254
255       else
256         puts "Please specify a plugin. Available plugins are:"
257         puts
258
259         load_errors = []
260
261         known_plugin_types.each do |plugin_type|
262           puts "%s:" % plugin_type[0]
263
264           PluginManager.find(plugin_type[1], "ddl").each do |ddl|
265             begin
266               help = DDL.new(ddl, plugin_type[1])
267               pluginname = ddl.gsub(/_#{plugin_type[1]}$/, "")
268               puts "  %-25s %s" % [pluginname, help.meta[:description]]
269             rescue => e
270               load_errors << [plugin_type[1], ddl, e]
271             end
272           end
273
274           puts
275         end
276
277         unless load_errors.empty?
278           puts "Plugin Load Errors:"
279
280           load_errors.each do |e|
281             puts "  %-25s %s" % ["#{e[0]}/#{e[1]}", Util.colorize(:yellow, e[2])]
282           end
283         end
284       end
285     end
286
287     # Creates the correct package plugin object.
288     def prepare_plugin
289       plugintype = set_plugin_type unless configuration[:plugintype]
290       configuration[:format] = "ospackage" unless configuration[:format]
291       PluginPackager.load_packagers
292       plugin_class = PluginPackager[configuration[:plugintype]]
293
294       if configuration[:dependency] && configuration[:dependency].size == 1
295         configuration[:dependency] = configuration[:dependency][0].split(" ")
296       elsif configuration[:dependency]
297         configuration[:dependency].map!{|dep| {:name => dep, :version => nil}}
298       end
299
300       mcdependency = {:mcname => configuration[:mcname], :mcversion => configuration[:mcversion]}
301
302       #Deprecation warning for --iteration
303       if configuration[:iteration]
304         puts 'Warning. The --iteration flag has been deprecated. Please use --revision instead.'
305         configuration[:revision] = configuration[:iteration] unless configuration[:revision]
306       end
307
308       plugin_class.new(configuration, mcdependency, plugintype)
309     end
310
311     def directory_for_type(type)
312       File.directory?(File.join(configuration[:target], type))
313     end
314
315     # Identify plugin type if not provided.
316     def set_plugin_type
317       if directory_for_type("agent") || directory_for_type("application")
318         configuration[:plugintype] = "AgentDefinition"
319         return "Agent"
320       elsif directory_for_type(plugintype = identify_plugin)
321         configuration[:plugintype] = "StandardDefinition"
322         return plugintype
323       else
324         raise RuntimeError, "target directory is not a valid mcollective plugin"
325       end
326     end
327
328     # If plugintype is StandardDefinition, identify which of the special
329     # plugin types we are dealing with based on directory structure.
330     # To keep it simple we limit it to one type per target directory.
331     def identify_plugin
332       plugintype = Dir.glob(File.join(configuration[:target], "*")).select do |file|
333         File.directory?(file) && file.match(/(connector|facts|registration|security|audit|pluginpackager|data|discovery|validator)/)
334       end
335
336       raise RuntimeError, "more than one plugin type detected in directory" if plugintype.size > 1
337       raise RuntimeError, "no plugins detected in directory" if plugintype.size < 1
338
339       File.basename(plugintype[0])
340     end
341
342     # Load preset metadata values from config if they are present
343     # This makes it possible to override metadata values in a local
344     # client config file.
345     #
346     # Example : plugin.metadata.license = Apache 2
347     def load_plugin_config_values
348       config = Config.instance
349       [:pluginname, :description, :author, :license, :version, :url, :timeout].each do |confoption|
350         configuration[confoption] = config.pluginconf["metadata.#{confoption}"] unless configuration[confoption]
351       end
352     end
353
354     def main
355         abort "No action specified, please run 'mco help plugin' for help" unless configuration.include?(:action)
356
357         cmd = "#{configuration[:action]}_command"
358
359         if respond_to? cmd
360           send cmd
361         else
362           abort "Invalid action #{configuration[:action]}, please run 'mco help plugin' for help."
363         end
364     end
365   end
366 end