diff --git a/lib/puppet.rb b/lib/puppet.rb index 978d63a55..7852785b6 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,131 +1,133 @@ # Try to load rubygems. Hey rubygems, I hate you. begin require 'rubygems' rescue LoadError end # see the bottom of the file for further inclusions require 'singleton' require 'facter' require 'puppet/error' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/util/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' require 'puppet/util/run_mode' #------------------------------------------------------------ # the top-level module # # all this really does is dictate how the whole system behaves, through # preferences for things like debugging # # it's also a place to find top-level commands like 'debug' module Puppet PUPPETVERSION = '2.7.12' def Puppet.version PUPPETVERSION end class << self include Puppet::Util attr_reader :features attr_writer :name end # the hash that determines how our system behaves @@settings = Puppet::Util::Settings.new # The services running in this process. @services ||= [] require 'puppet/util/logging' extend Puppet::Util::Logging # The feature collection @features = Puppet::Util::Feature.new('puppet/feature') # Load the base features. require 'puppet/feature/base' # Store a new default value. def self.setdefaults(section, hash) @@settings.setdefaults(section, hash) end # configuration parameter access and stuff def self.[](param) if param == :debug return Puppet::Util::Log.level == :debug else return @@settings[param] end end # configuration parameter access and stuff def self.[]=(param,value) @@settings[param] = value end def self.clear @@settings.clear end def self.debug=(value) if value Puppet::Util::Log.level=(:debug) else Puppet::Util::Log.level=(:notice) end end def self.settings @@settings end def self.run_mode $puppet_application_mode || Puppet::Util::RunMode[:user] end def self.application_name $puppet_application_name ||= "apply" end # Load all of the configuration parameters. require 'puppet/defaults' def self.genmanifest if Puppet[:genmanifest] puts Puppet.settings.to_manifest exit(0) end end # Parse the config file for this process. def self.parse_config Puppet.settings.parse end # Create a new type. Just proxy to the Type class. The mirroring query # code was deprecated in 2008, but this is still in heavy use. I suppose # this can count as a soft deprecation for the next dev. --daniel 2011-04-12 def self.newtype(name, options = {}, &block) Puppet::Type.newtype(name, options, &block) end end -# TODO cprice: hmmmm.... +# This feels weird to me; I would really like for us to get to a state where there is never a "require" statement +# anywhere besides the very top of a file. That would not be possible at the moment without a great deal of +# effort, but I think we should strive for it and revisit this at some point. --cprice 2012-03-16 require 'puppet/type' require 'puppet/parser' require 'puppet/resource' require 'puppet/network' require 'puppet/ssl' require 'puppet/module' require 'puppet/util/storage' require 'puppet/status' require 'puppet/file_bucket/file' diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index f5ac84468..c289d7197 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -1,431 +1,432 @@ require 'optparse' require 'puppet/util/plugins' require 'puppet/error' # This class handles all the aspects of a Puppet application/executable # * setting up options # * setting up logs # * choosing what to run # * representing execution status # # === Usage # An application is a subclass of Puppet::Application. # # For legacy compatibility, # Puppet::Application[:example].run # is equivalent to # Puppet::Application::Example.new.run # # # class Puppet::Application::Example << Puppet::Application # # def preinit # # perform some pre initialization # @all = false # end # # # run_command is called to actually run the specified command # def run_command # send Puppet::Util::CommandLine.new.args.shift # end # # # option uses metaprogramming to create a method # # and also tells the option parser how to invoke that method # option("--arg ARGUMENT") do |v| # @args << v # end # # option("--debug", "-d") do |v| # @debug = v # end # # option("--all", "-a:) do |v| # @all = v # end # # def handle_unknown(opt,arg) # # last chance to manage an option # ... # # let's say to the framework we finally handle this option # true # end # # def read # # read action # end # # def write # # writeaction # end # # end # # === Preinit # The preinit block is the first code to be called in your application, before option parsing, # setup or command execution. # # === Options # Puppet::Application uses +OptionParser+ to manage the application options. # Options are defined with the +option+ method to which are passed various # arguments, including the long option, the short option, a description... # Refer to +OptionParser+ documentation for the exact format. # * If the option method is given a block, this one will be called whenever # the option is encountered in the command-line argument. # * If the option method has no block, a default functionnality will be used, that # stores the argument (or true/false if the option doesn't require an argument) in # the global (to the application) options array. # * If a given option was not defined by a the +option+ method, but it exists as a Puppet settings: # * if +unknown+ was used with a block, it will be called with the option name and argument # * if +unknown+ wasn't used, then the option/argument is handed to Puppet.settings.handlearg for # a default behavior # # --help is managed directly by the Puppet::Application class, but can be overriden. # # === Setup # Applications can use the setup block to perform any initialization. # The defaul +setup+ behaviour is to: read Puppet configuration and manage log level and destination # # === What and how to run # If the +dispatch+ block is defined it is called. This block should return the name of the registered command # to be run. # If it doesn't exist, it defaults to execute the +main+ command if defined. # # === Execution state # The class attributes/methods of Puppet::Application serve as a global place to set and query the execution # status of the application: stopping, restarting, etc. The setting of the application status does not directly # aftect its running status; it's assumed that the various components within the application will consult these # settings appropriately and affect their own processing accordingly. Control operations (signal handlers and # the like) should set the status appropriately to indicate to the overall system that it's the process of # stopping or restarting (or just running as usual). # # So, if something in your application needs to stop the process, for some reason, you might consider: # # def stop_me! # # indicate that we're stopping # Puppet::Application.stop! # # ...do stuff... # end # # And, if you have some component that involves a long-running process, you might want to consider: # # def my_long_process(giant_list_to_munge) # giant_list_to_munge.collect do |member| # # bail if we're stopping # return if Puppet::Application.stop_requested? # process_member(member) # end # end module Puppet class Application require 'puppet/util' include Puppet::Util DOCPATTERN = ::File.expand_path(::File.dirname(__FILE__) + "/util/command_line/*" ) class << self include Puppet::Util attr_accessor :run_status def clear! self.run_status = nil end def stop! self.run_status = :stop_requested end def restart! self.run_status = :restart_requested end # Indicates that Puppet::Application.restart! has been invoked and components should # do what is necessary to facilitate a restart. def restart_requested? :restart_requested == run_status end # Indicates that Puppet::Application.stop! has been invoked and components should do what is necessary # for a clean stop. def stop_requested? :stop_requested == run_status end # Indicates that one of stop! or start! was invoked on Puppet::Application, and some kind of process # shutdown/short-circuit may be necessary. def interrupted? [:restart_requested, :stop_requested].include? run_status end # Indicates that Puppet::Application believes that it's in usual running run_mode (no stop/restart request # currently active). def clear? run_status.nil? end # Only executes the given block if the run status of Puppet::Application is clear (no restarts, stops, # etc. requested). # Upon block execution, checks the run status again; if a restart has been requested during the block's # execution, then controlled_run will send a new HUP signal to the current process. # Thus, long-running background processes can potentially finish their work before a restart. def controlled_run(&block) return unless clear? result = block.call Process.kill(:HUP, $PID) if restart_requested? result end # used to declare code that handle an option def option(*options, &block) long = options.find { |opt| opt =~ /^--/ }.gsub(/^--(?:\[no-\])?([^ =]+).*$/, '\1' ).gsub('-','_') fname = symbolize("handle_#{long}") if (block_given?) define_method(fname, &block) else define_method(fname) do |value| self.options["#{long}".to_sym] = value end end self.option_parser_commands << [options, fname] end def banner(banner = nil) @banner ||= banner end def option_parser_commands @option_parser_commands ||= ( superclass.respond_to?(:option_parser_commands) ? superclass.option_parser_commands.dup : [] ) @option_parser_commands end def find(name) klass = name.to_s.capitalize begin require ::File.join('puppet', 'application', name.to_s.downcase) rescue LoadError => e puts "Unable to find application '#{name}'. #{e}" Kernel::exit(1) end self.const_get(klass) end def [](name) find(name).new end # - # TODO cprice: look into changing this into two methods (getter/setter) - # --cprice 2012-03-06 + # I think that it would be nice to look into changing this into two methods (getter/setter); however, + # it sounds like this is a desirable feature of our ruby DSL. --cprice 2012-03-06 # # Sets or gets the run_mode name. Sets the run_mode name if a mode_name is # passed. Otherwise, gets the run_mode or a default run_mode # def run_mode( mode_name = nil) return @run_mode if @run_mode and not mode_name require 'puppet/util/run_mode' @run_mode = Puppet::Util::RunMode[ mode_name || :user ] end end attr_reader :options, :command_line # Every app responds to --version option("--version", "-V") do |arg| puts "#{Puppet.version}" exit end # Every app responds to --help option("--help", "-h") do |v| puts help exit end def app_defaults() { :name => name, - :run_mode => @run_mode.name, - :confdir => @run_mode.conf_dir, - :vardir => @run_mode.var_dir, - :rundir => @run_mode.run_dir, - :logdir => @run_mode.log_dir, + :run_mode => self.class.run_mode.name, + :confdir => self.class.run_mode.conf_dir, + :vardir => self.class.run_mode.var_dir, + :rundir => self.class.run_mode.run_dir, + :logdir => self.class.run_mode.log_dir, } end def initialize_app_defaults() Puppet.settings.initialize_app_defaults(app_defaults) end # override to execute code before running anything else def preinit end def initialize(command_line = nil) require 'puppet/util/command_line' @command_line = command_line || Puppet::Util::CommandLine.new - set_run_mode self.class.run_mode + # TODO cprice: cleanup + #set_run_mode self.class.run_mode @options = {} end - # WARNING: This is a totally scary, frightening, and nasty internal API. We - # strongly advise that you do not use this, and if you insist, we will - # politely allow you to keep both pieces of your broken code. + ## WARNING: This is a totally scary, frightening, and nasty internal API. We + ## strongly advise that you do not use this, and if you insist, we will + ## politely allow you to keep both pieces of your broken code. + ## + ## We plan to provide a supported, long-term API to deliver this in a way + ## that you can use. Please make sure that you let us know if you do require + ## this, and this message is still present in the code. --daniel 2011-02-03 + #def set_run_mode(mode) + # @run_mode = mode + # $puppet_application_mode = @run_mode + # $puppet_application_name = name # - # We plan to provide a supported, long-term API to deliver this in a way - # that you can use. Please make sure that you let us know if you do require - # this, and this message is still present in the code. --daniel 2011-02-03 - def set_run_mode(mode) - @run_mode = mode - $puppet_application_mode = @run_mode - $puppet_application_name = name - - ## XXXXXXXXXXX TODO cprice: this is crap. need to refactor this so that applications have a hook to initialize - ## the settings that are specific to them; need to decide whether to formally specify the list of such - ## settings, or just allow them to run willy-nilly. - #if (mode.name == :master) - # Puppet.settings.set_value(:facts_terminus, "yaml", :mutable_defaults) - #end - # - # - ### TODO cprice: get rid of this whole block, push it to some kind of application-specific hook or something - # - #if Puppet.respond_to? :settings - # # This is to reduce the amount of confusion in rspec - # # because it might have loaded defaults.rb before the globals were set - # # and thus have the wrong defaults for the current application - # Puppet.settings.set_value(:confdir, Puppet.run_mode.conf_dir, :mutable_defaults) - # Puppet.settings.set_value(:vardir, Puppet.run_mode.var_dir, :mutable_defaults) - # Puppet.settings.set_value(:name, Puppet.application_name.to_s, :mutable_defaults) - # #Puppet.settings.set_value(:logdir, Puppet.run_mode.logopts, :mutable_defaults) - # Puppet.settings.set_value(:rundir, Puppet.run_mode.run_dir, :mutable_defaults) - # Puppet.settings.set_value(:run_mode, Puppet.run_mode.name.to_s, :mutable_defaults) - #end - end + # ## XXXXXXXXXXX TODO cprice: this is crap. need to refactor this so that applications have a hook to initialize + # ## the settings that are specific to them; need to decide whether to formally specify the list of such + # ## settings, or just allow them to run willy-nilly. + # #if (mode.name == :master) + # # Puppet.settings.set_value(:facts_terminus, "yaml", :mutable_defaults) + # #end + # # + # # + # ### TODO cprice: get rid of this whole block, push it to some kind of application-specific hook or something + # # + # #if Puppet.respond_to? :settings + # # # This is to reduce the amount of confusion in rspec + # # # because it might have loaded defaults.rb before the globals were set + # # # and thus have the wrong defaults for the current application + # # Puppet.settings.set_value(:confdir, Puppet.run_mode.conf_dir, :mutable_defaults) + # # Puppet.settings.set_value(:vardir, Puppet.run_mode.var_dir, :mutable_defaults) + # # Puppet.settings.set_value(:name, Puppet.application_name.to_s, :mutable_defaults) + # # #Puppet.settings.set_value(:logdir, Puppet.run_mode.logopts, :mutable_defaults) + # # Puppet.settings.set_value(:rundir, Puppet.run_mode.run_dir, :mutable_defaults) + # # Puppet.settings.set_value(:run_mode, Puppet.run_mode.name.to_s, :mutable_defaults) + # #end + #end # This is the main application entry point def run # TODO cprice: the names of these lifecycle phases really suck... but I'm not sure we can do anything about them # because existing apps/faces use them... maybe make some deprecated aliases? exit_on_fail("get application-specific default settings") do plugin_hook('initialize_app_defaults') { initialize_app_defaults } end require 'puppet' require 'puppet/util/instrumentation' Puppet::Util::Instrumentation.init exit_on_fail("initialize") { plugin_hook('preinit') { preinit } } exit_on_fail("parse application options") { plugin_hook('parse_options') { parse_options } } exit_on_fail("prepare for execution") { plugin_hook('setup') { setup } } exit_on_fail("configure routes from #{Puppet[:route_file]}") { configure_indirector_routes } exit_on_fail("run") { plugin_hook('run_command') { run_command } } end def main raise NotImplementedError, "No valid command or main" end def run_command main end def setup setup_logs end def setup_logs if options[:debug] or options[:verbose] Puppet::Util::Log.newdestination(:console) if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end end Puppet::Util::Log.setup_default unless options[:setdest] end def configure_indirector_routes route_file = Puppet[:route_file] if ::File.exists?(route_file) routes = YAML.load_file(route_file) application_routes = routes[name.to_s] Puppet::Indirector.configure_routes(application_routes) if application_routes end end def parse_options # Create an option parser option_parser = OptionParser.new(self.class.banner) # TODO cprice: document this better. We need to include the globals so that the app # has an opportunity to override them. # Might be able to make this a little more efficient by sharing the Parser object # between the command line class and this one. # # Add all global options to it. Puppet.settings.optparse_addargs([]).each do |option| option_parser.on(*option) do |arg| handlearg(option[0], arg) end end # Add options that are local to this application, which were # created using the "option()" metaprogramming method. If there # are any conflicts, this application's options will be favored. self.class.option_parser_commands.each do |options, fname| option_parser.on(*options) do |value| # Call the method that "option()" created. self.send(fname, value) end end # Scan command line. We just hand any exceptions to our upper levels, # rather than printing help and exiting, so that we can meaningfully # respond with context-sensitive help if we want to. --daniel 2011-04-12 option_parser.parse!(self.command_line.args) end def handlearg(opt, val) opt, val = Puppet::Util::CommandLine.clean_opt(opt, val) send(:handle_unknown, opt, val) if respond_to?(:handle_unknown) end # this is used for testing def self.exit(code) exit(code) end def name self.class.to_s.sub(/.*::/,"").downcase.to_sym end def help "No help available for puppet #{name}" end def plugin_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 private :plugin_hook end end diff --git a/lib/puppet/application/certificate.rb b/lib/puppet/application/certificate.rb index de5b2c499..743637098 100644 --- a/lib/puppet/application/certificate.rb +++ b/lib/puppet/application/certificate.rb @@ -1,13 +1,14 @@ require 'puppet/application/indirection_base' class Puppet::Application::Certificate < Puppet::Application::IndirectionBase - def setup - location = Puppet::SSL::Host.ca_location - if location == :local && !Puppet::SSL::CertificateAuthority.ca? - self.class.run_mode("master") - self.set_run_mode self.class.run_mode - end - - super - end + # TODO cprice: trying to get rid of set_run_mode, need clean this up. + #def setup + # location = Puppet::SSL::Host.ca_location + # if location == :local && !Puppet::SSL::CertificateAuthority.ca? + # self.class.run_mode("master") + # self.set_run_mode self.class.run_mode + # end + # + # super + #end end diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb index bb61a71ca..3f7f6f9e9 100644 --- a/lib/puppet/application/face_base.rb +++ b/lib/puppet/application/face_base.rb @@ -1,262 +1,263 @@ require 'puppet/application' require 'puppet/face' require 'optparse' require 'pp' class Puppet::Application::FaceBase < Puppet::Application run_mode :agent option("--debug", "-d") do |arg| Puppet::Util::Log.level = :debug end option("--verbose", "-v") do Puppet::Util::Log.level = :info end option("--render-as FORMAT") do |format| self.render_as = format.to_sym end option("--mode RUNMODE", "-r") do |arg| raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) self.class.run_mode(arg.to_sym) - set_run_mode self.class.run_mode + # TODO cprice: testing this out--clean up. + #set_run_mode self.class.run_mode end attr_accessor :face, :action, :type, :arguments, :render_as def render_as=(format) if format == :json then @render_as = Puppet::Network::FormatHandler.format(:pson) else @render_as = Puppet::Network::FormatHandler.format(format) end @render_as or raise ArgumentError, "I don't know how to render '#{format}'" end def render(result, args_and_options) hook = action.when_rendering(render_as.name) if hook # when defining when_rendering on your action you can optionally # include arguments and options if hook.arity > 1 result = hook.call(result, *args_and_options) else result = hook.call(result) end end render_as.render(result) end def preinit super Signal.trap(:INT) do $stderr.puts "Cancelling Face" exit(0) end end def parse_options # We need to parse enough of the command line out early, to identify what # the action is, so that we can obtain the full set of options to parse. # REVISIT: These should be configurable versions, through a global # '--version' option, but we don't implement that yet... --daniel 2011-03-29 @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym @face = Puppet::Face[@type, :current] # Now, walk the command line and identify the action. We skip over # arguments based on introspecting the action and all, and find the first # non-option word to use as the action. action_name = nil index = -1 until action_name or (index += 1) >= command_line.args.length do item = command_line.args[index] if item =~ /^-/ then option = @face.options.find do |name| item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ end if option then option = @face.get_option(option) # If we have an inline argument, just carry on. We don't need to # care about optional vs mandatory in that case because we do a real # parse later, and that will totally take care of raising the error # when we get there. --daniel 2011-04-04 if option.takes_argument? and !item.index('=') then index += 1 unless (option.optional_argument? and command_line.args[index + 1] =~ /^-/) end elsif option = find_global_settings_argument(item) then unless Puppet.settings.boolean? option.name then # As far as I can tell, we treat non-bool options as always having # a mandatory argument. --daniel 2011-04-05 index += 1 # ...so skip the argument. end elsif option = find_application_argument(item) then index += 1 if (option[:argument] and not option[:optional]) else raise OptionParser::InvalidOption.new(item.sub(/=.*$/, '')) end else # Stash away the requested action name for later, and try to fetch the # action object it represents; if this is an invalid action name that # will be nil, and handled later. action_name = item.to_sym @action = Puppet::Face.find_action(@face.name, action_name) @face = @action.face if @action end end if @action.nil? if @action = @face.get_default_action() then @is_default_action = true else # REVISIT: ...and this horror thanks to our log setup, which doesn't # initialize destinations until the setup method, which we will never # reach. We could also just print here, but that is actually a little # uglier and nastier in the long term, in which we should do log setup # earlier if at all possible. --daniel 2011-05-31 Puppet::Util::Log.newdestination(:console) face = @face.name action = action_name.nil? ? 'default' : "'#{action_name}'" msg = "'#{face}' has no #{action} action. See `puppet help #{face}`." Puppet.err(msg) exit false end end # Now we can interact with the default option code to build behaviour # around the full set of options we now know we support. @action.options.each do |option| option = @action.get_option(option) # make it the object. self.class.option(*option.optparse) # ...and make the CLI parse it. end # ...and invoke our parent to parse all the command line options. super end def find_global_settings_argument(item) Puppet.settings.each do |name, object| object.optparse_args.each do |arg| next unless arg =~ /^-/ # sadly, we have to emulate some of optparse here... pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ pattern.match item and return object end end return nil # nothing found. end def find_application_argument(item) self.class.option_parser_commands.each do |options, function| options.each do |option| next unless option =~ /^-/ pattern = /^#{option.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ next unless pattern.match(item) return { :argument => option =~ /[ =]/, :optional => option =~ /[ =]\[/ } end end return nil # not found end def setup Puppet::Util::Log.newdestination :console @arguments = command_line.args # Note: because of our definition of where the action is set, we end up # with it *always* being the first word of the remaining set of command # line arguments. So, strip that off when we construct the arguments to # pass down to the face action. --daniel 2011-04-04 # Of course, now that we have default actions, we should leave the # "action" name on if we didn't actually consume it when we found our # action. @arguments.delete_at(0) unless @is_default_action # We copy all of the app options to the end of the call; This allows each # action to read in the options. This replaces the older model where we # would invoke the action with options set as global state in the # interface object. --daniel 2011-03-28 @arguments << options # If we don't have a rendering format, set one early. self.render_as ||= (@action.render_as || :console) end def main status = false # Call the method associated with the provided action (e.g., 'find'). unless @action puts Puppet::Face[:help, :current].help(@face.name) raise "#{face} does not respond to action #{arguments.first}" end # We need to do arity checking here because this is generic code # calling generic methods – that have argument defaulting. We need to # make sure we don't accidentally pass the options as the first # argument to a method that takes one argument. eg: # # puppet facts find # => options => {} # @arguments => [{}] # => @face.send :bar, {} # # def face.bar(argument, options = {}) # => bar({}, {}) # oops! we thought the options were the # # positional argument!! # # We could also fix this by making it mandatory to pass the options on # every call, but that would make the Ruby API much more annoying to # work with; having the defaulting is a much nicer convention to have. # # We could also pass the arguments implicitly, by having a magic # 'options' method that was visible in the scope of the action, which # returned the right stuff. # # That sounds attractive, but adds complications to all sorts of # things, especially when you think about how to pass options when you # are writing Ruby code that calls multiple faces. Especially if # faces are involved in that. ;) # # --daniel 2011-04-27 if (arity = @action.positional_arg_count) > 0 unless (count = arguments.length) == arity then s = arity == 2 ? '' : 's' raise ArgumentError, "puppet #{@face.name} #{@action.name} takes #{arity-1} argument#{s}, but you gave #{count-1}" end end result = @face.send(@action.name, *arguments) puts render(result, arguments) unless result.nil? status = true # We need an easy way for the action to set a specific exit code, so we # rescue SystemExit here; This allows each action to set the desired exit # code by simply calling Kernel::exit. eg: # # exit(2) # # --kelsey 2012-02-14 rescue SystemExit => detail status = detail.status rescue Exception => detail Puppet.log_exception(detail) Puppet.err "Try 'puppet help #{@face.name} #{@action.name}' for usage" ensure exit status end end