diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index f72714b12..c0bfe8f7b 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -1,371 +1,376 @@ require 'puppet' require 'optparse' # 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.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 class Puppet::Application include Puppet::Util BINDIRS = %w{sbin bin}.map{|dir| File.expand_path(File.dirname(__FILE__)) + "/../../#{dir}/*"}.join(" ") 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 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, $$) if restart_requested? result end def should_parse_config @parse_config = true end def should_not_parse_config @parse_config = false end def should_parse_config? if ! defined? @parse_config @parse_config = true end return @parse_config 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 - @opt_parser_commands ||= [] - @opt_parser_commands << [options, fname] + self.option_parser_commands << [options, fname] end def banner(banner = nil) - @banner = banner unless banner.nil? + @banner ||= banner end - def new_option_parser( target ) - @banner ||= nil - - opt_parser = OptionParser.new(@banner) - - @opt_parser_commands ||= [] - @opt_parser_commands.each do |options, fname| - opt_parser.on(*options) do |value| - target.send(fname, value) - end - end - opt_parser + 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) self.const_get(name.to_s.capitalize) end def [](name) find(name).new end end - attr_reader :options, :opt_parser, :command_line + attr_reader :options, :command_line # Every app responds to --version option("--version", "-V") do |arg| puts "%s" % Puppet.version exit end # Every app responds to --help option("--help", "-h") do |v| help end def should_parse_config? self.class.should_parse_config? end # override to execute code before running anything else def preinit end + def option_parser + return @option_parser if defined? @option_parser + + @option_parser = OptionParser.new(self.class.banner) + + self.class.option_parser_commands.each do |options, fname| + @option_parser.on(*options) do |value| + self.send(fname, value) + end + end + @option_parser.default_argv = self.command_line.args + @option_parser + end + def initialize(command_line = nil) @command_line = command_line || Puppet::Util::CommandLine.new - @opt_parser = self.class.new_option_parser( self ) @options = {} end # This is the main application entry point def run exit_on_fail("initialize") { preinit } exit_on_fail("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 } end def main raise NotImplementedError, "No valid command or main" end def run_command main end def setup # Handle the logging settings 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 unless options[:setdest] Puppet::Util::Log.newdestination(:syslog) end end def parse_options # get all puppet options optparse_opt = [] optparse_opt = Puppet.settings.optparse_addargs(optparse_opt) # convert them to OptionParser format optparse_opt.each do |option| - @opt_parser.on(*option) do |arg| + self.option_parser.on(*option) do |arg| handlearg(option[0], arg) end end # scan command line argument begin - @opt_parser.parse! + self.option_parser.parse! rescue OptionParser::ParseError => detail $stderr.puts detail $stderr.puts "Try '#{$0} --help'" exit(1) end end def handlearg(opt, arg) # rewrite --[no-]option to --no-option if that's what was given if opt =~ /\[no-\]/ and !arg opt = opt.gsub(/\[no-\]/,'no-') end # otherwise remove the [no-] prefix to not confuse everybody opt = opt.gsub(/\[no-\]/, '') unless respond_to?(:handle_unknown) and send(:handle_unknown, opt, arg) # Puppet.settings.handlearg doesn't handle direct true/false :-) if arg.is_a?(FalseClass) arg = "false" elsif arg.is_a?(TrueClass) arg = "true" end Puppet.settings.handlearg(opt, arg) end 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 if Puppet.features.usage? # RH:FIXME: My goodness, this is ugly. ::RDoc.const_set("PuppetSourceFile", name) def (::RDoc).caller docfile = `grep -l 'Puppet::Application\\[:#{::RDoc::PuppetSourceFile}\\]' #{BINDIRS}`.chomp super << "#{docfile}:0" end ::RDoc::usage && exit else puts "No help available unless you have RDoc::usage installed" exit end rescue Errno::ENOENT puts "No help available for puppet #{name}" exit end private def exit_on_fail(message, code = 1) begin yield rescue RuntimeError, NotImplementedError => detail puts detail.backtrace if Puppet[:trace] $stderr.puts "Could not %s: %s" % [message, detail] exit(code) end end end diff --git a/spec/unit/application.rb b/spec/unit/application.rb index c18aa1903..daaed0c9a 100755 --- a/spec/unit/application.rb +++ b/spec/unit/application.rb @@ -1,487 +1,512 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/application' require 'puppet' require 'getoptlong' describe Puppet::Application do before do @app = Class.new(Puppet::Application).new end it "should have a run entry-point" do @app.should respond_to(:run) end it "should have a read accessor to options" do @app.should respond_to(:options) end it "should include a default setup method" do @app.should respond_to(:setup) end it "should include a default preinit method" do @app.should respond_to(:preinit) end it "should include a default run_command method" do @app.should respond_to(:run_command) end it "should invoke main as the default" do @app.expects( :main ) @app.run_command end describe 'when invoking clear!' do before :each do Puppet::Application.run_status = :stop_requested Puppet::Application.clear! end it 'should have nil run_status' do Puppet::Application.run_status.should be_nil end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return false for interrupted?' do Puppet::Application.interrupted?.should be_false end it 'should return true for clear?' do Puppet::Application.clear?.should be_true end end describe 'after invoking stop!' do before :each do Puppet::Application.run_status = nil Puppet::Application.stop! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :stop_requested' do Puppet::Application.run_status.should == :stop_requested end it 'should return true for stop_requested?' do Puppet::Application.stop_requested?.should be_true end it 'should return false for restart_requested?' do Puppet::Application.restart_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when invoking restart!' do before :each do Puppet::Application.run_status = nil Puppet::Application.restart! end after :each do Puppet::Application.run_status = nil end it 'should have run_status of :restart_requested' do Puppet::Application.run_status.should == :restart_requested end it 'should return true for restart_requested?' do Puppet::Application.restart_requested?.should be_true end it 'should return false for stop_requested?' do Puppet::Application.stop_requested?.should be_false end it 'should return true for interrupted?' do Puppet::Application.interrupted?.should be_true end it 'should return false for clear?' do Puppet::Application.clear?.should be_false end end describe 'when performing a controlled_run' do it 'should not execute block if not :clear?' do Puppet::Application.run_status = :stop_requested target = mock 'target' target.expects(:some_method).never Puppet::Application.controlled_run do target.some_method end end it 'should execute block if :clear?' do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once Puppet::Application.controlled_run do target.some_method end end it 'should signal process with HUP after block if restart requested during block execution' do Puppet::Application.run_status = nil target = mock 'target' target.expects(:some_method).once old_handler = trap('HUP') { target.some_method } begin Puppet::Application.controlled_run do Puppet::Application.run_status = :restart_requested end ensure trap('HUP', old_handler) end end after :each do Puppet::Application.run_status = nil end end describe "when parsing command-line options" do before :each do - @argv_bak = ARGV.dup - ARGV.clear + @app.command_line.stubs(:args).returns([]) Puppet.settings.stubs(:optparse_addargs).returns([]) end - after :each do - ARGV.clear - ARGV << @argv_bak + it "should create a new option parser when needed" do + option_parser = stub "option parser" + option_parser.stubs(:on) + option_parser.stubs(:default_argv=) + OptionParser.expects(:new).returns(option_parser).once + @app.option_parser.should == option_parser + @app.option_parser.should == option_parser + end + + it "should pass the banner to the option parser" do + option_parser = stub "option parser" + option_parser.stubs(:on) + option_parser.stubs(:default_argv=) + @app.class.instance_eval do + banner "banner" + end + + OptionParser.expects(:new).with("banner").returns(option_parser) + + @app.option_parser + end + + it "should set the optionparser's args to the command line args" do + option_parser = stub "option parser" + option_parser.stubs(:on) + option_parser.expects(:default_argv=).with(%w{ fake args }) + @app.command_line.stubs(:args).returns(%w{ fake args }) + OptionParser.expects(:new).returns(option_parser) + @app.option_parser end it "should get options from Puppet.settings.optparse_addargs" do Puppet.settings.expects(:optparse_addargs).returns([]) @app.parse_options end it "should add Puppet.settings options to OptionParser" do Puppet.settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option"]]) - @app.opt_parser.expects(:on).with { |*arg| arg == ["--option","-o", "Funny Option"] } + @app.option_parser.expects(:on).with { |*arg| arg == ["--option","-o", "Funny Option"] } @app.parse_options end it "should ask OptionParser to parse the command-line argument" do - @app.opt_parser.expects(:parse!) + @app.option_parser.expects(:parse!) @app.parse_options end describe "when using --help" do confine "rdoc" => Puppet.features.usage? it "should call RDoc::usage and exit" do @app.expects(:exit) RDoc.expects(:usage).returns(true) @app.handle_help(nil) end end describe "when using --version" do it "should declare a version option" do @app.should respond_to(:handle_version) end it "should exit after printing the version" do @app.stubs(:puts) lambda { @app.handle_version(nil) }.should raise_error(SystemExit) end end describe "when dealing with an argument not declared directly by the application" do it "should pass it to handle_unknown if this method exists" do Puppet.settings.stubs(:optparse_addargs).returns([["--not-handled"]]) - @app.opt_parser.stubs(:on).yields("value") + @app.option_parser.stubs(:on).yields("value") @app.expects(:handle_unknown).with("--not-handled", "value").returns(true) @app.parse_options end it "should pass it to Puppet.settings if handle_unknown says so" do Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet"]]) - @app.opt_parser.stubs(:on).yields("value") + @app.option_parser.stubs(:on).yields("value") @app.stubs(:handle_unknown).with("--topuppet", "value").returns(false) Puppet.settings.expects(:handlearg).with("--topuppet", "value") @app.parse_options end it "should pass it to Puppet.settings if there is no handle_unknown method" do Puppet.settings.stubs(:optparse_addargs).returns([["--topuppet"]]) - @app.opt_parser.stubs(:on).yields("value") + @app.option_parser.stubs(:on).yields("value") @app.stubs(:respond_to?).returns(false) Puppet.settings.expects(:handlearg).with("--topuppet", "value") @app.parse_options end it "should transform boolean false value to string for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "false") @app.handlearg("--option", false) end it "should transform boolean true value to string for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "true") @app.handlearg("--option", true) end it "should transform boolean option to normal form for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--option", "true") @app.handlearg("--[no-]option", true) end it "should transform boolean option to no- form for Puppet.settings" do Puppet.settings.expects(:handlearg).with("--no-option", "false") @app.handlearg("--[no-]option", false) end end it "should exit if OptionParser raises an error" do $stderr.stubs(:puts) - @app.opt_parser.stubs(:parse!).raises(OptionParser::ParseError.new("blah blah")) + @app.option_parser.stubs(:parse!).raises(OptionParser::ParseError.new("blah blah")) @app.expects(:exit) lambda { @app.parse_options }.should_not raise_error end end describe "when calling default setup" do before :each do @app.stubs(:should_parse_config?).returns(false) @app.options.stubs(:[]) end [ :debug, :verbose ].each do |level| it "should honor option #{level}" do @app.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.expects(:level=).with(level == :verbose ? :info : :debug) @app.setup end end it "should honor setdest option" do @app.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:newdestination).with(:syslog) @app.setup end end describe "when running" do before :each do @app.stubs(:preinit) @app.stubs(:setup) @app.stubs(:parse_options) end it "should call preinit" do @app.stubs(:run_command) @app.expects(:preinit) @app.run end it "should call parse_options" do @app.stubs(:run_command) @app.expects(:parse_options) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should parse Puppet configuration if should_parse_config is called" do @app.stubs(:run_command) @app.class.should_parse_config Puppet.settings.expects(:parse) @app.run end it "should not parse_option if should_not_parse_config is called" do @app.stubs(:run_command) @app.class.should_not_parse_config Puppet.settings.expects(:parse).never @app.run end it "should parse Puppet configuration if needed" do @app.stubs(:run_command) @app.stubs(:should_parse_config?).returns(true) Puppet.settings.expects(:parse) @app.run end it "should call run_command" do @app.expects(:run_command) @app.run end it "should call main as the default command" do @app.expects(:main) @app.run end it "should warn and exit if no command can be called" do $stderr.expects(:puts) @app.expects(:exit).with(1) @app.run end it "should raise an error if dispatch returns no command" do @app.stubs(:get_command).returns(nil) $stderr.expects(:puts) @app.expects(:exit).with(1) @app.run end it "should raise an error if dispatch returns an invalid command" do @app.stubs(:get_command).returns(:this_function_doesnt_exist) $stderr.expects(:puts) @app.expects(:exit).with(1) @app.run end end describe "when metaprogramming" do describe "when calling option" do it "should create a new method named after the option" do @app.class.option("--test1","-t") do end @app.should respond_to(:handle_test1) end it "should transpose in option name any '-' into '_'" do @app.class.option("--test-dashes-again","-t") do end @app.should respond_to(:handle_test_dashes_again) end it "should create a new method called handle_test2 with option(\"--[no-]test2\")" do @app.class.option("--[no-]test2","-t") do end @app.should respond_to(:handle_test2) end describe "when a block is passed" do it "should create a new method with it" do @app.class.option("--[no-]test2","-t") do raise "I can't believe it, it works!" end lambda { @app.handle_test2 }.should raise_error end it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with { |*arg| arg[0] == "--[no-]test3" } @app.class.option("--[no-]test3","-t") do end - @app.class.new_option_parser( @app ) + @app.option_parser end it "should pass a block that calls our defined method" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with('--test4','-t').yields(nil) @app.expects(:send).with(:handle_test4, nil) @app.class.option("--test4","-t") do end - @app.class.new_option_parser( @app ) + @app.option_parser end end describe "when no block is given" do it "should declare the option to OptionParser" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.expects(:on).with("--test4","-t") @app.class.option("--test4","-t") - @app.class.new_option_parser( @app ) + @app.option_parser end it "should give to OptionParser a block that adds the the value to the options array" do OptionParser.any_instance.stubs(:on) OptionParser.any_instance.stubs(:on).with("--test4","-t").yields(nil) @app.options.expects(:[]=).with(:test4,nil) @app.class.option("--test4","-t") - @app.class.new_option_parser( @app ) + @app.option_parser end end end end end diff --git a/spec/unit/application/agent.rb b/spec/unit/application/agent.rb index fbcc61be3..2439feae2 100755 --- a/spec/unit/application/agent.rb +++ b/spec/unit/application/agent.rb @@ -1,603 +1,597 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/agent' require 'puppet/network/server' describe Puppet::Application::Agent do before :each do @puppetd = Puppet::Application[:agent] @puppetd.stubs(:puts) @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) @agent = stub_everything 'agent' Puppet::Agent.stubs(:new).returns(@agent) @puppetd.preinit Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) end it "should ask Puppet::Application to parse Puppet configuration file" do @puppetd.should_parse_config?.should be_true end it "should declare a main command" do @puppetd.should respond_to(:main) end it "should declare a onetime command" do @puppetd.should respond_to(:onetime) end it "should declare a fingerprint command" do @puppetd.should respond_to(:fingerprint) end it "should declare a preinit block" do @puppetd.should respond_to(:preinit) end describe "in preinit" do before :each do @puppetd.stubs(:trap) end it "should catch INT" do @puppetd.expects(:trap).with { |arg,block| arg == :INT } @puppetd.preinit end it "should set waitforcert to 120" do @puppetd.preinit @puppetd.options[:waitforcert].should == 120 end it "should init client to true" do @puppetd.preinit @puppetd.options[:client].should be_true end it "should init fqdn to nil" do @puppetd.preinit @puppetd.options[:fqdn].should be_nil end it "should init serve to []" do @puppetd.preinit @puppetd.options[:serve].should == [] end it "should use MD5 as default digest algorithm" do @puppetd.preinit @puppetd.options[:digest].should == :MD5 end it "should not fingerprint by default" do @puppetd.preinit @puppetd.options[:fingerprint].should be_false end end describe "when handling options" do before do - @old_argv = ARGV.dup - ARGV.clear - end - - after do - ARGV.clear - @old_argv.each { |a| ARGV << a } + @puppetd.command_line.stubs(:args).returns([]) end [:centrallogging, :disable, :enable, :debug, :fqdn, :test, :verbose, :digest].each do |option| it "should declare handle_#{option} method" do @puppetd.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @puppetd.options.expects(:[]=).with(option, 'arg') @puppetd.send("handle_#{option}".to_sym, 'arg') end end it "should set an existing handler on server" do Puppet::Network::Handler.stubs(:handler).with("handler").returns(true) @puppetd.handle_serve("handler") @puppetd.options[:serve].should == [ :handler ] end it "should set client to false with --no-client" do @puppetd.handle_no_client(nil) @puppetd.options[:client].should be_false end it "should set onetime to ture with --onetime" do @puppetd.handle_onetime(nil) @puppetd.options[:onetime].should be_true end it "should set waitforcert to 0 with --onetime and if --waitforcert wasn't given" do @puppetd.explicit_waitforcert = false @puppetd.handle_onetime(nil) @puppetd.options[:waitforcert].should == 0 end it "should not reset waitforcert with --onetime when --waitforcert is used" do @puppetd.explicit_waitforcert = true @puppetd.handle_onetime(nil) @puppetd.options[:waitforcert].should_not == 0 end it "should set the log destination with --logdest" do @puppetd.options.stubs(:[]=).with { |opt,val| opt == :setdest } Puppet::Log.expects(:newdestination).with("console") @puppetd.handle_logdest("console") end it "should put the setdest options to true" do @puppetd.options.expects(:[]=).with(:setdest,true) @puppetd.handle_logdest("console") end - it "should parse the log destination from ARGV" do - ARGV << "--logdest" << "/my/file" + it "should parse the log destination from the command line" do + @puppetd.command_line.stubs(:args).returns(%w[--logdest /my/file]) Puppet::Util::Log.expects(:newdestination).with("/my/file") @puppetd.parse_options end it "should store the waitforcert options with --waitforcert" do @puppetd.options.expects(:[]=).with(:waitforcert,42) @puppetd.handle_waitforcert("42") end it "should mark explicit_waitforcert to true with --waitforcert" do @puppetd.options.stubs(:[]=) @puppetd.handle_waitforcert("42") @puppetd.explicit_waitforcert.should be_true end it "should set args[:Port] with --port" do @puppetd.handle_port("42") @puppetd.args[:Port].should == "42" end end describe "during setup" do before :each do @puppetd.options.stubs(:[]) Puppet.stubs(:info) FileTest.stubs(:exists?).returns(true) Puppet.stubs(:[]) Puppet.stubs(:[]).with(:libdir).returns("/dev/null/lib") Puppet.settings.stubs(:print_config?) Puppet.settings.stubs(:print_config) Puppet::SSL::Host.stubs(:ca_location=) Puppet::Transaction::Report.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) @host = stub_everything 'host' Puppet::SSL::Host.stubs(:new).returns(@host) Puppet.stubs(:settraps) end describe "with --test" do before :each do Puppet.settings.stubs(:handlearg) @puppetd.options.stubs(:[]=) end it "should call setup_test" do @puppetd.options.stubs(:[]).with(:test).returns(true) @puppetd.expects(:setup_test) @puppetd.setup end it "should set options[:verbose] to true" do @puppetd.options.expects(:[]=).with(:verbose,true) @puppetd.setup_test end it "should set options[:onetime] to true" do @puppetd.options.expects(:[]=).with(:onetime,true) @puppetd.setup_test end it "should set options[:detailed_exitcodes] to true" do @puppetd.options.expects(:[]=).with(:detailed_exitcodes,true) @puppetd.setup_test end it "should set waitforcert to 0" do @puppetd.options.expects(:[]=).with(:waitforcert,0) @puppetd.setup_test end end it "should call setup_logs" do @puppetd.expects(:setup_logs) @puppetd.setup end describe "when setting up logs" do before :each do Puppet::Util::Log.stubs(:newdestination) end it "should set log level to debug if --debug was passed" do @puppetd.options.stubs(:[]).with(:debug).returns(true) Puppet::Util::Log.expects(:level=).with(:debug) @puppetd.setup_logs end it "should set log level to info if --verbose was passed" do @puppetd.options.stubs(:[]).with(:verbose).returns(true) Puppet::Util::Log.expects(:level=).with(:info) @puppetd.setup_logs end [:verbose, :debug].each do |level| it "should set console as the log destination with level #{level}" do @puppetd.options.stubs(:[]).with(level).returns(true) Puppet::Util::Log.expects(:newdestination).with(:console) @puppetd.setup_logs end end it "should set syslog as the log destination if no --logdest" do @puppetd.options.stubs(:[]).with(:setdest).returns(false) Puppet::Util::Log.expects(:newdestination).with(:syslog) @puppetd.setup_logs end end it "should print puppet config if asked to in Puppet config" do @puppetd.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs) @puppetd.setup end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) lambda { @puppetd.setup }.should raise_error(SystemExit) end it "should set a central log destination with --centrallogs" do @puppetd.options.stubs(:[]).with(:centrallogs).returns(true) Puppet.stubs(:[]).with(:server).returns("puppet.reductivelabs.com") Puppet::Util::Log.stubs(:newdestination).with(:syslog) Puppet::Util::Log.expects(:newdestination).with("puppet.reductivelabs.com") @puppetd.setup end it "should use :main, :puppetd, and :ssl" do Puppet.settings.expects(:use).with(:main, :puppetd, :ssl) @puppetd.setup end it "should install a remote ca location" do Puppet::SSL::Host.expects(:ca_location=).with(:remote) @puppetd.setup end it "should install a none ca location in fingerprint mode" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:none) @puppetd.setup end it "should tell the report handler to use REST" do Puppet::Transaction::Report.expects(:terminus_class=).with(:rest) @puppetd.setup end it "should change the catalog_terminus setting to 'rest'" do Puppet.expects(:[]=).with(:catalog_terminus, :rest) @puppetd.setup end it "should tell the catalog handler to use cache" do Puppet::Resource::Catalog.expects(:cache_class=).with(:yaml) @puppetd.setup end it "should tell the facts to use facter" do Puppet::Node::Facts.expects(:terminus_class=).with(:facter) @puppetd.setup end it "should create an agent" do Puppet::Agent.stubs(:new).with(Puppet::Configurer) @puppetd.setup end [:enable, :disable].each do |action| it "should delegate to enable_disable_client if we #{action} the agent" do @puppetd.options.stubs(:[]).with(action).returns(true) @puppetd.expects(:enable_disable_client).with(@agent) @puppetd.setup end end describe "when enabling or disabling agent" do [:enable, :disable].each do |action| it "should call client.#{action}" do @puppetd.stubs(:exit) @puppetd.options.stubs(:[]).with(action).returns(true) @agent.expects(action) @puppetd.enable_disable_client(@agent) end end it "should finally exit" do lambda { @puppetd.enable_disable_client(@agent) }.should raise_error(SystemExit) end end it "should inform the daemon about our agent if :client is set to 'true'" do @puppetd.options.expects(:[]).with(:client).returns true @daemon.expects(:agent=).with(@agent) @puppetd.setup end it "should not inform the daemon about our agent if :client is set to 'false'" do @puppetd.options[:client] = false @daemon.expects(:agent=).never @puppetd.setup end it "should daemonize if needed" do Puppet.stubs(:[]).with(:daemonize).returns(true) @daemon.expects(:daemonize) @puppetd.setup end it "should wait for a certificate" do @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).with(123) @puppetd.setup end it "should not wait for a certificate in fingerprint mode" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.options.stubs(:[]).with(:waitforcert).returns(123) @host.expects(:wait_for_cert).never @puppetd.setup end it "should setup listen if told to and not onetime" do Puppet.stubs(:[]).with(:listen).returns(true) @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.expects(:setup_listen) @puppetd.setup end describe "when setting up listen" do before :each do Puppet.stubs(:[]).with(:authconfig).returns('auth') FileTest.stubs(:exists?).with('auth').returns(true) File.stubs(:exist?).returns(true) @puppetd.options.stubs(:[]).with(:serve).returns([]) @puppetd.stubs(:exit) @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) end it "should exit if no authorization file" do Puppet.stubs(:err) FileTest.stubs(:exists?).with('auth').returns(false) @puppetd.expects(:exit) @puppetd.setup_listen end it "should create a server to listen on at least the Runner handler" do Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Runner] } @puppetd.setup_listen end it "should create a server to listen for specific handlers" do @puppetd.options.stubs(:[]).with(:serve).returns([:handler]) Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:handler] } @puppetd.setup_listen end it "should use puppet default port" do Puppet.stubs(:[]).with(:puppetport).returns(:port) Puppet::Network::Server.expects(:new).with { |args| args[:port] == :port } @puppetd.setup_listen end end end describe "when running" do before :each do @puppetd.agent = @agent @puppetd.daemon = @daemon @puppetd.options.stubs(:[]).with(:fingerprint).returns(false) end it "should dispatch to fingerprint if --fingerprint is used" do @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.stubs(:fingerprint) @puppetd.run_command end it "should dispatch to onetime if --onetime is used" do @puppetd.options.stubs(:[]).with(:onetime).returns(true) @puppetd.stubs(:onetime) @puppetd.run_command end it "should dispatch to main if --onetime and --fingerprint are not used" do @puppetd.options.stubs(:[]).with(:onetime).returns(false) @puppetd.stubs(:main) @puppetd.run_command end describe "with --onetime" do before :each do @agent.stubs(:run).returns(:report) @puppetd.options.stubs(:[]).with(:client).returns(:client) @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(false) @puppetd.stubs(:exit).with(0) Puppet.stubs(:newservice) end it "should exit if no defined --client" do $stderr.stubs(:puts) @puppetd.options.stubs(:[]).with(:client).returns(nil) @puppetd.expects(:exit).with(43) @puppetd.onetime end it "should setup traps" do @daemon.expects(:set_signal_traps) @puppetd.onetime end it "should let the agent run" do @agent.expects(:run).returns(:report) @puppetd.onetime end it "should finish by exiting with 0 error code" do @puppetd.expects(:exit).with(0) @puppetd.onetime end describe "and --detailed-exitcodes" do before :each do @puppetd.options.stubs(:[]).with(:detailed_exitcodes).returns(true) end it "should exit with report's computed exit status" do Puppet.stubs(:[]).with(:noop).returns(false) report = stub 'report', :exit_status => 666 @agent.stubs(:run).returns(report) @puppetd.expects(:exit).with(666) @puppetd.onetime end it "should always exit with 0 if --noop" do Puppet.stubs(:[]).with(:noop).returns(true) report = stub 'report', :exit_status => 666 @agent.stubs(:run).returns(report) @puppetd.expects(:exit).with(0) @puppetd.onetime end end end describe "with --fingerprint" do before :each do @cert = stub_everything 'cert' @puppetd.options.stubs(:[]).with(:fingerprint).returns(true) @puppetd.options.stubs(:[]).with(:digest).returns(:MD5) @host = stub_everything 'host' @puppetd.stubs(:host).returns(@host) end it "should fingerprint the certificate if it exists" do @host.expects(:certificate).returns(@cert) @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" @puppetd.fingerprint end it "should fingerprint the certificate request if no certificate have been signed" do @host.expects(:certificate).returns(nil) @host.expects(:certificate_request).returns(@cert) @cert.expects(:fingerprint).with(:MD5).returns "fingerprint" @puppetd.fingerprint end it "should display the fingerprint" do @host.stubs(:certificate).returns(@cert) @cert.stubs(:fingerprint).with(:MD5).returns("DIGEST") Puppet.expects(:notice).with("DIGEST") @puppetd.fingerprint end end describe "without --onetime and --fingerprint" do before :each do Puppet.stubs(:notice) @puppetd.options.stubs(:[]).with(:client) end it "should start our daemon" do @daemon.expects(:start) @puppetd.main end end end end diff --git a/spec/unit/application/master.rb b/spec/unit/application/master.rb index 2672fdb16..362769e53 100644 --- a/spec/unit/application/master.rb +++ b/spec/unit/application/master.rb @@ -1,456 +1,450 @@ #!/usr/bin/env ruby require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/application/master' describe Puppet::Application::Master do before :each do @master = Puppet::Application[:master] @daemon = stub_everything 'daemon' Puppet::Daemon.stubs(:new).returns(@daemon) Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:level=) Puppet::Node.stubs(:terminus_class=) Puppet::Node.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) Puppet::Node::Facts.stubs(:cache_class=) Puppet::Transaction::Report.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:terminus_class=) end it "should ask Puppet::Application to parse Puppet configuration file" do @master.should_parse_config?.should be_true end it "should declare a main command" do @master.should respond_to(:main) end it "should declare a parseonly command" do @master.should respond_to(:parseonly) end it "should declare a compile command" do @master.should respond_to(:compile) end it "should declare a preinit block" do @master.should respond_to(:preinit) end describe "during preinit" do before :each do @master.stubs(:trap) end it "should catch INT" do @master.stubs(:trap).with { |arg,block| arg == :INT } @master.preinit end it "should create a Puppet Daemon" do Puppet::Daemon.expects(:new).returns(@daemon) @master.preinit end it "should give ARGV to the Daemon" do argv = stub 'argv' ARGV.stubs(:dup).returns(argv) @daemon.expects(:argv=).with(argv) @master.preinit end end [:debug,:verbose].each do |option| it "should declare handle_#{option} method" do @master.should respond_to("handle_#{option}".to_sym) end it "should store argument value when calling handle_#{option}" do @master.options.expects(:[]=).with(option, 'arg') @master.send("handle_#{option}".to_sym, 'arg') end end describe "when applying options" do before do - @old_argv = ARGV.dup - ARGV.clear - end - - after do - ARGV.clear - @old_argv.each { |a| ARGV << a } + @master.command_line.stubs(:args).returns([]) end it "should set the log destination with --logdest" do Puppet::Log.expects(:newdestination).with("console") @master.handle_logdest("console") end it "should put the setdest options to true" do @master.options.expects(:[]=).with(:setdest,true) @master.handle_logdest("console") end it "should parse the log destination from ARGV" do - ARGV << "--logdest" << "/my/file" + @master.command_line.stubs(:args).returns(%w[--logdest /my/file]) Puppet::Util::Log.expects(:newdestination).with("/my/file") @master.parse_options end end describe "during setup" do before :each do Puppet::Log.stubs(:newdestination) Puppet.stubs(:settraps) Puppet::Log.stubs(:level=) Puppet::SSL::CertificateAuthority.stubs(:instance) Puppet::SSL::CertificateAuthority.stubs(:ca?) Puppet.settings.stubs(:use) @master.options.stubs(:[]).with(any_parameters) end it "should set log level to debug if --debug was passed" do @master.options.stubs(:[]).with(:debug).returns(true) Puppet::Log.expects(:level=).with(:debug) @master.setup end it "should set log level to info if --verbose was passed" do @master.options.stubs(:[]).with(:verbose).returns(true) Puppet::Log.expects(:level=).with(:info) @master.setup end it "should set console as the log destination if no --logdest and --daemonize" do @master.stubs(:[]).with(:daemonize).returns(:false) Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should set syslog as the log destination if no --logdest and not --daemonize" do Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should set syslog as the log destination if --rack" do @master.options.stubs(:[]).with(:rack).returns(:true) Puppet::Log.expects(:newdestination).with(:syslog) @master.setup end it "should print puppet config if asked to in Puppet config" do @master.stubs(:exit) Puppet.settings.stubs(:print_configs?).returns(true) Puppet.settings.expects(:print_configs) @master.setup end it "should exit after printing puppet config if asked to in Puppet config" do Puppet.settings.stubs(:print_configs?).returns(true) lambda { @master.setup }.should raise_error(SystemExit) end it "should tell Puppet.settings to use :main,:ssl and :puppetmasterd category" do Puppet.settings.expects(:use).with(:main,:puppetmasterd,:ssl) @master.setup end it "should set node facst terminus to yaml" do Puppet::Node::Facts.expects(:terminus_class=).with(:yaml) @master.setup end it "should cache class in yaml" do Puppet::Node.expects(:cache_class=).with(:yaml) @master.setup end describe "with no ca" do it "should set the ca_location to none" do Puppet::SSL::Host.expects(:ca_location=).with(:none) @master.setup end end describe "with a ca configured" do before :each do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) end it "should set the ca_location to local" do Puppet::SSL::Host.expects(:ca_location=).with(:local) @master.setup end it "should tell Puppet.settings to use :ca category" do Puppet.settings.expects(:use).with(:ca) @master.setup end it "should instantiate the CertificateAuthority singleton" do Puppet::SSL::CertificateAuthority.expects(:instance) @master.setup end end end describe "when running" do before do @master.preinit end it "should dispatch to parseonly if parseonly is set" do Puppet.stubs(:[]).with(:parseonly).returns(true) @master.options[:node] = nil @master.expects(:parseonly) @master.run_command end it "should dispatch to compile if called with --compile" do @master.options[:node] = "foo" @master.expects(:compile) @master.run_command end it "should dispatch to main if parseonly is not set" do Puppet.stubs(:[]).with(:parseonly).returns(false) @master.options[:node] = nil @master.expects(:main) @master.run_command end describe "the parseonly command" do before :each do Puppet.stubs(:[]).with(:environment) Puppet.stubs(:[]).with(:manifest).returns("site.pp") Puppet.stubs(:err) @master.stubs(:exit) @collection = stub_everything Puppet::Resource::TypeCollection.stubs(:new).returns(@collection) end it "should use a Puppet Resource Type Collection to parse the file" do @collection.expects(:perform_initial_import) @master.parseonly end it "should exit with exit code 0 if no error" do @master.expects(:exit).with(0) @master.parseonly end it "should exit with exit code 1 if error" do @collection.stubs(:perform_initial_import).raises(Puppet::ParseError) @master.expects(:exit).with(1) @master.parseonly end end describe "the compile command" do before do Puppet.stubs(:[]).with(:environment) Puppet.stubs(:[]).with(:manifest).returns("site.pp") Puppet.stubs(:err) @master.stubs(:exit) Puppet.features.stubs(:pson?).returns true end it "should fail if pson isn't available" do Puppet.features.expects(:pson?).returns false lambda { @master.compile }.should raise_error end it "should compile a catalog for the specified node" do @master.options[:node] = "foo" Puppet::Resource::Catalog.expects(:find).with("foo").returns Puppet::Resource::Catalog.new $stdout.stubs(:puts) @master.compile end it "should render the catalog to pson and print the output" do @master.options[:node] = "foo" catalog = Puppet::Resource::Catalog.new catalog.expects(:render).with(:pson).returns "mypson" Puppet::Resource::Catalog.expects(:find).returns catalog $stdout.expects(:puts).with("mypson") @master.compile end it "should exit with error code 30 if no catalog can be found" do @master.options[:node] = "foo" Puppet::Resource::Catalog.expects(:find).returns nil @master.expects(:exit).with(30) $stderr.expects(:puts) @master.compile end it "should exit with error code 30 if there's a failure" do @master.options[:node] = "foo" Puppet::Resource::Catalog.expects(:find).raises ArgumentError @master.expects(:exit).with(30) $stderr.expects(:puts) @master.compile end end describe "the main command" do before :each do @master.preinit @server = stub_everything 'server' Puppet::Network::Server.stubs(:new).returns(@server) @app = stub_everything 'app' Puppet::SSL::Host.stubs(:localhost) Puppet::SSL::CertificateAuthority.stubs(:ca?) Process.stubs(:uid).returns(1000) Puppet.stubs(:service) Puppet.stubs(:[]) Puppet.stubs(:notice) Puppet.stubs(:start) end it "should create a Server" do Puppet::Network::Server.expects(:new) @master.main end it "should give the server to the daemon" do @daemon.expects(:server=).with(@server) @master.main end it "should create the server with the right XMLRPC handlers" do Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket]} @master.main end it "should create the server with a :ca xmlrpc handler if needed" do Puppet.stubs(:[]).with(:ca).returns(true) Puppet::Network::Server.expects(:new).with { |args| args[:xmlrpc_handlers].include?(:CA) } @master.main end it "should generate a SSL cert for localhost" do Puppet::SSL::Host.expects(:localhost) @master.main end it "should make sure to *only* hit the CA for data" do Puppet::SSL::CertificateAuthority.stubs(:ca?).returns(true) Puppet::SSL::Host.expects(:ca_location=).with(:only) @master.main end it "should drop privileges if running as root" do Process.stubs(:uid).returns(0) Puppet::Util.expects(:chuser) @master.main end it "should daemonize if needed" do Puppet.stubs(:[]).with(:daemonize).returns(true) @daemon.expects(:daemonize) @master.main end it "should start the service" do @daemon.expects(:start) @master.main end describe "with --rack" do confine "Rack is not available" => Puppet.features.rack? before do require 'puppet/network/http/rack' Puppet::Network::HTTP::Rack.stubs(:new).returns(@app) end it "it should create the app with REST and XMLRPC support" do @master.options.stubs(:[]).with(:rack).returns(:true) Puppet::Network::HTTP::Rack.expects(:new).with { |args| args[:xmlrpc_handlers] == [:Status, :FileServer, :Master, :Report, :Filebucket] and args[:protocols] == [:rest, :xmlrpc] } @master.main end it "it should not start a daemon" do @master.options.stubs(:[]).with(:rack).returns(:true) @daemon.expects(:start).never @master.main end it "it should return the app" do @master.options.stubs(:[]).with(:rack).returns(:true) app = @master.main app.should equal(@app) end end end end end