Update version according to OSCI-856
[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            :dsecription => "Don't remove artifacts after building packages",
127            :arguments   => ['--keep-artifacts'],
128            :type        => :boolean
129
130     # Handle alternative format that optparser can't parse.
131     def post_option_parser(configuration)
132       if ARGV.length >= 1
133         configuration[:action] = ARGV.delete_at(0)
134
135         configuration[:target] = ARGV.delete_at(0) || "."
136
137         if configuration[:action] == "generate"
138           unless ARGV[0] && ARGV[0].match(/(actions|outputs)=(.+)/i)
139             unless configuration[:pluginname]
140               configuration[:pluginname] = ARGV.delete_at(0)
141             else
142               ARGV.delete_at(0)
143             end
144           end
145
146           ARGV.each do |argument|
147             if argument.match(/(actions|outputs)=(.+)/i)
148               configuration[$1.downcase.to_sym]= $2.split(",")
149             else
150               raise "Could not parse --arg '#{argument}'"
151             end
152           end
153         end
154       end
155     end
156
157     # Display info about plugin
158     def info_command
159       plugin = prepare_plugin
160       packager = PluginPackager["#{configuration[:format].capitalize}Packager"]
161       packager.new(plugin).package_information
162     end
163
164     # Generate a plugin skeleton
165     def generate_command
166       raise "undefined plugin type. cannot generate plugin. valid types are 'agent' and 'data'" if configuration["target"] == '.'
167
168       unless configuration[:pluginname]
169         puts "No plugin name specified. Using 'new_plugin'"
170         configuration[:pluginname] = "new_plugin"
171       end
172
173       load_plugin_config_values
174
175       case configuration[:target].downcase
176       when 'agent'
177         Generators::AgentGenerator.new(configuration[:pluginname], configuration[:actions], configuration[:pluginname],
178                                        configuration[:description], configuration[:author], configuration[:license],
179                                        configuration[:version], configuration[:url], configuration[:timeout])
180       when 'data'
181         raise "data plugin must have at least one output" unless configuration[:outputs]
182         Generators::DataGenerator.new(configuration[:pluginname], configuration[:outputs], configuration[:pluginname],
183                                        configuration[:description], configuration[:author], configuration[:license],
184                                        configuration[:version], configuration[:url], configuration[:timeout])
185       else
186         raise "invalid plugin type. cannot generate plugin '#{configuration[:target]}'"
187       end
188     end
189
190     # Package plugin
191     def package_command
192       if configuration[:sign] && Config.instance.pluginconf.include?("debian_packager.keyname")
193         configuration[:sign] = Config.instance.pluginconf["debian_packager.keyname"]
194         configuration[:sign] = "\"#{configuration[:sign]}\"" unless configuration[:sign].match(/\".*\"/)
195       end
196
197       plugin = prepare_plugin
198       (configuration[:pluginpath] = configuration[:pluginpath] + "/") if (configuration[:pluginpath] && !configuration[:pluginpath].match(/^.*\/$/))
199       packager = PluginPackager["#{configuration[:format].capitalize}Packager"]
200       packager.new(plugin, configuration[:pluginpath], configuration[:sign], options[:verbose], configuration[:keep_artifacts]).create_packages
201     end
202
203     # Agents are just called 'agent' but newer plugin types are
204     # called plugin_plugintype for example facter_facts etc so
205     # this will first try the old way then the new way.
206     def load_plugin_ddl(plugin, type)
207       [plugin, "#{plugin}_#{type}"].each do |p|
208         ddl = DDL.new(p, type, false)
209         if ddl.findddlfile(p, type)
210           ddl.loadddlfile
211           return ddl
212         end
213       end
214
215       return nil
216     end
217
218     # Show application list and plugin help
219     def doc_command
220       known_plugin_types = [["Agents", :agent], ["Aggregate", :aggregate], ["Data Queries", :data], ["Discovery Methods", :discovery], ["Validator Plugins", :validator]]
221
222       if configuration.include?(:target) && configuration[:target] != "."
223         if configuration[:target] =~ /^(.+?)\/(.+)$/
224           ddl = load_plugin_ddl($2.to_sym, $1)
225         else
226           found_plugin_type = nil
227
228           known_plugin_types.each do |plugin_type|
229             PluginManager.find(plugin_type[1], "ddl").each do |ddl|
230               pluginname = ddl.gsub(/_#{plugin_type[1]}$/, "")
231               if pluginname == configuration[:target]
232                 abort "Duplicate plugin name found, please specify a full path like agent/rpcutil" if found_plugin_type
233                 found_plugin_type = plugin_type[1]
234               end
235             end
236           end
237
238           abort "Could not find a plugin named '%s' in any supported plugin type" % configuration[:target] unless found_plugin_type
239           ddl = load_plugin_ddl(configuration[:target], found_plugin_type)
240         end
241
242         if ddl
243           puts ddl.help(configuration[:rpctemplate])
244         else
245           abort "Could not find a '%s' plugin named '%s'" % configuration[:target].split('/')
246         end
247
248       else
249         puts "Please specify a plugin. Available plugins are:"
250         puts
251
252         load_errors = []
253
254         known_plugin_types.each do |plugin_type|
255           puts "%s:" % plugin_type[0]
256
257           PluginManager.find(plugin_type[1], "ddl").each do |ddl|
258             begin
259               help = DDL.new(ddl, plugin_type[1])
260               pluginname = ddl.gsub(/_#{plugin_type[1]}$/, "")
261               puts "  %-25s %s" % [pluginname, help.meta[:description]]
262             rescue => e
263               load_errors << [plugin_type[1], ddl, e]
264             end
265           end
266
267           puts
268         end
269
270         unless load_errors.empty?
271           puts "Plugin Load Errors:"
272
273           load_errors.each do |e|
274             puts "  %-25s %s" % ["#{e[0]}/#{e[1]}", Util.colorize(:yellow, e[2])]
275           end
276         end
277       end
278     end
279
280     # Creates the correct package plugin object.
281     def prepare_plugin
282       plugintype = set_plugin_type unless configuration[:plugintype]
283       configuration[:format] = "ospackage" unless configuration[:format]
284       PluginPackager.load_packagers
285       plugin_class = PluginPackager[configuration[:plugintype]]
286
287       if configuration[:dependency] && configuration[:dependency].size == 1
288         configuration[:dependency] = configuration[:dependency][0].split(" ")
289       elsif configuration[:dependency]
290         configuration[:dependency].map!{|dep| {:name => dep, :version => nil}}
291       end
292
293       mcdependency = {:mcname => configuration[:mcname], :mcversion => configuration[:mcversion]}
294
295       #Deprecation warning for --iteration
296       if configuration[:iteration]
297         puts 'Warning. The --iteration flag has been deprecated. Please use --revision instead.'
298         configuration[:revision] = configuration[:iteration] unless configuration[:revision]
299       end
300
301       plugin_class.new(configuration, mcdependency, plugintype)
302     end
303
304     def directory_for_type(type)
305       File.directory?(File.join(configuration[:target], type))
306     end
307
308     # Identify plugin type if not provided.
309     def set_plugin_type
310       if directory_for_type("agent") || directory_for_type("application")
311         configuration[:plugintype] = "AgentDefinition"
312         return "Agent"
313       elsif directory_for_type(plugintype = identify_plugin)
314         configuration[:plugintype] = "StandardDefinition"
315         return plugintype
316       else
317         raise RuntimeError, "target directory is not a valid mcollective plugin"
318       end
319     end
320
321     # If plugintype is StandardDefinition, identify which of the special
322     # plugin types we are dealing with based on directory structure.
323     # To keep it simple we limit it to one type per target directory.
324     def identify_plugin
325       plugintype = Dir.glob(File.join(configuration[:target], "*")).select do |file|
326         File.directory?(file) && file.match(/(connector|facts|registration|security|audit|pluginpackager|data|discovery|validator)/)
327       end
328
329       raise RuntimeError, "more than one plugin type detected in directory" if plugintype.size > 1
330       raise RuntimeError, "no plugins detected in directory" if plugintype.size < 1
331
332       File.basename(plugintype[0])
333     end
334
335     # Load preset metadata values from config if they are present
336     # This makes it possible to override metadata values in a local
337     # client config file.
338     #
339     # Example : plugin.metadata.license = Apache 2
340     def load_plugin_config_values
341       config = Config.instance
342       [:pluginname, :description, :author, :license, :version, :url, :timeout].each do |confoption|
343         configuration[confoption] = config.pluginconf["metadata.#{confoption}"] unless configuration[confoption]
344       end
345     end
346
347     def main
348         abort "No action specified, please run 'mco help plugin' for help" unless configuration.include?(:action)
349
350         cmd = "#{configuration[:action]}_command"
351
352         if respond_to? cmd
353           send cmd
354         else
355           abort "Invalid action #{configuration[:action]}, please run 'mco help plugin' for help."
356         end
357     end
358   end
359 end