4
require 'puppet/external/event-loop'
5
require 'puppet/util/cacher'
6
require 'puppet/util/loadedfile'
8
# The class for handling configuration files.
9
class Puppet::Util::Settings
11
include Puppet::Util::Cacher
13
require 'puppet/util/settings/setting'
14
require 'puppet/util/settings/file_setting'
15
require 'puppet/util/settings/boolean_setting'
20
ReadOnly = [:run_mode, :name]
22
# Retrieve a config value
27
# Set a config value. This doesn't set the defaults, it sets the value itself.
29
set_value(param, value, :memory)
32
# Generate the list of valid arguments, in a format that GetoptLong can
33
# understand, and add them to the passed option list.
35
# Add all of the config parameters as valid options.
36
self.each { |name, setting|
37
setting.getopt_args.each { |args| options << args }
43
# Generate the list of valid arguments, in a format that OptionParser can
44
# understand, and add them to the passed option list.
45
def optparse_addargs(options)
46
# Add all of the config parameters as valid options.
47
self.each { |name, setting|
48
options << setting.optparse_args
54
# Is our parameter a boolean parameter?
57
!!(@config.include?(param) and @config[param].kind_of? BooleanSetting)
60
# Remove all set values, potentially skipping cli values.
61
def clear(exceptcli = false)
63
unsafe_clear(exceptcli)
67
# Remove all set values, potentially skipping cli values.
68
def unsafe_clear(exceptcli = false)
69
@values.each do |name, values|
70
@values.delete(name) unless exceptcli and name == :cli
73
# Don't clear the 'used' in this case, since it's a config file reparse,
74
# and we want to retain this info.
75
@used = [] unless exceptcli
80
# This is mostly just used for testing.
86
# Do variable interpolation on the value.
87
def convert(value, environment = nil)
88
return value unless value
89
return value unless value.is_a? String
90
newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value|
92
if varname == "environment" and environment
94
elsif pval = self.value(varname, environment)
97
raise Puppet::DevError, "Could not find value for #{value}"
104
# Return a value's description.
105
def description(name)
106
if obj = @config[name.to_sym]
114
@config.each { |name, object|
119
# Iterate over each section name.
122
@config.each do |name, object|
123
section = object.section
124
unless yielded.include? section
131
# Return an object by name.
137
# Handle a command-line argument.
138
def handlearg(opt, value = nil)
140
value &&= munge_value(value)
141
str = opt.sub(/^--/,'')
144
newstr = str.sub(/^no-/, '')
151
if @config[str].is_a?(Puppet::Util::Settings::BooleanSetting)
152
if value == "" or value.nil?
157
set_value(str, value, :cli)
161
name = name.intern if name.is_a? String
162
@config.include?(name)
165
# check to see if a short name is already defined
166
def shortinclude?(short)
167
short = short.intern if name.is_a? String
168
@shortnames.include?(short)
171
# Create a new collection of config settings.
179
# Mutex-like thing to protect @values
182
# Keep track of set values.
183
@values = Hash.new { |hash, key| hash[key] = {} }
185
# And keep a per-environment cache
186
@cache = Hash.new { |hash, key| hash[key] = {} }
188
# The list of sections we've used.
192
# NOTE: ACS ahh the util classes. . .sigh
193
# as part of a fix for 1183, I pulled the logic for the following 5 methods out of the executables and puppet.rb
194
# They probably deserve their own class, but I don't want to do that until I can refactor environments
195
# its a little better than where they were
197
# Prints the contents of a config file with the available config settings, or it
198
# prints a single value of a config setting.
199
def print_config_options
200
env = value(:environment)
201
val = value(:configprint)
205
val = value(name,env)
206
val = val.inspect if val == ""
209
hash.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, val|
210
puts "#{name} = #{val}"
213
val.split(/\s*,\s*/).sort.each do |v|
215
#if there is only one value, just print it for back compatibility
220
puts "#{v} = #{value(v,env)}"
222
puts "invalid parameter: #{v}"
235
def generate_manifest
241
return print_config_options if value(:configprint) != ""
242
return generate_config if value(:genconfig)
243
generate_manifest if value(:genmanifest)
247
(value(:configprint) != "" || value(:genconfig) || value(:genmanifest)) && true
250
# Return a given object's file metadata.
252
if obj = @config[param.to_sym] and obj.is_a?(FileSetting)
253
return [:owner, :group, :mode].inject({}) do |meta, p|
264
# Make a directory with the appropriate user, group, and mode
266
obj = get_config_file_default(default)
268
Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do
269
mode = obj.mode || 0750
270
Dir.mkdir(obj.value, mode)
274
# Figure out the section name for the run_mode.
279
# Return all of the parameters associated with a given section.
280
def params(section = nil)
282
section = section.intern if section.is_a? String
283
@config.find_all { |name, obj|
284
obj.section == section
285
}.collect { |name, obj|
293
# Parse the configuration file. Just provides
296
raise "No :config setting defined; cannot parse unknown config file" unless self[:config]
299
unsafe_parse(self[:config])
302
# Create a timer so that this file will get checked automatically
303
# and reparsed if necessary.
304
set_filetimeout_timer
307
# Unsafely parse the file -- this isn't thread-safe and causes plenty of problems if used directly.
308
def unsafe_parse(file)
309
return unless FileTest.exist?(file)
311
data = parse_file(file)
313
puts details.backtrace if Puppet[:trace]
314
Puppet.err "Could not parse #{file}: #{details}"
321
data.each do |area, values|
322
metas[area] = values.delete(:_meta)
323
values.each do |key,value|
324
set_value(key, value, area, :dont_trigger_handles => true, :ignore_bad_settings => true )
328
# Determine our environment, if we have one.
329
if @config[:environment]
330
env = self.value(:environment).to_sym
335
# Call any hooks we should be calling.
336
settings_with_hooks.each do |setting|
337
each_source(env) do |source|
338
if value = @values[source][setting.name]
339
# We still have to use value to retrieve the value, since
340
# we want the fully interpolated value, not $vardir/lib or whatever.
341
# This results in extra work, but so few of the settings
342
# will have associated hooks that it ends up being less work this
344
setting.handle(self.value(setting.name, env))
350
# We have to do it in the reverse of the search path,
351
# because multiple sections could set the same value
352
# and I'm too lazy to only set the metadata once.
353
searchpath.reverse.each do |source|
354
source = run_mode if source == :run_mode
355
source = @name if (@name && source == :name)
356
if meta = metas[source]
362
# Create a new setting. The value is passed in because it's used to determine
363
# what kind of setting we're creating, but the value itself might be either
364
# a default or a value, so we can't actually assign it.
367
hash[:section] = hash[:section].to_sym if hash[:section]
368
if type = hash[:type]
369
unless klass = {:setting => Setting, :file => FileSetting, :boolean => BooleanSetting}[type]
370
raise ArgumentError, "Invalid setting type '#{type}'"
375
when true, false, "true", "false"
376
klass = BooleanSetting
377
when /^\$\w+\//, /^\//, /^\w:\//
379
when String, Integer, Float # nothing
382
raise ArgumentError, "Invalid value '#{hash[:default].inspect}' for #{hash[:name]}"
385
hash[:settings] = self
386
setting = klass.new(hash)
391
# This has to be private, because it doesn't add the settings to @config
394
# Iterate across all of the objects in a given section.
395
def persection(section)
396
section = section.to_sym
397
self.each { |name, obj|
398
if obj.section == section
404
# Cache this in an easily clearable way, since we were
405
# having trouble cleaning it up after tests.
406
cached_attr(:file) do
407
if path = self[:config] and FileTest.exist?(path)
408
Puppet::Util::LoadedFile.new(path)
412
# Reparse our config file, if necessary.
414
if file and file.changed?
415
Puppet.notice "Reparsing #{file.file}"
422
return unless defined?(@used)
423
@sync.synchronize do # yay, thread-safe
430
# The order in which to search for values.
431
def searchpath(environment = nil)
433
[:cli, :memory, environment, :run_mode, :main, :mutable_defaults]
435
[:cli, :memory, :run_mode, :main, :mutable_defaults]
439
# Get a list of objects per section
442
self.each { |name, obj|
443
section = obj.section || "puppet"
444
sections[section] ||= []
445
sectionlist << section unless sectionlist.include?(section)
446
sections[section] << obj
449
return sectionlist, sections
452
def service_user_available?
453
return @service_user_available if defined?(@service_user_available)
455
return @service_user_available = false unless user_name = self[:user]
457
user = Puppet::Type.type(:user).new :name => self[:user], :audit => :ensure
459
@service_user_available = user.exists?
462
def legacy_to_mode(type, param)
463
if not defined?(@app_names)
464
require 'puppet/util/command_line'
465
command_line = Puppet::Util::CommandLine.new
466
@app_names = Puppet::Util::CommandLine::LegacyName.inject({}) do |hash, pair|
468
command_line.require_application app
469
hash[legacy.to_sym] = Puppet::Application.find(app).run_mode.name
473
if new_type = @app_names[type]
474
Puppet.warning "You have configuration parameter $#{param} specified in [#{type}], which is a deprecated section. I'm assuming you meant [#{new_type}]"
480
def set_value(param, value, type, options = {})
482
unless setting = @config[param]
483
if options[:ignore_bad_settings]
487
"Attempt to assign a value to unknown configuration parameter #{param.inspect}"
490
value = setting.munge(value) if setting.respond_to?(:munge)
491
setting.handle(value) if setting.respond_to?(:handle) and not options[:dont_trigger_handles]
492
if ReadOnly.include? param and type != :mutable_defaults
494
"You're attempting to set configuration parameter $#{param}, which is read-only."
496
type = legacy_to_mode(type, param)
497
@sync.synchronize do # yay, thread-safe
498
@values[type][param] = value
503
# Clear the list of environments, because they cache, at least, the module path.
504
# We *could* preferentially just clear them if the modulepath is changed,
505
# but we don't really know if, say, the vardir is changed and the modulepath
506
# is defined relative to it. We need the defined?(stuff) because of loading
508
Puppet::Node::Environment.clear if defined?(Puppet::Node) and defined?(Puppet::Node::Environment)
514
# Set a bunch of defaults in a given section. The sections are actually pretty
515
# pointless, but they help break things up a bit, anyway.
516
def setdefaults(section, defs)
517
section = section.to_sym
519
defs.each { |name, hash|
521
unless hash.length == 2
522
raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription"
526
[:default, :desc].zip(tmp).each { |p,v| hash[p] = v }
530
hash[:section] = section
531
raise ArgumentError, "Parameter #{name} is already defined" if @config.include?(name)
532
tryconfig = newsetting(hash)
533
if short = tryconfig.short
534
if other = @shortnames[short]
535
raise ArgumentError, "Parameter #{other.name} is already using short name '#{short}'"
537
@shortnames[short] = tryconfig
539
@config[name] = tryconfig
541
# Collect the settings that need to have their hooks called immediately.
542
# We have to collect them so that we can be sure we're fully initialized before
543
# the hook is called.
544
call << tryconfig if tryconfig.call_on_define
547
call.each { |setting| setting.handle(self.value(setting.name)) }
550
# Create a timer to check whether the file should be reparsed.
551
def set_filetimeout_timer
552
return unless timeout = self[:filetimeout] and timeout = Integer(timeout) and timeout > 0
553
timer = EventLoop::Timer.new(:interval => timeout, :tolerance => 1, :start? => true) { self.reparse }
556
# Convert the settings we manage into a catalog full of resources that model those settings.
557
def to_catalog(*sections)
558
sections = nil if sections.empty?
560
catalog = Puppet::Resource::Catalog.new("Settings")
562
@config.values.find_all { |value| value.is_a?(FileSetting) }.each do |file|
563
next unless (sections.nil? or sections.include?(file.section))
564
next unless resource = file.to_resource
565
next if catalog.resource(resource.ref)
567
catalog.add_resource(resource)
570
add_user_resources(catalog, sections)
575
# Convert our list of config settings into a configuration file.
577
str = %{The configuration file for #{Puppet[:name]}. Note that this file
578
is likely to have unused configuration parameters in it; any parameter that's
579
valid anywhere in Puppet can be in any config file, even if it's not used.
581
Every section can specify three special parameters: owner, group, and mode.
582
These parameters affect the required permissions of any files specified after
583
their specification. Puppet will sometimes use these parameters to check its
584
own configured state, so they can be used to make Puppet a bit more self-managing.
586
Generated on #{Time.now}.
590
# Add a section heading that matches our name.
591
if @config.include?(:run_mode)
592
str += "[#{self[:run_mode]}]\n"
594
eachsection do |section|
595
persection(section) do |obj|
596
str += obj.to_config + "\n" unless ReadOnly.include? obj.name or obj.name == :genconfig
603
# Convert to a parseable manifest
606
catalog.resource_refs.collect do |ref|
607
catalog.resource(ref).to_manifest
611
# Create the necessary objects to use a section. This is idempotent;
612
# you can 'use' a section as many times as you want.
614
sections = sections.collect { |s| s.to_sym }
615
@sync.synchronize do # yay, thread-safe
616
sections = sections.reject { |s| @used.include?(s) }
618
return if sections.empty?
621
catalog = to_catalog(*sections).to_ral
623
puts detail.backtrace if Puppet[:trace]
624
Puppet.err "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}"
626
# We need some way to get rid of any resources created during the catalog creation
627
# but not cleaned up.
631
catalog.host_config = false
632
catalog.apply do |transaction|
633
if transaction.any_failed?
634
report = transaction.report
635
failures = report.logs.find_all { |log| log.level == :err }
636
raise "Got #{failures.length} failure(s) while initializing: #{failures.collect { |l| l.to_s }.join("; ")}"
640
sections.each { |s| @used << s }
647
@config.has_key?(param)
650
def uninterpolated_value(param, environment = nil)
652
environment &&= environment.to_sym
654
# See if we can find it within our searchable list of values
655
val = catch :foundval do
656
each_source(environment) do |source|
657
# Look for the value. We have to test the hash for whether
658
# it exists, because the value might be false.
660
throw :foundval, @values[source][param] if @values[source].include?(param)
666
# If we didn't get a value, use the default
667
val = @config[param].default if val.nil?
672
# Find the correct value using our search path. Optionally accept an environment
673
# in which to search before the other configuration sections.
674
def value(param, environment = nil)
676
environment &&= environment.to_sym
678
# Short circuit to nil for undefined parameters.
679
return nil unless @config.include?(param)
682
#self.reparse unless [:config, :filetimeout].include?(param)
684
# Check the cache first. It needs to be a per-environment
685
# cache so that we don't spread values from one env
687
if cached = @cache[environment||"none"][param]
691
val = uninterpolated_value(param, environment)
694
# if we interpolate code, all hell breaks loose.
698
# Convert it if necessary
699
val = convert(val, environment)
702
@cache[environment||"none"][param] = val
706
# Open a file with the appropriate user, group, and mode
707
def write(default, *args, &bloc)
708
obj = get_config_file_default(default)
709
writesub(default, value(obj.name), *args, &bloc)
712
# Open a non-default file under a default dir with the appropriate user,
714
def writesub(default, file, *args, &bloc)
715
obj = get_config_file_default(default)
717
if Puppet.features.root?
718
chown = [obj.owner, obj.group]
723
Puppet::Util::SUIDManager.asuser(*chown) do
724
mode = obj.mode || 0640
725
args << "w" if args.empty?
729
# Update the umask to make non-executable files
730
Puppet::Util.withumask(File.umask ^ 0111) do
731
File.open(file, *args) do |file|
738
def readwritelock(default, *args, &bloc)
739
file = value(get_config_file_default(default).name)
740
tmpfile = file + ".tmp"
742
raise Puppet::DevError, "Cannot create #{file}; directory #{File.dirname(file)} does not exist" unless FileTest.directory?(File.dirname(tmpfile))
744
sync.synchronize(Sync::EX) do
745
File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf|
747
if File.exist?(tmpfile)
748
raise Puppet::Error, ".tmp file already exists for #{file}; Aborting locked write. Check the .tmp file and delete if appropriate"
751
# If there's a failure, remove our tmpfile
753
writesub(default, tmpfile, *args, &bloc)
755
File.unlink(tmpfile) if FileTest.exist?(tmpfile)
760
File.rename(tmpfile, file)
762
Puppet.err "Could not rename #{file} to #{tmpfile}: #{detail}"
763
File.unlink(tmpfile) if FileTest.exist?(tmpfile)
772
def get_config_file_default(default)
774
unless obj = @config[default]
775
raise ArgumentError, "Unknown default #{default}"
778
raise ArgumentError, "Default #{default} is not a file" unless obj.is_a? FileSetting
783
# Create the transportable objects for users and groups.
784
def add_user_resources(catalog, sections)
785
return unless Puppet.features.root?
786
return unless self[:mkusers]
788
@config.each do |name, setting|
789
next unless setting.respond_to?(:owner)
790
next unless sections.nil? or sections.include?(setting.section)
792
if user = setting.owner and user != "root" and catalog.resource(:user, user).nil?
793
resource = Puppet::Resource.new(:user, user, :parameters => {:ensure => :present})
794
resource[:gid] = self[:group] if self[:group]
795
catalog.add_resource resource
797
if group = setting.group and ! %w{root wheel}.include?(group) and catalog.resource(:group, group).nil?
798
catalog.add_resource Puppet::Resource.new(:group, group, :parameters => {:ensure => :present})
803
# Yield each search source in turn.
804
def each_source(environment)
805
searchpath(environment).each do |source|
806
# Modify the source as necessary.
807
source = self.run_mode if source == :run_mode
812
# Return all settings that have associated hooks; this is so
813
# we can call them after parsing the configuration file.
814
def settings_with_hooks
815
@config.values.find_all { |setting| setting.respond_to?(:handle) }
818
# Extract extra setting information for files.
819
def extract_fileinfo(string)
821
value = string.sub(/\{\s*([^}]+)\s*\}/) do
823
params.split(/\s*,\s*/).each do |str|
824
if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/
825
param, value = $1.intern, $2
826
result[param] = value
827
raise ArgumentError, "Invalid file option '#{param}'" unless [:owner, :mode, :group].include?(param)
829
if param == :mode and value !~ /^\d+$/
830
raise ArgumentError, "File modes must be numbers"
833
raise ArgumentError, "Could not parse '#{string}'"
838
result[:value] = value.sub(/\s*$/, '')
842
# Convert arguments into booleans, integers, or whatever.
843
def munge_value(value)
844
# Handle different data types correctly
846
when /^false$/i; false
848
when /^\d+$/i; Integer(value)
852
value.gsub(/^["']|["']$/,'').sub(/\s+$/, '')
856
# This method just turns a file in to a hash of hashes.
858
text = read_file(file)
860
result = Hash.new { |names, name|
866
# Default to 'main' for the section.
868
result[section][:_meta] = {}
869
text.split(/\n/).each { |line|
872
when /^\s*\[(\w+)\]\s*$/
873
section = $1.intern # Section names
875
result[section][:_meta] ||= {}
876
when /^\s*#/; next # Skip comments
877
when /^\s*$/; next # Skip blanks
878
when /^\s*(\w+)\s*=\s*(.*?)\s*$/ # settings
881
# We don't want to munge modes, because they're specified in octal, so we'll
882
# just leave them as a String, since Puppet handles that case correctly.
886
value = munge_value($2)
889
# Check to see if this is a file argument and it has extra options
891
if value.is_a?(String) and options = extract_fileinfo(value)
892
value = options[:value]
893
options.delete(:value)
894
result[section][:_meta][var] = options
896
result[section][var] = value
897
rescue Puppet::Error => detail
903
error = Puppet::Error.new("Could not match line #{line}")
916
return File.read(file)
918
raise ArgumentError, "No such file #{file}"
920
raise ArgumentError, "Permission denied to file #{file}"
925
def set_metadata(meta)
926
meta.each do |var, values|
927
values.each do |param, value|
928
@config[var].send(param.to_s + "=", value)