diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index f10ecada5..e17cb85b9 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -1,199 +1,181 @@ # Bundler and rubygems maintain a set of directories from which to # load gems. If Bundler is loaded, let it determine what can be # loaded. If it's not loaded, then use rubygems. But do this before # loading any puppet code, so that our gem loading system is sane. if not defined? ::Bundler begin require 'rubygems' rescue LoadError end end require 'puppet' require 'puppet/util' require "puppet/util/plugins" require "puppet/util/rubygems" require "puppet/util/limits" require 'puppet/util/colors' module Puppet module Util # This is the main entry point for all puppet applications / faces; it # is basically where the bootstrapping process / lifecycle of an app # begins. class CommandLine include Puppet::Util::Limits OPTION_OR_MANIFEST_FILE = /^-|\.pp$/ # @param zero [String] the name of the executable # @param argv [Array] the arguments passed on the command line # @param stdin [IO] (unused) def initialize(zero = $0, argv = ARGV, stdin = STDIN) @command = File.basename(zero, '.rb') @argv = argv Puppet::Plugins.on_commandline_initialization(:command_line_object => self) end # @return [String] name of the subcommand is being executed # @api public def subcommand_name return @command if @command != 'puppet' if @argv.first =~ OPTION_OR_MANIFEST_FILE nil else @argv.first end end # @return [Array] the command line arguments being passed to the subcommand # @api public def args return @argv if @command != 'puppet' if subcommand_name.nil? @argv else @argv[1..-1] end end - # @api private - # @deprecated - def self.available_subcommands - Puppet.deprecation_warning('Puppet::Util::CommandLine.available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.') - Puppet::Application.available_application_names - 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 - # @api private - # @deprecated - def available_subcommands - Puppet.deprecation_warning('Puppet::Util::CommandLine#available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.') - Puppet::Application.available_application_names - end - # Run the puppet subcommand. If the subcommand is determined to be an # external executable, this method will never return and the current # process will be replaced via {Kernel#exec}. # # @return [void] def execute Puppet::Util.exit_on_fail("initialize global default settings") do Puppet.initialize_settings(args) end setpriority(Puppet[:priority]) find_subcommand.run end # @api private def external_subcommand Puppet::Util.which("puppet-#{subcommand_name}") end private def find_subcommand if subcommand_name.nil? NilSubcommand.new(self) elsif Puppet::Application.available_application_names.include?(subcommand_name) ApplicationSubcommand.new(subcommand_name, self) elsif path_to_subcommand = external_subcommand ExternalSubcommand.new(path_to_subcommand, self) else UnknownSubcommand.new(subcommand_name, self) end end # @api private class ApplicationSubcommand def initialize(subcommand_name, command_line) @subcommand_name = subcommand_name @command_line = command_line end def run # For most applications, we want to be able to load code from the modulepath, # such as apply, describe, resource, and faces. # For agent, we only want to load pluginsync'ed code from libdir. # For master, we shouldn't ever be loading per-enviroment code into the master's # ruby process, but that requires fixing (#17210, #12173, #8750). So for now # we try to restrict to only code that can be autoloaded from the node's # environment. # PUP-2114 - at this point in the bootstrapping process we do not # have an appropriate application-wide current_environment set. # If we cannot find the configured environment, which may not exist, # we do not attempt to add plugin directories to the load path. # if @subcommand_name != 'master' and @subcommand_name != 'agent' if configured_environment = Puppet.lookup(:environments).get(Puppet[:environment]) configured_environment.each_plugin_directory do |dir| $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) end end end app = Puppet::Application.find(@subcommand_name).new(@command_line) Puppet::Plugins.on_application_initialization(:application_object => @command_line) app.run end end # @api private class ExternalSubcommand def initialize(path_to_subcommand, command_line) @path_to_subcommand = path_to_subcommand @command_line = command_line end def run Kernel.exec(@path_to_subcommand, *@command_line.args) end end # @api private class NilSubcommand include Puppet::Util::Colors def initialize(command_line) @command_line = command_line end def run args = @command_line.args if args.include? "--version" or args.include? "-V" puts Puppet.version elsif @command_line.subcommand_name.nil? && args.count > 0 # If the subcommand is truly nil and there is an arg, it's an option; print out the invalid option message puts colorize(:hred, "Error: Could not parse application options: invalid option: #{args[0]}") exit 1 else puts "See 'puppet help' for help on available puppet subcommands" end end end # @api private class UnknownSubcommand < NilSubcommand def initialize(subcommand_name, command_line) @subcommand_name = subcommand_name super(command_line) end def run puts colorize(:hred, "Error: Unknown Puppet subcommand '#{@subcommand_name}'") super exit 1 end end end end end diff --git a/spec/unit/util/command_line_spec.rb b/spec/unit/util/command_line_spec.rb index cb6ec7a48..37a77b4e6 100755 --- a/spec/unit/util/command_line_spec.rb +++ b/spec/unit/util/command_line_spec.rb @@ -1,185 +1,168 @@ #! /usr/bin/env ruby require 'spec_helper' require 'puppet/face' require 'puppet/util/command_line' describe Puppet::Util::CommandLine do include PuppetSpec::Files 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 }) 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 }) 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 flag" do command_line = Puppet::Util::CommandLine.new("puppet", %w{ --debug }) 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{ - }) 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 }) command_line.subcommand_name.should == nil end it "should return nil if there are no arguments" do command_line = Puppet::Util::CommandLine.new("puppet", []) command_line.subcommand_name.should == nil command_line.args.should == [] end it "should pick up changes to the array of arguments" do args = %w{subcommand} command_line = Puppet::Util::CommandLine.new("puppet", args) args[0] = 'different_subcommand' command_line.subcommand_name.should == 'different_subcommand' 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 described_class.new("puppet", [arg]).execute end.to have_printed(/^#{Regexp.escape(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", []) 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']) Puppet::Util.expects(:which).with('puppet-whatever'). returns('/dev/null/puppet-whatever') Kernel.expects(:exec).with('/dev/null/puppet-whatever', 'argument') commandline.execute end describe "and an external implementation cannot be found" do before :each do Puppet::Util::CommandLine::UnknownSubcommand.any_instance.stubs(:console_has_color?).returns false end it "should abort and show the usage message" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/Unknown Puppet subcommand 'whatever'/).and_exit_with(1) end it "should abort and show the help message" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', 'argument']) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(/See 'puppet help' for help on available puppet subcommands/).and_exit_with(1) end %w{--version -V}.each do |arg| it "should abort and display #{arg} information" do Puppet::Util.expects(:which).with('puppet-whatever').returns(nil) commandline = Puppet::Util::CommandLine.new("puppet", ['whatever', arg]) commandline.expects(:exec).never expect { commandline.execute }.to have_printed(%r[^#{Regexp.escape(Puppet.version)}$]).and_exit_with(1) end end end end - describe 'when loading commands' do - it "should deprecate the available_subcommands instance method" do - Puppet::Application.expects(:available_application_names) - Puppet.expects(:deprecation_warning).with("Puppet::Util::CommandLine#available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.") - - command_line = Puppet::Util::CommandLine.new("foo", %w{ client --help whatever.pp }) - command_line.available_subcommands - end - - it "should deprecate the available_subcommands class method" do - Puppet::Application.expects(:available_application_names) - Puppet.expects(:deprecation_warning).with("Puppet::Util::CommandLine.available_subcommands is deprecated; please use Puppet::Application.available_application_names instead.") - - Puppet::Util::CommandLine.available_subcommands - end - end - describe 'when setting process priority' do let(:command_line) do Puppet::Util::CommandLine.new("puppet", %w{ agent }) end before :each do Puppet::Util::CommandLine::ApplicationSubcommand.any_instance.stubs(:run) end it 'should never set priority by default' do Process.expects(:setpriority).never command_line.execute end it 'should lower the process priority if one has been specified' do Puppet[:priority] = 10 Process.expects(:setpriority).with(0, Process.pid, 10) command_line.execute end it 'should warn if trying to raise priority, but not privileged user' do Puppet[:priority] = -10 Process.expects(:setpriority).raises(Errno::EACCES, 'Permission denied') Puppet.expects(:warning).with("Failed to set process priority to '-10'") command_line.execute end it "should warn if the platform doesn't support `Process.setpriority`" do Puppet[:priority] = 15 Process.expects(:setpriority).raises(NotImplementedError, 'NotImplementedError: setpriority() function is unimplemented on this machine') Puppet.expects(:warning).with("Failed to set process priority to '15'") command_line.execute end end end end