require 'puppet/application' require 'puppet/configurer' require 'puppet/util/profiler/aggregate' require 'puppet/parser/script_compiler' class Puppet::Application::Script < Puppet::Application option("--debug","-d") option("--execute EXECUTE","-e") do |arg| options[:code] = arg end option("--test","-t") option("--verbose","-v") option("--logdest LOGDEST", "-l") do |arg| handle_logdest_arg(arg) end def summary _("Run a puppet manifests as a script without compiling a catalog") end def help <<-HELP puppet-script(8) -- #{summary} ======== SYNOPSIS -------- Runs a puppet language script without compiling a catalog. USAGE ----- puppet script [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-e|--execute] [-l|--logdest syslog|eventlog||console] [--noop] DESCRIPTION ----------- This is a standalone puppet script runner tool; use it to run puppet code without compiling a catalog. When provided with a modulepath, via command line or config file, puppet script can load functions, types, tasks and plans from modules. OPTIONS ------- Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'environment' is a valid setting, so you can specify '--environment mytest' as an argument. See the configuration file documentation at https://puppet.com/docs/puppet/latest/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with '--genconfig'. * --debug: Enable full debugging. * --help: Print this help message * --logdest: Where to send log messages. Choose between 'syslog' (the POSIX syslog service), 'eventlog' (the Windows Event Log), 'console', or the path to a log file. Defaults to 'console'. A path ending with '.json' will receive structured output in JSON format. The log file will not have an ending ']' automatically written to it due to the appending nature of logging. It must be appended manually to make the content valid JSON. * --noop: Use 'noop' mode where Puppet runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. Applies to tasks only. * --execute: Execute a specific piece of Puppet code * --verbose: Print extra information. EXAMPLE ------- $ puppet script -l /tmp/manifest.log manifest.pp $ puppet script --modulepath=/root/dev/modules -e 'notice("hello world")' AUTHOR ------ Henrik Lindberg COPYRIGHT --------- Copyright (c) 2017 Puppet Inc., LLC Licensed under the Apache 2.0 License HELP end def app_defaults super.merge({ :default_file_terminus => :file_server, }) end def run_command if Puppet.features.bolt? Puppet.override(:bolt_executor => Bolt::Executor.new) do main end else raise _("Bolt must be installed to use the script application") end end def main # The tasks feature is always on Puppet[:tasks] = true # Set the puppet code or file to use. if options[:code] || command_line.args.length == 0 Puppet[:code] = options[:code] || STDIN.read else manifest = command_line.args.shift raise _("Could not find file %{manifest}") % { manifest: manifest } unless Puppet::FileSystem.exist?(manifest) Puppet.warning(_("Only one file can be used per run. Skipping %{files}") % { files: command_line.args.join(', ') }) if command_line.args.size > 0 end unless Puppet[:node_name_fact].empty? # Collect the facts specified for that node unless facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value]) raise _("Could not find facts for %{node}") % { node: Puppet[:node_name_value] } end Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end # Find the Node unless node = Puppet::Node.indirection.find(Puppet[:node_name_value]) raise _("Could not find node %{node}") % { node: Puppet[:node_name_value] } end configured_environment = node.environment || Puppet.lookup(:current_environment) apply_environment = manifest ? configured_environment.override_with(:manifest => manifest) : configured_environment # Modify the node descriptor to use the special apply_environment. # It is based on the actual environment from the node, or the locally # configured environment if the node does not specify one. # If a manifest file is passed on the command line, it overrides # the :manifest setting of the apply_environment. node.environment = apply_environment # TRANSLATION, the string "For puppet script" is not user facing Puppet.override({:current_environment => apply_environment}, "For puppet script") do # Merge in the facts. node.merge(facts.values) if facts # Add server facts so $server_facts[environment] exists when doing a puppet script # SCRIPT TODO: May be needed when running scripts under orchestrator. Leave it for now. # node.add_server_facts({}) begin # Compile the catalog # When compiling, the compiler traps and logs certain errors # Those that do not lead to an immediate exit are caught by the general # rule and gets logged. # begin # support the following features when evaluating puppet code # * $facts with facts from host running the script # * $settings with 'settings::*' namespace populated, and '$settings::all_local' hash # * $trusted as setup when using puppet apply # * an environment # # fixup trusted information node.sanitize() compiler = Puppet::Parser::ScriptCompiler.new(node.environment, node.name) topscope = compiler.topscope # When scripting the trusted data are always local, but set them anyway topscope.set_trusted(node.trusted_data) # Server facts are always about the local node's version etc. topscope.set_server_facts(node.server_facts) # Set $facts for the node running the script facts_hash = node.facts.nil? ? {} : node.facts.values topscope.set_facts(facts_hash) # create the $settings:: variables topscope.merge_settings(node.environment.name, false) compiler.compile() rescue Puppet::ParseErrorWithIssue, Puppet::Error # already logged and handled by the compiler for these two cases exit(1) end exit(0) rescue => detail Puppet.log_exception(detail) exit(1) end end ensure if @profiler Puppet::Util::Profiler.remove_profiler(@profiler) @profiler.shutdown end end def setup exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet::Util::Log.newdestination(:console) unless options[:setdest] Signal.trap(:INT) do $stderr.puts _("Exiting") exit(1) end # TODO: This skips applying the settings catalog for these settings, but # the effect of doing this is unknown. It may be that it only works if there is a puppet # installed where a settings catalog have already been applied... # This saves 1/5th of the startup time # Puppet.settings.use :main, :agent, :ssl # When running a script, the catalog is not relevant, and neither is caching of it Puppet::Resource::Catalog.indirection.cache_class = nil # we do not want the last report to be persisted Puppet::Transaction::Report.indirection.cache_class = nil set_log_level if Puppet[:profile] @profiler = Puppet::Util::Profiler.add_profiler(Puppet::Util::Profiler::Aggregate.new(Puppet.method(:info), "script")) end end end