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