# frozen_string_literal: true # 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. unless defined? ::Bundler begin require 'rubygems' rescue LoadError end end require_relative '../../puppet' require_relative '../../puppet/util' require_relative '../../puppet/util/rubygems' require_relative '../../puppet/util/limits' require_relative '../../puppet/util/colors' require_relative '../../puppet/gettext/module_translations' 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 = $PROGRAM_NAME, argv = ARGV, stdin = STDIN) @command = File.basename(zero, '.rb') @argv = argv 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..] end 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 require_config = true if @argv.first =~ /help|-h|--help|-V|--version/ require_config = false end Puppet::Util.exit_on_fail(_("Could not initialize global default settings")) do Puppet.initialize_settings(args, require_config) 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? if args.include?("--help") || args.include?("-h") ApplicationSubcommand.new("help", CommandLine.new("puppet", ["help"])) else NilSubcommand.new(self) end elsif Puppet::Application.available_application_names.include?(subcommand_name) ApplicationSubcommand.new(subcommand_name, self) else path_to_subcommand = external_subcommand if path_to_subcommand ExternalSubcommand.new(path_to_subcommand, self) else UnknownSubcommand.new(subcommand_name, self) end 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 and device in agent mode, we only want to load pluginsync'ed code from libdir. # For master, we shouldn't ever be loading per-environment 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. unless @subcommand_name == 'master' || @subcommand_name == 'agent' || (@subcommand_name == 'device' && (['--apply', '--facts', '--resource'] - @command_line.args).empty?) configured_environment = Puppet.lookup(:environments).get(Puppet[:environment]) if configured_environment configured_environment.each_plugin_directory do |dir| $LOAD_PATH << dir unless $LOAD_PATH.include?(dir) end Puppet::ModuleTranslations.load_from_modulepath(configured_environment.modules) Puppet::ModuleTranslations.load_from_vardir(Puppet[:vardir]) # Puppet requires Facter, which initializes its lookup paths. Reset Facter to # pickup the new $LOAD_PATH. Puppet.runtime[:facter].reset end end app = Puppet::Application.find(@subcommand_name).new(@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: %{opt}") % { opt: 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 '%{cmd}'") % { cmd: @subcommand_name }) super exit 1 end end end end end