diff --git a/ext/nagios/naggen b/ext/nagios/naggen index 6d5be7b0f..0d0e5824f 100755 --- a/ext/nagios/naggen +++ b/ext/nagios/naggen @@ -1,309 +1,309 @@ #!/usr/bin/env ruby # # = Synopsis # # Generate Nagios configurations from Puppet Resources in an ActiveRecord database # # = Usage # # naggen [-h|--help] [-d|--debug] [-v|--verbose] [--compare] # # = Description # # This executable is a means of short-circuiting the process of generating nagios # configurations on your server using Puppet Exported Resources. It skips any possible # naming conflicts resulting from Puppet's resource uniqueness requirements, and it # also skips the inefficiencies involved in converting and transporting large numbers # of Puppet resources. # # At the least, the machine that runs this will need ActiveRecord (2.0.2) installed, # along with any database libraries you use. # # = Options # -# Note that any configuration parameter that's valid in the configuration file +# Note that any setting 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. # # You can add naggen-specific settings to your puppet.conf in a '[naggen]' section, # just like any other executable. # # See the configuration file documentation at # http://reductivelabs.com/projects/puppet/reference/configref.html for # the full list of acceptable parameters. A commented list of all # configuration options can also be generated by running puppet with # '--genconfig'. # # compare:: # Compare new and old files and only backup and write if the files are different. # Potentially expensive computationally, but idempotent. Will exit with 0 if # no changes were made and 1 if there were. # # debug:: # Enable full debugging. # # 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. # # help:: # Print this help message # # verbose:: # Print extra information. # # = Example # # naggen --storeconfigs --confdir /foo --compare # # # = License # Copyright 2011 Luke Kanies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require 'puppet' require 'puppet/rails' require 'puppet/rails/resource' require 'puppet/rails/param_value' require 'puppet/network/client' require 'puppet/parser/collector' require 'puppet/provider/naginator' require 'getoptlong' # Monkey-patch the rails resources so we can # easily convert them to nagios instances. class Puppet::Rails::Resource def to_param_hash values = @params_hash || Puppet::Rails::ParamValue.find_all_params_from_resource(self) if values.size == 0 return {} end values.inject({}) do |hash, value| hash[value['name']] ||= [] hash[value['name']] << value["value"] hash end end def to_nagios unless nagios_type = Nagios::Base.type(restype.sub("Nagios_", '').to_sym) raise Puppet::DevError, "Could not find nagios type '%s'" % restype end result = nagios_type.new to_param_hash.each do |param, value| next unless nagios_type.parameter?(param) result[param] = value end result[:name] = self.title result end end class NagiosWriter class FakeScope def debug(string) Puppet.debug string end def host "this host doesn't exist" end end attr_accessor :nagios_type, :bucket def backup(target) return unless FileTest.exist?(target) and File.stat(target).size > 0 Puppet.info "Backing up %s" % target bucket.backup(target) end def collector collector = Puppet::Parser::Collector.new(FakeScope.new, "nagios_" + @nagios_type.to_s, nil, nil, :exported) # We don't have a scope, so we're stubbing everything out that would interact # with the scope. class << collector def collect_virtual(*args) [] end def exported_resource(res) res end end collector end def default_target "/etc/nagios/nagios_#{nagios_type.to_s}.cfg" end def evaluate return unless resources = rails_resources() resources_by_target = resources.inject({}) do |hash, resource| target = resource["target"] || default_target hash[target] ||= [] hash[target] << resource hash end changed = false resources_by_target.each do |target, resources| begin result = write(target, resources) rescue => detail $stderr.puts detail.backtrace Puppet.err "Could not write to %s: %s" % [target, detail] end changed = true if result end changed end def initialize(nagios_type) @nagios_type = nagios_type @bucket = Puppet::FileBucket::Dipper.new(:Path => Puppet[:clientbucketdir]) end def rails_resources collector.send(:collect_exported) end def write(target, resources) # Skip the nagios type when we have no resources and no existing # file. return if resources.empty? and ! FileTest.exist?(target) dir = File.dirname(target) unless FileTest.exist?(dir) FileUtils.mkdir_p(dir) end count = 0 tempfile = target + ".tmp" File.open(tempfile, "w") do |file| resources.each do |resource| count += 1 file.puts resource.to_nagios.to_s.gsub("_naginator_name", Puppet::Provider::Naginator::NAME_STRING) end end if $options[:compare] if FileTest.exist?(target) and File.read(tempfile) == File.read(target) return false end end backup(target) # Atomic rename File.rename(tempfile, target) Puppet.notice "Wrote %s resources to %s" % [count, target] return true ensure File.unlink(tempfile) if tempfile and FileTest.exist?(tempfile) end end arguments = [ [ "--compare", "-c", GetoptLong::NO_ARGUMENT ], [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ] ] Puppet.settings.addargs(arguments) result = GetoptLong.new(*arguments) $options = {} result.each { |opt,arg| case opt when "--help" begin require 'rdoc/usage' RDoc::usage && exit rescue LoadError docs = [] File.readlines(__FILE__).each do |line| next if line =~ /^#\!/ unless line =~ /^#/ next if docs.length == 0 # skip the first line or so break # else, we've passed the docs, so just break end docs << line.sub(/^# ?/, '') end print docs exit end when "--compare" $options[:compare] = true when "--verbose" $options[:verbose] = true when "--debug" $options[:debug] = true when "--debug" $options[:debug] = true else Puppet.settings.handlearg(opt, arg) end } # Read in Puppet settings, so we know how Puppet's configured. Puppet.initialize_settings Puppet::Util::Log.newdestination(:console) if $options[:debug] Puppet::Util::Log.level = :debug elsif $options[:verbose] Puppet::Util::Log.level = :info end # See if Naginator is installed directly, else load Puppet's version. begin require 'nagios' rescue LoadError require 'puppet/external/nagios' end changed = false Nagios::Base.eachtype do |name, type| writer = NagiosWriter.new(name) changed = true if writer.evaluate end if $options[:compare] and changed exit(1) else exit(0) end diff --git a/ext/puppet-test b/ext/puppet-test index 2078806e7..47c464f86 100755 --- a/ext/puppet-test +++ b/ext/puppet-test @@ -1,566 +1,566 @@ #!/usr/bin/env ruby # == Synopsis # # Test individual client performance. Can compile configurations, describe # files, or retrieve files. # # = Usage # # puppet-test [-c|--compile] [-D|--describe ] [-d|--debug] # [--fork ] [-h|--help] [-H|--hostname ] [-l|--list] [-r|--repeat ] # [-R|--retrieve ] [-t|--test ] [-V|--version] [-v|--verbose] # # = Description # # This is a simple script meant for doing performance tests with Puppet. By # default it pulls down a compiled configuration, but it supports multiple # other tests. # # = Options # -# Note that any configuration parameter that's valid in the configuration file +# Note that any setting 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://reductivelabs.com/projects/puppet/reference/configref.html for # the full list of acceptable parameters. A commented list of all # configuration $options can also be generated by running puppetd with # '--genconfig'. # # compile:: # Compile the client's configuration. The default. # # debug:: # Enable full debugging. # # describe:: # Describe the file being tested. This is a query to get information about # the file from the server, to determine if it should be copied, and is the # first half of every file transfer. # # fork:: # Fork the specified number of times, thus acting as multiple clients. # # fqdn:: # Set the fully-qualified domain name of the client. This is only used for # certificate purposes, but can be used to override the discovered hostname. # If you need to use this flag, it is generally an indication of a setup problem. # # help:: # Print this help message # # list:: # List all available tests. # # node:: # Specify the node to use. This is useful for looking up cached yaml data # in your :clientyaml directory, and forcing a specific host's configuration to # get compiled. # # pause:: # Pause before starting test (useful for testing with dtrace). # # repeat:: # How many times to perform the test. # # retrieve:: # Test file retrieval performance. Retrieves the specified file from the # remote system. Note that the server should be specified via --server, # so the argument to this option is just the remote module name and path, # e.g., "/dist/apps/myapp/myfile", where "dist" is the module and # "apps/myapp/myfile" is the path to the file relative to the module. # # test:: # Specify the test to run. You can see the list of tests by running this command with --list. # # verbose:: # Turn on verbose reporting. # # version:: # Print the puppet version number and exit. # # = Example # # puppet-test --retrieve /module/path/to/file # # = License # Copyright 2011 Luke Kanies # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # Do an initial trap, so that cancels don't get a stack trace. trap(:INT) do $stderr.puts "Cancelling startup" exit(1) end require 'puppet' require 'puppet/network/client' require 'getoptlong' class Suite attr_reader :name, :doc @@suites = {} @@tests = {} def self.[](name) @@suites[name] end # Run a test by first finding the suite then running the appropriate test. def self.run(test) unless suite_name = @@tests[test] raise "Unknown test %s" % test end unless suite = @@suites[suite_name] raise "Unknown test suite %s from test %s" % [suite_name, test] end suite.run(test) end # What suites are available? def self.suites @@suites.keys end def forked? defined? @forking end # Create a new test suite. def initialize(name, doc, &block) @name = name @doc = doc @tests = {} @@suites[name] = self raise "You must pass a block to the Test" unless block_given? instance_eval(&block) end # Define a new type of test on this suite. def newtest(name, doc, &block) @tests[name] = doc if @@tests[name] raise "Test names must be unique; cannot redefine %s" % name end @@tests[name] = @name meta_def(name, &block) end # Run the actual test. def run(test) unless doc = @tests[test] raise "Suite %s only supports tests %s; not %s" % [@name, @tests.keys.collect { |k| k.to_s }.join(","), test] end puts "Running %s %s test" % [@name, test] prepare() if respond_to?(:prepare) if $options[:pause] puts "Hit any key to continue" $stdin.readline puts "Continuing with test" end if $options[:fork] > 0 @forking = true $options[:fork].times { if pid = fork $pids << pid else break end } end $options[:repeat].times do |i| @count = i if forked? msg = doc + " in PID %s" % Process.pid else msg = doc end Puppet::Util.benchmark(:notice, msg) do begin send(test) rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "%s failed: %s" % [@name, detail.to_s] end end end end # What tests are available on this suite? def tests @tests.keys end end Suite.new :parser, "Manifest parsing" do newtest :parse, "Parsed files" do @parser = Puppet::Parser::Parser.new(:environment => Puppet[:environment]) @parser.file = Puppet[:manifest] @parser.parse end end Suite.new :local_catalog, "Local catalog handling" do def prepare @node = Puppet::Node.find($options[:nodes][0]) end newtest :compile, "Compiled catalog" do Puppet::Resource::Catalog.find(@node) end end Suite.new :resource_type, "Managing resource types" do newtest :find, "Find a type" do Puppet::Resource::Type.terminus_class = :parser ARGV.each do |name| json = Puppet::Resource::Type.find(name).to_pson data = PSON.parse(json) p Puppet::Resource::Type.from_data_hash(data) end end newtest :search_types, "Find all types" do Puppet::Resource::Type.terminus_class = :rest result = Puppet::Resource::Type.search("*") result.each { |r| p r } end newtest :restful_type, "Find a type and return it via REST" do Puppet::Resource::Type.terminus_class = :rest ARGV.each do |name| p Puppet::Resource::Type.find(name) end end end Suite.new :remote_catalog, "Remote catalog handling" do def prepare $args[:cache] = false # Create a config client and pull the config down @client = Puppet::Network::Client.master.new($args) unless @client.read_cert fail "Could not read client certificate" end if tmp = Puppet::Node::Facts.find($options[:nodes][0]) @facts = tmp.values else raise "Could not find facts for %s" % $optins[:nodes][0] end if host = $options[:fqdn] @facts["fqdn"] = host @facts["hostname"] = host.sub(/\..+/, '') @facts["domain"] = host.sub(/^[^.]+\./, '') end @facts = YAML.dump(@facts) end newtest :getconfig, "Compiled catalog" do @client.driver.getconfig(@facts, "yaml") end # This test will always force a false answer. newtest :fresh, "Checked freshness" do @client.driver.freshness end end Suite.new :file, "File interactions" do def prepare unless $options[:file] fail "You must specify a file (using --file ) to interact with on the server" end @client = Puppet::Network::Client.file.new($args) unless @client.read_cert fail "Could not read client certificate" end end newtest :describe, "Described file" do @client.describe($options[:file], :ignore) end newtest :retrieve, "Retrieved file" do @client.retrieve($options[:file], :ignore) end end Suite.new :filebucket, "Filebucket interactions" do def prepare require 'tempfile' @client = Puppet::FileBucket::Dipper.new($args) end newtest :backup, "Backed up file" do Tempfile.open("bucket_testing") do |f| f.print rand(1024) f.close @client.backup(f.path) end end end # Note that this uses an env variable to determine how many resources per # host to create (with a default of 10). 'repeat' determines how # many hosts to create. You probably will get mad failures if you # use forking and sqlite. # Here's an example run of this test, using sqlite: # RESOURCE_COUNT=50 ext/puppet-test --suite rails --test storage --confdir /tmp/storagetesting --vardir /tmp/storagetesting --repeat 10 Suite.new :rails, "Rails Interactions" do def prepare Puppet::Rails.init @facts = Facter.to_hash if num = ENV["RESOURCECOUNT"] @resources = Integer(num) else @resources = 10 end end Resource = Puppet::Parser::Resource def execute(test, msg) begin send(test) rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "%s failed: %s" % [@name, detail.to_s] end end def mkresource(type, title, parameters) source = "fakesource" res = Resource.new(:type => type, :title => title, :source => source, :scope => "fakescope") parameters.each do |param, value| res.set(param, value, source) end res end def ref(type, title) Resource::Reference.new(:type => type, :title => title) end newtest :storage, "Stored resources" do hostname = "host%s" % @count @facts["hostname"] = hostname args = {:facts => @facts, :name => hostname} # Make all of the resources we want. Make them at least slightly complex, # so we model real resources as close as we can. resources = [] args[:resources] = resources @resources.times do |resnum| exec = mkresource("exec", "exec%s" % resnum, :command => "/bin/echo do something %s" % resnum) exec.tags = %w{exec one} << "exec%s" % resnum user = mkresource("user", "user%s" % resnum, :uid => resnum.to_s, :require => ref("exec", "exec%s" % resnum)) user.tags = %w{user one two} << "user%s" % resnum file = mkresource("file", "/tmp/file%s" % resnum, :owner => resnum.to_s, :require => [ref("exec", "exec%s" % resnum), ref("user", "user%s" % resnum)]) file.tags = %w{file one three} << "file%s" % resnum file.exported = true resources << exec << user << file end Puppet::Rails::Host.store(args) end end Suite.new :report, "Reports interactions" do def prepare Puppet::Transaction::Report.terminus_class = :rest end newtest :empty, "send empty report" do report = Puppet::Transaction::Report.new report.time = Time.now report.save end newtest :fake, "send fake report" do report = Puppet::Transaction::Report.new resourcemetrics = { :total => 12, :out_of_sync => 20, :applied => 45, :skipped => 1, :restarted => 23, :failed_restarts => 1, :scheduled => 10 } report.newmetric(:resources, resourcemetrics) timemetrics = { :resource1 => 10, :resource2 => 50, :resource3 => 40, :resource4 => 20, } report.newmetric(:times, timemetrics) report.newmetric(:changes, :total => 20 ) report.time = Time.now report.save end end $cmdargs = [ [ "--compile", "-c", GetoptLong::NO_ARGUMENT ], [ "--describe", GetoptLong::REQUIRED_ARGUMENT ], [ "--retrieve", "-R", GetoptLong::REQUIRED_ARGUMENT ], [ "--fork", GetoptLong::REQUIRED_ARGUMENT ], [ "--fqdn", "-F", GetoptLong::REQUIRED_ARGUMENT ], [ "--suite", "-s", GetoptLong::REQUIRED_ARGUMENT ], [ "--test", "-t", GetoptLong::REQUIRED_ARGUMENT ], [ "--pause", "-p", GetoptLong::NO_ARGUMENT ], [ "--repeat", "-r", GetoptLong::REQUIRED_ARGUMENT ], [ "--node", "-n", GetoptLong::REQUIRED_ARGUMENT ], [ "--debug", "-d", GetoptLong::NO_ARGUMENT ], [ "--help", "-h", GetoptLong::NO_ARGUMENT ], [ "--list", "-l", GetoptLong::NO_ARGUMENT ], [ "--verbose", "-v", GetoptLong::NO_ARGUMENT ], [ "--version", "-V", GetoptLong::NO_ARGUMENT ], ] # Add all of the config parameters as valid $options. Puppet.settings.addargs($cmdargs) Puppet::Util::Log.newdestination(:console) Puppet::Node.terminus_class = :plain Puppet::Node.cache_class = :yaml Puppet::Node::Facts.terminus_class = :facter Puppet::Node::Facts.cache_class = :yaml result = GetoptLong.new(*$cmdargs) $args = {} $options = {:repeat => 1, :fork => 0, :pause => false, :nodes => []} begin explicit_waitforcert = false result.each { |opt,arg| case opt - # First check to see if the argument is a valid configuration parameter; + # First check to see if the argument is a valid setting; # if so, set it. when "--compile" $options[:suite] = :configuration $options[:test] = :compile when "--retrieve" $options[:suite] = :file $options[:test] = :retrieve $options[:file] = arg when "--fork" begin $options[:fork] = Integer(arg) rescue => detail $stderr.puts "The argument to 'fork' must be an integer" exit(14) end when "--describe" $options[:suite] = :file $options[:test] = :describe $options[:file] = arg when "--fqdn" $options[:fqdn] = arg when "--repeat" $options[:repeat] = Integer(arg) when "--help" if Puppet.features.usage? RDoc::usage && exit else puts "No help available unless you have RDoc::usage installed" exit end when "--version" puts "%s" % Puppet.version exit when "--verbose" Puppet::Util::Log.level = :info Puppet::Util::Log.newdestination(:console) when "--debug" Puppet::Util::Log.level = :debug Puppet::Util::Log.newdestination(:console) when "--suite" $options[:suite] = arg.intern when "--test" $options[:test] = arg.intern when "--file" $options[:file] = arg when "--pause" $options[:pause] = true when "--node" $options[:nodes] << arg when "--list" Suite.suites.sort { |a,b| a.to_s <=> b.to_s }.each do |suite_name| suite = Suite[suite_name] tests = suite.tests.sort { |a,b| a.to_s <=> b.to_s }.join(", ") puts "%20s: %s" % [suite_name, tests] end exit(0) else Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail $stderr.puts detail $stderr.puts "Try '#{$0} --help'" exit(1) end # Now parse the config Puppet.initialize_settings $options[:nodes] << Puppet.settings[:certname] if $options[:nodes].empty? $args[:Server] = Puppet[:server] unless $options[:test] $options[:suite] = :remote_catalog $options[:test] = :getconfig end unless $options[:test] raise "A suite was specified without a test" end $pids = [] Suite.run($options[:test]) if $options[:fork] > 0 Process.waitall end diff --git a/ext/upload_facts.rb b/ext/upload_facts.rb index 786c921d8..6e4e30792 100755 --- a/ext/upload_facts.rb +++ b/ext/upload_facts.rb @@ -1,119 +1,119 @@ #!/usr/bin/env ruby require 'net/https' require 'openssl' require 'openssl/x509' require 'optparse' require 'pathname' require 'yaml' require 'puppet' require 'puppet/network/http_pool' class Puppet::Application::UploadFacts < Puppet::Application run_mode :master option('--debug', '-d') option('--verbose', '-v') option('--logdest DEST', '-l DEST') do |arg| Puppet::Util::Log.newdestination(arg) options[:setdest] = true end option('--minutes MINUTES', '-m MINUTES') do |minutes| options[:time_limit] = 60 * minutes.to_i end def help print <] [-l|--logdest syslog||console] = Description This command will read YAML facts from the puppet master's YAML directory, and save them to the configured facts_terminus. It is intended to be used with the facts_terminus set to inventory_service, in order to ensure facts which have been cached locally due to a temporary failure are still uploaded to the inventory service. = Usage Notes upload_facts is intended to be run from cron, with the facts_terminus set to inventory_service. The +--minutes+ argument should be set to the length of time between upload_facts runs. This will ensure that only new YAML files are uploaded. = Options -Note that any configuration parameter that's valid in the configuration file +Note that any setting 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'. minutes:: Limit the upload only to YAML files which have been added within the last n minutes. HELP exit 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 Puppet::Util::Log.newdestination(:console) unless options[:setdest] end exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? end def main dir = Pathname.new(Puppet[:yamldir]) + 'facts' cutoff = options[:time_limit] ? Time.now - options[:time_limit] : Time.at(0) files = dir.children.select do |file| file.extname == '.yaml' && file.mtime > cutoff end failed = false terminus = Puppet::Node::Facts.indirection.terminus files.each do |file| facts = YAML.load_file(file) request = Puppet::Indirector::Request.new(:facts, :save, facts) # The terminus warns for us if we fail. if terminus.save(request) Puppet.info "Uploaded facts for #{facts.name} to inventory service" else failed = true end end exit !failed end end Puppet::Application::UploadFacts.new.run diff --git a/lib/puppet.rb b/lib/puppet.rb index ece55c6ab..6b1cee7e5 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -1,247 +1,247 @@ require 'puppet/version' # see the bottom of the file for further inclusions # Also see the new Vendor support - towards the end # require 'facter' require 'puppet/error' require 'puppet/util' require 'puppet/util/autoload' require 'puppet/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' require 'puppet/util/run_mode' require 'puppet/external/pson/common' require 'puppet/external/pson/version' require 'puppet/external/pson/pure' #------------------------------------------------------------ # the top-level module # # all this really does is dictate how the whole system behaves, through # preferences for things like debugging # # it's also a place to find top-level commands like 'debug' # The main Puppet class. Everything is contained here. # # @api public module Puppet require 'puppet/file_system' require 'puppet/context' require 'puppet/environments' class << self include Puppet::Util attr_reader :features attr_writer :name end # the hash that determines how our system behaves @@settings = Puppet::Settings.new # The services running in this process. @services ||= [] require 'puppet/util/logging' extend Puppet::Util::Logging # The feature collection @features = Puppet::Util::Feature.new('puppet/feature') # Load the base features. require 'puppet/feature/base' # Store a new default value. def self.define_settings(section, hash) @@settings.define_settings(section, hash) end # Get the value for a setting # # @param [Symbol] param the setting to retrieve # # @api public def self.[](param) if param == :debug return Puppet::Util::Log.level == :debug else return @@settings[param] end end - # configuration parameter access and stuff + # setting access and stuff def self.[]=(param,value) @@settings[param] = value end def self.clear @@settings.clear end def self.debug=(value) if value Puppet::Util::Log.level=(:debug) else Puppet::Util::Log.level=(:notice) end end def self.settings @@settings end def self.run_mode # This sucks (the existence of this method); there are a lot of places in our code that branch based the value of # "run mode", but there used to be some really confusing code paths that made it almost impossible to determine # when during the lifecycle of a puppet application run the value would be set properly. A lot of the lifecycle # stuff has been cleaned up now, but it still seems frightening that we rely so heavily on this value. # # I'd like to see about getting rid of the concept of "run_mode" entirely, but there are just too many places in # the code that call this method at the moment... so I've settled for isolating it inside of the Settings class # (rather than using a global variable, as we did previously...). Would be good to revisit this at some point. # # --cprice 2012-03-16 Puppet::Util::RunMode[@@settings.preferred_run_mode] end - # Load all of the configuration parameters. + # Load all of the settings. require 'puppet/defaults' def self.genmanifest if Puppet[:genmanifest] puts Puppet.settings.to_manifest exit(0) end end # Parse the config file for this process. # @deprecated Use {initialize_settings} def self.parse_config() Puppet.deprecation_warning("Puppet.parse_config is deprecated; please use Faces API (which will handle settings and state management for you), or (less desirable) call Puppet.initialize_settings") Puppet.initialize_settings end # Initialize puppet's settings. This is intended only for use by external tools that are not # built off of the Faces API or the Puppet::Util::Application class. It may also be used # to initialize state so that a Face may be used programatically, rather than as a stand-alone # command-line tool. # # @api public # @param args [Array] the command line arguments to use for initialization # @return [void] def self.initialize_settings(args = []) do_initialize_settings_for_run_mode(:user, args) end # Initialize puppet's settings for a specified run_mode. # # @deprecated Use {initialize_settings} def self.initialize_settings_for_run_mode(run_mode) Puppet.deprecation_warning("initialize_settings_for_run_mode may be removed in a future release, as may run_mode itself") do_initialize_settings_for_run_mode(run_mode, []) end # private helper method to provide the implementation details of initializing for a run mode, # but allowing us to control where the deprecation warning is issued def self.do_initialize_settings_for_run_mode(run_mode, args) Puppet.settings.initialize_global_settings(args) run_mode = Puppet::Util::RunMode[run_mode] Puppet.settings.initialize_app_defaults(Puppet::Settings.app_defaults_for_run_mode(run_mode)) Puppet.push_context(Puppet.base_context(Puppet.settings), "Initial context after settings initialization") Puppet::Parser::Functions.reset end private_class_method :do_initialize_settings_for_run_mode # Create a new type. Just proxy to the Type class. The mirroring query # code was deprecated in 2008, but this is still in heavy use. I suppose # this can count as a soft deprecation for the next dev. --daniel 2011-04-12 def self.newtype(name, options = {}, &block) Puppet::Type.newtype(name, options, &block) end # Load vendored (setup paths, and load what is needed upfront). # See the Vendor class for how to add additional vendored gems/code require "puppet/vendor" Puppet::Vendor.load_vendored # Set default for YAML.load to unsafe so we don't affect programs # requiring puppet -- in puppet we will call safe explicitly SafeYAML::OPTIONS[:default_mode] = :unsafe # The bindings used for initialization of puppet # @api private def self.base_context(settings) environments = settings[:environmentpath] modulepath = Puppet::Node::Environment.split_path(settings[:modulepath]) loaders = Puppet::Environments::Directories.from_path(environments, modulepath) loaders << Puppet::Environments::Legacy.new { :environments => Puppet::Environments::Combined.new(*loaders) } end # A simple set of bindings that is just enough to limp along to # initialization where the {base_context} bindings are put in place # @api private def self.bootstrap_context root_environment = Puppet::Node::Environment.create(:'*root*', [], '') { :current_environment => root_environment, :root_environment => root_environment } end # @param overrides [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @api private def self.push_context(overrides, description = "") @context.push(overrides, description) end # Return to the previous context. # @raise [StackUnderflow] if the current context is the root # @api private def self.pop_context @context.pop end # Lookup a binding by name or return a default value provided by a passed block (if given). # @api private def self.lookup(name, &block) @context.lookup(name, &block) end # @param bindings [Hash] A hash of bindings to be merged with the parent context. # @param description [String] A description of the context. # @yield [] A block executed in the context of the temporarily pushed bindings. # @api private def self.override(bindings, description = "", &block) @context.override(bindings, description, &block) end require 'puppet/node' # The single instance used for normal operation @context = Puppet::Context.new(bootstrap_context) end # This feels weird to me; I would really like for us to get to a state where there is never a "require" statement # anywhere besides the very top of a file. That would not be possible at the moment without a great deal of # effort, but I think we should strive for it and revisit this at some point. --cprice 2012-03-16 require 'puppet/indirector' require 'puppet/type' require 'puppet/resource' require 'puppet/parser' require 'puppet/network' require 'puppet/ssl' require 'puppet/module' require 'puppet/data_binding' require 'puppet/util/storage' require 'puppet/status' require 'puppet/file_bucket/file' diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb index 78348f7d0..b71ec8fae 100644 --- a/lib/puppet/application/apply.rb +++ b/lib/puppet/application/apply.rb @@ -1,290 +1,290 @@ require 'puppet/application' require 'puppet/configurer' class Puppet::Application::Apply < Puppet::Application option("--debug","-d") option("--execute EXECUTE","-e") do |arg| options[:code] = arg end option("--loadclasses","-L") option("--test","-t") option("--verbose","-v") option("--use-nodes") option("--detailed-exitcodes") option("--write-catalog-summary") option("--catalog catalog", "-c catalog") do |arg| options[:catalog] = arg end option("--logdest LOGDEST", "-l") do |arg| handle_logdest_arg(arg) end option("--parseonly") do |args| puts "--parseonly has been removed. Please use 'puppet parser validate '" exit 1 end def help <<-'HELP' puppet-apply(8) -- Apply Puppet manifests locally ======== SYNOPSIS -------- Applies a standalone Puppet manifest to the local system. USAGE ----- puppet apply [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-e|--execute] [--detailed-exitcodes] [-l|--logdest ] [--noop] [--catalog ] [--write-catalog-summary] DESCRIPTION ----------- This is the standalone puppet execution tool; use it to apply individual manifests. When provided with a modulepath, via command line or config file, puppet apply can effectively mimic the catalog that would be served by puppet master with access to the same modules, although there are some subtle differences. When combined with scheduling and an automated system for pushing manifests, this can be used to implement a serverless Puppet site. Most users should use 'puppet agent' and 'puppet master' for site-wide manifests. OPTIONS ------- -Note that any configuration parameter that's valid in the configuration +Note that any setting that's valid in the configuration file is also a valid long argument. For example, 'tags' is a -valid configuration parameter, so you can specify '--tags ,' +valid setting, so you can specify '--tags ,' 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 with '--genconfig'. * --debug: Enable full debugging. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were both changes and failures. * --help: Print this help message * --loadclasses: Load any stored classes. 'puppet agent' caches configured classes (usually at /etc/puppet/classes.txt), and setting this option causes all of those classes to be set in your puppet manifest. * --logdest: Where to send messages. Choose between syslog, the console, and a log file. Defaults to sending messages to the console. * --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. * --execute: Execute a specific piece of Puppet code * --test: Enable the most common options used for testing. These are 'verbose', 'detailed-exitcodes' and 'show_diff'. * --verbose: Print extra information. * --catalog: Apply a JSON catalog (such as one generated with 'puppet master --compile'). You can either specify a JSON file or pipe in JSON from standard input. * --write-catalog-summary After compiling the catalog saves the resource list and classes list to the node in the state directory named classes.txt and resources.txt EXAMPLE ------- $ puppet apply -l /tmp/manifest.log manifest.pp $ puppet apply --modulepath=/root/dev/modules -e "include ntpd::server" $ puppet apply --catalog catalog.json AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, 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 options[:catalog] apply else main end end def apply if options[:catalog] == "-" text = $stdin.read else text = ::File.read(options[:catalog]) end catalog = read_catalog(text) apply_catalog(catalog) end def main # Set our code or file to use. if options[:code] or command_line.args.length == 0 Puppet[:code] = options[:code] || STDIN.read else manifest = command_line.args.shift raise "Could not find file #{manifest}" unless Puppet::FileSystem.exist?(manifest) Puppet.warning("Only one file can be applied per run. Skipping #{command_line.args.join(', ')}") if command_line.args.size > 0 end unless Puppet[:node_name_fact].empty? # Collect our facts. unless facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value]) raise "Could not find facts for #{Puppet[:node_name_value]}" end Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]] facts.name = Puppet[:node_name_value] end configured_environment = Puppet.lookup(:environments).get(Puppet[:environment]) apply_environment = manifest ? configured_environment.override_with(:manifest => manifest) : configured_environment Puppet.override(:environments => Puppet::Environments::Static.new(apply_environment)) do # Find our Node unless node = Puppet::Node.indirection.find(Puppet[:node_name_value]) raise "Could not find node #{Puppet[:node_name_value]}" end # Merge in the facts. node.merge(facts.values) if facts # Allow users to load the classes that puppet agent creates. if options[:loadclasses] file = Puppet[:classfile] if Puppet::FileSystem.exist?(file) unless FileTest.readable?(file) $stderr.puts "#{file} is not readable" exit(63) end node.classes = ::File.read(file).split(/[\s\n]+/) end end begin # Compile our catalog starttime = Time.now catalog = Puppet::Resource::Catalog.indirection.find(node.name, :use_node => node) # Translate it to a RAL catalog catalog = catalog.to_ral catalog.finalize catalog.retrieval_duration = Time.now - starttime if options[:write_catalog_summary] catalog.write_class_file catalog.write_resource_file end exit_status = apply_catalog(catalog) if not exit_status exit(1) elsif options[:detailed_exitcodes] then exit(exit_status) else exit(0) end rescue => detail Puppet.log_exception(detail) exit(1) end end end # Enable all of the most common test options. def setup_test Puppet.settings.handlearg("--show_diff") options[:verbose] = true options[:detailed_exitcodes] = true end def setup setup_test if options[:test] 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 # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml set_log_level if Puppet[:profile] Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(Puppet.method(:debug), "apply") end end private def read_catalog(text) begin catalog = Puppet::Resource::Catalog.convert_from(Puppet::Resource::Catalog.default_format,text) catalog = Puppet::Resource::Catalog.pson_create(catalog) unless catalog.is_a?(Puppet::Resource::Catalog) rescue => detail raise Puppet::Error, "Could not deserialize catalog from pson: #{detail}", detail.backtrace end catalog.to_ral end def apply_catalog(catalog) configurer = Puppet::Configurer.new configurer.run(:catalog => catalog, :pluginsync => false) end end diff --git a/lib/puppet/application/cert.rb b/lib/puppet/application/cert.rb index 6ad901593..818e95cd5 100644 --- a/lib/puppet/application/cert.rb +++ b/lib/puppet/application/cert.rb @@ -1,277 +1,277 @@ require 'puppet/application' require 'puppet/ssl/certificate_authority/interface' class Puppet::Application::Cert < Puppet::Application run_mode :master attr_accessor :all, :ca, :digest, :signed def subcommand @subcommand end def subcommand=(name) # Handle the nasty, legacy mapping of "clean" to "destroy". sub = name.to_sym @subcommand = (sub == :clean ? :destroy : sub) end option("--clean", "-c") do |arg| self.subcommand = "destroy" end option("--all", "-a") do |arg| @all = true end option("--digest DIGEST") do |arg| @digest = arg end option("--signed", "-s") do |arg| @signed = true end option("--debug", "-d") do |arg| Puppet::Util::Log.level = :debug end option("--list", "-l") do |arg| self.subcommand = :list end option("--revoke", "-r") do |arg| self.subcommand = :revoke end option("--generate", "-g") do |arg| self.subcommand = :generate end option("--sign", "-s") do |arg| self.subcommand = :sign end option("--print", "-p") do |arg| self.subcommand = :print end option("--verify", "-v") do |arg| self.subcommand = :verify end option("--fingerprint", "-f") do |arg| self.subcommand = :fingerprint end option("--reinventory") do |arg| self.subcommand = :reinventory end option("--[no-]allow-dns-alt-names") do |value| options[:allow_dns_alt_names] = value end option("--verbose", "-v") do |arg| Puppet::Util::Log.level = :info end def help <<-'HELP' puppet-cert(8) -- Manage certificates and requests ======== SYNOPSIS -------- Standalone certificate authority. Capable of generating certificates, but mostly used for signing certificate requests from puppet clients. USAGE ----- puppet cert [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [--digest ] [] DESCRIPTION ----------- Because the puppet master service defaults to not signing client certificate requests, this script is available for signing outstanding requests. It can be used to list outstanding requests and then either sign them individually or sign all of them. ACTIONS ------- Every action except 'list' and 'generate' requires a hostname to act on, unless the '--all' option is set. * clean: Revoke a host's certificate (if applicable) and remove all files related to that host from puppet cert's storage. This is useful when rebuilding hosts, since new certificate signing requests will only be honored if puppet cert does not have a copy of a signed certificate for that host. If '--all' is specified then all host certificates, both signed and unsigned, will be removed. * fingerprint: Print the DIGEST (defaults to the signing algorithm) fingerprint of a host's certificate. * generate: Generate a certificate for a named client. A certificate/keypair will be generated for each client named on the command line. * list: List outstanding certificate requests. If '--all' is specified, signed certificates are also listed, prefixed by '+', and revoked or invalid certificates are prefixed by '-' (the verification outcome is printed in parenthesis). * print: Print the full-text version of a host's certificate. * revoke: Revoke the certificate of a client. The certificate can be specified either by its serial number (given as a hexadecimal number prefixed by '0x') or by its hostname. The certificate is revoked by adding it to the Certificate Revocation List given by the 'cacrl' configuration option. Note that the puppet master needs to be restarted after revoking certificates. * sign: Sign an outstanding certificate request. * verify: Verify the named certificate against the local CA certificate. * reinventory: Build an inventory of the issued certificates. This will destroy the current inventory file specified by 'cert_inventory' and recreate it from the certificates found in the 'certdir'. Ensure the puppet master is stopped before running this action. OPTIONS ------- -Note that any configuration parameter that's valid in the configuration +Note that any setting 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 +setting, 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 cert with '--genconfig'. * --all: Operate on all items. Currently only makes sense with the 'sign', 'clean', 'list', and 'fingerprint' actions. * --digest: Set the digest for fingerprinting (defaults to the the digest used when signing the cert). Valid values depends on your openssl and openssl ruby extension version. * --debug: Enable full debugging. * --help: Print this help message * --verbose: Enable verbosity. * --version: Print the puppet version number and exit. EXAMPLE ------- $ puppet cert list culain.madstop.com $ puppet cert sign culain.madstop.com AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def main if @all hosts = :all elsif @signed hosts = :signed else hosts = command_line.args.collect { |h| h.downcase } end begin apply(@ca, :revoke, options.merge(:to => hosts)) if subcommand == :destroy apply(@ca, subcommand, options.merge(:to => hosts, :digest => @digest)) rescue => detail Puppet.log_exception(detail) exit(24) end end def setup require 'puppet/ssl/certificate_authority' exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? Puppet::Util::Log.newdestination :console if [:generate, :destroy].include? subcommand Puppet::SSL::Host.ca_location = :local else Puppet::SSL::Host.ca_location = :only end # If we are generating, and the option came from the CLI, it gets added to # the data. This will do the right thing for non-local certificates, in # that the command line but *NOT* the config file option will apply. if subcommand == :generate if Puppet.settings.set_by_cli?(:dns_alt_names) options[:dns_alt_names] = Puppet[:dns_alt_names] end end begin @ca = Puppet::SSL::CertificateAuthority.new rescue => detail Puppet.log_exception(detail) exit(23) end end def parse_options # handle the bareword subcommand pattern. result = super unless self.subcommand then if sub = self.command_line.args.shift then self.subcommand = sub else puts help exit end end result end # Create and run an applicator. I wanted to build an interface where you could do # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. def apply(ca, method, options) raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] applier = Puppet::SSL::CertificateAuthority::Interface.new(method, options) applier.apply(ca) end end diff --git a/lib/puppet/application/device.rb b/lib/puppet/application/device.rb index 5eef49fef..a293ab483 100644 --- a/lib/puppet/application/device.rb +++ b/lib/puppet/application/device.rb @@ -1,239 +1,239 @@ require 'puppet/application' require 'puppet/util/network_device' class Puppet::Application::Device < Puppet::Application run_mode :agent attr_accessor :args, :agent, :host def app_defaults super.merge({ :catalog_terminus => :rest, :catalog_cache_terminus => :json, :node_terminus => :rest, :facts_terminus => :network_device, }) end 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, }.each do |opt,val| options[opt] = val end @args = {} end option("--centrallogging") option("--debug","-d") option("--verbose","-v") option("--detailed-exitcodes") do |arg| options[:detailed_exitcodes] = true end option("--logdest DEST", "-l DEST") do |arg| handle_logdest_arg(arg) 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-device(8) -- Manage remote network devices ======== SYNOPSIS -------- Retrieves all configurations from the puppet master and apply them to the remote devices configured in /etc/puppet/device.conf. Currently must be run out periodically, using cron or something similar. USAGE ----- puppet device [-d|--debug] [--detailed-exitcodes] [-V|--version] [-h|--help] [-l|--logdest syslog||console] [-v|--verbose] [-w|--waitforcert ] DESCRIPTION ----------- Once the client has a signed certificate for a given remote device, it will retrieve its configuration and apply it. USAGE NOTES ----------- One need a /etc/puppet/device.conf file with the following content: [remote.device.fqdn] type url where: * type: the current device type (the only value at this time is cisco) * url: an url allowing to connect to the device Supported url must conforms to: scheme://user:password@hostname/?query with: * scheme: either ssh or telnet * user: username, can be omitted depending on the switch/router configuration * password: the connection password * query: this is device specific. Cisco devices supports an enable parameter whose value would be the enable password. OPTIONS ------- -Note that any configuration parameter that's valid in the configuration file +Note that any setting 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. * --debug: Enable full debugging. * --detailed-exitcodes: Provide transaction information via exit codes. If this is enabled, an exit code of '2' means there were changes, an exit code of '4' means there were failures during the transaction, and an exit code of '6' means there were both changes and failures. * --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: Turn on verbose reporting. * --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 device --server puppet.domain.com AUTHOR ------ Brice Figureau COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def main vardir = Puppet[:vardir] confdir = Puppet[:confdir] certname = Puppet[:certname] # find device list require 'puppet/util/network_device/config' devices = Puppet::Util::NetworkDevice::Config.devices if devices.empty? Puppet.err "No device found in #{Puppet[:deviceconfig]}" exit(1) end devices.each_value do |device| begin Puppet.info "starting applying configuration to #{device.name} at #{device.url}" # override local $vardir and $certname Puppet.settings.set_value(:confdir, ::File.join(Puppet[:devicedir], device.name), :cli) Puppet.settings.set_value(:vardir, ::File.join(Puppet[:devicedir], device.name), :cli) Puppet.settings.set_value(:certname, device.name, :cli) # this will reload and recompute default settings and create the devices sub vardir, or we hope so :-) Puppet.settings.use :main, :agent, :ssl # this init the device singleton, so that the facts terminus # and the various network_device provider can use it Puppet::Util::NetworkDevice.init(device) # ask for a ssl cert if needed, but at least # setup the ssl system for this device. setup_host require 'puppet/configurer' configurer = Puppet::Configurer.new configurer.run(:network_device => true, :pluginsync => Puppet[:pluginsync]) rescue => detail Puppet.log_exception(detail) ensure Puppet.settings.set_value(:vardir, vardir, :cli) Puppet.settings.set_value(:confdir, confdir, :cli) Puppet.settings.set_value(:certname, certname, :cli) Puppet::SSL::Host.reset end end end def setup_host @host = Puppet::SSL::Host.new waitforcert = options[:waitforcert] || (Puppet[:onetime] ? 0 : Puppet[:waitforcert]) @host.wait_for_cert(waitforcert) end def setup setup_logs args[:Server] = Puppet[:server] if options[:centrallogs] logdest = args[:Server] logdest += ":" + args[:Port] if args.include?(:Port) Puppet::Util::Log.newdestination(logdest) end Puppet.settings.use :main, :agent, :device, :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 # 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 = :remote Puppet::Transaction::Report.indirection.terminus_class = :rest if Puppet[:catalog_cache_terminus] Puppet::Resource::Catalog.indirection.cache_class = Puppet[:catalog_cache_terminus].intern end end end diff --git a/lib/puppet/application/filebucket.rb b/lib/puppet/application/filebucket.rb index 3a2e10b86..3f59f7259 100644 --- a/lib/puppet/application/filebucket.rb +++ b/lib/puppet/application/filebucket.rb @@ -1,184 +1,184 @@ require 'puppet/application' class Puppet::Application::Filebucket < Puppet::Application option("--bucket BUCKET","-b") option("--debug","-d") option("--local","-l") option("--remote","-r") option("--verbose","-v") attr :args def help <<-'HELP' puppet-filebucket(8) -- Store and retrieve files in a filebucket ======== SYNOPSIS -------- A stand-alone Puppet filebucket client. USAGE ----- puppet filebucket [-h|--help] [-V|--version] [-d|--debug] [-v|--verbose] [-l|--local] [-r|--remote] [-s|--server ] [-b|--bucket ] ... Puppet filebucket can operate in three modes, with only one mode per call: backup: Send one or more files to the specified file bucket. Each sent file is printed with its resulting md5 sum. get: Return the text associated with an md5 sum. The text is printed to stdout, and only one file can be retrieved at a time. restore: Given a file path and an md5 sum, store the content associated with the sum into the specified file path. You can specify an entirely new path to this argument; you are not restricted to restoring the content to its original location. DESCRIPTION ----------- This is a stand-alone filebucket client for sending files to a local or central filebucket. Note that 'filebucket' defaults to using a network-based filebucket available on the server named 'puppet'. To use this, you'll have to be running as a user with valid Puppet certificates. Alternatively, you can use your local file bucket by specifying '--local'. OPTIONS ------- -Note that any configuration parameter that's valid in the configuration +Note that any setting 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 +setting, 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 with '--genconfig'. * --debug: Enable full debugging. * --help: Print this help message * --local: Use the local filebucket. This will use the default configuration information. * --remote: Use a remote filebucket. This will use the default configuration information. * --server: The server to send the file to, instead of locally. * --verbose: Print extra information. * --version: Print version information. EXAMPLE ------- $ puppet filebucket backup /etc/passwd /etc/passwd: 429b225650b912a2ee067b0a4cf1e949 $ puppet filebucket restore /tmp/passwd 429b225650b912a2ee067b0a4cf1e949 AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command @args = command_line.args command = args.shift return send(command) if %w{get backup restore}.include? command help end def get md5 = args.shift out = @client.getfile(md5) print out end def backup raise "You must specify a file to back up" unless args.length > 0 args.each do |file| unless Puppet::FileSystem.exist?(file) $stderr.puts "#{file}: no such file" next end unless FileTest.readable?(file) $stderr.puts "#{file}: cannot read file" next end md5 = @client.backup(file) puts "#{file}: #{md5}" end end def restore file = args.shift md5 = args.shift @client.restore(file, md5) end def setup Puppet::Log.newdestination(:console) @client = nil @server = nil Signal.trap(:INT) do $stderr.puts "Cancelling" exit(1) end if options[:debug] Puppet::Log.level = :debug elsif options[:verbose] Puppet::Log.level = :info end exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? require 'puppet/file_bucket/dipper' begin if options[:local] or options[:bucket] path = options[:bucket] || Puppet[:bucketdir] @client = Puppet::FileBucket::Dipper.new(:Path => path) else @client = Puppet::FileBucket::Dipper.new(:Server => Puppet[:server]) end rescue => detail Puppet.log_exception(detail) exit(1) end end end diff --git a/lib/puppet/application/kick.rb b/lib/puppet/application/kick.rb index 960cdc867..f5e36dd3c 100644 --- a/lib/puppet/application/kick.rb +++ b/lib/puppet/application/kick.rb @@ -1,351 +1,351 @@ require 'puppet/application' class Puppet::Application::Kick < Puppet::Application attr_accessor :hosts, :tags, :classes option("--all","-a") option("--foreground","-f") option("--debug","-d") option("--ping","-P") option("--test") option("--ignoreschedules") option("--host HOST") do |arg| @hosts << arg end option("--tag TAG", "-t") do |arg| @tags << arg end option("--class CLASS", "-c") do |arg| @classes << arg end option("--no-fqdn", "-n") do |arg| options[:fqdn] = false end option("--parallel PARALLEL", "-p") do |arg| begin options[:parallel] = Integer(arg) rescue $stderr.puts "Could not convert #{arg.inspect} to an integer" exit(23) end end def help <<-'HELP' puppet-kick(8) -- Remotely control puppet agent ======== SYNOPSIS -------- Trigger a puppet agent run on a set of hosts. USAGE ----- puppet kick [-a|--all] [-c|--class ] [-d|--debug] [-f|--foreground] [-h|--help] [--host ] [--no-fqdn] [--ignoreschedules] [-t|--tag ] [--test] [-p|--ping] [ [...]] DESCRIPTION ----------- This script can be used to connect to a set of machines running 'puppet agent' and trigger them to run their configurations. The most common usage would be to specify a class of hosts and a set of tags, and 'puppet kick' would look up in LDAP all of the hosts matching that class, then connect to each host and trigger a run of all of the objects with the specified tags. If you are not storing your host configurations in LDAP, you can specify hosts manually. You will most likely have to run 'puppet kick' as root to get access to the SSL certificates. 'puppet kick' reads 'puppet master''s configuration file, so that it can copy things like LDAP settings. USAGE NOTES ----------- Puppet kick needs the puppet agent on the target machine to be running as a daemon, be configured to listen for incoming network connections, and have an appropriate security configuration. The specific changes required are: * Set `listen = true` in the agent's `puppet.conf` file (or `--listen` on the command line) * Configure the node's firewall to allow incoming connections on port 8139 * Insert the following stanza at the top of the node's `auth.conf` file: # Allow puppet kick access path /run method save auth any allow workstation.example.com This example would allow the machine `workstation.example.com` to trigger a Puppet run; adjust the "allow" directive to suit your site. You may also use `allow *` to allow anyone to trigger a Puppet run, but that makes it possible to interfere with your site by triggering excessive Puppet runs. See `http://docs.puppetlabs.com/guides/rest_auth_conf.html` for more details about security settings. OPTIONS ------- -Note that any configuration parameter that's valid in the configuration +Note that any setting 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 +setting, so you can specify '--ssldir ' as an argument. See the configuration file documentation at http://docs.puppetlabs.com/references/latest/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'. * --all: Connect to all available hosts. Requires LDAP support at this point. * --class: Specify a class of machines to which to connect. This only works if you have LDAP configured, at the moment. * --debug: Enable full debugging. * --foreground: Run each configuration in the foreground; that is, when connecting to a host, do not return until the host has finished its run. The default is false. * --help: Print this help message * --host: A specific host to which to connect. This flag can be specified more than once. * --ignoreschedules: Whether the client should ignore schedules when running its configuration. This can be used to force the client to perform work it would not normally perform so soon. The default is false. * --parallel: How parallel to make the connections. Parallelization is provided by forking for each client to which to connect. The default is 1, meaning serial execution. * --puppetport: Use the specified TCP port to connect to agents. Defaults to 8139. * --tag: Specify a tag for selecting the objects to apply. Does not work with the --test option. * --test: Print the hosts you would connect to but do not actually connect. This option requires LDAP support at this point. * --ping: Do an ICMP echo against the target host. Skip hosts that don't respond to ping. EXAMPLE ------- $ sudo puppet kick -p 10 -t remotefile -t webserver host1 host2 AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def run_command @hosts += command_line.args options[:test] ? test : main end def test puts "Skipping execution in test mode" exit(0) end def main Puppet.warning "Failed to load ruby LDAP library. LDAP functionality will not be available" unless Puppet.features.ldap? require 'puppet/util/ldap/connection' todo = @hosts.dup failures = [] # Now do the actual work go = true while go # If we don't have enough children in process and we still have hosts left to # do, then do the next host. if @children.length < options[:parallel] and ! todo.empty? host = todo.shift pid = safe_posix_fork do run_for_host(host) end @children[pid] = host else # Else, see if we can reap a process. begin pid = Process.wait if host = @children[pid] # Remove our host from the list of children, so the parallelization # continues working. @children.delete(pid) failures << host if $CHILD_STATUS.exitstatus != 0 print "#{host} finished with exit code #{$CHILD_STATUS.exitstatus}\n" else $stderr.puts "Could not find host for PID #{pid} with status #{$CHILD_STATUS.exitstatus}" end rescue Errno::ECHILD # There are no children left, so just exit unless there are still # children left to do. next unless todo.empty? if failures.empty? puts "Finished" exit(0) else puts "Failed: #{failures.join(", ")}" exit(3) end end end end end def run_for_host(host) if options[:ping] %x{ping -c 1 #{host}} unless $CHILD_STATUS == 0 $stderr.print "Could not contact #{host}\n" exit($CHILD_STATUS) end end require 'puppet/run' Puppet::Run.indirection.terminus_class = :rest port = Puppet[:puppetport] url = ["https://#{host}:#{port}", "production", "run", host].join('/') print "Triggering #{host}\n" begin run_options = { :tags => @tags, :background => ! options[:foreground], :ignoreschedules => options[:ignoreschedules] } run = Puppet::Run.indirection.save(Puppet::Run.new( run_options ), url) puts "Getting status" result = run.status puts "status is #{result}" rescue => detail Puppet.log_exception(detail, "Host #{host} failed: #{detail}\n") exit(2) end case result when "success"; exit(0) when "running" $stderr.puts "Host #{host} is already running" exit(3) else $stderr.puts "Host #{host} returned unknown answer '#{result}'" exit(12) end end def initialize(*args) super @hosts = [] @classes = [] @tags = [] end def preinit [:INT, :TERM].each do |signal| Signal.trap(signal) do $stderr.puts "Cancelling" exit(1) end end options[:parallel] = 1 options[:verbose] = true options[:fqdn] = true options[:ignoreschedules] = false options[:foreground] = false end def setup super() raise Puppet::Error.new("Puppet kick is not supported on Microsoft Windows") if Puppet.features.microsoft_windows? Puppet.warning "Puppet kick is deprecated. See http://links.puppetlabs.com/puppet-kick-deprecation" if options[:debug] Puppet::Util::Log.level = :debug else Puppet::Util::Log.level = :info end if Puppet[:node_terminus] == :ldap and (options[:all] or @classes) if options[:all] @hosts = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn]).collect { |node| node.name } puts "all: #{@hosts.join(", ")}" else @hosts = [] @classes.each do |klass| list = Puppet::Node.indirection.search("whatever", :fqdn => options[:fqdn], :class => klass).collect { |node| node.name } puts "#{klass}: #{list.join(", ")}" @hosts += list end end elsif ! @classes.empty? $stderr.puts "You must be using LDAP to specify host classes" exit(24) end @children = {} # If we get a signal, then kill all of our children and get out. [:INT, :TERM].each do |signal| Signal.trap(signal) do Puppet.notice "Caught #{signal}; shutting down" @children.each do |pid, host| Process.kill("INT", pid) end waitall exit(1) end end end end diff --git a/lib/puppet/application/queue.rb b/lib/puppet/application/queue.rb index 0f6e95f7b..1efca9be2 100644 --- a/lib/puppet/application/queue.rb +++ b/lib/puppet/application/queue.rb @@ -1,161 +1,161 @@ require 'puppet/application' require 'puppet/util' require 'puppet/daemon' require 'puppet/util/pidlock' class Puppet::Application::Queue < Puppet::Application attr_accessor :daemon def app_defaults() super.merge( :pidfile => "$rundir/queue.pid" ) end def preinit @argv = ARGV.dup # Do an initial trap, so that cancels don't get a stack trace. # This exits with exit code 1 Signal.trap(:INT) do $stderr.puts "Caught SIGINT; shutting down" exit(1) end # This is a normal shutdown, so code 0 Signal.trap(:TERM) do $stderr.puts "Caught SIGTERM; shutting down" exit(0) end { :verbose => false, :debug => false }.each do |opt,val| options[opt] = val end end option("--debug","-d") option("--verbose","-v") def help <<-HELP puppet-queue(8) -- Deprecated queuing daemon for asynchronous storeconfigs ======== SYNOPSIS -------- Retrieves serialized storeconfigs records from a queue and processes them in order. THIS FEATURE IS DEPRECATED; use PuppetDB instead. USAGE ----- puppet queue [-d|--debug] [-v|--verbose] DESCRIPTION ----------- This application runs as a daemon and processes storeconfigs data, retrieving the data from a stomp server message queue and writing it to a database. It was once necessary as a workaround for the poor performance of ActiveRecord-based storeconfigs, but has been supplanted by the PuppetDB service, which gives better performance with less complexity. For more information, see the PuppetDB documentation at http://docs.puppetlabs.com/puppetdb/latest OPTIONS ------- -Note that any configuration parameter that's valid in the configuration +Note that any setting 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 +setting, 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 queue with '--genconfig'. * --debug: Enable full debugging. * --help: Print this help message * --verbose: Turn on verbose reporting. * --version: Print the puppet version number and exit. EXAMPLE ------- $ puppet queue AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end option("--logdest DEST", "-l DEST") do |arg| handle_logdest_arg(arg) end def main require 'puppet/indirector/catalog/queue' # provides Puppet::Indirector::Queue.subscribe Puppet.notice "Starting puppet queue #{Puppet.version}" Puppet::Resource::Catalog::Queue.subscribe do |catalog| # Once you have a Puppet::Resource::Catalog instance, passing it to save should suffice # to put it through to the database via its active_record indirector (which is determined # by the terminus_class = :active_record setting above) Puppet::Util.benchmark(:notice, "Processing queued catalog for #{catalog.name}") do begin Puppet::Resource::Catalog.indirection.save(catalog) rescue => detail Puppet.log_exception(detail, "Could not save queued catalog for #{catalog.name}: #{detail}") end end end Thread.list.each { |thread| thread.join } end def setup Puppet.warning "Puppet queue is deprecated. See http://links.puppetlabs.com/puppet-queue-deprecation" unless Puppet.features.stomp? raise ArgumentError, "Could not load the 'stomp' library, which must be present for queueing to work. You must install the required library." end setup_logs exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? require 'puppet/resource/catalog' Puppet::Resource::Catalog.indirection.terminus_class = :store_configs daemon = Puppet::Daemon.new(Puppet::Util::Pidlock.new(Puppet[:pidfile])) daemon.argv = @argv daemon.daemonize if Puppet[:daemonize] # We want to make sure that we don't have a cache # class set up, because if storeconfigs is enabled, # we'll get a loop of continually caching the catalog # for storage again. Puppet::Resource::Catalog.indirection.cache_class = nil end end diff --git a/lib/puppet/application/resource.rb b/lib/puppet/application/resource.rb index 35f9f155b..c03181671 100644 --- a/lib/puppet/application/resource.rb +++ b/lib/puppet/application/resource.rb @@ -1,229 +1,229 @@ require 'puppet/application' class Puppet::Application::Resource < Puppet::Application attr_accessor :host, :extra_params def preinit @extra_params = [] Facter.loadfacts end option("--debug","-d") option("--verbose","-v") option("--edit","-e") option("--host HOST","-H") do |arg| Puppet.warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource") @host = arg end option("--types", "-t") do |arg| types = [] Puppet::Type.loadall Puppet::Type.eachtype do |t| next if t.name == :component types << t.name.to_s end puts types.sort exit end option("--param PARAM", "-p") do |arg| @extra_params << arg.to_sym end def help <<-'HELP' puppet-resource(8) -- The resource abstraction layer shell ======== SYNOPSIS -------- Uses the Puppet RAL to directly interact with the system. USAGE ----- puppet resource [-h|--help] [-d|--debug] [-v|--verbose] [-e|--edit] [-H|--host ] [-p|--param ] [-t|--types] [] [= ...] DESCRIPTION ----------- This command provides simple facilities for converting current system state into Puppet code, along with some ability to modify the current state using Puppet's RAL. By default, you must at least provide a type to list, in which case puppet resource will tell you everything it knows about all resources of that type. You can optionally specify an instance name, and puppet resource will only describe that single instance. If given a type, a name, and a series of = pairs, puppet resource will modify the state of the specified resource. Alternately, if given a type, a name, and the '--edit' flag, puppet resource will write its output to a file, open that file in an editor, and then apply the saved file as a Puppet transaction. OPTIONS ------- -Note that any configuration parameter that's valid in the configuration +Note that any setting 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 +setting, 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 with '--genconfig'. * --debug: Enable full debugging. * --edit: Write the results of the query to a file, open the file in an editor, and read the file back in as an executable Puppet manifest. * --host: When specified, connect to the resource server on the named host and retrieve the list of resouces of the type specified. * --help: Print this help message. * --param: Add more parameters to be outputted from queries. * --types: List all available types. * --verbose: Print extra information. EXAMPLE ------- This example uses `puppet resource` to return a Puppet configuration for the user `luke`: $ puppet resource user luke user { 'luke': home => '/home/luke', uid => '100', ensure => 'present', comment => 'Luke Kanies,,,', gid => '1000', shell => '/bin/bash', groups => ['sysadmin','audio','video','puppet'] } AUTHOR ------ Luke Kanies COPYRIGHT --------- Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License HELP end def main type, name, params = parse_args(command_line.args) raise "You cannot edit a remote host" if options[:edit] and @host resources = find_or_save_resources(type, name, params) text = resources. map { |resource| resource.prune_parameters(:parameters_to_include => @extra_params).to_manifest }. join("\n") options[:edit] ? handle_editing(text) : (puts text) end def setup Puppet::Util::Log.newdestination(:console) set_log_level end private def remote_key(type, name) Puppet::Resource.indirection.terminus_class = :rest port = Puppet[:puppetport] ["https://#{@host}:#{port}", "production", "resources", type, name].join('/') end def local_key(type, name) [type, name].join('/') end def handle_editing(text) require 'tempfile' # Prefer the current directory, which is more likely to be secure # and, in the case of interactive use, accessible to the user. tmpfile = Tempfile.new('x2puppet', Dir.pwd) begin # sync write, so nothing buffers before we invoke the editor. tmpfile.sync = true tmpfile.puts text # edit the content system(ENV["EDITOR"] || 'vi', tmpfile.path) # ...and, now, pass that file to puppet to apply. Because # many editors rename or replace the original file we need to # feed the pathname, not the file content itself, to puppet. system('puppet apply -v ' + tmpfile.path) ensure # The temporary file will be safely removed. tmpfile.close(true) end end def parse_args(args) type = args.shift or raise "You must specify the type to display" Puppet::Type.type(type) or raise "Could not find type #{type}" name = args.shift params = {} args.each do |setting| if setting =~ /^(\w+)=(.+)$/ params[$1] = $2 else raise "Invalid parameter setting #{setting}" end end [type, name, params] end def find_or_save_resources(type, name, params) key = @host ? remote_key(type, name) : local_key(type, name) if name if params.empty? [ Puppet::Resource.indirection.find( key ) ] else resource = Puppet::Resource.new( type, name, :parameters => params ) # save returns [resource that was saved, transaction log from applying the resource] save_result = Puppet::Resource.indirection.save(resource, key) [ save_result.first ] end else if type == "file" raise "Listing all file instances is not supported. Please specify a file or directory, e.g. puppet resource file /etc" end Puppet::Resource.indirection.search( key, {} ) end end end diff --git a/lib/puppet/face/help/man.erb b/lib/puppet/face/help/man.erb index bd584889c..7e693dd5e 100644 --- a/lib/puppet/face/help/man.erb +++ b/lib/puppet/face/help/man.erb @@ -1,151 +1,151 @@ puppet-<%= face.name %>(8) -- <%= face.summary || "Undocumented subcommand." %> <%= '=' * (_erbout.length - 1) %> <% if face.synopsis -%> SYNOPSIS -------- <%= face.synopsis %> <% end if face.description -%> DESCRIPTION ----------- <%= face.description.strip %> <% end -%> OPTIONS ------- -Note that any configuration parameter that's valid in the configuration +Note that any setting that's valid in the configuration file is also a valid long argument, although it may or may not be relevant to the present action. For example, `server` and `run_mode` are valid -configuration parameters, so you can specify `--server `, or +settings, so you can specify `--server `, or `--run_mode ` as an argument. See the configuration file documentation at for the full list of acceptable parameters. A commented list of all configuration options can also be generated by running puppet with `--genconfig`. * --render-as FORMAT: The format in which to render output. The most common formats are `json`, `s` (string), `yaml`, and `console`, but other options such as `dot` are sometimes available. * --verbose: Whether to log verbosely. * --debug: Whether to log debug information. <% unless face.display_global_options.empty? face.display_global_options.uniq.sort.each do |name| option = name desc = Puppet.settings.setting(option).desc type = Puppet.settings.setting(option).default type ||= Puppet.settings.setting(option).type.to_s.upcase -%> <%= "* --#{option} #{type}" %>: <%= desc.gsub(/^/, ' ') || ' undocumented setting' %> <% end end -%> <% unless face.options.empty? face.options.sort.each do |name| option = face.get_option name -%> <%= "* " + option.optparse.join(" | " ) %>: <%= option.description.gsub(/^/, ' ') || ' ' + option.summary %> <% end end -%> ACTIONS ------- <% face.actions.each do |actionname| action = face.get_action(actionname) -%> * `<%= action.name.to_s %>` - <%= action.summary %>: <% if action.synopsis -%> `SYNOPSIS` <%= action.synopsis %> <% end -%> `DESCRIPTION` <% if action.description -%> <%= action.description.gsub(/^/, ' ') %> <% else -%> <%= action.summary || "Undocumented action." %> <% end -%> <% unique_options = action.options - face.options unique_display_global_options = action.display_global_options - face.display_global_options unless unique_options.empty? and unique_display_global_options.empty? -%> `OPTIONS` <% unique_display_global_options.uniq.sort.each do |name| option = name desc = Puppet.settings.setting(option).desc type = Puppet.settings.setting(option).default type ||= Puppet.settings.setting(option).type.to_s.upcase -%> <%= "<--#{option} #{type}>" %> - <%= desc.gsub(/^/, ' ') %> <% end -%> <% unique_options.sort.each do |name| option = action.get_option name text = (option.description || option.summary).chomp + "\n" -%> <%= '<' + option.optparse.join("> | <") + '>' %> - <%= text.gsub(/^/, ' ') %> <% end -%> <% end -%> <% if action.returns -%> `RETURNS` <%= action.returns.gsub(/^/, ' ') %> <% end if action.notes -%> `NOTES` <%= action.notes.gsub(/^/, ' ') %> <% end end if face.examples or face.actions.any? {|actionname| face.get_action(actionname).examples} -%> EXAMPLES -------- <% end if face.examples -%> <%= face.examples %> <% end face.actions.each do |actionname| action = face.get_action(actionname) if action.examples -%> `<%= action.name.to_s %>` <%= action.examples.strip %> <% end end -%> <% if face.notes or face.respond_to? :indirection -%> NOTES ----- <% if face.notes -%> <%= face.notes.strip %> <% end # notes if face.respond_to? :indirection -%> This subcommand is an indirector face, which exposes `find`, `search`, `save`, and `destroy` actions for an indirected subsystem of Puppet. Valid termini for this face include: * `<%= face.class.terminus_classes(face.indirection.name).join("`\n* `") %>` <% end # indirection end # notes or indirection unless face.authors.empty? -%> AUTHOR ------ <%= face.authors.join("\n").gsub(/^/, ' * ') %> <% end -%> COPYRIGHT AND LICENSE --------------------- <%= face.copyright %> <%= face.license %> diff --git a/lib/puppet/reference/configuration.rb b/lib/puppet/reference/configuration.rb index c1580fe35..0475067a3 100644 --- a/lib/puppet/reference/configuration.rb +++ b/lib/puppet/reference/configuration.rb @@ -1,73 +1,73 @@ -config = Puppet::Util::Reference.newreference(:configuration, :depth => 1, :doc => "A reference for all configuration parameters") do +config = Puppet::Util::Reference.newreference(:configuration, :depth => 1, :doc => "A reference for all settings") do docs = {} Puppet.settings.each do |name, object| docs[name] = object end str = "" docs.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |name, object| # Make each name an anchor header = name.to_s str << markdown_header(header, 3) # Print the doc string itself begin str << Puppet::Util::Docs.scrub(object.desc) rescue => detail Puppet.log_exception(detail) end str << "\n\n" # Now print the data about the item. val = object.default if name.to_s == "vardir" val = "/var/lib/puppet" elsif name.to_s == "confdir" val = "/etc/puppet" end # Leave out the section information; it was apparently confusing people. #str << "- **Section**: #{object.section}\n" unless val == "" str << "- *Default*: #{val}\n" end str << "\n" end return str end config.header = < run_mode.to_s, :run_mode => run_mode.name, :confdir => run_mode.conf_dir, :vardir => run_mode.var_dir, :rundir => run_mode.run_dir, :logdir => run_mode.log_dir, } end def self.default_certname() hostname = hostname_fact domain = domain_fact if domain and domain != "" fqdn = [hostname, domain].join(".") else fqdn = hostname end fqdn.to_s.gsub(/\.$/, '') end def self.hostname_fact() Facter["hostname"].value end def self.domain_fact() Facter["domain"].value end def self.default_config_file_name "puppet.conf" end # Create a new collection of config settings. def initialize @config = {} @shortnames = {} @created = [] # Keep track of set values. @value_sets = { :cli => Values.new(:cli, @config), :memory => Values.new(:memory, @config), :application_defaults => Values.new(:application_defaults, @config), } @configuration_file = nil # And keep a per-environment cache @cache = Hash.new { |hash, key| hash[key] = {} } # The list of sections we've used. @used = [] @hooks_to_call_on_application_initialization = [] @translate = Puppet::Settings::ValueTranslator.new @config_file_parser = Puppet::Settings::ConfigFile.new(@translate) end # @param name [Symbol] The name of the setting to fetch # @return [Puppet::Settings::BaseSetting] The setting object def setting(name) @config[name] end # Retrieve a config value def [](param) value(param) end # Set a config value. This doesn't set the defaults, it sets the value itself. def []=(param, value) @value_sets[:memory].set(param, value) unsafe_flush_cache end # Generate the list of valid arguments, in a format that GetoptLong can # understand, and add them to the passed option list. def addargs(options) - # Add all of the config parameters as valid options. + # Add all of the settings as valid options. self.each { |name, setting| setting.getopt_args.each { |args| options << args } } options end # Generate the list of valid arguments, in a format that OptionParser can # understand, and add them to the passed option list. def optparse_addargs(options) - # Add all of the config parameters as valid options. + # Add all of the settings as valid options. self.each { |name, setting| options << setting.optparse_args } options end - # Is our parameter a boolean parameter? + # Is our setting a boolean setting? def boolean?(param) param = param.to_sym @config.include?(param) and @config[param].kind_of?(BooleanSetting) end # Remove all set values, potentially skipping cli values. def clear unsafe_clear end # Remove all set values, potentially skipping cli values. def unsafe_clear(clear_cli = true, clear_application_defaults = false) if clear_application_defaults @value_sets[:application_defaults] = Values.new(:application_defaults, @config) @app_defaults_initialized = false end if clear_cli @value_sets[:cli] = Values.new(:cli, @config) # Only clear the 'used' values if we were explicitly asked to clear out # :cli values; otherwise, it may be just a config file reparse, # and we want to retain this cli values. @used = [] end @value_sets[:memory] = Values.new(:memory, @config) @cache.clear end private :unsafe_clear # Clear @cache, @used and the Environment. # # Whenever an object is returned by Settings, a copy is stored in @cache. # As long as Setting attributes that determine the content of returned # objects remain unchanged, Settings can keep returning objects from @cache # without re-fetching or re-generating them. # # Whenever a Settings attribute changes, such as @values or @preferred_run_mode, # this method must be called to clear out the caches so that updated # objects will be returned. def flush_cache unsafe_flush_cache end def unsafe_flush_cache clearused # Clear the list of environments, because they cache, at least, the module path. # We *could* preferentially just clear them if the modulepath is changed, # but we don't really know if, say, the vardir is changed and the modulepath # is defined relative to it. We need the defined?(stuff) because of loading # order issues. Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment) end private :unsafe_flush_cache def clearused @cache.clear @used = [] end def global_defaults_initialized?() @global_defaults_initialized end def initialize_global_settings(args = []) raise Puppet::DevError, "Attempting to initialize global default settings more than once!" if global_defaults_initialized? # The first two phases of the lifecycle of a puppet application are: # 1) Parse the command line options and handle any of them that are # registered, defined "global" puppet settings (mostly from defaults.rb). # 2) Parse the puppet config file(s). parse_global_options(args) parse_config_files @global_defaults_initialized = true end # This method is called during application bootstrapping. It is responsible for parsing all of the # command line options and initializing the settings accordingly. # # It will ignore options that are not defined in the global puppet settings list, because they may # be valid options for the specific application that we are about to launch... however, at this point # in the bootstrapping lifecycle, we don't yet know what that application is. def parse_global_options(args) # Create an option parser option_parser = PuppetOptionParser.new option_parser.ignore_invalid_options = true # Add all global options to it. self.optparse_addargs([]).each do |option| option_parser.on(*option) do |arg| opt, val = Puppet::Settings.clean_opt(option[0], arg) handlearg(opt, val) end end option_parser.on('--run_mode', "The effective 'run mode' of the application: master, agent, or user.", :REQUIRED) do |arg| Puppet.settings.preferred_run_mode = arg end option_parser.parse(args) # remove run_mode options from the arguments so that later parses don't think # it is an unknown option. while option_index = args.index('--run_mode') do args.delete_at option_index args.delete_at option_index end args.reject! { |arg| arg.start_with? '--run_mode=' } end private :parse_global_options # A utility method (public, is used by application.rb and perhaps elsewhere) that munges a command-line # option string into the format that Puppet.settings expects. (This mostly has to deal with handling the # "no-" prefix on flag/boolean options). # # @param [String] opt the command line option that we are munging # @param [String, TrueClass, FalseClass] val the value for the setting (as determined by the OptionParser) def self.clean_opt(opt, val) # rewrite --[no-]option to --no-option if that's what was given if opt =~ /\[no-\]/ and !val opt = opt.gsub(/\[no-\]/,'no-') end # otherwise remove the [no-] prefix to not confuse everybody opt = opt.gsub(/\[no-\]/, '') [opt, val] end def app_defaults_initialized? @app_defaults_initialized end def initialize_app_defaults(app_defaults) REQUIRED_APP_SETTINGS.each do |key| raise SettingsError, "missing required app default setting '#{key}'" unless app_defaults.has_key?(key) end app_defaults.each do |key, value| if key == :run_mode self.preferred_run_mode = value else @value_sets[:application_defaults].set(key, value) unsafe_flush_cache end end apply_metadata call_hooks_deferred_to_application_initialization @app_defaults_initialized = true end def call_hooks_deferred_to_application_initialization(options = {}) @hooks_to_call_on_application_initialization.each do |setting| begin setting.handle(self.value(setting.name)) rescue InterpolationError => err raise InterpolationError, err, err.backtrace unless options[:ignore_interpolation_dependency_errors] #swallow. We're not concerned if we can't call hooks because dependencies don't exist yet #we'll get another chance after application defaults are initialized end end end private :call_hooks_deferred_to_application_initialization # Return a value's description. def description(name) if obj = @config[name.to_sym] obj.desc else nil end end def each @config.each { |name, object| yield name, object } end # Iterate over each section name. def eachsection yielded = [] @config.each do |name, object| section = object.section unless yielded.include? section yield section yielded << section end end end # Return an object by name. def setting(param) param = param.to_sym @config[param] end # Handle a command-line argument. def handlearg(opt, value = nil) @cache.clear if value.is_a?(FalseClass) value = "false" elsif value.is_a?(TrueClass) value = "true" end value &&= @translate[value] str = opt.sub(/^--/,'') bool = true newstr = str.sub(/^no-/, '') if newstr != str str = newstr bool = false end str = str.intern if @config[str].is_a?(Puppet::Settings::BooleanSetting) if value == "" or value.nil? value = bool end end @value_sets[:cli].set(str, value) unsafe_flush_cache end def include?(name) name = name.intern if name.is_a? String @config.include?(name) end # check to see if a short name is already defined def shortinclude?(short) short = short.intern if name.is_a? String @shortnames.include?(short) end # Prints the contents of a config file with the available config settings, or it # prints a single value of a config setting. def print_config_options env = value(:environment) val = value(:configprint) if val == "all" hash = {} each do |name, obj| val = value(name,env) val = val.inspect if val == "" hash[name] = val end hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val| puts "#{name} = #{val}" end else val.split(/\s*,\s*/).sort.each do |v| if include?(v) #if there is only one value, just print it for back compatibility if v == val puts value(val,env) break end puts "#{v} = #{value(v,env)}" else - puts "invalid parameter: #{v}" + puts "invalid setting: #{v}" return false end end end true end def generate_config puts to_config true end def generate_manifest puts to_manifest true end def print_configs return print_config_options if value(:configprint) != "" return generate_config if value(:genconfig) generate_manifest if value(:genmanifest) end def print_configs? (value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true end # Return a given object's file metadata. def metadata(param) if obj = @config[param.to_sym] and obj.is_a?(FileSetting) { :owner => obj.owner, :group => obj.group, :mode => obj.mode }.delete_if { |key, value| value.nil? } else nil end end # Make a directory with the appropriate user, group, and mode def mkdir(default) obj = get_config_file_default(default) Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do mode = obj.mode || 0750 Dir.mkdir(obj.value, mode) end end # The currently configured run mode that is preferred for constructing the application configuration. def preferred_run_mode @preferred_run_mode_name || :user end # PRIVATE! This only exists because we need a hook to validate the run mode when it's being set, and # it should never, ever, ever, ever be called from outside of this file. # This method is also called when --run_mode MODE is used on the command line to set the default # # @param mode [String|Symbol] the name of the mode to have in effect # @api private def preferred_run_mode=(mode) mode = mode.to_s.downcase.intern raise ValidationError, "Invalid run mode '#{mode}'" unless [:master, :agent, :user].include?(mode) @preferred_run_mode_name = mode # Changing the run mode has far-reaching consequences. Flush any cached # settings so they will be re-generated. flush_cache mode end - # Return all of the parameters associated with a given section. + # Return all of the settings associated with a given section. def params(section = nil) if section section = section.intern if section.is_a? String @config.find_all { |name, obj| obj.section == section }.collect { |name, obj| name } else @config.keys end end def parse_config(text, file = "text") begin data = @config_file_parser.parse_file(file, text) rescue => detail Puppet.log_exception(detail, "Could not parse #{file}: #{detail}") return end # If we get here and don't have any data, we just return and don't muck with the current state of the world. return if data.nil? # If we get here then we have some data, so we need to clear out any previous settings that may have come from # config files. unsafe_clear(false, false) # And now we can repopulate with the values from our last parsing of the config files. @configuration_file = data # Determine our environment, if we have one. if @config[:environment] env = self.value(:environment).to_sym else env = "none" end # Call any hooks we should be calling. @config.values.select(&:has_hook?).each do |setting| value_sets_for(env, self.preferred_run_mode).each do |source| if source.include?(setting.name) # We still have to use value to retrieve the value, since # we want the fully interpolated value, not $vardir/lib or whatever. # This results in extra work, but so few of the settings # will have associated hooks that it ends up being less work this # way overall. if setting.call_hook_on_initialize? @hooks_to_call_on_application_initialization << setting else setting.handle(self.value(setting.name, env)) end break end end end call_hooks_deferred_to_application_initialization :ignore_interpolation_dependency_errors => true apply_metadata end # Parse the configuration file. Just provides thread safety. def parse_config_files file = which_configuration_file if Puppet::FileSystem.exist?(file) begin text = read_file(file) rescue => detail Puppet.log_exception(detail, "Could not load #{file}: #{detail}") return end else return end parse_config(text, file) end private :parse_config_files def main_config_file if explicit_config_file? return self[:config] else return File.join(Puppet::Util::RunMode[:master].conf_dir, config_file_name) end end private :main_config_file def user_config_file return File.join(Puppet::Util::RunMode[:user].conf_dir, config_file_name) end private :user_config_file # This method is here to get around some life-cycle issues. We need to be # able to determine the config file name before the settings / defaults are # fully loaded. However, we also need to respect any overrides of this value # that the user may have specified on the command line. # # The easiest way to do this is to attempt to read the setting, and if we # catch an error (meaning that it hasn't been set yet), we'll fall back to # the default value. def config_file_name begin return self[:config_file_name] if self[:config_file_name] rescue SettingsError # This just means that the setting wasn't explicitly set on the command line, so we will ignore it and # fall through to the default name. end return self.class.default_config_file_name end private :config_file_name def apply_metadata # We have to do it in the reverse of the search path, # because multiple sections could set the same value # and I'm too lazy to only set the metadata once. if @configuration_file searchpath.reverse.each do |source| source = preferred_run_mode if source == :run_mode if section = @configuration_file.sections[source] apply_metadata_from_section(section) end end end end private :apply_metadata def apply_metadata_from_section(section) section.settings.each do |setting| if setting.has_metadata? && type = @config[setting.name] type.set_meta(setting.meta) end end end SETTING_TYPES = { :string => StringSetting, :file => FileSetting, :directory => DirectorySetting, :path => PathSetting, :boolean => BooleanSetting, :terminus => TerminusSetting, :duration => DurationSetting, :enum => EnumSetting, :priority => PrioritySetting, :autosign => AutosignSetting, } # Create a new setting. The value is passed in because it's used to determine # what kind of setting we're creating, but the value itself might be either # a default or a value, so we can't actually assign it. # # See #define_settings for documentation on the legal values for the ":type" option. def newsetting(hash) klass = nil hash[:section] = hash[:section].to_sym if hash[:section] if type = hash[:type] unless klass = SETTING_TYPES[type] raise ArgumentError, "Invalid setting type '#{type}'" end hash.delete(:type) else # The only implicit typing we still do for settings is to fall back to "String" type if they didn't explicitly # specify a type. Personally I'd like to get rid of this too, and make the "type" option mandatory... but # there was a little resistance to taking things quite that far for now. --cprice 2012-03-19 klass = StringSetting end hash[:settings] = self setting = klass.new(hash) setting end # This has to be private, because it doesn't add the settings to @config private :newsetting # Iterate across all of the objects in a given section. def persection(section) section = section.to_sym self.each { |name, obj| if obj.section == section yield obj end } end # Reparse our config file, if necessary. def reparse_config_files if files if filename = any_files_changed? Puppet.notice "Config file #{filename} changed; triggering re-parse of all config files." parse_config_files reuse end end end def files return @files if @files @files = [] [main_config_file, user_config_file].each do |path| if Puppet::FileSystem.exist?(path) @files << Puppet::Util::WatchedFile.new(path) end end @files end private :files # Checks to see if any of the config files have been modified # @return the filename of the first file that is found to have changed, or # nil if no files have changed def any_files_changed? files.each do |file| return file.to_str if file.changed? end nil end private :any_files_changed? def reuse return unless defined?(@used) new = @used @used = [] self.use(*new) end # The order in which to search for values. def searchpath(environment = nil) if environment [:cli, :memory, environment, :run_mode, :main, :application_defaults] else [:cli, :memory, :run_mode, :main, :application_defaults] end end # Get a list of objects per section def sectionlist sectionlist = [] self.each { |name, obj| section = obj.section || "puppet" sections[section] ||= [] sectionlist << section unless sectionlist.include?(section) sections[section] << obj } return sectionlist, sections end def service_user_available? return @service_user_available if defined?(@service_user_available) if self[:user] user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure @service_user_available = user.exists? else @service_user_available = false end end def service_group_available? return @service_group_available if defined?(@service_group_available) if self[:group] group = Puppet::Type.type(:group).new :name => self[:group], :audit => :ensure @service_group_available = group.exists? else @service_group_available = false end end # Allow later inspection to determine if the setting was set on the # command line, or through some other code path. Used for the # `dns_alt_names` option during cert generate. --daniel 2011-10-18 def set_by_cli?(param) param = param.to_sym !@value_sets[:cli].lookup(param).nil? end def set_value(param, value, type, options = {}) Puppet.deprecation_warning("Puppet.settings.set_value is deprecated. Use Puppet[]= instead.") if @value_sets[type] @value_sets[type].set(param, value) end end # Deprecated; use #define_settings instead def setdefaults(section, defs) Puppet.deprecation_warning("'setdefaults' is deprecated and will be removed; please call 'define_settings' instead") define_settings(section, defs) end # Define a group of settings. # # @param [Symbol] section a symbol to use for grouping multiple settings together into a conceptual unit. This value # (and the conceptual separation) is not used very often; the main place where it will have a potential impact # is when code calls Settings#use method. See docs on that method for further details, but basically that method # just attempts to do any preparation that may be necessary before code attempts to leverage the value of a particular # setting. This has the most impact for file/directory settings, where #use will attempt to "ensure" those # files / directories. # @param [Hash[Hash]] defs the settings to be defined. This argument is a hash of hashes; each key should be a symbol, # which is basically the name of the setting that you are defining. The value should be another hash that specifies # the parameters for the particular setting. Legal values include: # [:default] => required; this is a string value that will be used as a default value for a setting if no other # value is specified (via cli, config file, etc.) This string may include "variables", demarcated with $ or ${}, # which will be interpolated with values of other settings. # [:desc] => required; a description of the setting, used in documentation / help generation # [:type] => not required, but highly encouraged! This specifies the data type that the setting represents. If # you do not specify it, it will default to "string". Legal values include: # :string - A generic string setting # :boolean - A boolean setting; values are expected to be "true" or "false" # :file - A (single) file path; puppet may attempt to create this file depending on how the settings are used. This type # also supports additional options such as "mode", "owner", "group" # :directory - A (single) directory path; puppet may attempt to create this file depending on how the settings are used. This type # also supports additional options such as "mode", "owner", "group" # :path - This is intended to be used for settings whose value can contain multiple directory paths, respresented # as strings separated by the system path separator (e.g. system path, module path, etc.). # [:mode] => an (optional) octal value to be used as the permissions/mode for :file and :directory settings # [:owner] => optional owner username/uid for :file and :directory settings # [:group] => optional group name/gid for :file and :directory settings # def define_settings(section, defs) section = section.to_sym call = [] defs.each do |name, hash| raise ArgumentError, "setting definition for '#{name}' is not a hash!" unless hash.is_a? Hash name = name.to_sym hash[:name] = name hash[:section] = section - raise ArgumentError, "Parameter #{name} is already defined" if @config.include?(name) + raise ArgumentError, "Setting #{name} is already defined" if @config.include?(name) tryconfig = newsetting(hash) if short = tryconfig.short if other = @shortnames[short] - raise ArgumentError, "Parameter #{other.name} is already using short name '#{short}'" + raise ArgumentError, "Setting #{other.name} is already using short name '#{short}'" end @shortnames[short] = tryconfig end @config[name] = tryconfig # Collect the settings that need to have their hooks called immediately. # We have to collect them so that we can be sure we're fully initialized before # the hook is called. if tryconfig.has_hook? if tryconfig.call_hook_on_define? call << tryconfig elsif tryconfig.call_hook_on_initialize? @hooks_to_call_on_application_initialization << tryconfig end end end call.each do |setting| setting.handle(self.value(setting.name)) end end # Convert the settings we manage into a catalog full of resources that model those settings. def to_catalog(*sections) sections = nil if sections.empty? catalog = Puppet::Resource::Catalog.new("Settings") @config.keys.find_all { |key| @config[key].is_a?(FileSetting) }.each do |key| file = @config[key] next unless (sections.nil? or sections.include?(file.section)) next unless resource = file.to_resource next if catalog.resource(resource.ref) Puppet.debug("Using settings: adding file resource '#{key}': '#{resource.inspect}'") catalog.add_resource(resource) end add_user_resources(catalog, sections) catalog end # Convert our list of config settings into a configuration file. def to_config str = %{The configuration file for #{Puppet.run_mode.name}. Note that this file -is likely to have unused configuration parameters in it; any parameter that's +is likely to have unused settings in it; any setting that's valid anywhere in Puppet can be in any config file, even if it's not used. Every section can specify three special parameters: owner, group, and mode. These parameters affect the required permissions of any files specified after their specification. Puppet will sometimes use these parameters to check its own configured state, so they can be used to make Puppet a bit more self-managing. The file format supports octothorpe-commented lines, but not partial-line comments. Generated on #{Time.now}. }.gsub(/^/, "# ") # Add a section heading that matches our name. str += "[#{preferred_run_mode}]\n" eachsection do |section| persection(section) do |obj| str += obj.to_config + "\n" unless obj.name == :genconfig end end return str end # Convert to a parseable manifest def to_manifest catalog = to_catalog catalog.resource_refs.collect do |ref| catalog.resource(ref).to_manifest end.join("\n\n") end # Create the necessary objects to use a section. This is idempotent; # you can 'use' a section as many times as you want. def use(*sections) sections = sections.collect { |s| s.to_sym } sections = sections.reject { |s| @used.include?(s) } return if sections.empty? begin catalog = to_catalog(*sections).to_ral rescue => detail Puppet.log_and_raise(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}") end catalog.host_config = false catalog.apply do |transaction| if transaction.any_failed? report = transaction.report status_failures = report.resource_statuses.values.select { |r| r.failed? } status_fail_msg = status_failures. collect(&:events). flatten. select { |event| event.status == 'failure' }. collect { |event| "#{event.resource}: #{event.message}" }.join("; ") raise "Got #{status_failures.length} failure(s) while initializing: #{status_fail_msg}" end end sections.each { |s| @used << s } @used.uniq! end def valid?(param) param = param.to_sym @config.has_key?(param) end def uninterpolated_value(param, environment = nil) Puppet.deprecation_warning("Puppet.settings.uninterpolated_value is deprecated. Use Puppet.settings.value instead") param = param.to_sym environment &&= environment.to_sym values(environment, self.preferred_run_mode).lookup(param) end # Retrieve an object that can be used for looking up values of configuration # settings. # # @param environment [Symbol] The name of the environment in which to lookup # @param section [Symbol] The name of the configuration section in which to lookup # @return [Puppet::Settings::ChainedValues] An object to perform lookups # @api public def values(environment, section) ChainedValues.new( section, environment, value_sets_for(environment, section), @config) end # Find the correct value using our search path. # # @param param [String, Symbol] The value to look up # @param environment [String, Symbol] The environment to check for the value # @param bypass_interpolation [true, false] Whether to skip interpolation # # @return [Object] The looked up value # # @raise [InterpolationError] def value(param, environment = nil, bypass_interpolation = false) param = param.to_sym environment &&= environment.to_sym setting = @config[param] - # Short circuit to nil for undefined parameters. + # Short circuit to nil for undefined settings. return nil if setting.nil? # Check the cache first. It needs to be a per-environment # cache so that we don't spread values from one env # to another. if @cache[environment||"none"].has_key?(param) return @cache[environment||"none"][param] elsif bypass_interpolation val = values(environment, self.preferred_run_mode).lookup(param) else val = values(environment, self.preferred_run_mode).interpolate(param) end @cache[environment||"none"][param] = val val end ## # (#15337) All of the logic to determine the configuration file to use # should be centralized into this method. The simplified approach is: # # 1. If there is an explicit configuration file, use that. (--confdir or # --config) # 2. If we're running as a root process, use the system puppet.conf # (usually /etc/puppet/puppet.conf) # 3. Otherwise, use the user puppet.conf (usually ~/.puppet/puppet.conf) # # @api private # @todo this code duplicates {Puppet::Util::RunMode#which_dir} as described # in {http://projects.puppetlabs.com/issues/16637 #16637} def which_configuration_file if explicit_config_file? or Puppet.features.root? then return main_config_file else return user_config_file end end private def get_config_file_default(default) obj = nil unless obj = @config[default] raise ArgumentError, "Unknown default #{default}" end raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting obj end def add_user_resources(catalog, sections) return unless Puppet.features.root? return if Puppet.features.microsoft_windows? return unless self[:mkusers] @config.each do |name, setting| next unless setting.respond_to?(:owner) next unless sections.nil? or sections.include?(setting.section) if user = setting.owner and user != "root" and catalog.resource(:user, user).nil? resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present}) resource[:gid] = self[:group] if self[:group] catalog.add_resource resource end if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil? catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present}) end end end # Yield each search source in turn. def value_sets_for(environment, mode) searchpath(environment).collect do |name| case name when :cli, :memory, :application_defaults @value_sets[name] when :run_mode if @configuration_file section = @configuration_file.sections[mode] if section ValuesFromSection.new(mode, section) end end else if @configuration_file section = @configuration_file.sections[name] if section ValuesFromSection.new(name, section) end end end end.compact end # This method just turns a file in to a hash of hashes. def parse_file(file) @config_file_parser.parse_file(file, read_file(file)) end # Read the file in. def read_file(file) begin return File.read(file) rescue Errno::ENOENT raise ArgumentError, "No such file #{file}", $!.backtrace rescue Errno::EACCES raise ArgumentError, "Permission denied to file #{file}", $!.backtrace end end # Private method for internal test use only; allows to do a comprehensive clear of all settings between tests. # # @return nil def clear_everything_for_tests() unsafe_clear(true, true) @global_defaults_initialized = false @app_defaults_initialized = false end private :clear_everything_for_tests def explicit_config_file? # Figure out if the user has provided an explicit configuration file. If # so, return the path to the file, if not return nil. # # The easiest way to determine whether an explicit one has been specified # is to simply attempt to evaluate the value of ":config". This will # obviously be successful if they've passed an explicit value for :config, # but it will also result in successful interpolation if they've only # passed an explicit value for :confdir. # # If they've specified neither, then the interpolation will fail and we'll # get an exception. # begin return true if self[:config] rescue InterpolationError # This means we failed to interpolate, which means that they didn't # explicitly specify either :config or :confdir... so we'll fall out to # the default value. return false end end private :explicit_config_file? # Lookup configuration setting value through a chain of different value sources. # # @api public class ChainedValues # @see Puppet::Settings.values # @api private def initialize(mode, environment, value_sets, defaults) @mode = mode @environment = environment @value_sets = value_sets @defaults = defaults end # Lookup the uninterpolated value. # # @param name [Symbol] The configuration setting name to look up # @return [Object] The configuration setting value or nil if the setting is not known # @api public def lookup(name) set = @value_sets.find do |set| set.include?(name) end if set value = set.lookup(name) if !value.nil? return value end end @defaults[name].default end # Lookup the interpolated value. All instances of `$name` in the value will # be replaced by performing a lookup of `name` and substituting the text # for `$name` in the original value. This interpolation is only performed # if the looked up value is a String. # # @param name [Symbol] The configuration setting name to look up # @return [Object] The configuration setting value or nil if the setting is not known # @api public def interpolate(name) setting = @defaults[name] if setting val = lookup(name) # if we interpolate code, all hell breaks loose. if name == :code val else # Convert it if necessary begin val = convert(val) rescue InterpolationError => err # This happens because we don't have access to the param name when the # exception is originally raised, but we want it in the message raise InterpolationError, "Error converting value for param '#{name}': #{err}", err.backtrace end setting.munge(val) end else nil end end private def convert(value) return nil if value.nil? return value unless value.is_a? String value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value| varname = $2 || $1 if varname == "environment" @environment elsif varname == "run_mode" @mode elsif pval = interpolate(varname.to_sym) pval else raise InterpolationError, "Could not find value for #{value}" end end end end class Values def initialize(name, defaults) @name = name @values = {} @defaults = defaults end def include?(name) @values.include?(name) end def set(name, value) if !@defaults[name] raise ArgumentError, - "Attempt to assign a value to unknown configuration parameter #{name.inspect}" + "Attempt to assign a value to unknown setting #{name.inspect}" end if @defaults[name].has_hook? @defaults[name].handle(value) end @values[name] = value end def lookup(name) @values[name] end end class ValuesFromSection def initialize(name, section) @name = name @section = section end def include?(name) !@section.setting(name).nil? end def lookup(name) setting = @section.setting(name) if setting setting.value end end end end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index 9351bc0fd..b49452c11 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -1,891 +1,891 @@ require 'digest/md5' require 'cgi' require 'etc' require 'uri' require 'fileutils' require 'enumerator' require 'pathname' require 'puppet/parameter/boolean' require 'puppet/util/diff' require 'puppet/util/checksums' require 'puppet/util/backups' require 'puppet/util/symbolic_file_mode' Puppet::Type.newtype(:file) do include Puppet::Util::MethodHelper include Puppet::Util::Checksums include Puppet::Util::Backups include Puppet::Util::SymbolicFileMode @doc = "Manages files, including their content, ownership, and permissions. The `file` type can manage normal files, directories, and symlinks; the type should be specified in the `ensure` attribute. Note that symlinks cannot be managed on Windows systems. File contents can be managed directly with the `content` attribute, or downloaded from a remote source using the `source` attribute; the latter can also be used to recursively serve directories (when the `recurse` attribute is set to `true` or `local`). On Windows, note that file contents are managed in binary mode; Puppet never automatically translates line endings. **Autorequires:** If Puppet is managing the user or group that owns a file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them." feature :manages_symlinks, "The provider can manage symbolic links." def self.title_patterns [ [ /^(.*?)\/*\Z/m, [ [ :path ] ] ] ] end newparam(:path) do desc <<-'EOT' The path to the file to manage. Must be fully qualified. On Windows, the path should include the drive letter and should use `/` as the separator character (rather than `\\`). EOT isnamevar validate do |value| unless Puppet::Util.absolute_path?(value) fail Puppet::Error, "File paths must be fully qualified, not '#{value}'" end end munge do |value| if value.start_with?('//') and ::File.basename(value) == "/" # This is a UNC path pointing to a share, so don't add a trailing slash ::File.expand_path(value) else ::File.join(::File.split(::File.expand_path(value))) end end end newparam(:backup) do desc <<-EOT Whether (and how) file content should be backed up before being replaced. This attribute works best as a resource default in the site manifest (`File { backup => main }`), so it can affect all file resources. * If set to `false`, file content won't be backed up. * If set to a string beginning with `.` (e.g., `.puppet-bak`), Puppet will use copy the file in the same directory with that value as the extension of the backup. (A value of `true` is a synonym for `.puppet-bak`.) * If set to any other string, Puppet will try to back up to a filebucket with that title. See the `filebucket` resource type for more details. (This is the preferred method for backup, since it can be centralized and queried.) Default value: `puppet`, which backs up to a filebucket of the same name. (Puppet automatically creates a **local** filebucket named `puppet` if one doesn't already exist.) Backing up to a local filebucket isn't particularly useful. If you want to make organized use of backups, you will generally want to use the puppet master server's filebucket service. This requires declaring a filebucket resource and a resource default for the `backup` attribute in site.pp: # /etc/puppet/manifests/site.pp filebucket { 'main': path => false, # This is required for remote filebuckets. server => 'puppet.example.com', # Optional; defaults to the configured puppet master. } File { backup => main, } If you are using multiple puppet master servers, you will want to centralize the contents of the filebucket. Either configure your load balancer to direct all filebucket traffic to a single master, or use something like an out-of-band rsync task to synchronize the content on all masters. EOT defaultto "puppet" munge do |value| # I don't really know how this is happening. value = value.shift if value.is_a?(Array) case value when false, "false", :false false when true, "true", ".puppet-bak", :true ".puppet-bak" when String value else self.fail "Invalid backup type #{value.inspect}" end end end newparam(:recurse) do desc "Whether and how to do recursive file management. Options are: * `inf,true` --- Regular style recursion on both remote and local directory structure. See `recurselimit` to specify a limit to the recursion depth. * `remote` --- Descends recursively into the remote (source) directory but not the local (destination) directory. Allows copying of a few files into a directory containing many unmanaged files without scanning all the local files. This can only be used when a source parameter is specified. * `false` --- Default of no recursion. " newvalues(:true, :false, :inf, :remote) validate { |arg| } munge do |value| newval = super(value) case newval when :true, :inf; true when :false; false when :remote; :remote else self.fail "Invalid recurse value #{value.inspect}" end end end newparam(:recurselimit) do desc "How deeply to do recursive management." newvalues(/^[0-9]+$/) munge do |value| newval = super(value) case newval when Integer, Fixnum, Bignum; value when /^\d+$/; Integer(value) else self.fail "Invalid recurselimit value #{value.inspect}" end end end newparam(:replace, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to replace a file or symlink that already exists on the local system but whose content doesn't match what the `source` or `content` attribute specifies. Setting this to false allows file resources to initialize files without overwriting future changes. Note that this only affects content; Puppet will still manage ownership and permissions. Defaults to `true`." defaultto :true end newparam(:force, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Perform the file operation even if it will destroy one or more directories. You must use `force` in order to: * `purge` subdirectories * Replace directories with files or links * Remove a directory when `ensure => absent`" defaultto false end newparam(:ignore) do desc "A parameter which omits action on files matching specified patterns during recursion. Uses Ruby's builtin globbing engine, so shell metacharacters are fully supported, e.g. `[a-z]*`. Matches that would descend into the directory structure are ignored, e.g., `*/*`." validate do |value| unless value.is_a?(Array) or value.is_a?(String) or value == false self.devfail "Ignore must be a string or an Array" end end end newparam(:links) do desc "How to handle links during file actions. During file copying, `follow` will copy the target file instead of the link, `manage` will copy the link itself, and `ignore` will just pass it by. When not copying, `manage` and `ignore` behave equivalently (because you cannot really ignore links entirely during local recursion), and `follow` will manage the file to which the link points." newvalues(:follow, :manage) defaultto :manage end newparam(:purge, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether unmanaged files should be purged. This option only makes sense when managing directories with `recurse => true`. * When recursively duplicating an entire directory with the `source` attribute, `purge => true` will automatically purge any files that are not in the source directory. * When managing files in a directory as individual resources, setting `purge => true` will purge any files that aren't being specifically managed. If you have a filebucket configured, the purged files will be uploaded, but if you do not, this will destroy data." defaultto :false end newparam(:sourceselect) do desc "Whether to copy all valid sources, or just the first one. This parameter only affects recursive directory copies; by default, the first valid source is the only one used, but if this parameter is set to `all`, then all valid sources will have all of their contents copied to the local system. If a given file exists in more than one source, the version from the earliest source in the list will be used." defaultto :first newvalues(:first, :all) end newparam(:show_diff, :boolean => true, :parent => Puppet::Parameter::Boolean) do desc "Whether to display differences when the file changes, defaulting to true. This parameter is useful for files that may contain passwords or other secret data, which might otherwise be included in Puppet reports or - other insecure outputs. If the global ``show_diff` configuration parameter + other insecure outputs. If the global ``show_diff` setting is false, then no diffs will be shown even if this parameter is true." defaultto :true end newparam(:validate_cmd) do desc "A command for validating the file's syntax before replacing it. If Puppet would need to rewrite a file due to new `source` or `content`, it will check the new content's validity first. If validation fails, the file resource will fail. This command must have a fully qualified path, and should contain a percent (`%`) token where it would expect an input file. It must exit `0` if the syntax is correct, and non-zero otherwise. The command will be run on the target system while applying the catalog, not on the puppet master. Example: file { '/etc/apache2/apache2.conf': content => 'example', validate_cmd => '/usr/sbin/apache2 -t -f %', } This would replace apache2.conf only if the test returned true. Note that if a validation command requires a `%` as part of its text, you can specify a different placeholder token with the `validate_replacement` attribute." end newparam(:validate_replacement) do desc "The replacement string in a `validate_cmd` that will be replaced with an input file name. Defaults to: `%`" defaultto '%' end # Autorequire the nearest ancestor directory found in the catalog. autorequire(:file) do req = [] path = Pathname.new(self[:path]) if !path.root? # Start at our parent, to avoid autorequiring ourself parents = path.parent.enum_for(:ascend) if found = parents.find { |p| catalog.resource(:file, p.to_s) } req << found.to_s end end # if the resource is a link, make sure the target is created first req << self[:target] if self[:target] req end # Autorequire the owner and group of the file. {:user => :owner, :group => :group}.each do |type, property| autorequire(type) do if @parameters.include?(property) # The user/group property automatically converts to IDs next unless should = @parameters[property].shouldorig val = should[0] if val.is_a?(Integer) or val =~ /^\d+$/ nil else val end end end end CREATORS = [:content, :source, :target] SOURCE_ONLY_CHECKSUMS = [:none, :ctime, :mtime] validate do creator_count = 0 CREATORS.each do |param| creator_count += 1 if self.should(param) end creator_count += 1 if @parameters.include?(:source) self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if creator_count > 1 self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote self.fail "You cannot specify source when using checksum 'none'" if self[:checksum] == :none && !self[:source].nil? SOURCE_ONLY_CHECKSUMS.each do |checksum_type| self.fail "You cannot specify content when using checksum '#{checksum_type}'" if self[:checksum] == checksum_type && !self[:content].nil? end self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit] provider.validate if provider.respond_to?(:validate) end def self.[](path) return nil unless path super(path.gsub(/\/+/, '/').sub(/\/$/, '')) end def self.instances return [] end # Determine the user to write files as. def asuser if self.should(:owner) and ! self.should(:owner).is_a?(Symbol) writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) { FileTest.writable?(::File.dirname(self[:path])) } # If the parent directory is writeable, then we execute # as the user in question. Otherwise we'll rely on # the 'owner' property to do things. asuser = self.should(:owner) if writeable end asuser end def bucket return @bucket if @bucket backup = self[:backup] return nil unless backup return nil if backup =~ /^\./ unless catalog or backup == "puppet" fail "Can not find filebucket for backups without a catalog" end unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet" fail "Could not find filebucket #{backup} specified in backup" end return default_bucket unless filebucket @bucket = filebucket.bucket @bucket end def default_bucket Puppet::Type.type(:filebucket).mkdefaultbucket.bucket end # Does the file currently exist? Just checks for whether # we have a stat def exist? stat ? true : false end def present?(current_values) super && current_values[:ensure] != :false end # We have to do some extra finishing, to retrieve our bucket if # there is one. def finish # Look up our bucket, if there is one bucket super end # Create any children via recursion or whatever. def eval_generate return [] unless self.recurse? recurse end def ancestors ancestors = Pathname.new(self[:path]).enum_for(:ascend).map(&:to_s) ancestors.delete(self[:path]) ancestors end def flush # We want to make sure we retrieve metadata anew on each transaction. @parameters.each do |name, param| param.flush if param.respond_to?(:flush) end @stat = :needs_stat end def initialize(hash) # Used for caching clients @clients = {} super # If they've specified a source, we get our 'should' values # from it. unless self[:ensure] if self[:target] self[:ensure] = :link elsif self[:content] self[:ensure] = :file end end @stat = :needs_stat end # Configure discovered resources to be purged. def mark_children_for_purging(children) children.each do |name, child| next if child[:source] child[:ensure] = :absent end end # Create a new file or directory object as a child to the current # object. def newchild(path) full_path = ::File.join(self[:path], path) # Add some new values to our original arguments -- these are the ones # set at initialization. We specifically want to exclude any param # values set by the :source property or any default values. # LAK:NOTE This is kind of silly, because the whole point here is that # the values set at initialization should live as long as the resource # but values set by default or by :source should only live for the transaction # or so. Unfortunately, we don't have a straightforward way to manage # the different lifetimes of this data, so we kludge it like this. # The right-side hash wins in the merge. options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? } # These should never be passed to our children. [:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param| options.delete(param) if options.include?(param) end self.class.new(options) end # Files handle paths specially, because they just lengthen their # path names, rather than including the full parent's title each # time. def pathbuilder # We specifically need to call the method here, so it looks # up our parent in the catalog graph. if parent = parent() # We only need to behave specially when our parent is also # a file if parent.is_a?(self.class) # Remove the parent file name list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else return super end else return [self.ref] end end # Recursively generate a list of file resources, which will # be used to copy remote files, manage local files, and/or make links # to map to another directory. def recurse children = (self[:recurse] == :remote) ? {} : recurse_local if self[:target] recurse_link(children) elsif self[:source] recurse_remote(children) end # If we're purging resources, then delete any resource that isn't on the # remote system. mark_children_for_purging(children) if self.purge? # REVISIT: sort_by is more efficient? result = children.values.sort { |a, b| a[:path] <=> b[:path] } remove_less_specific_files(result) end # This is to fix bug #2296, where two files recurse over the same # set of files. It's a rare case, and when it does happen you're # not likely to have many actual conflicts, which is good, because # this is a pretty inefficient implementation. def remove_less_specific_files(files) # REVISIT: is this Windows safe? AltSeparator? mypath = self[:path].split(::File::Separator) other_paths = catalog.vertices. select { |r| r.is_a?(self.class) and r[:path] != self[:path] }. collect { |r| r[:path].split(::File::Separator) }. select { |p| p[0,mypath.length] == mypath } return files if other_paths.empty? files.reject { |file| path = file[:path].split(::File::Separator) other_paths.any? { |p| path[0,p.length] == p } } end # A simple method for determining whether we should be recursing. def recurse? self[:recurse] == true or self[:recurse] == :remote end # Recurse the target of the link. def recurse_link(children) perform_recursion(self[:target]).each do |meta| if meta.relative_path == "." self[:ensure] = :directory next end children[meta.relative_path] ||= newchild(meta.relative_path) if meta.ftype == "directory" children[meta.relative_path][:ensure] = :directory else children[meta.relative_path][:ensure] = :link children[meta.relative_path][:target] = meta.full_path end end children end # Recurse the file itself, returning a Metadata instance for every found file. def recurse_local result = perform_recursion(self[:path]) return {} unless result result.inject({}) do |hash, meta| next hash if meta.relative_path == "." hash[meta.relative_path] = newchild(meta.relative_path) hash end end # Recurse against our remote file. def recurse_remote(children) sourceselect = self[:sourceselect] total = self[:source].collect do |source| next unless result = perform_recursion(source) return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory" result.each { |data| data.source = "#{source}/#{data.relative_path}" } break result if result and ! result.empty? and sourceselect == :first result end.flatten.compact # This only happens if we have sourceselect == :all unless sourceselect == :first found = [] total.reject! do |data| result = found.include?(data.relative_path) found << data.relative_path unless found.include?(data.relative_path) result end end total.each do |meta| if meta.relative_path == "." parameter(:source).metadata = meta next end children[meta.relative_path] ||= newchild(meta.relative_path) children[meta.relative_path][:source] = meta.source children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file" children[meta.relative_path].parameter(:source).metadata = meta end children end def perform_recursion(path) Puppet::FileServing::Metadata.indirection.search( path, :links => self[:links], :recurse => (self[:recurse] == :remote ? true : self[:recurse]), :recurselimit => self[:recurselimit], :ignore => self[:ignore], :checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none, :environment => catalog.environment ) end # Back up and remove the file or directory at `self[:path]`. # # @param [Symbol] should The file type replacing the current content. # @return [Boolean] True if the file was removed, else False # @raises [fail???] If the current file isn't one of %w{file link directory} and can't be removed. def remove_existing(should) wanted_type = should.to_s current_type = read_current_type if current_type.nil? return false end if can_backup?(current_type) backup_existing end if wanted_type != "link" and current_type == wanted_type return false end case current_type when "directory" return remove_directory(wanted_type) when "link", "file" return remove_file(current_type, wanted_type) else self.fail "Could not back up files of type #{current_type}" end end def retrieve if source = parameter(:source) source.copy_source_values end super end # Set the checksum, from another property. There are multiple # properties that modify the contents of a file, and they need the # ability to make sure that the checksum value is in sync. def setchecksum(sum = nil) if @parameters.include? :checksum if sum @parameters[:checksum].checksum = sum else # If they didn't pass in a sum, then tell checksum to # figure it out. currentvalue = @parameters[:checksum].retrieve @parameters[:checksum].checksum = currentvalue end end end # Should this thing be a normal file? This is a relatively complex # way of determining whether we're trying to create a normal file, # and it's here so that the logic isn't visible in the content property. def should_be_file? return true if self[:ensure] == :file # I.e., it's set to something like "directory" return false if e = self[:ensure] and e != :present # The user doesn't really care, apparently if self[:ensure] == :present return true unless s = stat return(s.ftype == "file" ? true : false) end # If we've gotten here, then :ensure isn't set return true if self[:content] return true if stat and stat.ftype == "file" false end # Stat our file. Depending on the value of the 'links' attribute, we # use either 'stat' or 'lstat', and we expect the properties to use the # resulting stat object accordingly (mostly by testing the 'ftype' # value). # # We use the initial value :needs_stat to ensure we only stat the file once, # but can also keep track of a failed stat (@stat == nil). This also allows # us to re-stat on demand by setting @stat = :needs_stat. def stat return @stat unless @stat == :needs_stat method = :stat # Files are the only types that support links if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy method = :lstat end @stat = begin Puppet::FileSystem.send(method, self[:path]) rescue Errno::ENOENT => error nil rescue Errno::ENOTDIR => error nil rescue Errno::EACCES => error warning "Could not stat; permission denied" nil end end def to_resource resource = super resource.delete(:target) if resource[:target] == :notlink resource end # Write out the file. Requires the property name for logging. # Write will be done by the content property, along with checksum computation def write(property) remove_existing(:file) mode = self.should(:mode) # might be nil mode_int = mode ? symbolic_mode_to_int(mode, Puppet::Util::DEFAULT_POSIX_MODE) : nil if write_temporary_file? Puppet::Util.replace_file(self[:path], mode_int) do |file| file.binmode content_checksum = write_content(file) file.flush fail_if_checksum_is_wrong(file.path, content_checksum) if validate_checksum? if self[:validate_cmd] output = Puppet::Util::Execution.execute(self[:validate_cmd].gsub(self[:validate_replacement], file.path), :failonfail => true, :combine => true) output.split(/\n/).each { |line| self.debug(line) } end end else umask = mode ? 000 : 022 Puppet::Util.withumask(umask) { ::File.open(self[:path], 'wb', mode_int ) { |f| write_content(f) } } end # make sure all of the modes are actually correct property_fix end private # @return [String] The type of the current file, cast to a string. def read_current_type stat_info = stat if stat_info stat_info.ftype.to_s else nil end end # @return [Boolean] If the current file can be backed up and needs to be backed up. def can_backup?(type) if type == "directory" and not force? # (#18110) Directories cannot be removed without :force, so it doesn't # make sense to back them up. false else true end end # @return [Boolean] True if the directory was removed # @api private def remove_directory(wanted_type) if force? debug "Removing existing directory for replacement with #{wanted_type}" FileUtils.rmtree(self[:path]) stat_needed true else notice "Not removing directory; use 'force' to override" false end end # @return [Boolean] if the file was removed (which is always true currently) # @api private def remove_file(current_type, wanted_type) debug "Removing existing #{current_type} for replacement with #{wanted_type}" Puppet::FileSystem.unlink(self[:path]) stat_needed true end def stat_needed @stat = :needs_stat end # Back up the existing file at a given prior to it being removed # @api private # @raise [Puppet::Error] if the file backup failed # @return [void] def backup_existing unless perform_backup raise Puppet::Error, "Could not back up; will not replace" end end # Should we validate the checksum of the file we're writing? def validate_checksum? self[:checksum] !~ /time/ end # Make sure the file we wrote out is what we think it is. def fail_if_checksum_is_wrong(path, content_checksum) newsum = parameter(:checksum).sum_file(path) return if [:absent, nil, content_checksum].include?(newsum) self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})" end # write the current content. Note that if there is no content property # simply opening the file with 'w' as done in write is enough to truncate # or write an empty length file. def write_content(file) (content = property(:content)) && content.write(file) end def write_temporary_file? # unfortunately we don't know the source file size before fetching it # so let's assume the file won't be empty (c = property(:content) and c.length) || @parameters[:source] end # There are some cases where all of the work does not get done on # file creation/modification, so we have to do some extra checking. def property_fix properties.each do |thing| next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name) # Make sure we get a new stat objct @stat = :needs_stat currentvalue = thing.retrieve thing.sync unless thing.safe_insync?(currentvalue) end end end # We put all of the properties in separate files, because there are so many # of them. The order these are loaded is important, because it determines # the order they are in the property lit. require 'puppet/type/file/checksum' require 'puppet/type/file/content' # can create the file require 'puppet/type/file/source' # can create the file require 'puppet/type/file/target' # creates a different type of file require 'puppet/type/file/ensure' # can create the file require 'puppet/type/file/owner' require 'puppet/type/file/group' require 'puppet/type/file/mode' require 'puppet/type/file/type' require 'puppet/type/file/selcontext' # SELinux file context require 'puppet/type/file/ctime' require 'puppet/type/file/mtime' diff --git a/spec/unit/hiera_puppet_spec.rb b/spec/unit/hiera_puppet_spec.rb index 782f12e25..221dfa9c6 100644 --- a/spec/unit/hiera_puppet_spec.rb +++ b/spec/unit/hiera_puppet_spec.rb @@ -1,118 +1,118 @@ require 'spec_helper' require 'hiera_puppet' require 'puppet_spec/scope' describe 'HieraPuppet' do include PuppetSpec::Scope after(:all) do HieraPuppet.instance_variable_set(:@hiera, nil) end describe 'HieraPuppet#hiera_config' do let(:hiera_config_data) do { :backend => 'yaml' } end context "when the hiera_config_file exists" do before do Hiera::Config.expects(:load).returns(hiera_config_data) HieraPuppet.expects(:hiera_config_file).returns(true) end it "should return a configuration hash" do expected_results = { :backend => 'yaml', :logger => 'puppet' } HieraPuppet.send(:hiera_config).should == expected_results end end context "when the hiera_config_file does not exist" do before do Hiera::Config.expects(:load).never HieraPuppet.expects(:hiera_config_file).returns(nil) end it "should return a configuration hash" do HieraPuppet.send(:hiera_config).should == { :logger => 'puppet' } end end end describe 'HieraPuppet#hiera_config_file' do it "should return nil when we cannot derive the hiera config file from Puppet.settings" do begin Puppet.settings[:hiera_config] = nil rescue ArgumentError => detail - raise unless detail.message =~ /unknown configuration parameter/ + raise unless detail.message =~ /unknown setting/ end HieraPuppet.send(:hiera_config_file).should be_nil end it "should use Puppet.settings[:hiera_config] as the hiera config file" do begin Puppet.settings[:hiera_config] = "/dev/null/my_hiera.yaml" rescue ArgumentError => detail - raise unless detail.message =~ /unknown configuration parameter/ + raise unless detail.message =~ /unknown setting/ pending("This example does not apply to Puppet #{Puppet.version} because it does not have this setting") end Puppet::FileSystem.stubs(:exist?).with(Puppet[:hiera_config]).returns(true) HieraPuppet.send(:hiera_config_file).should == Puppet[:hiera_config] end it "should use Puppet.settings[:confdir] as the base directory when hiera_config is not set" do begin Puppet.settings[:hiera_config] = nil rescue ArgumentError => detail - raise unless detail.message =~ /unknown configuration parameter/ + raise unless detail.message =~ /unknown setting/ end Puppet.settings[:confdir] = "/dev/null/puppet" hiera_config = File.join(Puppet[:confdir], 'hiera.yaml') Puppet::FileSystem.stubs(:exist?).with(hiera_config).returns(true) HieraPuppet.send(:hiera_config_file).should == hiera_config end end describe 'HieraPuppet#lookup' do let :scope do create_test_scope_for_node('foo') end before :each do Puppet[:hiera_config] = PuppetSpec::Files.tmpfile('hiera_config') end it "should return the value from Hiera" do Hiera.any_instance.stubs(:lookup).returns('8080') HieraPuppet.lookup('port', nil, scope, nil, :priority).should == '8080' Hiera.any_instance.stubs(:lookup).returns(['foo', 'bar']) HieraPuppet.lookup('ntpservers', nil, scope, nil, :array).should == ['foo', 'bar'] Hiera.any_instance.stubs(:lookup).returns({'uid' => '1000'}) HieraPuppet.lookup('user', nil, scope, nil, :hash).should == {'uid' => '1000'} end it "should raise a useful error when the answer is nil" do Hiera.any_instance.stubs(:lookup).returns(nil) expect do HieraPuppet.lookup('port', nil, scope, nil, :priority) end.to raise_error(Puppet::ParseError, /Could not find data item port in any Hiera data file and no default supplied/) end end describe 'HieraPuppet#parse_args' do it 'should return a 3 item array' do args = ['foo', '8080', nil, nil] HieraPuppet.parse_args(args).should == ['foo', '8080', nil] end it 'should raise a useful error when no key is supplied' do expect { HieraPuppet.parse_args([]) }.to raise_error(Puppet::ParseError, /Please supply a parameter to perform a Hiera lookup/) end end end diff --git a/spec/unit/settings_spec.rb b/spec/unit/settings_spec.rb index b201b3eb8..b2da588b3 100755 --- a/spec/unit/settings_spec.rb +++ b/spec/unit/settings_spec.rb @@ -1,1736 +1,1736 @@ #! /usr/bin/env ruby require 'spec_helper' require 'ostruct' require 'puppet/settings/errors' describe Puppet::Settings do include PuppetSpec::Files let(:main_config_file_default_location) do File.join(Puppet::Util::RunMode[:master].conf_dir, "puppet.conf") end let(:user_config_file_default_location) do File.join(Puppet::Util::RunMode[:user].conf_dir, "puppet.conf") end describe "when specifying defaults" do before do @settings = Puppet::Settings.new end it "should start with no defined parameters" do @settings.params.length.should == 0 end it "should not allow specification of default values associated with a section as an array" do expect { @settings.define_settings(:section, :myvalue => ["defaultval", "my description"]) }.to raise_error end it "should not allow duplicate parameter specifications" do @settings.define_settings(:section, :myvalue => { :default => "a", :desc => "b" }) lambda { @settings.define_settings(:section, :myvalue => { :default => "c", :desc => "d" }) }.should raise_error(ArgumentError) end it "should allow specification of default values associated with a section as a hash" do @settings.define_settings(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) end it "should consider defined parameters to be valid" do @settings.define_settings(:section, :myvalue => { :default => "defaultval", :desc => "my description" }) @settings.valid?(:myvalue).should be_true end it "should require a description when defaults are specified with a hash" do lambda { @settings.define_settings(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) end it "should support specifying owner, group, and mode when specifying files" do @settings.define_settings(:section, :myvalue => {:type => :file, :default => "/some/file", :owner => "service", :mode => "boo", :group => "service", :desc => "whatever"}) end it "should support specifying a short name" do @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) end it "should support specifying the setting type" do @settings.define_settings(:section, :myvalue => {:default => "/w", :desc => "b", :type => :string}) @settings.setting(:myvalue).should be_instance_of(Puppet::Settings::StringSetting) end it "should fail if an invalid setting type is specified" do lambda { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :type => :foo}) }.should raise_error(ArgumentError) end it "should fail when short names conflict" do @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) lambda { @settings.define_settings(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) end end describe "when initializing application defaults do" do let(:default_values) do values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| values[key] = 'default value' end values end before do @settings = Puppet::Settings.new @settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS) end it "should fail if the app defaults hash is missing any required values" do incomplete_default_values = default_values.reject { |key, _| key == :confdir } expect { @settings.initialize_app_defaults(default_values.reject { |key, _| key == :confdir }) }.to raise_error(Puppet::Settings::SettingsError) end # ultimately I'd like to stop treating "run_mode" as a normal setting, because it has so many special # case behaviors / uses. However, until that time... we need to make sure that our private run_mode= # setter method gets properly called during app initialization. it "sets the preferred run mode when initializing the app defaults" do @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) @settings.preferred_run_mode.should == :master end end describe "#call_hooks_deferred_to_application_initialization" do let(:good_default) { "yay" } let(:bad_default) { "$doesntexist" } before(:each) do @settings = Puppet::Settings.new end describe "when ignoring dependency interpolation errors" do let(:options) { {:ignore_interpolation_dependency_errors => true} } describe "if interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings(:section, :badhook => {:default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }}) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end describe "if no interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings(:section, :goodhook => {:default => good_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v }}) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end end describe "when not ignoring dependency interpolation errors" do [ {}, {:ignore_interpolation_dependency_errors => false}].each do |options| describe "if interpolation error" do it "should raise an error" do hook_values = [] @settings.define_settings( :section, :badhook => { :default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to raise_error(Puppet::Settings::InterpolationError) end it "should contain the setting name in error message" do hook_values = [] @settings.define_settings( :section, :badhook => { :default => bad_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to raise_error(Puppet::Settings::InterpolationError, /badhook/) end end describe "if no interpolation error" do it "should not raise an error" do hook_values = [] @settings.define_settings( :section, :goodhook => { :default => good_default, :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| hook_values << v } } ) expect do @settings.send(:call_hooks_deferred_to_application_initialization, options) end.to_not raise_error end end end end end describe "when setting values" do before do @settings = Puppet::Settings.new @settings.define_settings :main, :myval => { :default => "val", :desc => "desc" } @settings.define_settings :main, :bool => { :type => :boolean, :default => true, :desc => "desc" } end it "should provide a method for setting values from other objects" do @settings[:myval] = "something else" @settings[:myval].should == "something else" end it "should support a getopt-specific mechanism for setting values" do @settings.handlearg("--myval", "newval") @settings[:myval].should == "newval" end it "should support a getopt-specific mechanism for turning booleans off" do @settings[:bool] = true @settings.handlearg("--no-bool", "") @settings[:bool].should == false end it "should support a getopt-specific mechanism for turning booleans on" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool", "") @settings[:bool].should == true end it "should consider a cli setting with no argument to be a boolean" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool") @settings[:bool].should == true end it "should consider a cli setting with an empty string as an argument to be a boolean, if the setting itself is a boolean" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool", "") @settings[:bool].should == true end it "should consider a cli setting with an empty string as an argument to be an empty argument, if the setting itself is not a boolean" do @settings[:myval] = "bob" @settings.handlearg("--myval", "") @settings[:myval].should == "" end it "should consider a cli setting with a boolean as an argument to be a boolean" do # Turn it off first @settings[:bool] = false @settings.handlearg("--bool", "true") @settings[:bool].should == true end it "should not consider a cli setting of a non boolean with a boolean as an argument to be a boolean" do # Turn it off first @settings[:myval] = "bob" @settings.handlearg("--no-myval", "") @settings[:myval].should == "" end it "should flag string settings from the CLI" do @settings.handlearg("--myval", "12") @settings.set_by_cli?(:myval).should be_true end it "should flag bool settings from the CLI" do @settings[:bool] = false @settings.handlearg("--bool") @settings.set_by_cli?(:bool).should be_true end it "should not flag settings memory as from CLI" do @settings[:myval] = "12" @settings.set_by_cli?(:myval).should be_false end describe "setbycli" do it "should generate a deprecation warning" do Puppet.expects(:deprecation_warning).at_least(1) @settings.setting(:myval).setbycli = true end it "should set the value" do @settings[:myval] = "blah" @settings.setting(:myval).setbycli = true @settings.set_by_cli?(:myval).should be_true end it "should raise error if trying to unset value" do @settings.handlearg("--myval", "blah") expect do @settings.setting(:myval).setbycli = nil end.to raise_error(ArgumentError, /unset/) end end it "should clear the cache when setting getopt-specific values" do @settings.define_settings :mysection, :one => { :default => "whah", :desc => "yay" }, :two => { :default => "$one yay", :desc => "bah" } @settings.expects(:unsafe_flush_cache) @settings[:two].should == "whah yay" @settings.handlearg("--one", "else") @settings[:two].should == "else yay" end it "should clear the cache when the preferred_run_mode is changed" do @settings.expects(:flush_cache) @settings.preferred_run_mode = :master end it "should not clear other values when setting getopt-specific values" do @settings[:myval] = "yay" @settings.handlearg("--no-bool", "") @settings[:myval].should == "yay" end it "should clear the list of used sections" do @settings.expects(:clearused) @settings[:myval] = "yay" end describe "call_hook" do Puppet::Settings::StringSetting.available_call_hook_values.each do |val| describe "when :#{val}" do describe "and definition invalid" do it "should raise error if no hook defined" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val}) end.to raise_error(ArgumentError, /no :hook/) end it "should include the setting name in the error message" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val}) end.to raise_error(ArgumentError, /for :hooker/) end end describe "and definition valid" do before(:each) do hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val, :hook => lambda { |v| hook_values << v }}) end it "should call the hook when value written" do @settings.setting(:hooker).expects(:handle).with("something").once @settings[:hooker] = "something" end end end end it "should have a default value of :on_write_only" do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_hook.should == :on_write_only end describe "when nil" do it "should generate a warning" do Puppet.expects(:warning) @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }}) end it "should use default" do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => nil, :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_hook.should == :on_write_only end end describe "when invalid" do it "should raise an error" do expect do @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :foo, :hook => lambda { |v| hook_values << v }}) end.to raise_error(ArgumentError, /invalid.*call_hook/i) end end describe "when :on_define_and_write" do it "should call the hook at definition" do hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_hook.should == :on_define_and_write hook_values.should == %w{yay} end end describe "when :on_initialize_and_write" do before(:each) do @hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_initialize_and_write, :hook => lambda { |v| @hook_values << v }}) end it "should not call the hook at definition" do @hook_values.should == [] @hook_values.should_not == %w{yay} end it "should call the hook at initialization" do app_defaults = {} Puppet::Settings::REQUIRED_APP_SETTINGS.each do |key| app_defaults[key] = "foo" end app_defaults[:run_mode] = :user @settings.define_settings(:main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS) @settings.setting(:hooker).expects(:handle).with("yay").once @settings.initialize_app_defaults app_defaults end end end describe "call_on_define" do [true, false].each do |val| describe "to #{val}" do it "should generate a deprecation warning" do Puppet.expects(:deprecation_warning) values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_on_define => val, :hook => lambda { |v| values << v }}) end it "should should set call_hook" do values = [] name = "hooker_#{val}".to_sym @settings.define_settings(:section, name => {:default => "yay", :desc => "boo", :call_on_define => val, :hook => lambda { |v| values << v }}) @settings.setting(name).call_hook.should == :on_define_and_write if val @settings.setting(name).call_hook.should == :on_write_only unless val end end end end it "should call passed blocks when values are set" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) values.should == [] @settings[:hooker] = "something" values.should == %w{something} end it "should call passed blocks when values are set via the command line" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) values.should == [] @settings.handlearg("--hooker", "yay") values.should == %w{yay} end it "should provide an option to call passed blocks during definition" do values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }}) values.should == %w{yay} end it "should pass the fully interpolated value to the hook when called on definition" do values = [] @settings.define_settings(:section, :one => { :default => "test", :desc => "a" }) @settings.define_settings(:section, :hooker => {:default => "$one/yay", :desc => "boo", :call_hook => :on_define_and_write, :hook => lambda { |v| values << v }}) values.should == %w{test/yay} end it "should munge values using the setting-specific methods" do @settings[:bool] = "false" @settings[:bool].should == false end it "should prefer cli values to values set in Ruby code" do @settings.handlearg("--myval", "cliarg") @settings[:myval] = "memarg" @settings[:myval].should == "cliarg" end it "should clear the list of environments" do Puppet::Node::Environment.expects(:clear).at_least(1) @settings[:myval] = "memarg" end it "should raise an error if we try to set a setting that hasn't been defined'" do lambda{ @settings[:why_so_serious] = "foo" - }.should raise_error(ArgumentError, /unknown configuration parameter/) + }.should raise_error(ArgumentError, /unknown setting/) end end describe "when returning values" do before do @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => "/my/file", :desc => "eh" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b"}, :three => { :default => "$one $two THREE", :desc => "c"}, :four => { :default => "$two $three FOUR", :desc => "d"}, :five => { :default => nil, :desc => "e" } Puppet::FileSystem.stubs(:exist?).returns true end describe "call_on_define" do it "should generate a deprecation warning" do Puppet.expects(:deprecation_warning) @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_on_define end Puppet::Settings::StringSetting.available_call_hook_values.each do |val| it "should match value for call_hook => :#{val}" do hook_values = [] @settings.define_settings(:section, :hooker => {:default => "yay", :desc => "boo", :call_hook => val, :hook => lambda { |v| hook_values << v }}) @settings.setting(:hooker).call_on_define.should == @settings.setting(:hooker).call_hook_on_define? end end end it "should provide a mechanism for returning set values" do @settings[:one] = "other" @settings[:one].should == "other" end it "setting a value to nil causes it to return to its default" do default_values = { :one => "skipped value" } [:logdir, :confdir, :vardir].each do |key| default_values[key] = 'default value' end @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.initialize_app_defaults(default_values) @settings[:one] = "value will disappear" @settings[:one] = nil @settings[:one].should == "ONE" end it "should interpolate default values for other parameters into returned parameter values" do @settings[:one].should == "ONE" @settings[:two].should == "ONE TWO" @settings[:three].should == "ONE ONE TWO THREE" end it "should interpolate default values that themselves need to be interpolated" do @settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" end it "should provide a method for returning uninterpolated values" do @settings[:two] = "$one tw0" @settings.uninterpolated_value(:two).should == "$one tw0" @settings.uninterpolated_value(:four).should == "$two $three FOUR" end it "should interpolate set values for other parameters into returned parameter values" do @settings[:one] = "on3" @settings[:two] = "$one tw0" @settings[:three] = "$one $two thr33" @settings[:four] = "$one $two $three f0ur" @settings[:one].should == "on3" @settings[:two].should == "on3 tw0" @settings[:three].should == "on3 on3 tw0 thr33" @settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" end it "should not cache interpolated values such that stale information is returned" do @settings[:two].should == "ONE TWO" @settings[:one] = "one" @settings[:two].should == "one TWO" end it "should not cache values such that information from one environment is returned for another environment" do text = "[env1]\none = oneval\n[env2]\none = twoval\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings.value(:one, "env1").should == "oneval" @settings.value(:one, "env2").should == "twoval" end it "should have a run_mode that defaults to user" do @settings.preferred_run_mode.should == :user end describe "setbycli" do it "should generate a deprecation warning" do @settings.handlearg("--one", "blah") Puppet.expects(:deprecation_warning) @settings.setting(:one).setbycli end it "should be true" do @settings.handlearg("--one", "blah") @settings.setting(:one).setbycli.should be_true end end end describe "when choosing which value to return" do before do @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => "/my/file", :desc => "a" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "TWO", :desc => "b" } Puppet::FileSystem.stubs(:exist?).returns true @settings.preferred_run_mode = :agent end it "should return default values if no values have been set" do @settings[:one].should == "ONE" end it "should return values set on the cli before values set in the configuration file" do text = "[main]\none = fileval\n" @settings.stubs(:read_file).returns(text) @settings.handlearg("--one", "clival") @settings.send(:parse_config_files) @settings[:one].should == "clival" end it "should return values set on the cli before values set in Ruby" do @settings[:one] = "rubyval" @settings.handlearg("--one", "clival") @settings[:one].should == "clival" end it "should return values set in the mode-specific section before values set in the main section" do text = "[main]\none = mainval\n[agent]\none = modeval\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "modeval" end it "should not return values outside of its search path" do text = "[other]\none = oval\n" file = "/some/file" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "ONE" end it "should return values in a specified environment" do text = "[env]\none = envval\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings.value(:one, "env").should == "envval" end it 'should use the current environment for $environment' do @settings.define_settings :main, :myval => { :default => "$environment/foo", :desc => "mydocs" } @settings.value(:myval, "myenv").should == "myenv/foo" end it "should interpolate found values using the current environment" do text = "[main]\none = mainval\n[myname]\none = nameval\ntwo = $one/two\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings.value(:two, "myname").should == "nameval/two" end it "should return values in a specified environment before values in the main or name sections" do text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings.value(:one, "env").should == "envval" end end describe "when locating config files" do before do @settings = Puppet::Settings.new end describe "when root" do it "should look for the main config file default location config settings haven't been overridden'" do Puppet.features.stubs(:root?).returns(true) Puppet::FileSystem.expects(:exist?).with(main_config_file_default_location).returns(false) Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).never @settings.send(:parse_config_files) end end describe "when not root" do it "should look for user config file default location if config settings haven't been overridden'" do Puppet.features.stubs(:root?).returns(false) seq = sequence "load config files" Puppet::FileSystem.expects(:exist?).with(user_config_file_default_location).returns(false).in_sequence(seq) @settings.send(:parse_config_files) end end end describe "when parsing its configuration" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @settings.stubs(:service_group_available?).returns true @file = make_absolute("/some/file") @userconfig = make_absolute("/test/userconfigfile") @settings.define_settings :section, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :section, :config => { :type => :file, :default => @file, :desc => "eh" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } @settings.stubs(:user_config_file).returns(@userconfig) Puppet::FileSystem.stubs(:exist?).with(@file).returns true Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false end it "should not ignore the report setting" do @settings.define_settings :section, :report => { :default => "false", :desc => "a" } # This is needed in order to make sure we pass on windows myfile = File.expand_path(@file) @settings[:config] = myfile text = <<-CONF [puppetd] report=true CONF Puppet::FileSystem.expects(:exist?).with(myfile).returns(true) @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:report].should be_true end it "should use its current ':config' value for the file to parse" do myfile = make_absolute("/my/file") # do not stub expand_path here, as this leads to a stack overflow, when mocha tries to use it @settings[:config] = myfile Puppet::FileSystem.expects(:exist?).with(myfile).returns(true) File.expects(:read).with(myfile).returns "[main]" @settings.send(:parse_config_files) end it "should not try to parse non-existent files" do Puppet::FileSystem.expects(:exist?).with(@file).returns false File.expects(:read).with(@file).never @settings.send(:parse_config_files) end it "should return values set in the configuration file" do text = "[main] one = fileval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "fileval" end #484 - this should probably be in the regression area it "should not throw an exception on unknown parameters" do text = "[main]\nnosuchparam = mval\n" @settings.expects(:read_file).returns(text) lambda { @settings.send(:parse_config_files) }.should_not raise_error end it "should convert booleans in the configuration file into Ruby booleans" do text = "[main] one = true two = false " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == true @settings[:two].should == false end it "should convert integers in the configuration file into Ruby Integers" do text = "[main] one = 65 " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == 65 end it "should support specifying all metadata (owner, group, mode) in the configuration file" do @settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") @settings.parse_config(<<-CONF) [main] myfile = #{otherfile} {owner = service, group = service, mode = 644} CONF @settings[:myfile].should == otherfile @settings.metadata(:myfile).should == {:owner => "suser", :group => "sgroup", :mode => "644"} end it "should support specifying a single piece of metadata (owner, group, or mode) in the configuration file" do @settings.define_settings :section, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") @settings.parse_config(<<-CONF) [main] myfile = #{otherfile} {owner = service} CONF @settings[:myfile].should == otherfile @settings.metadata(:myfile).should == {:owner => "suser"} end it "should support loading metadata (owner, group, or mode) from a run_mode section in the configuration file" do default_values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| default_values[key] = 'default value' end @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.define_settings :master, :myfile => { :type => :file, :default => make_absolute("/myfile"), :desc => "a" } otherfile = make_absolute("/other/file") text = "[master] myfile = #{otherfile} {mode = 664} " @settings.expects(:read_file).returns(text) # will start initialization as user @settings.preferred_run_mode.should == :user @settings.send(:parse_config_files) # change app run_mode to master @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) @settings.preferred_run_mode.should == :master # initializing the app should have reloaded the metadata based on run_mode @settings[:myfile].should == otherfile @settings.metadata(:myfile).should == {:mode => "664"} end it "does not use the metadata from the same setting in a different section" do default_values = {} PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS.keys.each do |key| default_values[key] = 'default value' end file = make_absolute("/file") default_mode = "0600" @settings.define_settings :main, PuppetSpec::Settings::TEST_APP_DEFAULT_DEFINITIONS @settings.define_settings :master, :myfile => { :type => :file, :default => file, :desc => "a", :mode => default_mode } text = "[master] myfile = #{file}/foo [agent] myfile = #{file} {mode = 664} " @settings.expects(:read_file).returns(text) # will start initialization as user @settings.preferred_run_mode.should == :user @settings.send(:parse_config_files) # change app run_mode to master @settings.initialize_app_defaults(default_values.merge(:run_mode => :master)) @settings.preferred_run_mode.should == :master # initializing the app should have reloaded the metadata based on run_mode @settings[:myfile].should == "#{file}/foo" @settings.metadata(:myfile).should == { :mode => default_mode } end it "should call hooks associated with values set in the configuration file" do values = [] @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = setval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) values.should == ["setval"] end it "should not call the same hook for values set multiple times in the configuration file" do values = [] @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[user] mysetting = setval [main] mysetting = other " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) values.should == ["setval"] end it "should pass the environment-specific value to the hook when one is available" do values = [] @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} @settings.define_settings :section, :environment => { :default => "yay", :desc => "a" } @settings.define_settings :section, :environments => { :default => "yay,foo", :desc => "a" } text = "[main] mysetting = setval [yay] mysetting = other " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) values.should == ["other"] end it "should pass the interpolated value to the hook when one is available" do values = [] @settings.define_settings :section, :base => {:default => "yay", :desc => "a", :hook => proc { |v| values << v }} @settings.define_settings :section, :mysetting => {:default => "defval", :desc => "a", :hook => proc { |v| values << v }} text = "[main] mysetting = $base/setval " @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) values.should == ["yay/setval"] end it "should allow hooks invoked at parse time to be deferred" do hook_invoked = false @settings.define_settings :section, :deferred => {:desc => '', :hook => proc { |v| hook_invoked = true }, :call_hook => :on_initialize_and_write, } @settings.define_settings(:main, :logdir => { :type => :directory, :default => nil, :desc => "logdir" }, :confdir => { :type => :directory, :default => nil, :desc => "confdir" }, :vardir => { :type => :directory, :default => nil, :desc => "vardir" }) text = <<-EOD [main] deferred=$confdir/goose EOD @settings.stubs(:read_file).returns(text) @settings.initialize_global_settings hook_invoked.should be_false @settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir') hook_invoked.should be_true @settings[:deferred].should eq(File.expand_path('/path/to/confdir/goose')) end it "does not require the value for a setting without a hook to resolve during global setup" do hook_invoked = false @settings.define_settings :section, :can_cause_problems => {:desc => '' } @settings.define_settings(:main, :logdir => { :type => :directory, :default => nil, :desc => "logdir" }, :confdir => { :type => :directory, :default => nil, :desc => "confdir" }, :vardir => { :type => :directory, :default => nil, :desc => "vardir" }) text = <<-EOD [main] can_cause_problems=$confdir/goose EOD @settings.stubs(:read_file).returns(text) @settings.initialize_global_settings @settings.initialize_app_defaults(:logdir => '/path/to/logdir', :confdir => '/path/to/confdir', :vardir => '/path/to/vardir') @settings[:can_cause_problems].should eq(File.expand_path('/path/to/confdir/goose')) end it "should allow empty values" do @settings.define_settings :section, :myarg => { :default => "myfile", :desc => "a" } text = "[main] myarg = " @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:myarg].should == "" end describe "and when reading a non-positive filetimeout value from the config file" do before do @settings.define_settings :foo, :filetimeout => { :default => 5, :desc => "eh" } somefile = "/some/file" text = "[main] filetimeout = -1 " File.expects(:read).with(somefile).returns(text) File.expects(:expand_path).with(somefile).returns somefile @settings[:config] = somefile end end end describe "when there are multiple config files" do let(:main_config_text) { "[main]\none = main\ntwo = main2" } let(:user_config_text) { "[main]\none = user\n" } let(:seq) { sequence "config_file_sequence" } before :each do @settings = Puppet::Settings.new @settings.define_settings(:section, { :confdir => { :default => nil, :desc => "Conf dir" }, :config => { :default => "$confdir/puppet.conf", :desc => "Config" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "TWO", :desc => "b" }, }) end context "running non-root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(false) Puppet::FileSystem.expects(:exist?). with(user_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(user_config_file_default_location). returns(user_config_text).in_sequence(seq) end it "should return values from the user config file" do @settings.send(:parse_config_files) @settings[:one].should == "user" end it "should not return values from the main config file" do @settings.send(:parse_config_files) @settings[:two].should == "TWO" end end context "running as root without explicit config file" do before :each do Puppet.features.stubs(:root?).returns(true) Puppet::FileSystem.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(main_config_file_default_location). returns(main_config_text).in_sequence(seq) end it "should return values from the main config file" do @settings.send(:parse_config_files) @settings[:one].should == "main" end it "should not return values from the user config file" do @settings.send(:parse_config_files) @settings[:two].should == "main2" end end context "running with an explicit config file as a user (e.g. Apache + Passenger)" do before :each do Puppet.features.stubs(:root?).returns(false) @settings[:confdir] = File.dirname(main_config_file_default_location) Puppet::FileSystem.expects(:exist?). with(main_config_file_default_location). returns(true).in_sequence(seq) @settings.expects(:read_file). with(main_config_file_default_location). returns(main_config_text).in_sequence(seq) end it "should return values from the main config file" do @settings.send(:parse_config_files) @settings[:one].should == "main" end it "should not return values from the user config file" do @settings.send(:parse_config_files) @settings[:two].should == "main2" end end end describe "when reparsing its configuration" do before do @file = make_absolute("/test/file") @userconfig = make_absolute("/test/userconfigfile") @settings = Puppet::Settings.new @settings.define_settings :section, :config => { :type => :file, :default => @file, :desc => "a" }, :one => { :default => "ONE", :desc => "a" }, :two => { :default => "$one TWO", :desc => "b" }, :three => { :default => "$one $two THREE", :desc => "c" } Puppet::FileSystem.stubs(:exist?).with(@file).returns true Puppet::FileSystem.stubs(:exist?).with(@userconfig).returns false @settings.stubs(:user_config_file).returns(@userconfig) end it "does not create the WatchedFile instance and should not parse if the file does not exist" do Puppet::FileSystem.expects(:exist?).with(@file).returns false Puppet::Util::WatchedFile.expects(:new).never @settings.expects(:parse_config_files).never @settings.reparse_config_files end context "and watched file exists" do before do @watched_file = Puppet::Util::WatchedFile.new(@file) Puppet::Util::WatchedFile.expects(:new).with(@file).returns @watched_file end it "uses a WatchedFile instance to determine if the file has changed" do @watched_file.expects(:changed?) @settings.reparse_config_files end it "does not reparse if the file has not changed" do @watched_file.expects(:changed?).returns false @settings.expects(:parse_config_files).never @settings.reparse_config_files end it "reparses if the file has changed" do @watched_file.expects(:changed?).returns true @settings.expects(:parse_config_files) @settings.reparse_config_files end it "replaces in-memory values with on-file values" do @watched_file.stubs(:changed?).returns(true) @settings[:one] = "init" # Now replace the value text = "[main]\none = disk-replace\n" @settings.stubs(:read_file).returns(text) @settings.reparse_config_files @settings[:one].should == "disk-replace" end end it "should retain parameters set by cli when configuration files are reparsed" do @settings.handlearg("--one", "clival") text = "[main]\none = on-disk\n" @settings.stubs(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "clival" end it "should remove in-memory values that are no longer set in the file" do # Init the value text = "[main]\none = disk-init\n" @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "disk-init" # Now replace the value text = "[main]\ntwo = disk-replace\n" @settings.expects(:read_file).returns(text) @settings.send(:parse_config_files) # The originally-overridden value should be replaced with the default @settings[:one].should == "ONE" # and we should now have the new value in memory @settings[:two].should == "disk-replace" end it "should retain in-memory values if the file has a syntax error" do # Init the value text = "[main]\none = initial-value\n" @settings.expects(:read_file).with(@file).returns(text) @settings.send(:parse_config_files) @settings[:one].should == "initial-value" # Now replace the value with something bogus text = "[main]\nkenny = killed-by-what-follows\n1 is 2, blah blah florp\n" @settings.expects(:read_file).with(@file).returns(text) @settings.send(:parse_config_files) # The originally-overridden value should not be replaced with the default @settings[:one].should == "initial-value" # and we should not have the new value in memory @settings[:kenny].should be_nil end end it "should provide a method for creating a catalog of resources from its configuration" do Puppet::Settings.new.should respond_to(:to_catalog) end describe "when creating a catalog" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @prefix = Puppet.features.posix? ? "" : "C:" end it "should add all file resources to the catalog if no sections have been specified" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a"}, :seconddir => { :type => :directory, :default => @prefix+"/seconddir", :desc => "a"} @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog [@prefix+"/maindir", @prefix+"/seconddir", @prefix+"/otherdir"].each do |path| catalog.resource(:file, path).should be_instance_of(Puppet::Resource) end end it "should add only files in the specified sections if section names are provided" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/otherdir", :desc => "a" } catalog = @settings.to_catalog(:main) catalog.resource(:file, @prefix+"/otherdir").should be_nil catalog.resource(:file, @prefix+"/maindir").should be_instance_of(Puppet::Resource) end it "should not try to add the same file twice" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } lambda { @settings.to_catalog }.should_not raise_error end it "should ignore files whose :to_resource method returns nil" do @settings.define_settings :main, :maindir => { :type => :directory, :default => @prefix+"/maindir", :desc => "a" } @settings.setting(:maindir).expects(:to_resource).returns nil Puppet::Resource::Catalog.any_instance.expects(:add_resource).never @settings.to_catalog end describe "on Microsoft Windows" do before :each do Puppet.features.stubs(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns true @settings.define_settings :foo, :mkusers => { :type => :boolean, :default => true, :desc => "e" }, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => { :type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "it should not add users and groups to the catalog" do @catalog.resource(:user, "suser").should be_nil @catalog.resource(:group, "sgroup").should be_nil end end describe "when adding users and groups to the catalog" do before do Puppet.features.stubs(:root?).returns true Puppet.features.stubs(:microsoft_windows?).returns false @settings.define_settings :foo, :mkusers => { :type => :boolean, :default => true, :desc => "e" }, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} @catalog = @settings.to_catalog end it "should add each specified user and group to the catalog if :mkusers is a valid setting, is enabled, and we're running as root" do @catalog.resource(:user, "suser").should be_instance_of(Puppet::Resource) @catalog.resource(:group, "sgroup").should be_instance_of(Puppet::Resource) end it "should only add users and groups to the catalog from specified sections" do @settings.define_settings :yay, :yaydir => { :type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} catalog = @settings.to_catalog(:other) catalog.resource(:user, "jane").should be_nil catalog.resource(:group, "billy").should be_nil end it "should not add users or groups to the catalog if :mkusers not running as root" do Puppet.features.stubs(:root?).returns false catalog = @settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not add users or groups to the catalog if :mkusers is not a valid setting" do Puppet.features.stubs(:root?).returns true settings = Puppet::Settings.new settings.define_settings :other, :otherdir => {:type => :directory, :default => "/otherdir", :desc => "a", :owner => "service", :group => "service"} catalog = settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not add users or groups to the catalog if :mkusers is a valid setting but is disabled" do @settings[:mkusers] = false catalog = @settings.to_catalog catalog.resource(:user, "suser").should be_nil catalog.resource(:group, "sgroup").should be_nil end it "should not try to add users or groups to the catalog twice" do @settings.define_settings :yay, :yaydir => {:type => :directory, :default => "/yaydir", :desc => "a", :owner => "service", :group => "service"} # This would fail if users/groups were added twice lambda { @settings.to_catalog }.should_not raise_error end it "should set :ensure to :present on each created user and group" do @catalog.resource(:user, "suser")[:ensure].should == :present @catalog.resource(:group, "sgroup")[:ensure].should == :present end it "should set each created user's :gid to the service group" do @settings.to_catalog.resource(:user, "suser")[:gid].should == "sgroup" end it "should not attempt to manage the root user" do Puppet.features.stubs(:root?).returns true @settings.define_settings :foo, :foodir => {:type => :directory, :default => "/foodir", :desc => "a", :owner => "root", :group => "service"} @settings.to_catalog.resource(:user, "root").should be_nil end end end it "should be able to be converted to a manifest" do Puppet::Settings.new.should respond_to(:to_manifest) end describe "when being converted to a manifest" do it "should produce a string with the code for each resource joined by two carriage returns" do @settings = Puppet::Settings.new @settings.define_settings :main, :maindir => { :type => :directory, :default => "/maindir", :desc => "a"}, :seconddir => { :type => :directory, :default => "/seconddir", :desc => "a"} main = stub 'main_resource', :ref => "File[/maindir]" main.expects(:to_manifest).returns "maindir" second = stub 'second_resource', :ref => "File[/seconddir]" second.expects(:to_manifest).returns "seconddir" @settings.setting(:maindir).expects(:to_resource).returns main @settings.setting(:seconddir).expects(:to_resource).returns second @settings.to_manifest.split("\n\n").sort.should == %w{maindir seconddir} end end describe "when using sections of the configuration to manage the local host" do before do @settings = Puppet::Settings.new @settings.stubs(:service_user_available?).returns true @settings.stubs(:service_group_available?).returns true @settings.define_settings :main, :noop => { :default => false, :desc => "", :type => :boolean } @settings.define_settings :main, :maindir => { :type => :directory, :default => make_absolute("/maindir"), :desc => "a" }, :seconddir => { :type => :directory, :default => make_absolute("/seconddir"), :desc => "a"} @settings.define_settings :main, :user => { :default => "suser", :desc => "doc" }, :group => { :default => "sgroup", :desc => "doc" } @settings.define_settings :other, :otherdir => {:type => :directory, :default => make_absolute("/otherdir"), :desc => "a", :owner => "service", :group => "service", :mode => 0755} @settings.define_settings :third, :thirddir => { :type => :directory, :default => make_absolute("/thirddir"), :desc => "b"} @settings.define_settings :files, :myfile => {:type => :file, :default => make_absolute("/myfile"), :desc => "a", :mode => 0755} end it "should provide a method that creates directories with the correct modes" do Puppet::Util::SUIDManager.expects(:asuser).with("suser", "sgroup").yields Dir.expects(:mkdir).with(make_absolute("/otherdir"), 0755) @settings.mkdir(:otherdir) end it "should create a catalog with the specified sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should canonicalize the sections" do @settings.expects(:to_catalog).with(:main, :other).returns Puppet::Resource::Catalog.new("foo") @settings.use("main", "other") end it "should ignore sections that have already been used" do @settings.expects(:to_catalog).with(:main).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main) @settings.expects(:to_catalog).with(:other).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :other) end it "should convert the created catalog to a RAL catalog" do @catalog = Puppet::Resource::Catalog.new("foo") @settings.expects(:to_catalog).with(:main).returns @catalog @catalog.expects(:to_ral).returns @catalog @settings.use(:main) end it "should specify that it is not managing a host catalog" do catalog = Puppet::Resource::Catalog.new("foo") catalog.expects(:apply) @settings.expects(:to_catalog).returns catalog catalog.stubs(:to_ral).returns catalog catalog.expects(:host_config=).with false @settings.use(:main) end it "should support a method for re-using all currently used sections" do @settings.expects(:to_catalog).with(:main, :third).times(2).returns Puppet::Resource::Catalog.new("foo") @settings.use(:main, :third) @settings.reuse end it "should fail with an appropriate message if any resources fail" do @catalog = Puppet::Resource::Catalog.new("foo") @catalog.stubs(:to_ral).returns @catalog @settings.expects(:to_catalog).returns @catalog @trans = mock("transaction") @catalog.expects(:apply).yields(@trans) @trans.expects(:any_failed?).returns(true) resource = Puppet::Type.type(:notify).new(:title => 'failed') status = Puppet::Resource::Status.new(resource) event = Puppet::Transaction::Event.new( :name => 'failure', :status => 'failure', :message => 'My failure') status.add_event(event) report = Puppet::Transaction::Report.new('apply') report.add_resource_status(status) @trans.expects(:report).returns report @settings.expects(:raise).with(includes("My failure")) @settings.use(:whatever) end end describe "when dealing with printing configs" do before do @settings = Puppet::Settings.new #these are the magic default values @settings.stubs(:value).with(:configprint).returns("") @settings.stubs(:value).with(:genconfig).returns(false) @settings.stubs(:value).with(:genmanifest).returns(false) @settings.stubs(:value).with(:environment).returns(nil) end describe "when checking print_config?" do it "should return false when the :configprint, :genconfig and :genmanifest are not set" do @settings.print_configs?.should be_false end it "should return true when :configprint has a value" do @settings.stubs(:value).with(:configprint).returns("something") @settings.print_configs?.should be_true end it "should return true when :genconfig has a value" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.print_configs?.should be_true end it "should return true when :genmanifest has a value" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.print_configs?.should be_true end end describe "when printing configs" do describe "when :configprint has a value" do it "should call print_config_options" do @settings.stubs(:value).with(:configprint).returns("something") @settings.expects(:print_config_options) @settings.print_configs end it "should get the value of the option using the environment" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.expects(:value).with(:environment).returns("env") @settings.expects(:value).with("something", "env").returns("foo") @settings.stubs(:puts).with("foo") @settings.print_configs end it "should print the value of the option" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.expects(:puts).with("foo") @settings.print_configs end it "should print the value pairs if there are multiple options" do @settings.stubs(:value).with(:configprint).returns("bar,baz") @settings.stubs(:include?).with("bar").returns(true) @settings.stubs(:include?).with("baz").returns(true) @settings.stubs(:value).with("bar", nil).returns("foo") @settings.stubs(:value).with("baz", nil).returns("fud") @settings.expects(:puts).with("bar = foo") @settings.expects(:puts).with("baz = fud") @settings.print_configs end it "should return true after printing" do @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(true) @settings.stubs(:value).with("something", nil).returns("foo") @settings.stubs(:puts).with("foo") @settings.print_configs.should be_true end it "should return false if a config param is not found" do @settings.stubs :puts @settings.stubs(:value).with(:configprint).returns("something") @settings.stubs(:include?).with("something").returns(false) @settings.print_configs.should be_false end end describe "when genconfig is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.expects(:to_config) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genconfig).returns(true) @settings.stubs(:to_config) @settings.print_configs.should be_true end end describe "when genmanifest is true" do before do @settings.stubs :puts end it "should call to_config" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.expects(:to_manifest) @settings.print_configs end it "should return true from print_configs" do @settings.stubs(:value).with(:genmanifest).returns(true) @settings.stubs(:to_manifest) @settings.print_configs.should be_true end end end end describe "when determining if the service user is available" do let(:settings) do settings = Puppet::Settings.new settings.define_settings :main, :user => { :default => nil, :desc => "doc" } settings end def a_user_type_for(username) user = mock 'user' Puppet::Type.type(:user).expects(:new).with { |args| args[:name] == username }.returns user user end it "should return false if there is no user setting" do settings.should_not be_service_user_available end it "should return false if the user provider says the user is missing" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns false settings.should_not be_service_user_available end it "should return true if the user provider says the user is present" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns true settings.should be_service_user_available end it "caches the result of determining if the user is present" do settings[:user] = "foo" a_user_type_for("foo").expects(:exists?).returns true settings.should be_service_user_available settings.should be_service_user_available end end describe "when determining if the service group is available" do let(:settings) do settings = Puppet::Settings.new settings.define_settings :main, :group => { :default => nil, :desc => "doc" } settings end def a_group_type_for(groupname) group = mock 'group' Puppet::Type.type(:group).expects(:new).with { |args| args[:name] == groupname }.returns group group end it "should return false if there is no group setting" do settings.should_not be_service_group_available end it "should return false if the group provider says the group is missing" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns false settings.should_not be_service_group_available end it "should return true if the group provider says the group is present" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns true settings.should be_service_group_available end it "caches the result of determining if the group is present" do settings[:group] = "foo" a_group_type_for("foo").expects(:exists?).returns true settings.should be_service_group_available settings.should be_service_group_available end end describe "when dealing with command-line options" do let(:settings) { Puppet::Settings.new } it "should get options from Puppet.settings.optparse_addargs" do settings.expects(:optparse_addargs).returns([]) settings.send(:parse_global_options, []) end it "should add options to OptionParser" do settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]]) settings.expects(:handlearg).with("--option", true) settings.send(:parse_global_options, ["--option"]) end it "should not die if it sees an unrecognized option, because the app/face may handle it later" do expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error end it "should not pass an unrecognized option to handleargs" do settings.expects(:handlearg).with("--topuppet", "value").never expect { settings.send(:parse_global_options, ["--topuppet", "value"]) } .to_not raise_error end it "should pass valid puppet settings options to handlearg even if they appear after an unrecognized option" do settings.stubs(:optparse_addargs).returns( [["--option","-o", "Funny Option", :NONE]]) settings.expects(:handlearg).with("--option", true) settings.send(:parse_global_options, ["--invalidoption", "--option"]) end it "should transform boolean option to normal form" do Puppet::Settings.clean_opt("--[no-]option", true).should == ["--option", true] end it "should transform boolean option to no- form" do Puppet::Settings.clean_opt("--[no-]option", false).should == ["--no-option", false] end it "should set preferred run mode from --run_mode string without error" do args = ["--run_mode", "master"] settings.expects(:handlearg).with("--run_mode", "master").never expect { settings.send(:parse_global_options, args) } .to_not raise_error Puppet.settings.preferred_run_mode.should == :master args.empty?.should == true end it "should set preferred run mode from --run_mode= string without error" do args = ["--run_mode=master"] settings.expects(:handlearg).with("--run_mode", "master").never expect { settings.send(:parse_global_options, args) } .to_not raise_error Puppet.settings.preferred_run_mode.should == :master args.empty?.should == true end end describe "default_certname" do describe "using hostname and domainname" do before :each do Puppet::Settings.stubs(:hostname_fact).returns("testhostname") Puppet::Settings.stubs(:domain_fact).returns("domain.test.") end it "should use both to generate fqdn" do Puppet::Settings.default_certname.should =~ /testhostname\.domain\.test/ end it "should remove trailing dots from fqdn" do Puppet::Settings.default_certname.should == 'testhostname.domain.test' end end describe "using just hostname" do before :each do Puppet::Settings.stubs(:hostname_fact).returns("testhostname") Puppet::Settings.stubs(:domain_fact).returns("") end it "should use only hostname to generate fqdn" do Puppet::Settings.default_certname.should == "testhostname" end it "should removing trailing dots from fqdn" do Puppet::Settings.default_certname.should == "testhostname" end end end end