diff --git a/acceptance/tests/language/resource_refs_with_nested_arrays.rb b/acceptance/tests/language/resource_refs_with_nested_arrays.rb new file mode 100644 index 000000000..7bc797885 --- /dev/null +++ b/acceptance/tests/language/resource_refs_with_nested_arrays.rb @@ -0,0 +1,27 @@ +test_name "#7681: Allow using array variables in resource references" + +test_manifest = < "echo the first command", + path => "/usr/bin:/bin", + logoutput => true, +} +exec { "second": + command => "echo the second command", + path => "/usr/bin:/bin", + logoutput => true, +} +exec { "third": + command => "echo the final command", + path => "/usr/bin:/bin", + logoutput => true, + require => Exec[$exec_names], +} +MANIFEST + +results = apply_manifest_on agents, test_manifest + +results.each do |result| + assert_match(/Exec\[third\].*the final command/, "#{result.stdout}") +end diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index 19849c57a..06a158fb3 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -1,481 +1,493 @@ require 'puppet/application' class Puppet::Application::Agent < Puppet::Application should_parse_config run_mode :agent attr_accessor :args, :agent, :daemon, :host def preinit # Do an initial trap, so that cancels don't get a stack trace. Signal.trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end { :waitforcert => nil, :detailed_exitcodes => false, :verbose => false, :debug => false, :centrallogs => false, :setdest => false, :enable => false, :disable => false, :client => true, :fqdn => nil, :serve => [], :digest => :MD5, :fingerprint => false, }.each do |opt,val| options[opt] = val end @args = {} require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end option("--centrallogging") option("--disable") option("--enable") option("--debug","-d") option("--fqdn FQDN","-f") option("--test","-t") option("--verbose","-v") option("--fingerprint") option("--digest DIGEST") option("--serve HANDLER", "-s") do |arg| if Puppet::Network::Handler.handler(arg) options[:serve] << arg.to_sym else raise "Could not find handler for #{arg}" end end option("--no-client") do |arg| options[:client] = false end option("--detailed-exitcodes") do |arg| options[:detailed_exitcodes] = true end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail puts detail.backtrace if Puppet[:debug] $stderr.puts detail.to_s end end option("--waitforcert WAITFORCERT", "-w") do |arg| options[:waitforcert] = arg.to_i end option("--port PORT","-p") do |arg| @args[:Port] = arg end def help <<-HELP puppet-agent(8) -- The puppet agent daemon ======== SYNOPSIS -------- Retrieves the client configuration from the puppet master and applies it to the local host. This service may be run as a daemon, run periodically using cron (or something similar), or run interactively for testing purposes. USAGE ----- puppet agent [-D|--daemonize|--no-daemonize] [-d|--debug] [--detailed-exitcodes] [--disable] [--enable] [-h|--help] [--certname ] [-l|--logdest syslog||console] [-o|--onetime] [--serve ] [-t|--test] [--noop] [--digest ] [--fingerprint] [-V|--version] [-v|--verbose] [-w|--waitforcert ] DESCRIPTION ----------- This is the main puppet client. Its job is to retrieve the local machine's configuration from a remote server and apply it. In order to successfully communicate with the remote server, the client must have a certificate signed by a certificate authority that the server trusts; the recommended method for this, at the moment, is to run a certificate authority as part of the puppet server (which is the default). The client will connect and request a signed certificate, and will continue connecting until it receives one. Once the client has a signed certificate, it will retrieve its configuration and apply it. USAGE NOTES ----------- 'puppet agent' does its best to find a compromise between interactive use and daemon use. Run with no arguments and no configuration, it will go into the background, attempt to get a signed certificate, and retrieve and apply its configuration every 30 minutes. Some flags are meant specifically for interactive use -- in particular, 'test', 'tags' or 'fingerprint' are useful. 'test' enables verbose logging, causes the daemon to stay in the foreground, exits if the server's configuration is invalid (this happens if, for instance, you've left a syntax error on the server), and exits after running the configuration once (rather than hanging around as a long-running process). 'tags' allows you to specify what portions of a configuration you want to apply. Puppet elements are tagged with all of the class or definition names that contain them, and you can use the 'tags' flag to specify one of these names, causing only configuration elements contained within that class or definition to be applied. This is very useful when you are testing new configurations -- for instance, if you are just starting to manage 'ntpd', you would put all of the new elements into an 'ntpd' class, and call puppet with '--tags ntpd', which would only apply that small portion of the configuration during your testing, rather than applying the whole thing. 'fingerprint' is a one-time flag. In this mode 'puppet agent' will run once and display on the console (and in the log) the current certificate (or certificate request) fingerprint. Providing the '--digest' option allows to use a different digest algorithm to generate the fingerprint. The main use is to verify that before signing a certificate request on the master, the certificate request the master received is the same as the one the client sent (to prevent against man-in-the-middle attacks when signing certificates). OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'server' is a valid configuration parameter, so you can specify '--server ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet agent with '--genconfig'. * --daemonize: Send the process into the background. This is the default. * --no-daemonize: Do not send the process into the background. * --debug: Enable full debugging. * --digest: Change the certificate fingerprinting digest algorithm. The default is MD5. Valid values depends on the version of OpenSSL installed, but should always at least contain MD5, MD2, SHA1 and SHA256. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, and an exit code of '4' means that there were failures during the transaction. This option only makes sense in conjunction with --onetime. * --disable: Disable working on the local system. This puts a lock file in place, causing 'puppet agent' not to work on the system until the lock file is removed. This is useful if you are testing a configuration and do not want the central configuration to override the local state until everything is tested and committed. 'puppet agent' uses the same lock file while it is running, so no more than one 'puppet agent' process is working at a time. 'puppet agent' exits after executing this. * --enable: Enable working on the local system. This removes any lock file, causing 'puppet agent' to start managing the local system again (although it will continue to use its normal scheduling, so it might not start for another half hour). 'puppet agent' exits after executing this. * --certname: Set the certname (unique ID) of the client. The master reads this unique identifying string, which is usually set to the node's fully-qualified domain name, to determine which configurations the node will receive. Use this option to debug setup problems or implement unusual node identification schemes. * --help: Print this help message * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. * --no-client: Do not create a config client. This will cause the daemon to run without ever checking for its configuration automatically, and only makes sense * --onetime: Run the configuration once. Runs a single (normally daemonized) Puppet run. Useful for interactively running puppet agent when used in conjunction with the --no-daemonize option. * --fingerprint: Display the current certificate or certificate signing request fingerprint and then exit. Use the '--digest' option to change the digest algorithm used. * --serve: Start another type of server. By default, 'puppet agent' will start a service handler that allows authenticated and authorized remote nodes to trigger the configuration to be pulled down and applied. You can specify any handler here that does not require configuration, e.g., filebucket, ca, or resource. The handlers are in 'lib/puppet/network/handler', and the names must match exactly, both in the call to 'serve' and in 'namespaceauth.conf'. * --test: Enable the most common options used for testing. These are 'onetime', 'verbose', 'ignorecache', 'no-daemonize', 'no-usecacheonfailure', 'detailed-exit-codes', 'no-splay', and 'show_diff'. * --noop: Use 'noop' mode where the daemon runs in a no-op or dry-run mode. This is useful for seeing what changes Puppet will make without actually executing the changes. * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. * --waitforcert: This option only matters for daemons that do not yet have certificates and it is enabled by default, with a value of 120 (seconds). This causes 'puppet agent' to connect to the server every 2 minutes and ask it to sign a certificate request. This is useful for the initial setup of a puppet client. You can turn off waiting for certificates by specifying a time of 0. EXAMPLE ------- $ puppet agent --server puppet.domain.com +DIAGNOSTICS +----------- + +Puppet agent accepts the following signals: + +* SIGHUP: + Restart the puppet agent daemon. +* SIGINT and SIGTERM: + Shut down the puppet agent daemon. +* SIGUSR1: + Immediately retrieve and apply configurations from the puppet master. + AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command return fingerprint if options[:fingerprint] return onetime if Puppet[:onetime] main end def fingerprint unless cert = host.certificate || host.certificate_request $stderr.puts "Fingerprint asked but no certificate nor certificate request have yet been issued" exit(1) return end unless fingerprint = cert.fingerprint(options[:digest]) raise ArgumentError, "Could not get fingerprint for digest '#{options[:digest]}'" end Puppet.notice fingerprint end def onetime unless options[:client] $stderr.puts "onetime is specified but there is no client" exit(43) return end @daemon.set_signal_traps begin report = @agent.run rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err detail.to_s end if not report exit(1) elsif options[:detailed_exitcodes] then exit(report.exit_status) else exit(0) end end def main Puppet.notice "Starting Puppet client version #{Puppet.version}" @daemon.start end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--ignorecache") Puppet.settings.handlearg("--no-usecacheonfailure") Puppet.settings.handlearg("--no-splay") Puppet.settings.handlearg("--show_diff") Puppet.settings.handlearg("--no-daemonize") options[:verbose] = true Puppet[:onetime] = true options[:detailed_exitcodes] = true end # Handle the logging settings. def setup_logs 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 Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] end def enable_disable_client(agent) if options[:enable] agent.enable elsif options[:disable] agent.disable end exit(0) end def setup_listen unless FileTest.exists?(Puppet[:rest_authconfig]) Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" exit(14) end handlers = nil if options[:serve].empty? handlers = [:Runner] else handlers = options[:serve] end require 'puppet/network/server' # No REST handlers yet. server = Puppet::Network::Server.new(:xmlrpc_handlers => handlers, :port => Puppet[:puppetport]) @daemon.server = server end def setup_host @host = Puppet::SSL::Host.new waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : 120) cert = @host.wait_for_cert(waitforcert) unless options[:fingerprint] end def setup setup_test if options[:test] setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? # If noop is set, then also enable diffs Puppet[:show_diff] = true if Puppet[:noop] args[:Server] = Puppet[:server] if options[:fqdn] args[:FQDN] = options[:fqdn] Puppet[:certname] = options[:fqdn] end if options[:centrallogs] logdest = args[:Server] logdest += ":" + args[:Port] if args.include?(:Port) Puppet::Util::Log.newdestination(logdest) end Puppet.settings.use :main, :agent, :ssl # Always ignoreimport for agent. It really shouldn't even try to import, # but this is just a temporary band-aid. Puppet[:ignoreimport] = true # We need to specify a ca location for all of the SSL-related i # indirected classes to work; in fingerprint mode we just need # access to the local files and we don't need a ca. Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote Puppet::Transaction::Report.indirection.terminus_class = :rest # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml # Override the default; puppetd needs this, usually. # You can still override this on the command-line with, e.g., :compiler. Puppet[:catalog_terminus] = :rest # Override the default. Puppet[:facts_terminus] = :facter Puppet::Resource::Catalog.indirection.cache_class = :yaml # We need tomake the client either way, we just don't start it # if --no-client is set. require 'puppet/agent' require 'puppet/configurer' @agent = Puppet::Agent.new(Puppet::Configurer) enable_disable_client(@agent) if options[:enable] or options[:disable] @daemon.agent = agent if options[:client] # It'd be nice to daemonize later, but we have to daemonize before the # waitforcert happens. @daemon.daemonize if Puppet[:daemonize] setup_host @objects = [] # This has to go after the certs are dealt with. if Puppet[:listen] unless Puppet[:onetime] setup_listen else Puppet.notice "Ignoring --listen on onetime run" end end end end diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb index 7a5ce3400..ea5ba4aaf 100644 --- a/lib/puppet/application/face_base.rb +++ b/lib/puppet/application/face_base.rb @@ -1,232 +1,246 @@ require 'puppet/application' require 'puppet/face' require 'optparse' require 'pp' class Puppet::Application::FaceBase < Puppet::Application should_parse_config run_mode :agent option("--debug", "-d") do |arg| Puppet::Util::Log.level = :debug end option("--verbose", "-v") do Puppet::Util::Log.level = :info end option("--render-as FORMAT") do |format| self.render_as = format.to_sym end option("--mode RUNMODE", "-r") do |arg| raise "Invalid run mode #{arg}; supported modes are user, agent, master" unless %w{user agent master}.include?(arg) self.class.run_mode(arg.to_sym) set_run_mode self.class.run_mode end attr_accessor :face, :action, :type, :arguments, :render_as def render_as=(format) if format == :json then @render_as = Puppet::Network::FormatHandler.format(:pson) else @render_as = Puppet::Network::FormatHandler.format(format) end @render_as or raise ArgumentError, "I don't know how to render '#{format}'" end def render(result) # Invoke the rendering hook supplied by the user, if appropriate. if hook = action.when_rendering(render_as.name) result = hook.call(result) end render_as.render(result) end def preinit super Signal.trap(:INT) do $stderr.puts "Cancelling Face" exit(0) end end def parse_options # We need to parse enough of the command line out early, to identify what # the action is, so that we can obtain the full set of options to parse. # REVISIT: These should be configurable versions, through a global # '--version' option, but we don't implement that yet... --daniel 2011-03-29 @type = self.class.name.to_s.sub(/.+:/, '').downcase.to_sym @face = Puppet::Face[@type, :current] # Now, walk the command line and identify the action. We skip over # arguments based on introspecting the action and all, and find the first # non-option word to use as the action. - action = nil - index = -1 - until @action or (index += 1) >= command_line.args.length do + action_name = nil + index = -1 + until action_name or (index += 1) >= command_line.args.length do item = command_line.args[index] if item =~ /^-/ then option = @face.options.find do |name| item =~ /^-+#{name.to_s.gsub(/[-_]/, '[-_]')}(?:[ =].*)?$/ end if option then option = @face.get_option(option) # If we have an inline argument, just carry on. We don't need to # care about optional vs mandatory in that case because we do a real # parse later, and that will totally take care of raising the error # when we get there. --daniel 2011-04-04 if option.takes_argument? and !item.index('=') then index += 1 unless (option.optional_argument? and command_line.args[index + 1] =~ /^-/) end elsif option = find_global_settings_argument(item) then unless Puppet.settings.boolean? option.name then # As far as I can tell, we treat non-bool options as always having # a mandatory argument. --daniel 2011-04-05 index += 1 # ...so skip the argument. end elsif option = find_application_argument(item) then - index += 1 if (option[:argument] and option[:optional]) + index += 1 if (option[:argument] and not option[:optional]) else raise OptionParser::InvalidOption.new(item.sub(/=.*$/, '')) end else - @action = @face.get_action(item.to_sym) + # Stash away the requested action name for later, and try to fetch the + # action object it represents; if this is an invalid action name that + # will be nil, and handled later. + action_name = item.to_sym + @action = @face.get_action(action_name) end end if @action.nil? if @action = @face.get_default_action() then @is_default_action = true else - Puppet.err "#{face.name} does not have a default action, and no action was given" - Puppet.err Puppet::Face[:help, :current].help(@face.name) + # REVISIT: ...and this horror thanks to our log setup, which doesn't + # initialize destinations until the setup method, which we will never + # reach. We could also just print here, but that is actually a little + # uglier and nastier in the long term, in which we should do log setup + # earlier if at all possible. --daniel 2011-05-31 + Puppet::Util::Log.newdestination(:console) + + face = @face.name + action = action_name.nil? ? 'default' : "'#{action_name}'" + msg = "'#{face}' has no #{action} action. See `puppet help #{face}`." + Puppet.err(msg) + exit false end end # Now we can interact with the default option code to build behaviour # around the full set of options we now know we support. @action.options.each do |option| option = @action.get_option(option) # make it the object. self.class.option(*option.optparse) # ...and make the CLI parse it. end # ...and invoke our parent to parse all the command line options. super end def find_global_settings_argument(item) Puppet.settings.each do |name, object| object.optparse_args.each do |arg| next unless arg =~ /^-/ # sadly, we have to emulate some of optparse here... pattern = /^#{arg.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ pattern.match item and return object end end return nil # nothing found. end def find_application_argument(item) self.class.option_parser_commands.each do |options, function| options.each do |option| next unless option =~ /^-/ pattern = /^#{option.sub('[no-]', '').sub(/[ =].*$/, '')}(?:[ =].*)?$/ next unless pattern.match(item) return { :argument => option =~ /[ =]/, :optional => option =~ /[ =]\[/ } end end return nil # not found end def setup Puppet::Util::Log.newdestination :console @arguments = command_line.args # Note: because of our definition of where the action is set, we end up # with it *always* being the first word of the remaining set of command # line arguments. So, strip that off when we construct the arguments to # pass down to the face action. --daniel 2011-04-04 # Of course, now that we have default actions, we should leave the # "action" name on if we didn't actually consume it when we found our # action. @arguments.delete_at(0) unless @is_default_action # We copy all of the app options to the end of the call; This allows each # action to read in the options. This replaces the older model where we # would invoke the action with options set as global state in the # interface object. --daniel 2011-03-28 @arguments << options # If we don't have a rendering format, set one early. self.render_as ||= (@action.render_as || :console) end def main status = false # Call the method associated with the provided action (e.g., 'find'). unless @action puts Puppet::Face[:help, :current].help(@face.name) raise "#{face} does not respond to action #{arguments.first}" end # We need to do arity checking here because this is generic code # calling generic methods – that have argument defaulting. We need to # make sure we don't accidentally pass the options as the first # argument to a method that takes one argument. eg: # # puppet facts find # => options => {} # @arguments => [{}] # => @face.send :bar, {} # # def face.bar(argument, options = {}) # => bar({}, {}) # oops! we thought the options were the # # positional argument!! # # We could also fix this by making it mandatory to pass the options on # every call, but that would make the Ruby API much more annoying to # work with; having the defaulting is a much nicer convention to have. # # We could also pass the arguments implicitly, by having a magic # 'options' method that was visible in the scope of the action, which # returned the right stuff. # # That sounds attractive, but adds complications to all sorts of # things, especially when you think about how to pass options when you # are writing Ruby code that calls multiple faces. Especially if # faces are involved in that. ;) # # --daniel 2011-04-27 if (arity = @action.positional_arg_count) > 0 unless (count = arguments.length) == arity then s = arity == 2 ? '' : 's' raise ArgumentError, "puppet #{@face.name} #{@action.name} takes #{arity-1} argument#{s}, but you gave #{count-1}" end end result = @face.send(@action.name, *arguments) puts render(result) unless result.nil? status = true rescue Exception => detail puts detail.backtrace if Puppet[:trace] Puppet.err detail.to_s Puppet.err "Try 'puppet help #{@face.name} #{@action.name}' for usage" ensure exit status end end diff --git a/lib/puppet/application/faces.rb b/lib/puppet/application/faces.rb deleted file mode 100644 index e7fce66b1..000000000 --- a/lib/puppet/application/faces.rb +++ /dev/null @@ -1,126 +0,0 @@ -require 'puppet/application' -require 'puppet/face' - -class Puppet::Application::Faces < Puppet::Application - - should_parse_config - run_mode :agent - - option("--debug", "-d") do |arg| - Puppet::Util::Log.level = :debug - end - - option("--verbose", "-v") do - Puppet::Util::Log.level = :info - end - - def help - <<-HELP -puppet-faces(8) -- List available Faces and actions -======== - -SYNOPSIS --------- -Lists the available subcommands (with applicable terminuses and/or actions) -provided by the Puppet Faces API. This information is automatically read -from the Puppet code present on the system. By default, the output includes -all terminuses and actions. - -USAGE ------ -puppet faces [-d|--debug] [-v|--verbose] [actions|terminuses] - -OPTIONS -------- -Note that any configuration option valid in the configuration file is also -a valid long argument. See the configuration file documentation at -http://docs.puppetlabs.com/references/stable/configuration.html for the -full list of acceptable parameters. A commented list of all -configuration options can also be generated by running puppet agent with -'--genconfig'. - -* --verbose: - Sets the log level to "info." This option has no tangible effect at the time - of this writing. - -* --debug: - Sets the log level to "debug." This option has no tangible effect at the time - of this writing. - -AUTHOR ------- -Puppet Labs - -COPYRIGHT ---------- -Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License - - HELP - end - - def list(*arguments) - if arguments.empty? - arguments = %w{terminuses actions} - end - faces.each do |name| - str = "#{name}:\n" - if arguments.include?("terminuses") - begin - terms = terminus_classes(name.to_sym) - str << "\tTerminuses: #{terms.join(", ")}\n" - rescue => detail - puts detail.backtrace if Puppet[:trace] - $stderr.puts "Could not load terminuses for #{name}: #{detail}" - end - end - - if arguments.include?("actions") - begin - actions = actions(name.to_sym) - str << "\tActions: #{actions.join(", ")}\n" - rescue => detail - puts detail.backtrace if Puppet[:trace] - $stderr.puts "Could not load actions for #{name}: #{detail}" - end - end - - print str - end - end - - attr_accessor :name, :arguments - - def main - list(*arguments) - end - - def setup - Puppet::Util::Log.newdestination :console - - load_applications # Call this to load all of the apps - - @arguments = command_line.args - @arguments ||= [] - end - - def faces - Puppet::Face.faces - end - - def terminus_classes(indirection) - Puppet::Indirector::Terminus.terminus_classes(indirection).collect { |t| t.to_s }.sort - end - - def actions(indirection) - return [] unless face = Puppet::Face[indirection, '0.0.1'] - face.load_actions - return face.actions.sort { |a, b| a.to_s <=> b.to_s } - end - - def load_applications - command_line.available_subcommands.each do |app| - command_line.require_application app - end - end -end - diff --git a/lib/puppet/application/help.rb b/lib/puppet/application/help.rb index 4829a2036..66baa462e 100644 --- a/lib/puppet/application/help.rb +++ b/lib/puppet/application/help.rb @@ -1,7 +1,4 @@ require 'puppet/application/face_base' class Puppet::Application::Help < Puppet::Application::FaceBase - # Meh. Disable the default behaviour, which is to inspect the - # string and return that – not so helpful. --daniel 2011-04-11 - def render(result) result end end diff --git a/lib/puppet/application/man.rb b/lib/puppet/application/man.rb new file mode 100644 index 000000000..1ecc4d691 --- /dev/null +++ b/lib/puppet/application/man.rb @@ -0,0 +1,4 @@ +require 'puppet/application/face_base' + +class Puppet::Application::Man < Puppet::Application::FaceBase +end diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index a90829ae0..18425c8bc 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -1,231 +1,241 @@ require 'puppet/application' class Puppet::Application::Master < Puppet::Application should_parse_config run_mode :master option("--debug", "-d") option("--verbose", "-v") # internal option, only to be used by ext/rack/config.ru option("--rack") option("--compile host", "-c host") do |arg| options[:node] = arg end option("--logdest DEST", "-l DEST") do |arg| begin Puppet::Util::Log.newdestination(arg) options[:setdest] = true rescue => detail puts detail.backtrace if Puppet[:debug] $stderr.puts detail.to_s end end option("--parseonly") do puts "--parseonly has been removed. Please use 'puppet parser validate '" exit 1 end def help <<-HELP puppet-master(8) -- The puppet master daemon ======== SYNOPSIS -------- The central puppet server. Functions as a certificate authority by default. USAGE ----- puppet master [-D|--daemonize|--no-daemonize] [-d|--debug] [-h|--help] [-l|--logdest |console|syslog] [-v|--verbose] [-V|--version] [--compile ] DESCRIPTION ----------- This command starts an instance of puppet master, running as a daemon and using Ruby's built-in Webrick webserver. Puppet master can also be managed by other application servers; when this is the case, this executable is not used. OPTIONS ------- Note that any configuration parameter that's valid in the configuration file is also a valid long argument. For example, 'ssldir' is a valid configuration parameter, so you can specify '--ssldir ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/stable/configuration.html for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet master with '--genconfig'. * --daemonize: Send the process into the background. This is the default. * --no-daemonize: Do not send the process into the background. * --debug: Enable full debugging. * --help: Print this help message. * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to syslog, or the console if debugging or verbosity is enabled. * --verbose: Enable verbosity. * --version: Print the puppet version number and exit. * --compile: Compile a catalogue and output it in JSON from the puppet master. Uses facts contained in the $vardir/yaml/ directory to compile the catalog. EXAMPLE ------- puppet master +DIAGNOSTICS +----------- + +When running as a standalone daemon, puppet master accepts the +following signals: + +* SIGHUP: + Restart the puppet master server. +* SIGINT and SIGTERM: + Shut down the puppet master server. AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def preinit Signal.trap(:INT) do $stderr.puts "Cancelling startup" exit(0) end # Create this first-off, so we have ARGV require 'puppet/daemon' @daemon = Puppet::Daemon.new @daemon.argv = ARGV.dup end def run_command if options[:node] compile else main end end def compile Puppet::Util::Log.newdestination :console raise ArgumentError, "Cannot render compiled catalogs without pson support" unless Puppet.features.pson? begin unless catalog = Puppet::Resource::Catalog.indirection.find(options[:node]) raise "Could not compile catalog for #{options[:node]}" end jj catalog.to_resource rescue => detail $stderr.puts detail exit(30) end exit(0) end def main require 'etc' require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' xmlrpc_handlers = [:Status, :FileServer, :Master, :Report, :Filebucket] xmlrpc_handlers << :CA if Puppet[:ca] # Make sure we've got a localhost ssl cert Puppet::SSL::Host.localhost # And now configure our server to *only* hit the CA for data, because that's # all it will have write access to. Puppet::SSL::Host.ca_location = :only if Puppet::SSL::CertificateAuthority.ca? if Puppet.features.root? begin Puppet::Util.chuser rescue => detail puts detail.backtrace if Puppet[:trace] $stderr.puts "Could not change user to #{Puppet[:user]}: #{detail}" exit(39) end end unless options[:rack] require 'puppet/network/server' @daemon.server = Puppet::Network::Server.new(:xmlrpc_handlers => xmlrpc_handlers) @daemon.daemonize if Puppet[:daemonize] else require 'puppet/network/http/rack' @app = Puppet::Network::HTTP::Rack.new(:xmlrpc_handlers => xmlrpc_handlers, :protocols => [:rest, :xmlrpc]) end Puppet.notice "Starting Puppet master version #{Puppet.version}" unless options[:rack] @daemon.start else return @app end end def setup # Handle the logging settings. if options[:debug] or options[:verbose] if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end unless Puppet[:daemonize] or options[:rack] Puppet::Util::Log.newdestination(:console) options[:setdest] = true end end Puppet::Util::Log.newdestination(:syslog) unless options[:setdest] exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet.settings.use :main, :master, :ssl, :metrics # Cache our nodes in yaml. Currently not configurable. Puppet::Node.indirection.cache_class = :yaml # Configure all of the SSL stuff. if Puppet::SSL::CertificateAuthority.ca? Puppet::SSL::Host.ca_location = :local Puppet.settings.use :ca Puppet::SSL::CertificateAuthority.instance else Puppet::SSL::Host.ca_location = :none end end end diff --git a/lib/puppet/face/catalog.rb b/lib/puppet/face/catalog.rb index 4624313bc..13351540a 100644 --- a/lib/puppet/face/catalog.rb +++ b/lib/puppet/face/catalog.rb @@ -1,108 +1,116 @@ require 'puppet/indirector/face' Puppet::Indirector::Face.define(:catalog, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Compile, save, view, and convert catalogs." - description <<-EOT + description <<-'EOT' This face primarily interacts with the compiling subsystem. By default, it compiles a catalog using the default manifest and the hostname from `certname`, but you can choose to retrieve a catalog from the server by - specifying `--terminus rest`. You can also choose to print any catalog + specifying '--terminus rest'. You can also choose to print any catalog in 'dot' format (for easy graph viewing with OmniGraffle or Graphviz) with '--render-as dot'. EOT - notes <<-EOT - This is an indirector face, which exposes find, search, save, and - destroy actions for an indirected subsystem of Puppet. Valid terminuses - for this face include: - - * `active_record` - * `compiler` - * `queue` - * `rest` - * `yaml` - EOT + + get_action(:destroy).summary "Invalid for this face." + get_action(:search).summary "Query format unknown; potentially invalid for this face." action(:apply) do - summary "Apply a Puppet::Resource::Catalog object" - description <<-EOT - Applies a catalog object retrieved with the `download` action. This - action cannot consume a serialized catalog, and is not intended for - command-line use." + summary "Apply a Puppet::Resource::Catalog object." + description <<-'EOT' + Finds and applies a catalog. This action takes no arguments, but + the source of the catalog can be managed with the --terminus option. EOT - notes <<-EOT - This action returns a Puppet::Transaction::Report object. + returns <<-'EOT' + A Puppet::Transaction::Report object. EOT - examples <<-EOT - From `secret_agent.rb`: + examples <<-'EOT' + Apply the locally cached catalog: - Puppet::Face[:plugin, '0.0.1'].download + $ puppet catalog apply --terminus yaml + + Retrieve a catalog from the master and apply it, in one step: - facts = Puppet::Face[:facts, '0.0.1'].find(certname) - catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts) - report = Puppet::Face[:catalog, '0.0.1'].apply(catalog) + $ puppet catalog apply --terminus rest - Puppet::Face[:report, '0.0.1'].submit(report) + From `secret_agent.rb` (API example): + + # ... + Puppet::Face[:catalog, '0.0.1'].download + # (Termini are singletons; catalog.download has a side effect of + # setting the catalog terminus to yaml) + report = Puppet::Face[:catalog, '0.0.1'].apply + # ... EOT when_invoked do |options| catalog = Puppet::Face[:catalog, "0.0.1"].find(Puppet[:certname]) or raise "Could not find catalog for #{Puppet[:certname]}" catalog = catalog.to_ral report = Puppet::Transaction::Report.new("apply") report.configuration_version = catalog.version Puppet::Util::Log.newdestination(report) begin benchmark(:notice, "Finished catalog run") do catalog.apply(:report => report) end rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Failed to apply catalog: #{detail}" end report.finalize_report report end end action(:download) do - summary "Download this node's catalog from the puppet master server" - description <<-EOT - Retrieves a catalog from the puppet master. Unlike the `find` action, - `download` submits facts to the master as part of the request. This - action is not intended for command-line use. + summary "Download this node's catalog from the puppet master server." + description <<-'EOT' + Retrieves a catalog from the puppet master and saves it to the + local yaml cache. The saved catalog can be used in subsequent + catalog actions by specifying '--terminus rest'. + + This action always contacts the puppet master and will ignore + alternate termini. + EOT + returns "Nothing." + notes <<-'EOT' + As termini are singletons, this action has a side effect of + exporting Puppet::Resource::Catalog.indirection.terminus_class = + yaml to the calling context when used with the Ruby Faces API. The + terminus must be explicitly re-set for subsequent catalog actions. EOT - notes "This action returns a Puppet::Resource::Catalog object." - examples <<-EOT - From `secret_agent.rb`: + examples <<-'EOT' + Retrieve and store a catalog: - Puppet::Face[:plugin, '0.0.1'].download + $ puppet catalog download - facts = Puppet::Face[:facts, '0.0.1'].find(certname) - catalog = Puppet::Face[:catalog, '0.0.1'].download(certname, facts) - report = Puppet::Face[:catalog, '0.0.1'].apply(catalog) + From `secret_agent.rb` (API example): - Puppet::Face[:report, '0.0.1'].submit(report) + Puppet::Face[:plugin, '0.0.1'].download + Puppet::Face[:facts, '0.0.1'].upload + Puppet::Face[:catalog, '0.0.1'].download + # ... EOT when_invoked do |options| Puppet::Resource::Catalog.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.cache_class = nil catalog = nil retrieval_duration = thinmark do catalog = Puppet::Face[:catalog, '0.0.1'].find(Puppet[:certname]) end catalog.retrieval_duration = retrieval_duration catalog.write_class_file Puppet::Resource::Catalog.indirection.terminus_class = :yaml Puppet::Face[:catalog, "0.0.1"].save(catalog) Puppet.notice "Saved catalog for #{Puppet[:certname]} to yaml" nil end end end diff --git a/lib/puppet/face/catalog/select.rb b/lib/puppet/face/catalog/select.rb index a6c97a627..d86963e75 100644 --- a/lib/puppet/face/catalog/select.rb +++ b/lib/puppet/face/catalog/select.rb @@ -1,42 +1,49 @@ # Select and show a list of resources of a given type. Puppet::Face.define(:catalog, '0.0.1') do action :select do summary "Select and show a list of resources of a given type" - description <<-EOT + arguments " " + returns <<-'EOT' + An array of resource objects excised from a catalog. When used at + the command line, returns a list of resource references (Type[title]). + EOT + description <<-'EOT' Retrieves a catalog for the specified host and returns an array of - resources of the given type. This action is not intended for - command-line use. + resources of the given type. EOT - notes <<-NOTES - The type name for this action must be given in its capitalized form. - That is, calling `catalog select mynode file` will return an empty - array, whereas calling it with 'File' will return a list of the node's - file resources. - + notes <<-'NOTES' By default, this action will retrieve a catalog from Puppet's compiler subsystem; you must call the action with `--terminus rest` if you wish to retrieve a catalog from the puppet master. + + FORMATTING ISSUES: This action cannot currently render useful yaml; + instead, it returns an entire catalog. Use json instead. NOTES + examples <<-'EOT' + Ask the puppet master for a list of managed file resources for a node: + + $ puppet catalog select --terminus rest somenode.magpie.lan file + EOT when_invoked do |host, type, options| # REVISIT: Eventually, type should have a default value that triggers # the non-specific behaviour. For now, though, this will do. # --daniel 2011-05-03 catalog = Puppet::Resource::Catalog.indirection.find(host) if type == '*' catalog.resources else type = type.downcase catalog.resources.reject { |res| res.type.downcase != type } end end when_rendering :console do |value| if value.nil? then "no matching resources found" else value.map {|x| x.to_s }.join("\n") end end end end diff --git a/lib/puppet/face/certificate.rb b/lib/puppet/face/certificate.rb index 859946623..cab8817e3 100644 --- a/lib/puppet/face/certificate.rb +++ b/lib/puppet/face/certificate.rb @@ -1,76 +1,111 @@ require 'puppet/indirector/face' require 'puppet/ssl/host' Puppet::Indirector::Face.define(:certificate, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Provide access to the CA for certificate management" - description <<-EOT + description <<-'EOT' This face interacts with a local or remote Puppet certificate - authority. Currently, its behavior is not a full superset of puppet - cert; specifically, it is unable to mimic puppet cert's "clean" option, + authority. Currently, its behavior is not a full superset of `puppet + cert`; specifically, it is unable to mimic puppet cert's "clean" option, and its "generate" action submits a CSR rather than creating a signed certificate. EOT - notes <<-EOT - This is an indirector face, which exposes find, search, save, and - destroy actions for an indirected subsystem of Puppet. Valid terminuses - for this face include: - - * `ca` - * `file` - * `rest` - EOT option "--ca-location LOCATION" do summary "The certificate authority to query" - description <<-EOT + description <<-'EOT' Whether to act on the local certificate authority or one provided by a remote puppet master. Allowed values are 'local' and 'remote.' EOT before_action do |action, args, options| Puppet::SSL::Host.ca_location = options[:ca_location].to_sym end end action :generate do - summary "Generate a new certificate signing request for HOST" - description <<-EOT + summary "Generate a new certificate signing request for HOST." + arguments "" + returns "Nothing." + description <<-'EOT' Generates and submits a certificate signing request (CSR) for the - provided host identifier. This CSR will then have to be signed by a user + specified host. This CSR will then have to be signed by a user with the proper authorization on the certificate authority. - Puppet agent handles CSR submission automatically. This action is + Puppet agent usually handles CSR submission automatically. This action is primarily useful for requesting certificates for individual users and external applications. EOT + examples <<-'EOT' + Request a certificate for "somenode" from the site's CA: + + $ puppet certificate generate somenode.puppetlabs.lan --ca-location remote + EOT when_invoked do |name, options| host = Puppet::SSL::Host.new(name) host.generate_certificate_request host.certificate_request.class.indirection.save(host.certificate_request) end end action :list do - summary "List all certificate signing requests" + summary "List all certificate signing requests." + returns <<-'EOT' + An array of CSR object #inspect strings. This output is currently messy, + but does contain the names of nodes requesting certificates. + EOT when_invoked do |options| Puppet::SSL::Host.indirection.search("*", { :for => :certificate_request, }).map { |h| h.inspect } end end action :sign do - summary "Sign a certificate signing request for HOST" + summary "Sign a certificate signing request for HOST." + arguments "" + returns <<-'EOT' + A string that appears to be an x509 certificate, but is actually + not. Retrieve certificates using the `find` action. + EOT + examples <<-'EOT' + Sign somenode.puppetlabs.lan's certificate: + + $ puppet certificate sign somenode.puppetlabs.lan --ca-location remote + EOT when_invoked do |name, options| host = Puppet::SSL::Host.new(name) host.desired_state = 'signed' Puppet::SSL::Host.indirection.save(host) end end + + # Indirector action doc overrides + find = get_action(:find) + find.summary "Retrieve a certificate" + find.arguments "" + find.returns <<-'EOT' + An x509 SSL certificate. You will usually want to render this as a + string ('--render-as s'). + + Note that this action has a side effect of caching a copy of the + certificate in Puppet's `ssldir`. + EOT + + destroy = get_action(:destroy) + destroy.summary "Delete a local certificate." + destroy.arguments "" + destroy.returns "Nothing." + destroy.description <<-'EOT' + Deletes a certificate. This action currently only works with a local CA. + EOT + + get_action(:search).summary "Invalid for this face." + get_action(:save).summary "Invalid for this face." end diff --git a/lib/puppet/face/certificate_request.rb b/lib/puppet/face/certificate_request.rb index cc6021517..29cf7dc78 100644 --- a/lib/puppet/face/certificate_request.rb +++ b/lib/puppet/face/certificate_request.rb @@ -1,32 +1,45 @@ require 'puppet/indirector/face' Puppet::Indirector::Face.define(:certificate_request, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Manage certificate requests." - description <<-EOT + description <<-'EOT' Retrieves and submits certificate signing requests (CSRs). Invoke - `search` with an unread key to retrieve all outstanding CSRs, invoke + `search` with a dummy key to retrieve all outstanding CSRs, invoke `find` with a node certificate name to retrieve a specific request, and invoke `save` to submit a CSR. EOT - notes <<-EOT - This is an indirector face, which exposes find, search, save, and - destroy actions for an indirected subsystem of Puppet. Valid terminuses - for this face include: - - * `ca` - * `file` - * `rest` - EOT - examples <<-EOT - Retrieve all CSRs from the local CA: - puppet certificate_request search no_key --terminus ca + # Per-action doc overrides + get_action(:destroy).summary "Invalid for this face." + get_action(:find).summary "Retrieve a single CSR." + get_action(:find).arguments "" + get_action(:find).returns <<-'EOT' + A single certificate request. In most cases, you will want to render + this as a string ('--render-as s'). + EOT + get_action(:find).examples <<-'EOT' Retrieve a single CSR from the puppet master's CA: - puppet certificate_request find mynode.puppetlabs.lan --terminus rest + $ puppet certificate_request find somenode.puppetlabs.lan --terminus rest + EOT + + get_action(:search).summary "Retrieve all outstanding CSRs." + get_action(:search).arguments "" + get_action(:search).returns <<-'EOT' + An array of certificate request objects. In most cases, you will + want to render this as a string ('--render-as s'). EOT + get_action(:search).notes "This action always returns all CSRs, but requires a dummy search key." + get_action(:search).examples <<-'EOT' + Retrieve all CSRs from the local CA: + + $ puppet certificate_request search x --terminus ca + EOT + + get_action(:save).summary "Submit a certificate signing request." + get_action(:save).arguments "" end diff --git a/lib/puppet/face/certificate_revocation_list.rb b/lib/puppet/face/certificate_revocation_list.rb index 2722b20f2..0525a601f 100644 --- a/lib/puppet/face/certificate_revocation_list.rb +++ b/lib/puppet/face/certificate_revocation_list.rb @@ -1,30 +1,39 @@ require 'puppet/indirector/face' Puppet::Indirector::Face.define(:certificate_revocation_list, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" summary "Manage the list of revoked certificates." - description <<-EOT + description <<-'EOT' This face is primarily for retrieving the certificate revocation list from the CA. Although it exposes search/save/destroy methods, they shouldn't be used under normal circumstances. EOT - notes <<-EOT - Although the find action must be given an argument, this argument is - never read, and can contain the descriptive text of your choice. - This is an indirector face, which exposes find, search, save, and - destroy actions for an indirected subsystem of Puppet. Valid terminuses - for this face include: - - * `ca` - * `file` - * `rest` + get_action(:find).summary "Retrieve the certificate revocation list." + get_action(:find).arguments "" + get_action(:find).returns <<-'EOT' + A certificate revocation list. You will usually want to render this + as a string ('--render-as s'). EOT - examples <<-EXAMPLES - Retrieve the CRL: + get_action(:find).examples <<-'EXAMPLES' + Retrieve a copy of the puppet master's CRL: - puppet certificate_revocation_list find crl + $ puppet certificate_revocation_list find crl --terminus rest EXAMPLES + + get_action(:destroy).summary "Delete the certificate revocation list." + get_action(:destroy).arguments "" + get_action(:destroy).returns "Nothing." + get_action(:destroy).description <<-'EOT' + Deletes the certificate revocation list. This cannot be done over + REST, but it is possible to both delete the locally cached copy of + the CA's CRL and delete the CA's own copy (if running on the CA + machine and invoked with '--terminus ca'). Needless to say, don't do + this unless you know what you're up to. + EOT + + get_action(:search).summary "Invalid for this face." + get_action(:save).summary "Invalid for this face." end diff --git a/lib/puppet/face/config.rb b/lib/puppet/face/config.rb index 9ca41085e..6e5bff071 100644 --- a/lib/puppet/face/config.rb +++ b/lib/puppet/face/config.rb @@ -1,48 +1,47 @@ require 'puppet/face' Puppet::Face.define(:config, '0.0.1') do copyright "Puppet Labs", 2011 license "Apache 2 license; see COPYING" - summary "Interact with Puppet configuration options" + summary "Interact with Puppet's configuration options." action(:print) do - summary "Examine Puppet's current configuration options" - description <<-EOT + summary "Examine Puppet's current configuration options." + arguments "(all |