summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/application.rb16
-rw-r--r--lib/puppet/util/command_line.rb7
-rw-r--r--lib/puppet/util/plugins.rb82
3 files changed, 100 insertions, 5 deletions
diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb
index c3d7355f6..5e69baef4 100644
--- a/lib/puppet/application.rb
+++ b/lib/puppet/application.rb
@@ -1,4 +1,5 @@
require 'optparse'
+require 'puppet/util/plugins'
# This class handles all the aspects of a Puppet application/executable
# * setting up options
@@ -297,11 +298,11 @@ class Application
# This is the main application entry point
def run
- exit_on_fail("initialize") { preinit }
- exit_on_fail("parse options") { parse_options }
+ exit_on_fail("initialize") { hook('preinit') { preinit } }
+ exit_on_fail("parse options") { hook('parse_options') { parse_options } }
exit_on_fail("parse configuration file") { Puppet.settings.parse } if should_parse_config?
- exit_on_fail("prepare for execution") { setup }
- exit_on_fail("run") { run_command }
+ exit_on_fail("prepare for execution") { hook('setup') { setup } }
+ exit_on_fail("run") { hook('run_command') { run_command } }
end
def main
@@ -413,5 +414,12 @@ class Application
$stderr.puts "Could not #{message}: #{detail}"
exit(code)
end
+
+ def hook(step,&block)
+ Puppet::Plugins.send("before_application_#{step}",:application_object => self)
+ x = yield
+ Puppet::Plugins.send("after_application_#{step}",:application_object => self, :return_value => x)
+ x
+ end
end
end
diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb
index 7f74d266a..fb56b2898 100644
--- a/lib/puppet/util/command_line.rb
+++ b/lib/puppet/util/command_line.rb
@@ -1,3 +1,5 @@
+require "puppet/util/plugins"
+
module Puppet
module Util
class CommandLine
@@ -23,6 +25,7 @@ module Puppet
@stdin = stdin
@subcommand_name, @args = subcommand_and_args( @zero, @argv, @stdin )
+ Puppet::Plugins.on_commandline_initialization(:command_line_object => self)
end
attr :subcommand_name
@@ -56,7 +59,9 @@ module Puppet
puts usage_message
elsif available_subcommands.include?(subcommand_name) #subcommand
require_application subcommand_name
- Puppet::Application.find(subcommand_name).new(self).run
+ app = Puppet::Application.find(subcommand_name).new(self)
+ Puppet::Plugins.on_application_initialization(:appliation_object => self)
+ app.run
else
abort "Error: Unknown command #{subcommand_name}.\n#{usage_message}" unless execute_external_subcommand
end
diff --git a/lib/puppet/util/plugins.rb b/lib/puppet/util/plugins.rb
new file mode 100644
index 000000000..105fdcd75
--- /dev/null
+++ b/lib/puppet/util/plugins.rb
@@ -0,0 +1,82 @@
+#
+# This system manages an extensible set of metadata about plugins which it
+# collects by searching for files named "plugin_init.rb" in a series of
+# directories. Initially, these are simply the $LOAD_PATH.
+#
+# The contents of each file found is executed in the context of a Puppet::Plugins
+# object (and thus scoped). An example file might contain:
+#
+# -------------------------------------------------------
+# @name = "Greet the CA"
+#
+# @description = %q{
+# This plugin causes a friendly greeting to print out on a master
+# that is operating as the CA, after it has been set up but before
+# it does anything.
+# }
+#
+# def after_application_setup(options)
+# if options[:application_object].is_a?(Puppet::Application::Master) && Puppet::SSL::CertificateAuthority.ca?
+# puts "Hey, this is the CA!"
+# end
+# end
+# -------------------------------------------------------
+#
+# Note that the instance variables are local to this Puppet::Plugin (and so may be used
+# for maintaining state, etc.) but the plugin system does not provide any thread safety
+# assurances, so they may not be adequate for some complex use cases.
+#
+#
+module Puppet
+ class Plugins
+ Paths = [] # Where we might find plugin initialization code
+ Loaded = [] # Code we have found (one-to-one with paths once searched)
+ #
+ # Return all the Puppet::Plugins we know about, searching any new paths
+ #
+ def self.known
+ Paths[Loaded.length...Paths.length].each { |path|
+ file = File.join(path,'plugin_init.rb')
+ Loaded << (File.exist?(file) && new(file))
+ }
+ Loaded.compact
+ end
+ #
+ # Add more places to look for plugins without adding duplicates or changing the
+ # order of ones we've already found.
+ #
+ def self.look_in(*paths)
+ Paths.replace Paths | paths.flatten.collect { |path| File.expand_path(path) }
+ end
+ #
+ # Initially just look in $LOAD_PATH
+ #
+ look_in $LOAD_PATH
+ #
+ # Calling methods (hooks) on the class calls the method of the same name on
+ # all plugins that use that hook, passing in the same arguments to each
+ # and returning an array containing the results returned by each plugin as
+ # an array of [plugin_name,result] pairs.
+ #
+ def self.method_missing(hook,*args,&block)
+ known.
+ select { |p| p.respond_to? hook }.
+ collect { |p| [p.name,p.send(hook,*args,&block)] }
+ end
+ #
+ #
+ #
+ attr_reader :path,:name
+ def initialize(path)
+ @name = @path = path
+ class << self
+ private
+ def define_hooks
+ eval File.read(path),nil,path,1
+ end
+ end
+ define_hooks
+ end
+ end
+end
+