diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index 4dee5303c..aba94616c 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -1,123 +1,121 @@ require 'puppet' require "puppet/util/plugins" module Puppet module Util class CommandLine def initialize(zero = $0, argv = ARGV, stdin = STDIN) @zero = zero @argv = argv.dup @stdin = stdin @subcommand_name, @args = subcommand_and_args(@zero, @argv, @stdin) Puppet::Plugins.on_commandline_initialization(:command_line_object => self) end attr :subcommand_name attr :args def appdir File.join('puppet', 'application') end def self.available_subcommands # Eventually we probably want to replace this with a call to the autoloader. however, at the moment # the autoloader considers the module path when loading, and we don't want to allow apps / faces to load # from there. Once that is resolved, this should be replaced. --cprice 2012-03-06 absolute_appdirs = $LOAD_PATH.collect do |x| File.join(x,'puppet','application') end.select{ |x| File.directory?(x) } absolute_appdirs.inject([]) do |commands, dir| commands + Dir[File.join(dir, '*.rb')].map{|fn| File.basename(fn, '.rb')} end.uniq end # available_subcommands was previously an instance method, not a class # method, and we have an unknown number of user-implemented applications # that depend on that behaviour. Forwarding allows us to preserve a # backward compatible API. --daniel 2011-04-11 def available_subcommands self.class.available_subcommands end def require_application(application) require File.join(appdir, application) end # This is the main entry point for all puppet applications / faces; it # is basically where the bootstrapping process / lifecycle of an app # begins. def execute - # We support printing the global version very early, unconditionally. - # This doesn't replace declaring the option later, even if it means - # that particular bit will never trigger. - if @argv.include? "--version" or @argv.include? "-V" - puts Puppet.version - exit 0 - end - # Build up our settings - we don't need that until after version check. Puppet::Util.exit_on_fail("intialize global default settings") do Puppet.settings.initialize_global_settings(args) end # OK, now that we've processed the command line options and the config # files, we should be able to say that we definitively know where the # libdir is... which means that we can now look for our available # applications / subcommands / faces. if subcommand_name and available_subcommands.include?(subcommand_name) then require_application subcommand_name # This will need to be cleaned up to do something that is not so # application-specific (i.e.. so that we can load faces). # Longer-term, use the autoloader. See comments in # #available_subcommands method above. --cprice 2012-03-06 app = Puppet::Application.find(subcommand_name).new(self) Puppet::Plugins.on_application_initialization(:application_object => self) app.run elsif ! execute_external_subcommand then unless subcommand_name.nil? then puts "Error: Unknown Puppet subcommand '#{subcommand_name}'" end - puts "See 'puppet help' for help on available puppet subcommands" + + # If the user is just checking the version, print that and exit + if @argv.include? "--version" or @argv.include? "-V" + puts Puppet.version + else + puts "See 'puppet help' for help on available puppet subcommands" + end end end def execute_external_subcommand external_command = "puppet-#{subcommand_name}" require 'puppet/util' path_to_subcommand = Puppet::Util.which(external_command) return false unless path_to_subcommand exec(path_to_subcommand, *args) end private def subcommand_and_args(zero, argv, stdin) zero = File.basename(zero, '.rb') if zero == 'puppet' case argv.first # if they didn't pass a command, or passed a help flag, we will # fall back to showing a usage message. we no longer default to # 'apply' when nil, "--help", "-h", /^-|\.pp$|\.rb$/ [nil, argv] else [argv.first, argv[1..-1]] end else [zero, argv] end end end end end diff --git a/spec/unit/util/command_line_spec.rb b/spec/unit/util/command_line_spec.rb index d04fbbf18..5f3b8649f 100755 --- a/spec/unit/util/command_line_spec.rb +++ b/spec/unit/util/command_line_spec.rb @@ -1,157 +1,155 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/face' require 'puppet/util/command_line' describe Puppet::Util::CommandLine do include PuppetSpec::Files let :tty do stub("tty", :tty? => true) end let :pipe do stub("pipe", :tty? => false) end context "#initialize" do it "should pull off the first argument if it looks like a subcommand" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ client --help whatever.pp }, tty) command_line.subcommand_name.should == "client" command_line.args.should == %w{ --help whatever.pp } end it "should return nil if the first argument looks like a .pp file" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ whatever.pp }, tty) command_line.subcommand_name.should == nil command_line.args.should == %w{ whatever.pp } end it "should return nil if the first argument looks like a .rb file" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ whatever.rb }, tty) command_line.subcommand_name.should == nil command_line.args.should == %w{ whatever.rb } end it "should return nil if the first argument looks like a flag" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ --debug }, tty) command_line.subcommand_name.should == nil command_line.args.should == %w{ --debug } end it "should return nil if the first argument is -" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ - }, tty) command_line.subcommand_name.should == nil command_line.args.should == %w{ - } end it "should return nil if the first argument is --help" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ --help }, tty) command_line.subcommand_name.should == nil end it "should return nil if there are no arguments on a tty" do command_line = Puppet::Util::CommandLine.new("puppet", [], tty) command_line.subcommand_name.should == nil command_line.args.should == [] end it "should return nil if there are no arguments on a pipe" do command_line = Puppet::Util::CommandLine.new("puppet", [], pipe) command_line.subcommand_name.should == nil command_line.args.should == [] end end context "#execute" do %w{--version -V}.each do |arg| it "should print the version and exit if #{arg} is given" do expect do - expect do - described_class.new("puppet", [arg], tty).execute - end.to exit_with 0 - end.to have_printed(Puppet.version.to_s) + described_class.new("puppet", [arg], tty).execute + end.to have_printed(Puppet.version) end end end describe "when dealing with puppet commands" do it "should return the executable name if it is not puppet" do command_line = Puppet::Util::CommandLine.new("puppetmasterd", [], tty) command_line.subcommand_name.should == "puppetmasterd" end describe "when the subcommand is not implemented" do it "should find and invoke an executable with a hyphenated name" do commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument'], tty) Puppet::Util.expects(:which).with('puppet-whatever'). returns('/dev/null/puppet-whatever') # It is important that we abort at the point exec is called, because # the code (reasonably) assumes that if `exec` is called processing # immediately terminates, and we are replaced by the executed process. # # This raise isn't a perfect simulation of that, but it is enough to # validate that the system works, and ... well, if exec is broken we # have two problems, y'know. commandline.expects(:exec).with('/dev/null/puppet-whatever', 'argument'). raises(SystemExit) expect { commandline.execute }.to raise_error SystemExit end describe "and an external implementation cannot be found" do it "should abort and show the usage message" do commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument'], tty) Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/Unknown Puppet subcommand 'whatever'/) end end end describe 'when loading commands' do let :core_apps do %w{describe filebucket kick queue resource agent cert apply doc master} end let :command_line do Puppet::Util::CommandLine.new("foo", %w{ client --help whatever.pp }, tty) end it "should expose available_subcommands as a class method" do core_apps.each do |command| command_line.available_subcommands.should include command end end it 'should be able to find all existing commands' do core_apps.each do |command| command_line.available_subcommands.should include command end end describe 'when multiple paths have applications' do before do @dir=tmpdir('command_line_plugin_test') @appdir="#{@dir}/puppet/application" FileUtils.mkdir_p(@appdir) FileUtils.touch("#{@appdir}/foo.rb") $LOAD_PATH.unshift(@dir) # WARNING: MUST MATCH THE AFTER ACTIONS! end it 'should be able to find commands from both paths' do found = command_line.available_subcommands found.should include 'foo' core_apps.each { |cmd| found.should include cmd } end after do $LOAD_PATH.shift # WARNING: MUST MATCH THE BEFORE ACTIONS! end end end end end