diff options
| author | Michael V. O'Brien <michael@reductivelabs.com> | 2007-09-25 12:00:07 -0500 |
|---|---|---|
| committer | Michael V. O'Brien <michael@reductivelabs.com> | 2007-09-25 12:00:07 -0500 |
| commit | ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3 (patch) | |
| tree | 8c8960cac1d7b3e8b48e44163062be3b3f4c201f /lib | |
| parent | f8ab62b212788a4591276c95b5f67217f7517e4e (diff) | |
| parent | ffaa8ce07979f4db860950fa9be08ca37964206f (diff) | |
| download | puppet-ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3.tar.gz puppet-ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3.tar.xz puppet-ff2828f5dbe68ff1cb06a3503590a3e4bd1b59e3.zip | |
Merge branch 'master' of git://reductivelabs.com/puppet
Diffstat (limited to 'lib')
67 files changed, 1891 insertions, 1236 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb index 20d879fdf..c1f31e467 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -12,7 +12,7 @@ require 'puppet/external/event-loop' require 'puppet/util' require 'puppet/util/log' require 'puppet/util/autoload' -require 'puppet/util/config' +require 'puppet/util/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' @@ -45,7 +45,7 @@ module Puppet end # the hash that determines how our system behaves - @@config = Puppet::Util::Config.new + @@settings = Puppet::Util::Settings.new # The services running in this process. @services ||= [] @@ -76,7 +76,7 @@ module Puppet # Store a new default value. def self.setdefaults(section, hash) - @@config.setdefaults(section, hash) + @@settings.setdefaults(section, hash) end # configuration parameter access and stuff @@ -89,17 +89,17 @@ module Puppet return false end else - return @@config[param] + return @@settings[param] end end # configuration parameter access and stuff def self.[]=(param,value) - @@config[param] = value + @@settings[param] = value end def self.clear - @@config.clear + @@settings.clear end def self.debug=(value) @@ -110,8 +110,8 @@ module Puppet end end - def self.config - @@config + def self.settings + @@settings end # Load all of the configuration parameters. @@ -122,7 +122,7 @@ module Puppet val = Puppet[:configprint] if val == "all" hash = {} - Puppet.config.each do |name, obj| + Puppet.settings.each do |name, obj| val = obj.value case val when true, false, "": val = val.inspect @@ -134,7 +134,7 @@ module Puppet end elsif val =~ /,/ val.split(/\s*,\s*/).sort.each do |v| - if Puppet.config.include?(v) + if Puppet.settings.include?(v) puts "%s = %s" % [v, Puppet[v]] else puts "invalid parameter: %s" % v @@ -143,7 +143,7 @@ module Puppet end else val.split(/\s*,\s*/).sort.each do |v| - if Puppet.config.include?(v) + if Puppet.settings.include?(v) puts Puppet[val] else puts "invalid parameter: %s" % v @@ -154,14 +154,14 @@ module Puppet exit(0) end if Puppet[:genconfig] - puts Puppet.config.to_config + puts Puppet.settings.to_config exit(0) end end def self.genmanifest if Puppet[:genmanifest] - puts Puppet.config.to_manifest + puts Puppet.settings.to_manifest exit(0) end end @@ -208,14 +208,14 @@ module Puppet oldconfig ||= File.join(Puppet[:confdir], Puppet[:name].to_s + ".conf") if FileTest.exists?(oldconfig) and Puppet[:name] != "puppet" Puppet.warning "Individual config files are deprecated; remove %s and use puppet.conf" % oldconfig - Puppet.config.old_parse(oldconfig) + Puppet.settings.old_parse(oldconfig) return end # Now check for the normal config. if Puppet[:config] and File.exists? Puppet[:config] Puppet.debug "Parsing %s" % Puppet[:config] - Puppet.config.parse(Puppet[:config]) + Puppet.settings.parse(Puppet[:config]) end end diff --git a/lib/puppet/checksum.rb b/lib/puppet/checksum.rb new file mode 100644 index 000000000..c607953c1 --- /dev/null +++ b/lib/puppet/checksum.rb @@ -0,0 +1,64 @@ +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require 'puppet' +require 'puppet/indirector' + +# A checksum class to model translating checksums to file paths. This +# is the new filebucket. +class Puppet::Checksum + extend Puppet::Indirector + + indirects :checksum + + attr_reader :algorithm, :content + + def algorithm=(value) + unless respond_to?(value) + raise ArgumentError, "Checksum algorithm %s is not supported" % value + end + value = value.intern if value.is_a?(String) + @algorithm = value + # Reset the checksum so it's forced to be recalculated. + @checksum = nil + end + + # Calculate (if necessary) and return the checksum + def checksum + unless @checksum + @checksum = send(algorithm) + end + @checksum + end + + def initialize(content, algorithm = nil) + raise ArgumentError.new("You must specify the content") unless content + + @content = content + self.algorithm = algorithm || "md5" + + # Init to avoid warnings. + @checksum = nil + end + + # This can't be private, else respond_to? returns false. + def md5 + require 'digest/md5' + Digest::MD5.hexdigest(content) + end + + # This is here so the Indirector::File terminus works correctly. + def name + checksum + end + + def sha1 + require 'digest/sha1' + Digest::SHA1.hexdigest(content) + end + + def to_s + "Checksum<{%s}%s>" % [algorithm, checksum] + end +end diff --git a/lib/puppet/config_stores/rest.rb b/lib/puppet/config_stores/rest.rb index 980968bd8..bb3d937ac 100644 --- a/lib/puppet/config_stores/rest.rb +++ b/lib/puppet/config_stores/rest.rb @@ -1,4 +1,4 @@ -Puppet::Util::ConfigStore.newstore(:rest) do +Puppet::Util::SettingsStore.newstore(:rest) do desc "Store client configurations via a REST web service." require 'net/http' diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 78364e786..f76ae9b84 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -267,7 +267,7 @@ module Puppet ) # Define the config default. - self.setdefaults(self.config[:name], + self.setdefaults(self.settings[:name], :config => ["$confdir/puppet.conf", "The configuration file for #{Puppet[:name]}."], :pidfile => ["", "The pid file"], @@ -494,14 +494,18 @@ module Puppet "The server through which to send email reports."] ) - self.setdefaults(:facts, - :factstore => ["yaml", - "The backend store to use for client facts."] + # This needs to be in main because it's used too early in the system, such that + # we get an infinite loop otherwise. + self.setdefaults(:main, + :facts_terminus => ["yaml", + "The backend store to use for client facts."], + :checksum_terminus => ["file", + "The backend store to use for storing files by checksum (i.e., filebuckets)."] ) - self.setdefaults(:yamlfacts, - :yamlfactdir => ["$vardir/facts", - "The directory in which client facts are stored when the yaml fact store is used."] + self.setdefaults(:yaml, + :yamldir => ["$vardir/yaml", + "The directory in which YAML data is stored, usually in a subdirectory."] ) self.setdefaults(:rails, @@ -554,7 +558,7 @@ module Puppet setdefaults(:parser, :typecheck => [true, "Whether to validate types during parsing."], :paramcheck => [true, "Whether to validate parameters during parsing."], - :node_source => ["none", "Where to look for node configuration information. + :node_terminus => ["null", "Where to look for node configuration information. The default node source, ``none``, just returns a node with its facts filled in, which is required for normal functionality. See the `NodeSourceReference`:trac: for more information."] diff --git a/lib/puppet/dsl.rb b/lib/puppet/dsl.rb index 793578bca..3696cd9ee 100644 --- a/lib/puppet/dsl.rb +++ b/lib/puppet/dsl.rb @@ -67,11 +67,8 @@ module Puppet def apply bucket = export() - objects = bucket.to_type - master = Puppet::Network::Client.master.new :Master => "whatever" - master.objects = objects - - master.apply + configuration = bucket.to_configuration + configuration.apply end def export @@ -255,8 +252,7 @@ module Puppet def scope unless defined?(@scope) @interp = Puppet::Parser::Interpreter.new :Code => "" - # Load the class, so the node object class is available. - require 'puppet/network/handler/node' + require 'puppet/node' @node = Puppet::Node.new(Facter.value(:hostname)) @node.parameters = Facter.to_hash @interp = Puppet::Parser::Interpreter.new :Code => "" diff --git a/lib/puppet/fact_stores/yaml.rb b/lib/puppet/fact_stores/yaml.rb index a4b12a2e5..b33e162ba 100644 --- a/lib/puppet/fact_stores/yaml.rb +++ b/lib/puppet/fact_stores/yaml.rb @@ -16,7 +16,7 @@ Puppet::Util::FactStore.newstore(:yaml) do end def initialize - Puppet.config.use(:yamlfacts) + Puppet.settings.use(:yamlfacts) end # Store the facts to disk. diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 0ba538355..6ff2de1b4 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -1,76 +1,51 @@ # Manage indirections to termini. They are organized in terms of indirections - # - e.g., configuration, node, file, certificate -- and each indirection has one -# or more terminus types defined. The indirection must have its preferred terminus -# configured via a 'default' in the form of '<indirection>_terminus'; e.g., -# 'node_terminus = ldap'. +# or more terminus types defined. The indirection is configured via the +# +indirects+ method, which will be called by the class extending itself +# with this module. module Puppet::Indirector - # This manages reading in all of our files for us and then retrieving - # loaded instances. We still have to define the 'newX' method, but this - # does all of the rest -- loading, storing, and retrieving by name. - require 'puppet/util/instance_loader' - include Puppet::Util::InstanceLoader + # LAK:FIXME We need to figure out how to handle documentation for the + # different indirection types. - # Define a new indirection terminus. This method is used by the individual - # termini in their separate files. Again, the autoloader takes care of - # actually loading these files. - def register_terminus(name, options = {}, &block) - genclass(name, :hash => instance_hash(indirection.name), :attributes => options, :block => block) - end - - # Retrieve a terminus class by indirection and name. - def terminus(name) - loaded_instance(name) - end + require 'puppet/indirector/indirection' + require 'puppet/indirector/terminus' # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets # evaluated at parse time, which is before the user has had a chance # to override it. - def indirects(indirection, options) - @indirection = indirection - @indirect_terminus = options[:to] - - # Set up autoloading of the appropriate termini. - autoload "puppet/indirector/%s" % indirection + def indirects(indirection) + raise(ArgumentError, "Already handling indirection for %s; cannot also handle %s" % [@indirection.name, indirection]) if defined?(@indirection) and @indirection + # populate this class with the various new methods + extend ClassMethods + include InstanceMethods + + # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) + # & hook the instantiated Terminus into this class (Node: @indirection = terminus) + @indirection = Puppet::Indirector::Indirection.new(self, indirection) end - # Define methods for each of the HTTP methods. These just point to the - # termini, with consistent error-handling. Each method is called with - # the first argument being the indirection type and the rest of the - # arguments passed directly on to the indirection terminus. There is - # currently no attempt to standardize around what the rest of the arguments - # should allow or include or whatever. - # There is also no attempt to pre-validate that a given indirection supports - # the method in question. We should probably require that indirections - # declare supported methods, and then verify that termini implement all of - # those methods. - [:get, :post, :put, :delete].each do |method_name| - define_method(method_name) do |*args| - begin - terminus.send(method_name, *args) - rescue NoMethodError - raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name] - end - end - end + module ClassMethods + attr_reader :indirection + + def find(*args) + indirection.find(*args) + end - private + def destroy(*args) + indirection.destroy(*args) + end - # Create a new terminus instance. - def make_terminus(indirection) - # Load our terminus class. - unless klass = self.class.terminus(indirection, indirection.default) - raise ArgumentError, "Could not find terminus %s for indirection %s" % [indirection.default, indirection] - end - return klass.new + def search(*args) + indirection.search(*args) + end end - # Return the singleton terminus for this indirection. - def terminus - unless terminus = @termini[indirection.name] - terminus = @termini[indirection.name] = make_terminus(indirection) - end - terminus + module InstanceMethods + # these become instance methods + def save(*args) + self.class.indirection.save(self, *args) + end end end diff --git a/lib/puppet/indirector/code.rb b/lib/puppet/indirector/code.rb new file mode 100644 index 000000000..0c0ee146b --- /dev/null +++ b/lib/puppet/indirector/code.rb @@ -0,0 +1,6 @@ +require 'puppet/indirector/terminus' + +# Do nothing, requiring that the back-end terminus do all +# of the work. +class Puppet::Indirector::Code < Puppet::Indirector::Terminus +end diff --git a/lib/puppet/indirector/code/configuration.rb b/lib/puppet/indirector/code/configuration.rb new file mode 100644 index 000000000..6d0317204 --- /dev/null +++ b/lib/puppet/indirector/code/configuration.rb @@ -0,0 +1,171 @@ +require 'puppet/node' +require 'puppet/node/configuration' +require 'puppet/indirector/code' +require 'puppet/parser/interpreter' +require 'yaml' + +class Puppet::Indirector::Code::Configuration < Puppet::Indirector::Code + desc "Puppet's configuration compilation interface. Passed a node name + or other key, retrieves information about the node (using the ``node_source``) + and returns a compiled configuration." + + include Puppet::Util + + attr_accessor :code + + # Compile a node's configuration. + def find(key, client = nil, clientip = nil) + # If we want to use the cert name as our key + if Puppet[:node_name] == 'cert' and client + key = client + end + + # Note that this is reasonable, because either their node source should actually + # know about the node, or they should be using the ``none`` node source, which + # will always return data. + unless node = Puppet::Node.search(key) + raise Puppet::Error, "Could not find node '%s'" % key + end + + # Add any external data to the node. + add_node_data(node) + + configuration = compile(node) + + return configuration + end + + def initialize + set_server_facts + end + + # Create/return our interpreter. + def interpreter + unless defined?(@interpreter) and @interpreter + @interpreter = create_interpreter + end + @interpreter + end + + # Return the configuration version. + def version(client = nil, clientip = nil) + if client and node = Puppet::Node.search(client) + update_node_check(node) + return interpreter.configuration_version(node) + else + # Just return something that will always result in a recompile, because + # this is local. + return (Time.now + 1000).to_i + end + end + + private + + # Add any extra data necessary to the node. + def add_node_data(node) + # Merge in our server-side facts, so they can be used during compilation. + node.merge(@server_facts) + end + + # Compile the actual configuration. + def compile(node) + # Ask the interpreter to compile the configuration. + str = "Compiled configuration for %s" % node.name + if node.environment + str += " in environment %s" % node.environment + end + config = nil + + # LAK:FIXME This should log at :none when our client is + # local, since we don't want 'puppet' (vs. puppetmasterd) to + # log compile times. + benchmark(:notice, "Compiled configuration for %s" % node.name) do + begin + config = interpreter.compile(node) + rescue Puppet::Error => detail + if Puppet[:trace] + puts detail.backtrace + end + unless local? + Puppet.err detail.to_s + end + raise XMLRPC::FaultException.new( + 1, detail.to_s + ) + end + end + + return config + end + + # Create our interpreter object. + def create_interpreter + args = {} + + # Allow specification of a code snippet or of a file + if self.code + args[:Code] = self.code + end + + # LAK:FIXME This needs to be handled somehow. + #if options.include?(:UseNodes) + # args[:UseNodes] = options[:UseNodes] + #elsif @local + # args[:UseNodes] = false + #end + + return Puppet::Parser::Interpreter.new(args) + end + + # Initialize our server fact hash; we add these to each client, and they + # won't change while we're running, so it's safe to cache the values. + def set_server_facts + @server_facts = {} + + # Add our server version to the fact list + @server_facts["serverversion"] = Puppet.version.to_s + + # And then add the server name and IP + {"servername" => "fqdn", + "serverip" => "ipaddress" + }.each do |var, fact| + if value = Facter.value(fact) + @server_facts[var] = value + else + Puppet.warning "Could not retrieve fact %s" % fact + end + end + + if @server_facts["servername"].nil? + host = Facter.value(:hostname) + if domain = Facter.value(:domain) + @server_facts["servername"] = [host, domain].join(".") + else + @server_facts["servername"] = host + end + end + end + + # Translate our configuration appropriately for sending back to a client. + # LAK:FIXME This method should probably be part of the protocol, but it + # shouldn't be here. + def translate(config) + if local? + config + else + CGI.escape(config.to_yaml(:UseBlock => true)) + end + end + + # Mark that the node has checked in. LAK:FIXME this needs to be moved into + # the Node class, or somewhere that's got abstract backends. + def update_node_check(node) + if Puppet.features.rails? and Puppet[:storeconfigs] + Puppet::Rails.connect + + host = Puppet::Rails::Host.find_or_create_by_name(node.name) + host.last_freshcheck = Time.now + host.save + end + end +end diff --git a/lib/puppet/indirector/exec.rb b/lib/puppet/indirector/exec.rb new file mode 100644 index 000000000..7e4ac8d18 --- /dev/null +++ b/lib/puppet/indirector/exec.rb @@ -0,0 +1,57 @@ +require 'puppet/indirector/terminus' +require 'puppet/util' + +class Puppet::Indirector::Exec < Puppet::Indirector::Terminus + # Look for external node definitions. + def find(name) + # Run the command. + unless output = query(name) + return nil + end + + # Translate the output to ruby. + return output + end + + private + + # Proxy the execution, so it's easier to test. + def execute(command) + Puppet::Util.execute(command) + end + + # Call the external command and see if it returns our output. + def query(name) + external_command = command + + # Make sure it's an arry + unless external_command.is_a?(Array) + raise Puppet::DevError, "Exec commands must be an array" + end + + # Make sure it's fully qualified. + unless external_command[0][0] == File::SEPARATOR[0] + raise ArgumentError, "You must set the exec parameter to a fully qualified command" + end + + # Add our name to it. + external_command << name + begin + output = execute(external_command) + rescue Puppet::ExecutionFailure => detail + if $?.exitstatus == 1 + return nil + else + Puppet.err "Could not retrieve external node information for %s: %s" % [name, detail] + end + return nil + end + + if output =~ /\A\s*\Z/ # all whitespace + Puppet.debug "Empty response for %s from exec %s terminus" % [name, self.name] + return nil + else + return output + end + end +end diff --git a/lib/puppet/indirector/exec/node.rb b/lib/puppet/indirector/exec/node.rb new file mode 100644 index 000000000..033afe3f0 --- /dev/null +++ b/lib/puppet/indirector/exec/node.rb @@ -0,0 +1,50 @@ +require 'puppet/indirector/exec' + +class Puppet::Indirector::Exec::Node < Puppet::Indirector::Exec + desc "Call an external program to get node information." + include Puppet::Util + + def command + command = Puppet[:external_nodes] + unless command != "none" + raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node terminus" + end + command.split + end + + # Look for external node definitions. + def find(name) + output = super or return nil + + # Translate the output to ruby. + result = translate(name, output) + + return create_node(name, result) + end + + private + + # Turn our outputted objects into a Puppet::Node instance. + def create_node(name, result) + node = Puppet::Node.new(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + node.fact_merge + return node + end + + # Translate the yaml string into Ruby objects. + def translate(name, output) + begin + YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } + rescue => detail + raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] + end + end +end diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb new file mode 100644 index 000000000..c2d36c46b --- /dev/null +++ b/lib/puppet/indirector/file.rb @@ -0,0 +1,54 @@ +require 'puppet/indirector/terminus' + +# An empty terminus type, meant to just return empty objects. +class Puppet::Indirector::File < Puppet::Indirector::Terminus + def destroy(file) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + raise Puppet::Error.new("File %s does not exist; cannot destroy" % [file]) unless File.exist?(path) + + begin + File.unlink(path) + rescue => detail + raise Puppet::Error, "Could not remove %s: %s" % [file, detail] + end + end + + def find(name) + if respond_to?(:path) + path = path(name) + else + path = name + end + + return nil unless File.exist?(path) + + begin + content = File.read(path) + rescue => detail + raise Puppet::Error, "Could not retrieve path %s: %s" % [path, detail] + end + + return model.new(content) + end + + def save(file) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + dir = File.dirname(path) + + raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [file, dir]) unless File.directory?(dir) + + begin + File.open(path, "w") { |f| f.print file.content } + rescue => detail + raise Puppet::Error, "Could not write %s: %s" % [file, detail] + end + end +end diff --git a/lib/puppet/indirector/file/checksum.rb b/lib/puppet/indirector/file/checksum.rb new file mode 100644 index 000000000..2f0974ced --- /dev/null +++ b/lib/puppet/indirector/file/checksum.rb @@ -0,0 +1,33 @@ +require 'puppet/checksum' +require 'puppet/indirector/file' + +class Puppet::Indirector::File::Checksum < Puppet::Indirector::File + desc "Store files in a directory set based on their checksums." + + def initialize + Puppet.settings.use(:filebucket) + end + + def path(checksum) + path = [] + path << Puppet[:bucketdir] # Start with the base directory + path << checksum[0..7].split("").join(File::SEPARATOR) # Add sets of directories based on the checksum + path << checksum # And the full checksum name itself + path << "contents" # And the actual file name + + path.join(File::SEPARATOR) + end + + def save(file) + path = File.dirname(path(file.name)) + + # Make the directories if necessary. + unless FileTest.directory?(path) + Puppet::Util.withumask(0007) do + FileUtils.mkdir_p(path) + end + end + + super + end +end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb new file mode 100644 index 000000000..8afe0012d --- /dev/null +++ b/lib/puppet/indirector/indirection.rb @@ -0,0 +1,91 @@ +# An actual indirection. +class Puppet::Indirector::Indirection + @@indirections = [] + + # Clear all cached termini from all indirections. + def self.clear_cache + @@indirections.each { |ind| ind.clear_cache } + end + + # Find an indirection by name. This is provided so that Terminus classes + # can specifically hook up with the indirections they are associated with. + def self.instance(name) + @@indirections.find { |i| i.name == name } + end + + attr_accessor :name, :model + + # Clear our cached list of termini. + # This is only used for testing. + def clear_cache + @termini.clear + end + + # This is only used for testing. + def delete + @@indirections.delete(self) if @@indirections.include?(self) + end + + def initialize(model, name, options = {}) + @model = model + @name = name + options.each do |name, value| + begin + send(name.to_s + "=", value) + rescue NoMethodError + raise ArgumentError, "%s is not a valid Indirection parameter" % name + end + end + @termini = {} + @terminus_types = {} + raise(ArgumentError, "Indirection %s is already defined" % @name) if @@indirections.find { |i| i.name == @name } + @@indirections << self + end + + # Return the singleton terminus for this indirection. + def terminus(terminus_name = nil) + # Get the name of the terminus. + unless terminus_name + param_name = "%s_terminus" % self.name + if Puppet.settings.valid?(param_name) + terminus_name = Puppet.settings[param_name] + else + terminus_name = Puppet[:default_terminus] + end + unless terminus_name and terminus_name.to_s != "" + raise ArgumentError, "Invalid terminus name %s" % terminus_name.inspect + end + terminus_name = terminus_name.intern if terminus_name.is_a?(String) + end + + return @termini[terminus_name] ||= make_terminus(terminus_name) + end + + def find(*args) + terminus.find(*args) + end + + def destroy(*args) + terminus.destroy(*args) + end + + def search(*args) + terminus.search(*args) + end + + # these become instance methods + def save(*args) + terminus.save(*args) + end + + private + + # Create a new terminus instance. + def make_terminus(name) + # Load our terminus class. + unless klass = Puppet::Indirector::Terminus.terminus_class(name, self.name) + raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, self.name] + end + return klass.new + end +end diff --git a/lib/puppet/indirector/ldap.rb b/lib/puppet/indirector/ldap.rb new file mode 100644 index 000000000..fb883def6 --- /dev/null +++ b/lib/puppet/indirector/ldap.rb @@ -0,0 +1,90 @@ +require 'puppet/indirector/terminus' + +class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus + # Perform our ldap search and process the result. + def find(name) + # We have to use 'yield' here because the LDAP::Entry objects + # get destroyed outside the scope of the search, strangely. + ldapsearch(name) { |entry| return process(name, entry) } + + # Return nil if we haven't found something. + return nil + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(name, entry) + raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name + end + + # Default to all attributes. + def search_attributes + nil + end + + def search_base + Puppet[:ldapbase] + end + + # The ldap search filter to use. + def search_filter(name) + raise Puppet::DevError, "No search string set for LDAP terminus for %s" % self.name + end + + # Find the ldap node, return the class list and parent node specially, + # and everything else in a parameter hash. + def ldapsearch(node) + raise ArgumentError.new("You must pass a block to ldapsearch") unless block_given? + + found = false + count = 0 + + begin + connection.search(search_base, 2, search_filter(node), search_attributes) do |entry| + found = true + yield entry + end + rescue => detail + if count == 0 + # Try reconnecting to ldap if we get an exception and we haven't yet retried. + count += 1 + @connection = nil + Puppet.warning "Retrying LDAP connection" + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end + + return found + end + + private + + # Create an ldap connection. + def connection + unless defined? @connection and @connection + unless Puppet.features.ldap? + raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" + end + begin + if Puppet[:ldapssl] + @connection = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @connection = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @connection = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @connection.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @connection.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + return @connection + end +end diff --git a/lib/puppet/indirector/ldap/node.rb b/lib/puppet/indirector/ldap/node.rb new file mode 100644 index 000000000..6f35b575c --- /dev/null +++ b/lib/puppet/indirector/ldap/node.rb @@ -0,0 +1,115 @@ +require 'puppet/indirector/ldap' + +class Puppet::Indirector::Ldap::Node < Puppet::Indirector::Ldap + desc "Search in LDAP for node configuration information." + + # The attributes that Puppet class information is stored in. + def class_attributes + Puppet[:ldapclassattrs].split(/\s*,\s*/) + end + + # Look for our node in ldap. + def find(name) + return nil unless information = super + node = Puppet::Node.new(name) + + parent_info = nil + parent = information[:parent] + parents = [name] + while parent + if parents.include?(parent) + raise ArgumentError, "Found loop in LDAP node parents; %s appears twice" % parent + end + parents << parent + ldapsearch(parent) do |entry| + parent_info = process(parent, entry) + end + information[:classes] += parent_info[:classes] + parent_info[:parameters].each do |param, value| + # Specifically test for whether it's set, so false values are handled + # correctly. + information[:parameters][param] = value unless information[:parameters].include?(param) + end + + parent = parent_info[:parent] + end + + node.classes = information[:classes].uniq unless information[:classes].empty? + node.parameters = information[:parameters] unless information[:parameters].empty? + node.fact_merge + + return node + end + + # The parent attribute, if we have one. + def parent_attribute + if pattr = Puppet[:ldapparentattr] and ! pattr.empty? + pattr + else + nil + end + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(name, entry) + result = {} + if pattr = parent_attribute + if values = entry.vals(pattr) + if values.length > 1 + raise Puppet::Error, + "Node %s has more than one parent: %s" % [name, values.inspect] + end + unless values.empty? + result[:parent] = values.shift + end + end + end + + result[:classes] = [] + class_attributes.each { |attr| + if values = entry.vals(attr) + values.each do |v| result[:classes] << v end + end + } + + result[:parameters] = entry.to_hash.inject({}) do |hash, ary| + if ary[1].length == 1 + hash[ary[0]] = ary[1].shift + else + hash[ary[0]] = ary[1] + end + hash + end + + return result + end + + # Default to all attributes. + def search_attributes + ldapattrs = Puppet[:ldapattrs] + + # results in everything getting returned + return nil if ldapattrs == "all" + + search_attrs = class_attributes + ldapattrs.split(/\s*,\s*/) + + if pattr = parent_attribute + search_attrs << pattr + end + + search_attrs + end + + # The ldap search filter to use. + def search_filter(name) + filter = Puppet[:ldapstring] + + if filter.include? "%s" + # Don't replace the string in-line, since that would hard-code our node + # info. + filter = filter.gsub('%s', name) + end + filter + end +end diff --git a/lib/puppet/indirector/memory.rb b/lib/puppet/indirector/memory.rb new file mode 100644 index 000000000..5bfcec95d --- /dev/null +++ b/lib/puppet/indirector/memory.rb @@ -0,0 +1,21 @@ +require 'puppet/indirector/terminus' + +# Manage a memory-cached list of instances. +class Puppet::Indirector::Memory < Puppet::Indirector::Terminus + def initialize + @instances = {} + end + + def destroy(instance) + raise ArgumentError.new("Could not find %s to destroy" % instance) unless @instances.include?(instance.name) + @instances.delete(instance.name) + end + + def find(name) + @instances[name] + end + + def save(instance) + @instances[instance.name] = instance + end +end diff --git a/lib/puppet/indirector/memory/node.rb b/lib/puppet/indirector/memory/node.rb new file mode 100644 index 000000000..c5000b879 --- /dev/null +++ b/lib/puppet/indirector/memory/node.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/memory' + +class Puppet::Indirector::Memory::Node < Puppet::Indirector::Memory + desc "Keep track of nodes in memory but nowhere else. This is used for + one-time compiles, such as what the stand-alone ``puppet`` does. + To use this terminus, you must load it with the data you want it + to contain." +end diff --git a/lib/puppet/indirector/null.rb b/lib/puppet/indirector/null.rb new file mode 100644 index 000000000..db2b1db1c --- /dev/null +++ b/lib/puppet/indirector/null.rb @@ -0,0 +1,9 @@ +require 'puppet/indirector/terminus' + +# An empty terminus type, meant to just return empty objects. +class Puppet::Indirector::Null < Puppet::Indirector::Terminus + # Just return nothing. + def find(name) + indirection.model.new(name) + end +end diff --git a/lib/puppet/indirector/null/node.rb b/lib/puppet/indirector/null/node.rb new file mode 100644 index 000000000..eb08f5697 --- /dev/null +++ b/lib/puppet/indirector/null/node.rb @@ -0,0 +1,14 @@ +require 'puppet/indirector/null' + +class Puppet::Indirector::Null::Node < Puppet::Indirector::Null + desc "Always return an empty node object. This is the node source you should + use when you don't have some other, functional source you want to use, + as the compiler will not work without this node information." + + # Just return an empty node. + def find(name) + node = super + node.fact_merge + node + end +end diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb new file mode 100644 index 000000000..bcff08d79 --- /dev/null +++ b/lib/puppet/indirector/terminus.rb @@ -0,0 +1,117 @@ +require 'puppet/indirector' +require 'puppet/indirector/indirection' +require 'puppet/util/instance_loader' + +# A simple class that can function as the base class for indirected types. +class Puppet::Indirector::Terminus + require 'puppet/util/docs' + extend Puppet::Util::Docs + + class << self + include Puppet::Util::InstanceLoader + + attr_accessor :name, :terminus_type + attr_reader :abstract_terminus, :indirection + + # Are we an abstract terminus type, rather than an instance with an + # associated indirection? + def abstract_terminus? + abstract_terminus + end + + # Look up the indirection if we were only provided a name. + def indirection=(name) + if name.is_a?(Puppet::Indirector::Indirection) + @indirection = name + elsif ind = Puppet::Indirector::Indirection.instance(name) + @indirection = ind + else + raise ArgumentError, "Could not find indirection instance %s" % name + end + end + + # Register our subclass with the appropriate indirection. + # This follows the convention that our terminus is named after the + # indirection. + def inherited(subclass) + longname = subclass.to_s + if longname =~ /#<Class/ + raise ArgumentError, "Terminus subclasses must have associated constants" + end + names = longname.split("::") + name = names.pop.downcase.intern + + subclass.name = name + + # Short-circuit the abstract types, which are those that directly subclass + # the Terminus class. + if self == Puppet::Indirector::Terminus + subclass.mark_as_abstract_terminus + return + end + + # Set the terminus type to be the name of the abstract terminus type. + # Yay, class/instance confusion. + subclass.terminus_type = self.name + + # This will throw an exception if the indirection instance cannot be found. + # Do this last, because it also registers the terminus type with the indirection, + # which needs the above information. + subclass.indirection = name + + # And add this instance to the instance hash. + Puppet::Indirector::Terminus.register_terminus_class(subclass) + end + + # Mark that this instance is abstract. + def mark_as_abstract_terminus + @abstract_terminus = true + end + + def model + indirection.model + end + + # Register a class, probably autoloaded. + def register_terminus_class(klass) + setup_instance_loading klass.terminus_type + instance_hash(klass.terminus_type)[klass.name] = klass + end + + # Return a terminus by name, using the autoloader. + def terminus_class(type, name) + setup_instance_loading type + loaded_instance(type, name) + end + + private + + def setup_instance_loading(type) + unless instance_loading?(type) + instance_load type, "puppet/indirector/%s" % type + end + end + end + + def initialize + if self.class.abstract_terminus? + raise Puppet::DevError, "Cannot create instances of abstract terminus types" + end + end + + def terminus_type + self.class.terminus_type + end + + def name + self.class.name + end + + def model + self.class.model + end + + def indirection + self.class.indirection + end +end diff --git a/lib/puppet/indirector/yaml.rb b/lib/puppet/indirector/yaml.rb new file mode 100644 index 000000000..b9ea54f39 --- /dev/null +++ b/lib/puppet/indirector/yaml.rb @@ -0,0 +1,45 @@ +require 'puppet/indirector/terminus' + +# The base class for YAML indirection termini. +class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus + def initialize + # Make sure our base directory exists. + Puppet.settings.use(:yaml) + end + + # Read a given name's file in and convert it from YAML. + def find(name) + raise ArgumentError.new("You must specify the name of the object to retrieve") unless name + file = path(name) + return nil unless FileTest.exist?(file) + + begin + return YAML.load(File.read(file)) + rescue => detail + raise Puppet::Error, "Could not read YAML data for %s(%s): %s" % [indirection.name, name, detail] + end + end + + # Convert our object to YAML and store it to the disk. + def save(object) + raise ArgumentError.new("You can only save objects that respond to :name") unless object.respond_to?(:name) + + file = path(object.name) + + basedir = File.dirname(file) + + # This is quite likely a bad idea, since we're not managing ownership or modes. + unless FileTest.exist?(basedir) + Dir.mkdir(basedir) + end + + File.open(file, "w", 0660) { |f| f.print YAML.dump(object) } + end + + private + + # Return the path to a given node's file. + def path(name) + File.join(Puppet[:yamldir], self.name.to_s, name.to_s + ".yaml") + end +end diff --git a/lib/puppet/indirector/yaml/facts.rb b/lib/puppet/indirector/yaml/facts.rb new file mode 100644 index 000000000..754b0d5a6 --- /dev/null +++ b/lib/puppet/indirector/yaml/facts.rb @@ -0,0 +1,5 @@ +require 'puppet/indirector/yaml' + +class Puppet::Indirector::Yaml::Facts < Puppet::Indirector::Yaml + desc "Store client facts as flat files, serialized using YAML." +end diff --git a/lib/puppet/metatype/closure.rb b/lib/puppet/metatype/closure.rb index efb4712c6..727bc6884 100644 --- a/lib/puppet/metatype/closure.rb +++ b/lib/puppet/metatype/closure.rb @@ -1,19 +1,6 @@ class Puppet::Type attr_writer :implicit - def self.implicitcreate(hash) - unless hash.include?(:implicit) - hash[:implicit] = true - end - if obj = self.create(hash) - obj.implicit = true - - return obj - else - return nil - end - end - # Is this type's name isomorphic with the object? That is, if the # name conflicts, does it necessarily mean that the objects conflict? # Defaults to true. diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb index 7c44a7def..7bbccf8a0 100644 --- a/lib/puppet/metatype/container.rb +++ b/lib/puppet/metatype/container.rb @@ -14,14 +14,6 @@ class Puppet::Type self.class.depthfirst? end - def parent=(parent) - if self.parentof?(parent) - devfail "%s[%s] is already the parent of %s[%s]" % - [self.class.name, self.title, parent.class.name, parent.title] - end - @parent = parent - end - # Add a hook for testing for recursion. def parentof?(child) if (self == child) diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb index f6c2fdd34..4af230b28 100644 --- a/lib/puppet/metatype/instances.rb +++ b/lib/puppet/metatype/instances.rb @@ -79,8 +79,7 @@ class Puppet::Type end # Force users to call this, so that we can merge objects if - # necessary. FIXME This method should be responsible for most of the - # error handling. + # necessary. def self.create(args) # Don't modify the original hash; instead, create a duplicate and modify it. # We have to dup and use the ! so that it stays a TransObject if it is @@ -138,9 +137,8 @@ class Puppet::Type # now pass through and create the new object elsif implicit - Puppet.notice "Ignoring implicit %s" % title - - return retobj + Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title] + return nil else # If only one of the objects is being managed, then merge them if retobj.managed? @@ -308,8 +306,8 @@ class Puppet::Type # Create the path for logging and such. def pathbuilder - if defined? @parent and @parent - [@parent.pathbuilder, self.ref].flatten + if p = parent + [p.pathbuilder, self.ref].flatten else [self.ref] end diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 924958bbe..dc30d8167 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -9,7 +9,7 @@ class Puppet::Module # parameter. Only consider paths that are absolute and existing # directories def self.modulepath(environment = nil) - dirs = Puppet.config.value(:modulepath, environment).split(":") + dirs = Puppet.settings.value(:modulepath, environment).split(":") if ENV["PUPPETLIB"] dirs = ENV["PUPPETLIB"].split(":") + dirs else @@ -61,7 +61,7 @@ class Puppet::Module if mod return mod.template(file) else - return File.join(Puppet.config.value(:templatedir, environment), template) + return File.join(Puppet.settings.value(:templatedir, environment), template) end end diff --git a/lib/puppet/network/client/ca.rb b/lib/puppet/network/client/ca.rb index 412c9c59f..46fb9f51f 100644 --- a/lib/puppet/network/client/ca.rb +++ b/lib/puppet/network/client/ca.rb @@ -14,9 +14,9 @@ class Puppet::Network::Client::CA < Puppet::Network::Client end # This client is really only able to request certificates for the - # current host. It uses the Puppet.config settings to figure everything out. + # current host. It uses the Puppet.settings settings to figure everything out. def request_cert - Puppet.config.use(:main, :ssl) + Puppet.settings.use(:main, :ssl) if cert = read_cert return cert @@ -49,8 +49,8 @@ class Puppet::Network::Client::CA < Puppet::Network::Client end # Only write the cert out if it passes validating. - Puppet.config.write(:hostcert) do |f| f.print cert end - Puppet.config.write(:localcacert) do |f| f.print cacert end + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end return @cert end diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index c6d7cd75d..f950a6059 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -7,7 +7,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client @@sync = Sync.new end - attr_accessor :objects + attr_accessor :configuration attr_reader :compile_time class << self @@ -46,51 +46,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Return the list of dynamic facts as an array of symbols def self.dynamic_facts - Puppet.config[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase } - end - - # This method actually applies the configuration. - def apply(tags = nil, ignoreschedules = false) - unless defined? @objects - raise Puppet::Error, "Cannot apply; objects not defined" - end - - transaction = @objects.evaluate - - if tags - transaction.tags = tags - end - - if ignoreschedules - transaction.ignoreschedules = true - end - - transaction.addtimes :config_retrieval => @configtime - - begin - transaction.evaluate - rescue Puppet::Error => detail - Puppet.err "Could not apply complete configuration: %s" % - detail - rescue => detail - Puppet.err "Got an uncaught exception of type %s: %s" % - [detail.class, detail] - if Puppet[:trace] - puts detail.backtrace - end - ensure - Puppet::Util::Storage.store - end - - if Puppet[:report] or Puppet[:summarize] - report(transaction) - end - - return transaction - ensure - if defined? transaction and transaction - transaction.cleanup - end + Puppet.settings[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase } end # Cache the config @@ -111,10 +67,10 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end def clear - @objects.remove(true) if @objects + @configuration.clear(true) if @configuration Puppet::Type.allclear mkdefault_objects - @objects = nil + @configuration = nil end # Initialize and load storage @@ -183,15 +139,15 @@ class Puppet::Network::Client::Master < Puppet::Network::Client facts = self.class.facts end - if self.objects or FileTest.exists?(self.cachefile) + if self.configuration or FileTest.exists?(self.cachefile) if self.fresh?(facts) Puppet.info "Config is up to date" - if self.objects + if self.configuration return end if oldtext = self.retrievecache begin - @objects = YAML.load(oldtext).to_type + @configuration = YAML.load(oldtext).to_configuration rescue => detail Puppet.warning "Could not load cached configuration: %s" % detail end @@ -213,7 +169,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end unless objects = get_actual_config(facts) - @objects = nil + @configuration = nil return end @@ -225,21 +181,21 @@ class Puppet::Network::Client::Master < Puppet::Network::Client self.setclasses(objects.classes) # Clear all existing objects, so we can recreate our stack. - if self.objects + if self.configuration clear() end - # Now convert the objects to real Puppet objects - @objects = objects.to_type + # Now convert the objects to a puppet configuration graph. + @configuration = objects.to_configuration - if @objects.nil? + if @configuration.nil? raise Puppet::Error, "Configuration could not be processed" end - # and perform any necessary final actions before we evaluate. - @objects.finalize + # Keep the state database up to date. + @configuration.host_config = true - return @objects + return @configuration end # A simple proxy method, so it's easy to test. @@ -249,12 +205,9 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Just so we can specify that we are "the" instance. def initialize(*args) - Puppet.config.use(:main, :ssl, :puppetd) + Puppet.settings.use(:main, :ssl, :puppetd) super - # This might be nil - @configtime = 0 - self.class.instance = self @running = false @@ -297,7 +250,9 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end # The code that actually runs the configuration. - def run(tags = nil, ignoreschedules = false) + # This just passes any options on to the configuration, + # which accepts :tags and :ignoreschedules. + def run(options = {}) got_lock = false splay Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do @@ -307,19 +262,20 @@ class Puppet::Network::Client::Master < Puppet::Network::Client else got_lock = true begin - @configtime = thinmark do + duration = thinmark do self.getconfig end rescue => detail Puppet.err "Could not retrieve configuration: %s" % detail end - if defined? @objects and @objects + if defined? @configuration and @configuration + @configuration.retrieval_duration = duration unless @local Puppet.notice "Starting configuration run" end benchmark(:notice, "Finished configuration run") do - self.apply(tags, ignoreschedules) + @configuration.apply(options) end end end @@ -366,9 +322,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Download files from the remote server, returning a list of all # changed files. def self.download(args) - objects = Puppet::Type.type(:component).create( - :name => "#{args[:name]}_collector" - ) hash = { :path => args[:dest], :recurse => true, @@ -383,18 +336,23 @@ class Puppet::Network::Client::Master < Puppet::Network::Client if args[:ignore] hash[:ignore] = args[:ignore].split(/\s+/) end - objects.push Puppet::Type.type(:file).create(hash) + downconfig = Puppet::Node::Configuration.new("downloading") + downconfig.add_resource Puppet::Type.type(:file).create(hash) Puppet.info "Retrieving #{args[:name]}s" noop = Puppet[:noop] Puppet[:noop] = false + files = [] begin - trans = objects.evaluate - trans.ignoretags = true Timeout::timeout(self.timeout) do - trans.evaluate + downconfig.apply do |trans| + trans.changed?.find_all do |resource| + yield resource if block_given? + files << resource[:path] + end + end end rescue Puppet::Error, Timeout::Error => detail if Puppet[:debug] @@ -403,18 +361,10 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.err "Could not retrieve #{args[:name]}s: %s" % detail end - # Now source all of the changed objects, but only source those - # that are top-level. - files = [] - trans.changed?.find_all do |object| - yield object if block_given? - files << object[:path] - end - trans.cleanup - # Now clean up after ourselves - objects.remove - files + downconfig.clear + + return files ensure # I can't imagine why this is necessary, but apparently at last one person has had problems with noop # being nil here. @@ -427,21 +377,21 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Retrieve facts from the central server. def self.getfacts - # Clear all existing definitions. - Facter.clear # Download the new facts path = Puppet[:factpath].split(":") files = [] download(:dest => Puppet[:factdest], :source => Puppet[:factsource], - :ignore => Puppet[:factsignore], :name => "fact") do |object| - - next unless path.include?(::File.dirname(object[:path])) + :ignore => Puppet[:factsignore], :name => "fact") do |resource| - files << object[:path] + next unless path.include?(::File.dirname(resource[:path])) + files << resource[:path] end ensure + # Clear all existing definitions. + Facter.clear + # Reload everything. if Facter.respond_to? :loadfacts Facter.loadfacts @@ -461,20 +411,20 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # changed plugins, because Puppet::Type loads plugins on demand. def self.getplugins download(:dest => Puppet[:plugindest], :source => Puppet[:pluginsource], - :ignore => Puppet[:pluginsignore], :name => "plugin") do |object| + :ignore => Puppet[:pluginsignore], :name => "plugin") do |resource| - next if FileTest.directory?(object[:path]) - path = object[:path].sub(Puppet[:plugindest], '').sub(/^\/+/, '') + next if FileTest.directory?(resource[:path]) + path = resource[:path].sub(Puppet[:plugindest], '').sub(/^\/+/, '') unless Puppet::Util::Autoload.loaded?(path) next end begin Puppet.info "Reloading downloaded file %s" % path - load object[:path] + load resource[:path] rescue => detail Puppet.warning "Could not reload downloaded file %s: %s" % - [object[:path], detail] + [resource[:path], detail] end end end @@ -517,42 +467,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client return timeout end - - # Send off the transaction report. - def report(transaction) - begin - report = transaction.generate_report() - rescue => detail - Puppet.err "Could not generate report: %s" % detail - return - end - - if Puppet[:rrdgraph] == true - report.graph() - end - - if Puppet[:summarize] - puts report.summary - end - - if Puppet[:report] - begin - reportclient().report(report) - rescue => detail - Puppet.err "Reporting failed: %s" % detail - end - end - end - - def reportclient - unless defined? @reportclient - @reportclient = Puppet::Network::Client.report.new( - :Server => Puppet[:reportserver] - ) - end - - @reportclient - end loadfacts() @@ -633,7 +547,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.err "Could not retrieve configuration: %s" % detail unless Puppet[:usecacheonfailure] - @objects = nil + @configuration = nil Puppet.warning "Not using cache on failed configuration" return end diff --git a/lib/puppet/network/handler/ca.rb b/lib/puppet/network/handler/ca.rb index 422b21ae1..052eb5c19 100644 --- a/lib/puppet/network/handler/ca.rb +++ b/lib/puppet/network/handler/ca.rb @@ -60,7 +60,7 @@ class Puppet::Network::Handler end def initialize(hash = {}) - Puppet.config.use(:main, :ssl, :ca) + Puppet.settings.use(:main, :ssl, :ca) if hash.include? :autosign @autosign = hash[:autosign] end diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb index 2c72d3d2b..2df1b3ab4 100644 --- a/lib/puppet/network/handler/configuration.rb +++ b/lib/puppet/network/handler/configuration.rb @@ -30,7 +30,7 @@ class Puppet::Network::Handler # Note that this is reasonable, because either their node source should actually # know about the node, or they should be using the ``none`` node source, which # will always return data. - unless node = node_handler.details(key) + unless node = Puppet::Node.search(key) raise Puppet::Error, "Could not find node '%s'" % key end @@ -64,7 +64,7 @@ class Puppet::Network::Handler # Return the configuration version. def version(client = nil, clientip = nil) - if client and node = node_handler.details(client) + if client and node = Puppet::Node.search(client) update_node_check(node) return interpreter.configuration_version(node) else @@ -79,7 +79,7 @@ class Puppet::Network::Handler # Add any extra data necessary to the node. def add_node_data(node) # Merge in our server-side facts, so they can be used during compilation. - node.fact_merge(@server_facts) + node.merge(@server_facts) # Add any specified classes to the node's class list. if classes = @options[:Classes] @@ -159,14 +159,6 @@ class Puppet::Network::Handler @interpreter end - # Create a node handler instance for looking up our nodes. - def node_handler - unless defined?(@node_handler) - @node_handler = Puppet::Network::Handler.handler(:node).create - end - @node_handler - end - # Initialize our server fact hash; we add these to each client, and they # won't change while we're running, so it's safe to cache the values. def set_server_facts diff --git a/lib/puppet/network/handler/facts.rb b/lib/puppet/network/handler/facts.rb deleted file mode 100755 index 4767e8be4..000000000 --- a/lib/puppet/network/handler/facts.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'yaml' -require 'puppet/util/fact_store' - -class Puppet::Network::Handler - # Receive logs from remote hosts. - class Facts < Handler - desc "An interface for storing and retrieving client facts. Currently only - used internally by Puppet." - - @interface = XMLRPC::Service::Interface.new("facts") { |iface| - iface.add_method("void set(string, string)") - iface.add_method("string get(string)") - iface.add_method("integer store_date(string)") - } - - def initialize(hash = {}) - super - - backend = Puppet[:factstore] - - unless klass = Puppet::Util::FactStore.store(backend) - raise Puppet::Error, "Could not find fact store %s" % backend - end - - @backend = klass.new - end - - # Get the facts from our back end. - def get(node) - if facts = @backend.get(node) - return strip_internal(facts) - else - return nil - end - end - - # Set the facts in the backend. - def set(node, facts) - @backend.set(node, add_internal(facts)) - nil - end - - # Retrieve a client's storage date. - def store_date(node) - if facts = get(node) - facts[:_puppet_timestamp].to_i - else - nil - end - end - - private - - # Add internal data to the facts for storage. - def add_internal(facts) - facts = facts.dup - facts[:_puppet_timestamp] = Time.now - facts - end - - # Strip out that internal data. - def strip_internal(facts) - facts = facts.dup - facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } - facts - end - end -end diff --git a/lib/puppet/network/handler/filebucket.rb b/lib/puppet/network/handler/filebucket.rb index bb6a0e6d3..1bf8da854 100755 --- a/lib/puppet/network/handler/filebucket.rb +++ b/lib/puppet/network/handler/filebucket.rb @@ -64,7 +64,7 @@ class Puppet::Network::Handler # :nodoc: end end - Puppet.config.use(:filebucket) + Puppet.settings.use(:filebucket) @name = "Filebucket[#{@path}]" end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index a429412d2..ae0e6553d 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -243,7 +243,10 @@ class Puppet::Network::Handler # the modules. def modules_mount(module_name, client) # Find our environment, if we have one. - if node = node_handler.details(client || Facter.value("hostname")) + unless hostname = (client || Facter.value("hostname")) + raise ArgumentError, "Could not find hostname" + end + if node = Puppet::Node.find(hostname) env = node.environment else env = nil @@ -258,14 +261,6 @@ class Puppet::Network::Handler end end - # Create a node handler instance for looking up our nodes. - def node_handler - unless defined?(@node_handler) - @node_handler = Puppet::Network::Handler.handler(:node).create - end - @node_handler - end - # Read the configuration file. def readconfig(check = true) return if @noreadconfig @@ -455,7 +450,7 @@ class Puppet::Network::Handler def getfileobject(dir, links) unless FileTest.exists?(dir) - self.notice "File source %s does not exist" % dir + self.debug "File source %s does not exist" % dir return nil end diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index c8db277ba..030950c61 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -74,7 +74,7 @@ class Puppet::Network::Handler client, clientip = clientname(client, clientip, facts) # Pass the facts to the fact handler - fact_handler.set(client, facts) + Puppet::Node::Facts.new(client, facts).save # And get the configuration from the config handler begin @@ -134,13 +134,6 @@ class Puppet::Network::Handler return facts end - def fact_handler - unless defined? @fact_handler - @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local? - end - @fact_handler - end - # Translate our configuration appropriately for sending back to a client. def translate(config) if local? diff --git a/lib/puppet/network/handler/node.rb b/lib/puppet/network/handler/node.rb deleted file mode 100644 index c6ccc2eb6..000000000 --- a/lib/puppet/network/handler/node.rb +++ /dev/null @@ -1,242 +0,0 @@ -# Created by Luke A. Kanies on 2007-08-13. -# Copyright (c) 2007. All rights reserved. - -require 'puppet/util' -require 'puppet/node' -require 'puppet/util/classgen' -require 'puppet/util/instance_loader' - -# Look up a node, along with all the details about it. -class Puppet::Network::Handler::Node < Puppet::Network::Handler - desc "Retrieve information about nodes." - - # Create a singleton node handler - def self.create - unless @handler - @handler = new - end - @handler - end - - # Add a new node source. - def self.newnode_source(name, options = {}, &block) - name = symbolize(name) - - fact_merge = options[:fact_merge] - mod = genmodule(name, :extend => SourceBase, :hash => instance_hash(:node_source), :block => block) - mod.send(:define_method, :fact_merge?) do - fact_merge - end - mod - end - - # Collect the docs for all of our node sources. - def self.node_source_docs - docs = "" - - # Use this method so they all get loaded - instance_loader(:node_source).loadall - loaded_instances(:node_source).sort { |a,b| a.to_s <=> b.to_s }.each do |name| - mod = self.node_source(name) - docs += "%s\n%s\n" % [name, "-" * name.to_s.length] - - docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n" - end - - docs - end - - # List each of the node sources. - def self.node_sources - instance_loader(:node_source).loadall - loaded_instances(:node_source) - end - - # Remove a defined node source; basically only used for testing. - def self.rm_node_source(name) - rmclass(name, :hash => instance_hash(:node_source)) - end - - extend Puppet::Util::ClassGen - extend Puppet::Util::InstanceLoader - - # A simple base module we can use for modifying how our node sources work. - module SourceBase - include Puppet::Util::Docs - end - - @interface = XMLRPC::Service::Interface.new("nodes") { |iface| - iface.add_method("string details(key)") - iface.add_method("string parameters(key)") - iface.add_method("string environment(key)") - iface.add_method("string classes(key)") - } - - # Set up autoloading and retrieving of reports. - instance_load :node_source, 'puppet/node_source' - - attr_reader :source - - # Return a given node's classes. - def classes(key) - if node = details(key) - node.classes - else - nil - end - end - - # Return an entire node configuration. This uses the 'nodesearch' method - # defined in the node_source to look for the node. - def details(key, client = nil, clientip = nil) - return nil unless key - if node = cached?(key) - return node - end - facts = node_facts(key) - node = nil - names = node_names(key, facts) - names.each do |name| - name = name.to_s if name.is_a?(Symbol) - if node = nodesearch(name) and @source != "none" - Puppet.info "Found %s in %s" % [name, @source] - break - end - end - - # If they made it this far, we haven't found anything, so look for a - # default node. - unless node or names.include?("default") - if node = nodesearch("default") - Puppet.notice "Using default node for %s" % key - end - end - - if node - node.source = @source - node.names = names - - # Merge the facts into the parameters. - if fact_merge? - node.fact_merge(facts) - end - - cache(node) - - return node - else - return nil - end - end - - # Return a given node's environment. - def environment(key, client = nil, clientip = nil) - if node = details(key) - node.environment - else - nil - end - end - - # Create our node lookup tool. - def initialize(hash = {}) - @source = hash[:Source] || Puppet[:node_source] - - unless mod = self.class.node_source(@source) - raise ArgumentError, "Unknown node source '%s'" % @source - end - - extend(mod) - - super - - # We cache node info for speed - @node_cache = {} - end - - # Try to retrieve a given node's parameters. - def parameters(key, client = nil, clientip = nil) - if node = details(key) - node.parameters - else - nil - end - end - - private - - # Store the node to make things a bit faster. - def cache(node) - @node_cache[node.name] = node - end - - # If the node is cached, return it. - def cached?(name) - # Don't use cache when the filetimeout is set to 0 - return false if [0, "0"].include?(Puppet[:filetimeout]) - - if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] - return node - else - return false - end - end - - # Create/cache a fact handler. - def fact_handler - unless defined?(@fact_handler) - @fact_handler = Puppet::Network::Handler.handler(:facts).new - end - @fact_handler - end - - # Short-hand for creating a new node, so the node sources don't need to - # specify the constant. - def newnode(options) - Puppet::Node.new(options) - end - - # Look up the node facts from our fact handler. - def node_facts(key) - if facts = fact_handler.get(key) - facts - else - {} - end - end - - # Calculate the list of node names we should use for looking - # up our node. - def node_names(key, facts = nil) - facts ||= node_facts(key) - names = [] - - if hostname = facts["hostname"] - unless hostname == key - names << hostname - end - else - hostname = key - end - - if fqdn = facts["fqdn"] - hostname = fqdn - names << fqdn - end - - # Make sure both the fqdn and the short name of the - # host can be used in the manifest - if hostname =~ /\./ - names << hostname.sub(/\..+/,'') - elsif domain = facts['domain'] - names << hostname + "." + domain - end - - # Sort the names inversely by name length. - names.sort! { |a,b| b.length <=> a.length } - - # And make sure the key is first, since that's the most - # likely usage. - ([key] + names).uniq - end -end diff --git a/lib/puppet/network/handler/report.rb b/lib/puppet/network/handler/report.rb index 81aee6a3c..62e9cfdec 100755 --- a/lib/puppet/network/handler/report.rb +++ b/lib/puppet/network/handler/report.rb @@ -71,8 +71,8 @@ class Puppet::Network::Handler def initialize(*args) super - Puppet.config.use(:reporting) - Puppet.config.use(:metrics) + Puppet.settings.use(:reporting) + Puppet.settings.use(:metrics) end # Accept a report from a client. diff --git a/lib/puppet/network/handler/resource.rb b/lib/puppet/network/handler/resource.rb index ac29dce53..7709b85fe 100755 --- a/lib/puppet/network/handler/resource.rb +++ b/lib/puppet/network/handler/resource.rb @@ -39,21 +39,14 @@ class Puppet::Network::Handler end end - component = bucket.to_type - - # Create a client, but specify the remote machine as the server - # because the class requires it, even though it's unused - client = Puppet::Network::Client.client(:Master).new(:Master => client||"localhost") - - # Set the objects - client.objects = component + config = bucket.to_configuration # And then apply the configuration. This way we're reusing all # the code in there. It should probably just be separated out, though. - transaction = client.apply + transaction = config.apply # And then clean up - component.remove + config.clear(true) # It'd be nice to return some kind of report, but... at this point # we have no such facility. diff --git a/lib/puppet/network/handler/runner.rb b/lib/puppet/network/handler/runner.rb index c41e83608..4b9ccab75 100755 --- a/lib/puppet/network/handler/runner.rb +++ b/lib/puppet/network/handler/runner.rb @@ -50,10 +50,10 @@ class Puppet::Network::Handler # And then we need to tell it to run, with this extra info. if fg - master.run(tags, ignoreschedules) + master.run(:tags => tags, :ignoreschedules => ignoreschedules) else Puppet.newthread do - master.run(tags, ignoreschedules) + master.run(:tags => tags, :ignoreschedules => ignoreschedules) end end diff --git a/lib/puppet/network/server/webrick.rb b/lib/puppet/network/server/webrick.rb index 3af0cfd3f..f24411ab3 100644 --- a/lib/puppet/network/server/webrick.rb +++ b/lib/puppet/network/server/webrick.rb @@ -48,7 +48,7 @@ module Puppet # yuck; separate http logs file = nil - Puppet.config.use(:main, :ssl, Puppet[:name]) + Puppet.settings.use(:main, :ssl, Puppet[:name]) if Puppet[:name] == "puppetmasterd" file = Puppet[:masterhttplog] else diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 2d3ac712e..d71bd507e 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -1,10 +1,27 @@ +require 'puppet/indirector' + # A simplistic class for managing the node information itself. class Puppet::Node + require 'puppet/node/facts' + + # Set up indirection, so that nodes can be looked for in + # the node sources. + extend Puppet::Indirector + + # Use the node source as the indirection terminus. + indirects :node + + # Add the node-searching methods. This is what people will actually + # interact with that will find the node with the list of names or + # will search for a default node. + require 'puppet/node/searching' + extend Puppet::Node::Searching + attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names attr_reader :time attr_writer :environment - # Do not return environments tha are empty string, and use + # Do not return environments that are the empty string, and use # explicitly set environments, then facts, then a central env # value. def environment @@ -21,6 +38,9 @@ class Puppet::Node end def initialize(name, options = {}) + unless name + raise ArgumentError, "Node names cannot be nil" + end @name = name # Provide a default value. @@ -52,9 +72,15 @@ class Puppet::Node end # Merge the node facts with parameters from the node source. - # This is only called if the node source has 'fact_merge' set to true. - def fact_merge(facts) - facts.each do |name, value| + def fact_merge + if facts = Puppet::Node::Facts.find(name) + merge(facts.values) + end + end + + # Merge any random parameters into our parameter list. + def merge(params) + params.each do |name, value| @parameters[name] = value unless @parameters.include?(name) end end diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 4f93fdbe5..53f63d003 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -1,13 +1,34 @@ +require 'puppet/indirector' require 'puppet/external/gratr/digraph' # This class models a node configuration. It is the thing # meant to be passed from server to client, and it contains all # of the information in the configuration, including the resources # and the relationships between them. -class Puppet::Node::Configuration < GRATR::Digraph - attr_accessor :name, :version +class Puppet::Node::Configuration < Puppet::PGraph + extend Puppet::Indirector + indirects :configuration + + # The host name this is a configuration for. + attr_accessor :name + + # The configuration version. Used for testing whether a configuration + # is up to date. + attr_accessor :version + + # How long this configuration took to retrieve. Used for reporting stats. + attr_accessor :retrieval_duration + + # How we should extract the configuration for sending to the client. attr_reader :extraction_format + # Whether this is a host configuration, which behaves very differently. + # In particular, reports are sent, graphs are made, and state is + # stored in the state database. If this is set incorrectly, then you often + # end up in infinite loops, because configurations are used to make things + # that the host configuration needs. + attr_accessor :host_config + # Add classes to our class list. def add_class(*classes) classes.each do |klass| @@ -18,10 +39,133 @@ class Puppet::Node::Configuration < GRATR::Digraph tag(*classes) end + # Add one or more resources to our graph and to our resource table. + def add_resource(*resources) + resources.each do |resource| + unless resource.respond_to?(:ref) + raise ArgumentError, "Can only add objects that respond to :ref" + end + + ref = resource.ref + if @resource_table.include?(ref) + raise ArgumentError, "Resource %s is already defined" % ref + else + @resource_table[ref] = resource + end + resource.configuration = self + add_vertex!(resource) + end + end + + # Apply our configuration to the local host. Valid options + # are: + # :tags - set the tags that restrict what resources run + # during the transaction + # :ignoreschedules - tell the transaction to ignore schedules + # when determining the resources to run + def apply(options = {}) + @applying = true + + Puppet::Util::Storage.load if host_config? + transaction = Puppet::Transaction.new(self) + + transaction.tags = options[:tags] if options[:tags] + transaction.ignoreschedules = true if options[:ignoreschedules] + + transaction.addtimes :config_retrieval => @retrieval_duration + + begin + transaction.evaluate + rescue Puppet::Error => detail + Puppet.err "Could not apply complete configuration: %s" % detail + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Got an uncaught exception of type %s: %s" % [detail.class, detail] + ensure + # Don't try to store state unless we're a host config + # too recursive. + Puppet::Util::Storage.store if host_config? + end + + if block_given? + yield transaction + end + + if host_config and (Puppet[:report] or Puppet[:summarize]) + transaction.send_report + end + + return transaction + ensure + @applying = false + cleanup() + if defined? transaction and transaction + transaction.cleanup + end + end + + # Are we in the middle of applying the configuration? + def applying? + @applying + end + + def clear(remove_resources = true) + super() + # We have to do this so that the resources clean themselves up. + @resource_table.values.each { |resource| resource.remove } if remove_resources + @resource_table.clear + + if defined?(@relationship_graph) and @relationship_graph + @relationship_graph.clear(false) + @relationship_graph = nil + end + end + def classes @classes.dup end + # Create an implicit resource, meaning that it will lose out + # to any explicitly defined resources. This method often returns + # nil. + # The quirk of this method is that it's not possible to create + # an implicit resource before an explicit resource of the same name, + # because all explicit resources are created before any generate() + # methods are called on the individual resources. Thus, this + # method can safely just check if an explicit resource already exists + # and toss this implicit resource if so. + def create_implicit_resource(type, options) + unless options.include?(:implicit) + options[:implicit] = true + end + + # This will return nil if an equivalent explicit resource already exists. + # When resource classes no longer retain references to resource instances, + # this will need to be modified to catch that conflict and discard + # implicit resources. + if resource = create_resource(type, options) + resource.implicit = true + + return resource + else + return nil + end + end + + # Create a new resource and register it in the configuration. + def create_resource(type, options) + unless klass = Puppet::Type.type(type) + raise ArgumentError, "Unknown resource type %s" % type + end + return unless resource = klass.create(options) + + @transient_resources << resource if applying? + add_resource(resource) + resource + end + # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) @@ -94,12 +238,101 @@ class Puppet::Node::Configuration < GRATR::Digraph return result end - def initialize(name) + # Make sure all of our resources are "finished". + def finalize + @resource_table.values.each { |resource| resource.finish } + + write_graph(:resources) + end + + def host_config? + host_config || false + end + + def initialize(name = nil) super() - @name = name + @name = name if name @extraction_format ||= :transportable @tags = [] @classes = [] + @resource_table = {} + @transient_resources = [] + @applying = false + @relationship_graph = nil + + if block_given? + yield(self) + finalize() + end + end + + # Create a graph of all of the relationships in our configuration. + def relationship_graph + unless defined? @relationship_graph and @relationship_graph + # It's important that we assign the graph immediately, because + # the debug messages below use the relationships in the + # relationship graph to determine the path to the resources + # spitting out the messages. If this is not set, + # then we get into an infinite loop. + @relationship_graph = Puppet::Node::Configuration.new + @relationship_graph.host_config = host_config? + + # First create the dependency graph + self.vertices.each do |vertex| + @relationship_graph.add_vertex! vertex + vertex.builddepends.each do |edge| + @relationship_graph.add_edge!(edge) + end + end + + # Lastly, add in any autorequires + @relationship_graph.vertices.each do |vertex| + vertex.autorequire.each do |edge| + unless @relationship_graph.edge?(edge) + unless @relationship_graph.edge?(edge.target, edge.source) + vertex.debug "Autorequiring %s" % [edge.source] + @relationship_graph.add_edge!(edge) + else + vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) + end + end + end + end + + @relationship_graph.write_graph(:relationships) + + # Then splice in the container information + @relationship_graph.splice!(self, Puppet::Type::Component) + + @relationship_graph.write_graph(:expanded_relationships) + end + @relationship_graph + end + + # Remove the resource from our configuration. Notice that we also call + # 'remove' on the resource, at least until resource classes no longer maintain + # references to the resource instances. + def remove_resource(*resources) + resources.each do |resource| + @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) + remove_vertex!(resource) if vertex?(resource) + @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource) + resource.remove + end + end + + # Look a resource up by its reference (e.g., File[/etc/passwd]). + def resource(type, title = nil) + if title + ref = "%s[%s]" % [type.to_s.capitalize, title] + else + ref = type + end + if resource = @resource_table[ref] + return resource + elsif defined?(@relationship_graph) and @relationship_graph + @relationship_graph.resource(ref) + end end # Add a tag. @@ -120,4 +353,29 @@ class Puppet::Node::Configuration < GRATR::Digraph def tags @tags.dup end + + # Produce the graph files if requested. + def write_graph(name) + # We only want to graph the main host configuration. + return unless host_config? + + return unless Puppet[:graph] + + Puppet.settings.use(:graphing) + + file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) + File.open(file, "w") { |f| + f.puts to_dot("name" => name.to_s.capitalize) + } + end + + private + + def cleanup + unless @transient_resources.empty? + remove_resource(*@transient_resources) + @transient_resources.clear + @relationship_graph = nil + end + end end diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb new file mode 100755 index 000000000..a2e6d9c04 --- /dev/null +++ b/lib/puppet/node/facts.rb @@ -0,0 +1,36 @@ +require 'puppet/node' +require 'puppet/indirector' + +# Manage a given node's facts. This either accepts facts and stores them, or +# returns facts for a given node. +class Puppet::Node::Facts + # Set up indirection, so that nodes can be looked for in + # the node sources. + extend Puppet::Indirector + + # Use the node source as the indirection terminus. + indirects :facts + + attr_accessor :name, :values + + def initialize(name, values = {}) + @name = name + @values = values + + add_internal + end + + private + + # Add internal data to the facts for storage. + def add_internal + self.values[:_timestamp] = Time.now + end + + # Strip out that internal data. + def strip_internal + newvals = values.dup + newvals.find_all { |name, value| name.to_s =~ /^_/ }.each { |name, value| newvals.delete(name) } + newvals + end +end diff --git a/lib/puppet/node/searching.rb b/lib/puppet/node/searching.rb new file mode 100644 index 000000000..10dd588ab --- /dev/null +++ b/lib/puppet/node/searching.rb @@ -0,0 +1,106 @@ +# The module that handles actually searching for nodes. This is only included +# in the Node class, but it's completely stand-alone functionality, so it's +# worth making it a separate module to simplify testing. +module Puppet::Node::Searching + # Retrieve a node from the node source, with some additional munging + # thrown in for kicks. + def search(key) + return nil unless key + if node = cached?(key) + return node + end + facts = node_facts(key) + node = nil + names = node_names(key, facts) + names.each do |name| + name = name.to_s if name.is_a?(Symbol) + if node = find(name) + #Puppet.info "Found %s in %s" % [name, @source] + break + end + end + + # If they made it this far, we haven't found anything, so look for a + # default node. + unless node or names.include?("default") + if node = find("default") + Puppet.notice "Using default node for %s" % key + end + end + + if node + node.names = names + + cache(node) + + return node + else + return nil + end + end + + private + + # Store the node to make things a bit faster. + def cache(node) + @node_cache ||= {} + @node_cache[node.name] = node + end + + # If the node is cached, return it. + def cached?(name) + # Don't use cache when the filetimeout is set to 0 + return false if [0, "0"].include?(Puppet[:filetimeout]) + @node_cache ||= {} + + if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] + return node + else + return false + end + end + + # Look up the node facts from our fact handler. + def node_facts(key) + if facts = Puppet::Node::Facts.find(key) + facts.values + else + {} + end + end + + # Calculate the list of node names we should use for looking + # up our node. + def node_names(key, facts = nil) + facts ||= node_facts(key) + names = [] + + if hostname = facts["hostname"] + unless hostname == key + names << hostname + end + else + hostname = key + end + + if fqdn = facts["fqdn"] + hostname = fqdn + names << fqdn + end + + # Make sure both the fqdn and the short name of the + # host can be used in the manifest + if hostname =~ /\./ + names << hostname.sub(/\..+/,'') + elsif domain = facts['domain'] + names << hostname + "." + domain + end + + # Sort the names inversely by name length. + names.sort! { |a,b| b.length <=> a.length } + + # And make sure the key is first, since that's the most + # likely usage. + ([key] + names).uniq + end +end diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb deleted file mode 100644 index 1d7fe9cdd..000000000 --- a/lib/puppet/parser/ast/component.rb +++ /dev/null @@ -1,224 +0,0 @@ -require 'puppet/parser/ast/branch' - -class Puppet::Parser::AST - # Evaluate the stored parse tree for a given component. This will - # receive the arguments passed to the component and also the type and - # name of the component. - class Component < AST::Branch - include Puppet::Util - include Puppet::Util::Warnings - include Puppet::Util::MethodHelper - class << self - attr_accessor :name - end - - # The class name - @name = :definition - - attr_accessor :classname, :arguments, :code, :scope, :keyword - attr_accessor :exported, :namespace, :parser, :virtual - - # These are retrieved when looking up the superclass - attr_accessor :name - - attr_reader :parentclass - - def child_of?(klass) - false - end - - def evaluate_resource(hash) - origscope = hash[:scope] - title = hash[:title] - args = symbolize_options(hash[:arguments] || {}) - - name = args[:name] || title - - exported = hash[:exported] - virtual = hash[:virtual] - - pscope = origscope - scope = subscope(pscope, title) - - if virtual or origscope.virtual? - scope.virtual = true - end - - if exported or origscope.exported? - scope.exported = true - end - - # Additionally, add a tag for whatever kind of class - # we are - if @classname != "" and ! @classname.nil? - @classname.split(/::/).each { |tag| scope.tag(tag) } - end - - [name, title].each do |str| - unless str.nil? or str =~ /[^\w]/ or str == "" - scope.tag(str) - end - end - - # define all of the arguments in our local scope - if self.arguments - # Verify that all required arguments are either present or - # have been provided with defaults. - self.arguments.each { |arg, default| - arg = symbolize(arg) - unless args.include?(arg) - if defined? default and ! default.nil? - default = default.safeevaluate :scope => scope - args[arg] = default - #Puppet.debug "Got default %s for %s in %s" % - # [default.inspect, arg.inspect, @name.inspect] - else - parsefail "Must pass %s to %s of type %s" % - [arg,title,@classname] - end - end - } - end - - # Set each of the provided arguments as variables in the - # component's scope. - args.each { |arg,value| - unless validattr?(arg) - parsefail "%s does not accept attribute %s" % [@classname, arg] - end - - exceptwrap do - scope.setvar(arg.to_s,args[arg]) - end - } - - unless args.include? :title - scope.setvar("title",title) - end - - unless args.include? :name - scope.setvar("name",name) - end - - if self.code - return self.code.safeevaluate(:scope => scope) - else - return nil - end - end - - def initialize(hash = {}) - @arguments = nil - @parentclass = nil - super - - # Convert the arguments to a hash for ease of later use. - if @arguments - unless @arguments.is_a? Array - @arguments = [@arguments] - end - oldargs = @arguments - @arguments = {} - oldargs.each do |arg, val| - @arguments[arg] = val - end - else - @arguments = {} - end - - # Deal with metaparams in the argument list. - @arguments.each do |arg, defvalue| - next unless Puppet::Type.metaparamclass(arg) - if defvalue - warnonce "%s is a metaparam; this value will inherit to all contained elements" % arg - else - raise Puppet::ParseError, - "%s is a metaparameter; please choose another name" % - name - end - end - end - - def find_parentclass - @parser.findclass(namespace, parentclass) - end - - # Set our parent class, with a little check to avoid some potential - # weirdness. - def parentclass=(name) - if name == self.classname - parsefail "Parent classes must have dissimilar names" - end - - @parentclass = name - end - - # Hunt down our class object. - def parentobj - if @parentclass - # Cache our result, since it should never change. - unless defined?(@parentobj) - unless tmp = find_parentclass - parsefail "Could not find %s %s" % [self.class.name, @parentclass] - end - - if tmp == self - parsefail "Parent classes must have dissimilar names" - end - - @parentobj = tmp - end - @parentobj - else - nil - end - end - - # Create a new subscope in which to evaluate our code. - def subscope(scope, name = nil) - args = { - :type => self.classname, - :keyword => self.keyword, - :namespace => self.namespace - } - - args[:name] = name if name - scope = scope.newscope(args) - scope.source = self - - return scope - end - - def to_s - classname - end - - # Check whether a given argument is valid. Searches up through - # any parent classes that might exist. - def validattr?(param) - param = param.to_s - - if @arguments.include?(param) - # It's a valid arg for us - return true - elsif param == "name" - return true -# elsif defined? @parentclass and @parentclass -# # Else, check any existing parent -# if parent = @scope.lookuptype(@parentclass) and parent != [] -# return parent.validarg?(param) -# elsif builtin = Puppet::Type.type(@parentclass) -# return builtin.validattr?(param) -# else -# raise Puppet::Error, "Could not find parent class %s" % -# @parentclass -# end - elsif Puppet::Type.metaparam?(param) - return true - else - # Or just return false - return false - end - end - end -end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index a4ea26572..81867c84b 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -68,7 +68,7 @@ class Puppet::Parser::Interpreter elsif self.file parser.file = self.file else - file = Puppet.config.value(:manifest, environment) + file = Puppet.settings.value(:manifest, environment) parser.file = file end parser.parse diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index 07bdff4bb..2399d1651 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -84,7 +84,7 @@ class Puppet::PGraph < GRATR::Digraph def matching_edges(events, base = nil) events.collect do |event| source = base || event.source - + unless vertex?(source) Puppet.warning "Got an event from invalid vertex %s" % source.ref next @@ -95,7 +95,7 @@ class Puppet::PGraph < GRATR::Digraph adjacent(source, :direction => :out, :type => :edges).find_all do |edge| edge.match?(event.event) end - end.flatten + end.compact.flatten end # Take container information from another graph and use it @@ -209,5 +209,3 @@ class Puppet::PGraph < GRATR::Digraph end end end - -# $Id$ diff --git a/lib/puppet/provider/maillist/mailman.rb b/lib/puppet/provider/maillist/mailman.rb index b556eecd7..903689178 100755 --- a/lib/puppet/provider/maillist/mailman.rb +++ b/lib/puppet/provider/maillist/mailman.rb @@ -101,9 +101,9 @@ Puppet::Type.type(:maillist).provide(:mailman) do # Pull the current state of the list from the full list. We're # getting some double entendre here.... def query - provider.class.instances.each do |list| + self.class.instances.each do |list| if list.name == self.name or list.name.downcase == self.name - return list.property_hash + return list.properties end end nil diff --git a/lib/puppet/provider/mount.rb b/lib/puppet/provider/mount.rb index 446ed641c..6a05d9826 100644 --- a/lib/puppet/provider/mount.rb +++ b/lib/puppet/provider/mount.rb @@ -50,5 +50,3 @@ module Puppet::Provider::Mount end end end - -# $Id$ diff --git a/lib/puppet/rails.rb b/lib/puppet/rails.rb index 670c622eb..53dafce79 100644 --- a/lib/puppet/rails.rb +++ b/lib/puppet/rails.rb @@ -9,7 +9,7 @@ module Puppet::Rails # This global init does not work for testing, because we remove # the state dir on every test. unless ActiveRecord::Base.connected? - Puppet.config.use(:main, :rails, :puppetmasterd) + Puppet.settings.use(:main, :rails, :puppetmasterd) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) begin @@ -103,7 +103,7 @@ module Puppet::Rails raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" end - Puppet.config.use(:puppetmasterd, :rails) + Puppet.settings.use(:puppetmasterd, :rails) begin ActiveRecord::Base.establish_connection(database_arguments()) diff --git a/lib/puppet/reference/configuration.rb b/lib/puppet/reference/configuration.rb index 65fdbeb7d..4b09002b8 100644 --- a/lib/puppet/reference/configuration.rb +++ b/lib/puppet/reference/configuration.rb @@ -1,6 +1,6 @@ config = Puppet::Util::Reference.newreference(:configuration, :depth => 1, :doc => "A reference for all configuration parameters") do docs = {} - Puppet.config.each do |name, object| + Puppet.settings.each do |name, object| docs[name] = object end diff --git a/lib/puppet/reports/rrdgraph.rb b/lib/puppet/reports/rrdgraph.rb index 7209a8401..1516d331a 100644 --- a/lib/puppet/reports/rrdgraph.rb +++ b/lib/puppet/reports/rrdgraph.rb @@ -102,7 +102,7 @@ Puppet::Network::Handler.report.newreport(:rrdgraph) do unless File.directory?(hostdir) and FileTest.writable?(hostdir) # Some hackishness to create the dir with all of the right modes and ownership - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new config.setdefaults(:reports, :hostdir => {:default => hostdir, :owner => Puppet[:user], :mode => 0755, :group => Puppet[:group], :desc => "eh"}) # This creates the dir. diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb index 8d6e11379..389de4e6e 100644 --- a/lib/puppet/reports/store.rb +++ b/lib/puppet/reports/store.rb @@ -1,7 +1,7 @@ require 'puppet' Puppet::Network::Handler.report.newreport(:store, :useyaml => true) do - Puppet.config.use(:reporting) + Puppet.settings.use(:reporting) desc "Store the yaml report on disk. Each host sends its report as a YAML dump and this just stores the file on disk, in the ``reportdir`` directory. @@ -11,9 +11,9 @@ Puppet::Network::Handler.report.newreport(:store, :useyaml => true) do default report)." def mkclientdir(client, dir) - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new config.setdefaults("reportclient-#{client}", - "clientdir-#{client}" => { :default => dir, + "client-#{client}-dir" => { :default => dir, :mode => 0750, :desc => "Client dir for %s" % client, :owner => Puppet[:user], diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index b62a6d2d3..8c65524ee 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -31,7 +31,7 @@ Puppet::Network::Handler.report.newreport(:tagmail) do " - Puppet.config.use(:tagmail) + Puppet.settings.use(:tagmail) # Find all matching messages. def match(taglists) diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb index 5e355f7fe..fd416dd5e 100644 --- a/lib/puppet/sslcertificates/ca.rb +++ b/lib/puppet/sslcertificates/ca.rb @@ -53,7 +53,7 @@ class Puppet::SSLCertificates::CA end def initialize(hash = {}) - Puppet.config.use(:main, :ca, :ssl) + Puppet.settings.use(:main, :ca, :ssl) self.setconfig(hash) if Puppet[:capass] @@ -73,7 +73,7 @@ class Puppet::SSLCertificates::CA self.getcert init_crl unless FileTest.exists?(@config[:serial]) - Puppet.config.write(:serial) do |f| + Puppet.settings.write(:serial) do |f| f << "%04X" % 1 end end @@ -85,7 +85,7 @@ class Puppet::SSLCertificates::CA 20.times { pass += (rand(74) + 48).chr } begin - Puppet.config.write(:capass) { |f| f.print pass } + Puppet.settings.write(:capass) { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, detail.to_s end @@ -163,10 +163,10 @@ class Puppet::SSLCertificates::CA Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do @cert = cert.mkselfsigned end - Puppet.config.write(:cacert) do |f| + Puppet.settings.write(:cacert) do |f| f.puts @cert.to_pem end - Puppet.config.write(:capub) do |f| + Puppet.settings.write(:capub) do |f| f.puts @cert.public_key end return cert @@ -201,7 +201,7 @@ class Puppet::SSLCertificates::CA # Take the Puppet config and store it locally. def setconfig(hash) @config = {} - Puppet.config.params("ca").each { |param| + Puppet.settings.params("ca").each { |param| param = param.intern if param.is_a? String if hash.include?(param) @config[param] = hash[param] @@ -303,7 +303,7 @@ class Puppet::SSLCertificates::CA raise Puppet::Error, "Certificate request for %s already exists" % host end - Puppet.config.writesub(:csrdir, csrfile) do |f| + Puppet.settings.writesub(:csrdir, csrfile) do |f| f.print csr.to_pem end end @@ -319,7 +319,7 @@ class Puppet::SSLCertificates::CA end Puppet::SSLCertificates::Inventory::add(cert) - Puppet.config.writesub(:signeddir, certfile) do |f| + Puppet.settings.writesub(:signeddir, certfile) do |f| f.print cert.to_pem end end @@ -389,7 +389,7 @@ class Puppet::SSLCertificates::CA @crl.next_update = now + 5 * 365*24*60*60 sign_with_key(@crl) - Puppet.config.write(:cacrl) do |f| + Puppet.settings.write(:cacrl) do |f| f.puts @crl.to_pem end end diff --git a/lib/puppet/sslcertificates/inventory.rb b/lib/puppet/sslcertificates/inventory.rb index d475c1c86..14d2155a8 100644 --- a/lib/puppet/sslcertificates/inventory.rb +++ b/lib/puppet/sslcertificates/inventory.rb @@ -11,7 +11,7 @@ module Puppet::SSLCertificates inited = false end - Puppet.config.write(:cert_inventory, "a") do |f| + Puppet.settings.write(:cert_inventory, "a") do |f| f.puts((inited ? nil : self.init).to_s + format(cert)) end end diff --git a/lib/puppet/sslcertificates/support.rb b/lib/puppet/sslcertificates/support.rb index e37a52038..c8d2e846b 100644 --- a/lib/puppet/sslcertificates/support.rb +++ b/lib/puppet/sslcertificates/support.rb @@ -44,7 +44,7 @@ module Puppet::SSLCertificates::Support unless instance_variable_get(var) unless cert = send(reader) cert = send(maker) - Puppet.config.write(param) { |f| f.puts cert.to_pem } + Puppet.settings.write(param) { |f| f.puts cert.to_pem } end instance_variable_set(var, cert) end @@ -59,7 +59,7 @@ module Puppet::SSLCertificates::Support # Our key meta programming can only handle one file, so we have # to separately write out the public key. - Puppet.config.write(:hostpubkey) do |f| + Puppet.settings.write(:hostpubkey) do |f| f.print key.public_key.to_pem end return key @@ -104,8 +104,8 @@ module Puppet::SSLCertificates::Support if cert.nil? or cert == "" return nil end - Puppet.config.write(:hostcert) do |f| f.print cert end - Puppet.config.write(:localcacert) do |f| f.print cacert end + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end #File.open(@certfile, "w", 0644) { |f| f.print cert } #File.open(@cacertfile, "w", 0644) { |f| f.print cacert } begin diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 46bac3058..4e2ab702f 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -6,10 +6,14 @@ require 'puppet/propertychange' module Puppet class Transaction - attr_accessor :component, :resources, :ignoreschedules, :ignoretags - attr_accessor :relgraph, :sorted_resources, :configurator + attr_accessor :component, :configuration, :ignoreschedules + attr_accessor :sorted_resources, :configurator + # The report, once generated. attr_reader :report + + # The list of events generated in this transaction. + attr_reader :events attr_writer :tags @@ -22,13 +26,14 @@ class Transaction end end - # Check to see if we should actually allow deleition. + # Check to see if we should actually allow processing, but this really only + # matters when a resource is getting deleted. def allow_processing?(resource, changes) # If a resource is going to be deleted but it still has # dependencies, then don't delete it unless it's implicit or the # dependency is itself being deleted. if resource.purging? and resource.deleting? - if deps = @relgraph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? } + if deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? } resource.warning "%s still depend%s on me -- not purging" % [deps.collect { |r| r.ref }.join(","), deps.length > 1 ? "":"s"] return false @@ -129,6 +134,9 @@ class Transaction # Find all of the changed resources. def changed? @changes.find_all { |change| change.changed }.collect { |change| + unless change.property.resource + raise "No resource for %s" % change.inspect + end change.property.resource }.uniq end @@ -137,14 +145,8 @@ class Transaction # contained resources might never get cleaned up. def cleanup if defined? @generated - @generated.each do |resource| - resource.remove - end + relationship_graph.remove_resource(*@generated) end - if defined? @relgraph - @relgraph.clear - end - @resources.clear end # Copy an important relationships from the parent to the newly-generated @@ -158,10 +160,11 @@ class Transaction else edge = [resource, gen_child] end - unless @relgraph.edge?(edge[1], edge[0]) - @relgraph.add_edge!(*edge) + relationship_graph.add_resource(gen_child) unless relationship_graph.resource(gen_child.ref) + + unless relationship_graph.edge?(edge[1], edge[0]) + relationship_graph.add_edge!(*edge) else - @relgraph.add_vertex!(gen_child) resource.debug "Skipping automatic relationship to %s" % gen_child end end @@ -192,7 +195,8 @@ class Transaction children.each do |child| child.finish # Make sure that the vertex is in the relationship graph. - @relgraph.add_vertex!(child) + relationship_graph.add_resource(child) unless relationship_graph.resource(child.ref) + child.configuration = relationship_graph end @generated += children return children @@ -265,9 +269,12 @@ class Transaction # Collect the targets of any subscriptions to those events. We pass # the parent resource in so it will override the source in the events, # since eval_generated children can't have direct relationships. - @relgraph.matching_edges(events, resource).each do |edge| - edge = edge.dup - label = edge.label + relationship_graph.matching_edges(events, resource).each do |orig_edge| + # We have to dup the label here, else we modify the original edge label, + # which affects whether a given event will match on the next run, which is, + # of course, bad. + edge = orig_edge.class.new(orig_edge.source, orig_edge.target) + label = orig_edge.label.dup label[:event] = events.collect { |e| e.event } edge.label = label set_trigger(edge) @@ -283,8 +290,6 @@ class Transaction def evaluate @count = 0 - graph(@resources, :resources) - # Start logging. Puppet::Util::Log.newdestination(@report) @@ -314,6 +319,7 @@ class Transaction Puppet.debug "Finishing transaction %s with %s changes" % [self.object_id, @count] + @events = allevents allevents end @@ -333,7 +339,7 @@ class Transaction # enough to check the immediate dependencies, which is why we use # a tree from the reversed graph. skip = false - deps = @relgraph.dependencies(resource) + deps = relationship_graph.dependencies(resource) deps.each do |dep| if fails = failed?(dep) resource.notice "Dependency %s[%s] has %s failures" % @@ -347,7 +353,7 @@ class Transaction # Collect any dynamically generated resources. def generate - list = @resources.vertices + list = @configuration.vertices # Store a list of all generated resources, so that we can clean them up # after the transaction closes. @@ -369,7 +375,8 @@ class Transaction end made.uniq! made.each do |res| - @resources.add_vertex!(res) + @configuration.add_resource(res) + res.configuration = configuration newlist << res @generated << res res.finish @@ -410,32 +417,24 @@ class Transaction return @report end - # Produce the graph files if requested. - def graph(gr, name) - # We don't want to graph the configuration process. - return if self.configurator - - return unless Puppet[:graph] - - Puppet.config.use(:graphing) - - file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) - File.open(file, "w") { |f| - f.puts gr.to_dot("name" => name.to_s.capitalize) - } + # Should we ignore tags? + def ignore_tags? + ! @configuration.host_config? end # this should only be called by a Puppet::Type::Component resource now # and it should only receive an array def initialize(resources) - if resources.is_a?(Puppet::PGraph) - @resources = resources + if resources.is_a?(Puppet::Node::Configuration) + @configuration = resources + elsif resources.is_a?(Puppet::PGraph) + raise "Transactions should get configurations now, not PGraph" else - @resources = resources.to_graph + raise "Transactions require configurations" end @resourcemetrics = { - :total => @resources.vertices.length, + :total => @configuration.vertices.length, :out_of_sync => 0, # The number of resources that had changes :applied => 0, # The number of resources fixed :skipped => 0, # The number of resources skipped @@ -474,7 +473,7 @@ class Transaction # types, just providers. def prefetch prefetchers = {} - @resources.each do |resource| + @configuration.each do |resource| if provider = resource.provider and provider.class.respond_to?(:prefetch) prefetchers[provider.class] ||= {} prefetchers[provider.class][resource.title] = resource @@ -501,48 +500,49 @@ class Transaction # Now add any dynamically generated resources generate() - - # Create a relationship graph from our resource graph - @relgraph = relationship_graph # This will throw an error if there are cycles in the graph. - @sorted_resources = @relgraph.topsort + @sorted_resources = relationship_graph.topsort end - - # Create a graph of all of the relationships in our resource graph. + def relationship_graph - graph = Puppet::PGraph.new - - # First create the dependency graph - @resources.vertices.each do |vertex| - graph.add_vertex!(vertex) - vertex.builddepends.each do |edge| - graph.add_edge!(edge) - end + configuration.relationship_graph + end + + # Send off the transaction report. + def send_report + begin + report = generate_report() + rescue => detail + Puppet.err "Could not generate report: %s" % detail + return + end + + if Puppet[:rrdgraph] == true + report.graph() + end + + if Puppet[:summarize] + puts report.summary end - # Lastly, add in any autorequires - graph.vertices.each do |vertex| - vertex.autorequire.each do |edge| - unless graph.edge?(edge) - unless graph.edge?(edge.target, edge.source) - vertex.debug "Autorequiring %s" % [edge.source] - graph.add_edge!(edge) - else - vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) - end - end + if Puppet[:report] + begin + reportclient().report(report) + rescue => detail + Puppet.err "Reporting failed: %s" % detail end end - - graph(graph, :relationships) - - # Then splice in the container information - graph.splice!(@resources, Puppet::Type::Component) + end - graph(graph, :expanded_relationships) - - return graph + def reportclient + unless defined? @reportclient + @reportclient = Puppet::Network::Client.report.new( + :Server => Puppet[:reportserver] + ) + end + + @reportclient end # Roll all completed changes back. @@ -572,7 +572,7 @@ class Transaction end # FIXME This won't work right now. - @relgraph.matching_edges(events).each do |edge| + relationship_graph.matching_edges(events).each do |edge| @targets[edge.target] << edge end @@ -606,7 +606,7 @@ class Transaction # Should this resource be skipped? def skip?(resource) skip = false - if ! tagged?(resource) + if missing_tags?(resource) resource.debug "Not tagged with %s" % tags.join(", ") elsif ! scheduled?(resource) resource.debug "Not scheduled" @@ -620,29 +620,22 @@ class Transaction # The tags we should be checking. def tags - # Allow the tags to be overridden unless defined? @tags - @tags = Puppet[:tags] - end - - unless defined? @processed_tags - if @tags.nil? or @tags == "" + tags = Puppet[:tags] + if tags.nil? or tags == "" @tags = [] else - @tags = [@tags] unless @tags.is_a? Array - @tags = @tags.collect do |tag| - tag.split(/\s*,\s*/) - end.flatten + @tags = tags.split(/\s*,\s*/) end - @processed_tags = true end @tags end # Is this resource tagged appropriately? - def tagged?(resource) - self.ignoretags or tags.empty? or resource.tagged?(tags) + def missing_tags?(resource) + return false if self.ignore_tags? or tags.empty? + return true unless resource.tagged?(tags) end # Are there any edges that target this resource? diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index aa7eb92f7..ecd179ed7 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -60,7 +60,18 @@ module Puppet instance_variables end - def to_type(parent = nil) + def to_ref + unless defined? @ref + if self.type and self.name + @ref = "%s[%s]" % [self.type, self.name] + else + @ref = nil + end + end + @ref + end + + def to_type retobj = nil if typeklass = Puppet::Type.type(self.type) # FIXME This should really be done differently, but... @@ -77,10 +88,6 @@ module Puppet raise Puppet::Error.new("Could not find object type %s" % self.type) end - if parent - parent.push retobj - end - return retobj end end @@ -167,12 +174,7 @@ module Puppet end end - str = nil - if self.top - str = "%s" - else - str = "#{@keyword} #{@type} {\n%s\n}" - end + str = "#{@keyword} #{@name} {\n%s\n}" str % @children.collect { |child| child.to_manifest }.collect { |str| @@ -188,7 +190,44 @@ module Puppet instance_variables end - def to_type(parent = nil) + # Create a resource graph from our structure. + def to_configuration + configuration = Puppet::Node::Configuration.new(Facter.value("hostname")) do |config| + delver = proc do |obj| + unless container = config.resource(obj.to_ref) + container = obj.to_type + config.add_resource container + end + obj.each do |child| + unless resource = config.resource(child.to_ref) + next unless resource = child.to_type + config.add_resource resource + end + config.add_edge!(container, resource) + if child.is_a?(self.class) + delver.call(child) + end + end + end + + delver.call(self) + end + + return configuration + end + + def to_ref + unless defined? @ref + if self.type and self.name + @ref = "%s[%s]" % [self.type, self.name] + else + @ref = nil + end + end + @ref + end + + def to_type # this container will contain the equivalent of all objects at # this level #container = Puppet::Component.new(:name => @name, :type => @type) @@ -235,45 +274,16 @@ module Puppet #Puppet.debug "%s[%s] has no parameters" % [@type, @name] end - #if parent - # hash[:parent] = parent - #end container = Puppet::Type::Component.create(hash) end #Puppet.info container.inspect - if parent - parent.push container - end - # unless we successfully created the container, return an error unless container Puppet.warning "Got no container back" return nil end - self.each { |child| - # the fact that we descend here means that we are - # always going to execute depth-first - # which is _probably_ a good thing, but one never knows... - unless child.is_a?(Puppet::TransBucket) or - child.is_a?(Puppet::TransObject) - raise Puppet::DevError, - "TransBucket#to_type cannot handle objects of type %s" % - child.class - end - - # Now just call to_type on them with the container as a parent - begin - child.to_type(container) - rescue => detail - if Puppet[:trace] and ! detail.is_a?(Puppet::Error) - puts detail.backtrace - end - Puppet.err detail.to_s - end - } - # at this point, no objects at are level are still Transportable # objects return container @@ -287,7 +297,5 @@ module Puppet end end - #------------------------------------------------------------ end -# $Id$ diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index b4b6e3b18..19f676ff4 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -44,7 +44,9 @@ class Type # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. attr_accessor :file, :line - attr_reader :parent + + # The configuration that this resource is stored in. + attr_accessor :configuration attr_writer :title attr_writer :noop @@ -310,6 +312,25 @@ class Type return self[:name] end + # Look up our parent in the configuration, if we have one. + def parent + return nil unless configuration + + # This is kinda weird. + if implicit? + parents = configuration.relationship_graph.adjacent(self, :direction => :in) + else + parents = configuration.adjacent(self, :direction => :in) + end + if parents + # We should never have more than one parent, so let's just ignore + # it if we happen to. + return parents.shift + else + return nil + end + end + # Return the "type[name]" style reference. def ref "%s[%s]" % [self.class.name.to_s.capitalize, self.title] diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 5905d85ab..c5842e0e7 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -52,6 +52,7 @@ Puppet::Type.newtype(:component) do # this is only called on one component over the whole system # this also won't work with scheduling, but eh def evaluate + raise "Component#evaluate is deprecated" self.finalize unless self.finalized? transaction = Puppet::Transaction.new(self) transaction.component = self @@ -127,27 +128,6 @@ Puppet::Type.newtype(:component) do return false end end - - def push(*childs) - unless defined? @children - @children = [] - end - childs.each { |child| - # Make sure we don't have any loops here. - if parentof?(child) - devfail "Already the parent of %s[%s]" % [child.class.name, child.title] - end - unless child.is_a?(Puppet::Type) - self.debug "Got object of type %s" % child.class - self.devfail( - "Containers can only contain Puppet resources, not %s" % - child.class - ) - end - @children.push(child) - child.parent = self - } - end # Component paths are special because they function as containers. def pathbuilder @@ -162,30 +142,15 @@ Puppet::Type.newtype(:component) do else myname = self.title end - if self.parent - return [@parent.pathbuilder, myname] + if p = self.parent + return [p.pathbuilder, myname] else return [myname] end end - # Remove an object. The argument determines whether the object's - # subscriptions get eliminated, too. - def remove(rmdeps = true) - # Our children remove themselves from our @children array (else the object - # we called this on at the top would not be removed), so we duplicate the - # array and iterate over that. If we don't do this, only half of the - # objects get removed. - @children.dup.each { |child| - child.remove(rmdeps) - } - - @children.clear - - # Get rid of params and provider, too. - super - - @parent = nil + def ref + title end # We have a different way of setting the title @@ -203,30 +168,12 @@ Puppet::Type.newtype(:component) do end def refresh - @children.collect { |child| + configuration.adjacent(self).each do |child| if child.respond_to?(:refresh) child.refresh child.log "triggering %s" % :refresh end - } - end - - # Convert to a graph object with all of the container info. - def to_graph - graph = Puppet::PGraph.new - - delver = proc do |obj| - obj.each do |child| - graph.add_edge!(obj, child) - if child.is_a?(self.class) - delver.call(child) - end - end end - - delver.call(self) - - return graph end def to_s @@ -237,5 +184,3 @@ Puppet::Type.newtype(:component) do end end end - -# $Id$ diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 99b5a7435..5bf9fef78 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -574,6 +574,9 @@ module Puppet # Create a new file or directory object as a child to the current # object. def newchild(path, local, hash = {}) + unless configuration + raise Puppet::DevError, "File recursion cannot happen without a configuration" + end # make local copy of arguments args = symbolize_options(@arghash) @@ -608,14 +611,14 @@ module Puppet } child = nil - klass = self.class - + # The child might already exist because 'localrecurse' runs # before 'sourcerecurse'. I could push the override stuff into # a separate method or something, but the work is the same other # than this last bit, so it doesn't really make sense. - if child = klass[path] + if child = configuration.resource(:file, path) unless child.parent.object_id == self.object_id + puts("Parent is %s, I am %s" % [child.parent.ref, self.ref]) if child.parent self.debug "Not managing more explicit file %s" % path return nil @@ -640,28 +643,22 @@ module Puppet #notice "Creating new file with args %s" % args.inspect args[:parent] = self begin - child = klass.implicitcreate(args) - - # implicit creation can return nil - if child.nil? - return nil - end - rescue Puppet::Error => detail - self.notice( - "Cannot manage: %s" % - [detail.message] - ) - self.debug args.inspect - child = nil + # This method is used by subclasses of :file, so use the class name rather than hard-coding + # :file. + return nil unless child = configuration.create_implicit_resource(self.class.name, args) rescue => detail - self.notice( - "Cannot manage: %s" % - [detail] - ) - self.debug args.inspect - child = nil + puts detail.backtrace + self.notice "Cannot manage: %s" % [detail] + return nil end end + + # LAK:FIXME This shouldn't be necessary, but as long as we're + # modeling the relationship graph specifically, it is. + configuration.relationship_graph.add_edge! self, child + unless child.parent + raise "Did not set parent of %s" % child.ref + end return child end @@ -669,12 +666,14 @@ module Puppet # path names, rather than including the full parent's title each # time. def pathbuilder - if defined? @parent + # We specifically need to call the method here, so it looks + # up our parent in the configuration graph. + if parent = parent() # We only need to behave specially when our parent is also # a file - if @parent.is_a?(self.class) + if parent.is_a?(self.class) # Remove the parent file name - list = @parent.pathbuilder + list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else @@ -695,9 +694,7 @@ module Puppet # files. def recurse # are we at the end of the recursion? - unless self.recurse? - return - end + return unless self.recurse? recurse = self[:recurse] # we might have a string, rather than a number diff --git a/lib/puppet/util/feature.rb b/lib/puppet/util/feature.rb index 30c38e286..2669d1ab1 100644 --- a/lib/puppet/util/feature.rb +++ b/lib/puppet/util/feature.rb @@ -16,8 +16,7 @@ class Puppet::Util::Feature if self.class.respond_to?(method) raise ArgumentError, "Feature %s is already defined" % name end - - result = true + if block_given? begin result = yield @@ -25,33 +24,21 @@ class Puppet::Util::Feature warn "Failed to load feature test for %s: %s" % [name, detail] result = false end - end - - if ary = options[:libs] - ary = [ary] unless ary.is_a?(Array) - - ary.each do |lib| - unless lib.is_a?(String) - raise ArgumentError, "Libraries must be passed as strings not %s" % lib.class - end - - begin - require lib - rescue Exception - Puppet.debug "Failed to load library '%s' for feature '%s'" % [lib, name] - result = false - end - end + @results[name] = result end meta_def(method) do - result + unless @results.include?(name) + @results[name] = test(name, options) + end + @results[name] end end # Create a new feature collection. def initialize(path) @path = path + @results = {} @loader = Puppet::Util::Autoload.new(self, @path) end @@ -71,6 +58,28 @@ class Puppet::Util::Feature return false end end -end -# $Id$ + # Actually test whether the feature is present. We only want to test when + # someone asks for the feature, so we don't unnecessarily load + # files. + def test(name, options) + result = true + if ary = options[:libs] + ary = [ary] unless ary.is_a?(Array) + + ary.each do |lib| + unless lib.is_a?(String) + raise ArgumentError, "Libraries must be passed as strings not %s" % lib.class + end + + begin + require lib + rescue Exception + Puppet.debug "Failed to load library '%s' for feature '%s'" % [lib, name] + result = false + end + end + end + result + end +end diff --git a/lib/puppet/util/instance_loader.rb b/lib/puppet/util/instance_loader.rb index 1a64c9c69..f280014eb 100755 --- a/lib/puppet/util/instance_loader.rb +++ b/lib/puppet/util/instance_loader.rb @@ -5,6 +5,12 @@ require 'puppet/util' # of Puppet::Util::Autoload module Puppet::Util::InstanceLoader include Puppet::Util + + # Are we instance-loading this type? + def instance_loading?(type) + defined?(@autoloaders) and @autoloaders.include?(symbolize(type)) + end + # Define a new type of autoloading. def instance_load(type, path, options = {}) @autoloaders ||= {} @@ -54,7 +60,7 @@ module Puppet::Util::InstanceLoader # Retrieve an alread-loaded instance, or attempt to load our instance. def loaded_instance(type, name) name = symbolize(name) - instances = instance_hash(type) + return nil unless instances = instance_hash(type) unless instances.include? name if instance_loader(type).load(name) unless instances.include? name @@ -70,5 +76,3 @@ module Puppet::Util::InstanceLoader instances[name] end end - -# $Id$ diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb index 13bbc2af1..133aa9c2a 100644 --- a/lib/puppet/util/metric.rb +++ b/lib/puppet/util/metric.rb @@ -21,7 +21,7 @@ class Puppet::Util::Metric end def create(start = nil) - Puppet.config.use(:metrics) + Puppet.settings.use(:metrics) start ||= Time.now.to_i - 5 diff --git a/lib/puppet/util/config.rb b/lib/puppet/util/settings.rb index 9cdb4cfe3..1478cd8a5 100644 --- a/lib/puppet/util/config.rb +++ b/lib/puppet/util/settings.rb @@ -5,7 +5,7 @@ require 'getoptlong' # The class for handling configuration files. -class Puppet::Util::Config +class Puppet::Util::Settings include Enumerable include Puppet::Util @@ -45,15 +45,17 @@ class Puppet::Util::Config end # A simplified equality operator. - def ==(other) - self.each { |myname, myobj| - unless other[myname] == value(myname) - return false - end - } - - return true - end + # LAK: For some reason, this causes mocha to not be able to mock + # the 'value' method, and it's not used anywhere. +# def ==(other) +# self.each { |myname, myobj| +# unless other[myname] == value(myname) +# return false +# end +# } +# +# return true +# end # Generate the list of valid arguments, in a format that GetoptLong can # understand, and add them to the passed option list. @@ -69,14 +71,14 @@ class Puppet::Util::Config return options end - # Turn the config into a transaction and apply it + # Turn the config into a Puppet configuration and apply it def apply trans = self.to_transportable begin - comp = trans.to_type - trans = comp.evaluate - trans.evaluate - comp.remove + config = trans.to_configuration + config.store_state = false + config.apply + config.clear rescue => detail if Puppet[:trace] puts detail.backtrace @@ -476,57 +478,15 @@ class Puppet::Util::Config end # Convert a single section into transportable objects. - def section_to_transportable(section, done = nil, includefiles = true) + def section_to_transportable(section, done = nil) done ||= Hash.new { |hash, key| hash[key] = {} } objects = [] persection(section) do |obj| if @config[:mkusers] and value(:mkusers) - [:owner, :group].each do |attr| - type = nil - if attr == :owner - type = :user - else - type = attr - end - # If a user and/or group is set, then make sure we're - # managing that object - if obj.respond_to? attr and name = obj.send(attr) - # Skip root or wheel - next if %w{root wheel}.include?(name.to_s) - - # Skip owners and groups we've already done, but tag - # them with our section if necessary - if done[type].include?(name) - tags = done[type][name].tags - unless tags.include?(section) - done[type][name].tags = tags << section - end - elsif newobj = Puppet::Type.type(type)[name] - unless newobj.property(:ensure) - newobj[:ensure] = "present" - end - newobj.tag(section) - if type == :user - newobj[:comment] ||= "%s user" % name - end - else - newobj = Puppet::TransObject.new(name, type.to_s) - newobj.tags = ["puppet", "configuration", section] - newobj[:ensure] = "present" - if type == :user - newobj[:comment] ||= "%s user" % name - end - # Set the group appropriately for the user - if type == :user - newobj[:gid] = Puppet[:group] - end - done[type][name] = newobj - objects << newobj - end - end - end + objects += add_user_resources(section, obj, done) end + # Only files are convertable to transportable resources. if obj.respond_to? :to_transportable next if value(obj.name) =~ /^\/dev/ transobjects = obj.to_transportable @@ -544,7 +504,8 @@ class Puppet::Util::Config end bucket = Puppet::TransBucket.new - bucket.type = section + bucket.type = "Settings" + bucket.name = section bucket.push(*objects) bucket.keyword = "class" @@ -596,9 +557,9 @@ class Puppet::Util::Config end # Convert our list of objects into a component that can be applied. - def to_component + def to_configuration transport = self.to_transportable - return transport.to_type + return transport.to_configuration end # Convert our list of objects into a configuration file. @@ -634,7 +595,7 @@ Generated on #{Time.now}. end # Convert our configuration into a list of transportable objects. - def to_transportable + def to_transportable(*sections) done = Hash.new { |hash, key| hash[key] = {} } @@ -643,14 +604,20 @@ Generated on #{Time.now}. if defined? @file.file and @file.file topbucket.name = @file.file else - topbucket.name = "configtop" + topbucket.name = "top" end - topbucket.type = "puppetconfig" + topbucket.type = "Settings" topbucket.top = true # Now iterate over each section - eachsection do |section| - topbucket.push section_to_transportable(section, done) + if sections.empty? + eachsection do |section| + sections << section + end + end + sections.each do |section| + obj = section_to_transportable(section, done) + topbucket.push obj end topbucket @@ -683,37 +650,31 @@ Generated on #{Time.now}. } return if runners.empty? - bucket = Puppet::TransBucket.new - bucket.type = "puppetconfig" - bucket.top = true - - # Create a hash to keep track of what we've done so far. - @done = Hash.new { |hash, key| hash[key] = {} } - runners.each do |section| - bucket.push section_to_transportable(section, @done, false) - end - - objects = bucket.to_type - - objects.finalize - tags = nil - if Puppet[:tags] - tags = Puppet[:tags] - Puppet[:tags] = "" - end - trans = objects.evaluate - trans.ignoretags = true - trans.configurator = true - trans.evaluate - if tags - Puppet[:tags] = tags - end - - # Remove is a recursive process, so it's sufficient to just call - # it on the component. - objects.remove(true) - - objects = nil + bucket = to_transportable(*sections) + + config = bucket.to_configuration + config.host_config = false + config.apply + config.clear + +# tags = nil +# if Puppet[:tags] +# tags = Puppet[:tags] +# Puppet[:tags] = "" +# end +# trans = objects.evaluate +# trans.ignoretags = true +# trans.configurator = true +# trans.evaluate +# if tags +# Puppet[:tags] = tags +# end +# +# # Remove is a recursive process, so it's sufficient to just call +# # it on the component. +# objects.remove(true) +# +# objects = nil runners.each { |s| @used << s } end @@ -845,6 +806,48 @@ Generated on #{Time.now}. private + # Create the transportable objects for users and groups. + def add_user_resources(section, obj, done) + resources = [] + [:owner, :group].each do |attr| + type = nil + if attr == :owner + type = :user + else + type = attr + end + # If a user and/or group is set, then make sure we're + # managing that object + if obj.respond_to? attr and name = obj.send(attr) + # Skip root or wheel + next if %w{root wheel}.include?(name.to_s) + + # Skip owners and groups we've already done, but tag + # them with our section if necessary + if done[type].include?(name) + tags = done[type][name].tags + unless tags.include?(section) + done[type][name].tags = tags << section + end + else + newobj = Puppet::TransObject.new(name, type.to_s) + newobj.tags = ["puppet", "configuration", section] + newobj[:ensure] = :present + if type == :user + newobj[:comment] ||= "%s user" % name + end + # Set the group appropriately for the user + if type == :user + newobj[:gid] = Puppet[:group] + end + done[type][name] = newobj + resources << newobj + end + end + end + resources + end + # Extract extra setting information for files. def extract_fileinfo(string) result = {} @@ -1105,6 +1108,11 @@ Generated on #{Time.now}. # Set the type appropriately. Yep, a hack. This supports either naming # the variable 'dir', or adding a slash at the end. def munge(value) + # If it's not a fully qualified path... + if value.is_a?(String) and value !~ /^\$/ and value !~ /^\// + # Make it one + value = File.join(Dir.getwd, value) + end if value.to_s =~ /\/$/ @type = :directory return value.sub(/\/$/, '') @@ -1127,9 +1135,6 @@ Generated on #{Time.now}. end # Convert the object to a TransObject instance. - # FIXME There's no dependency system in place right now; if you use - # a section that requires another section, there's nothing done to - # correct that for you, at the moment. def to_transportable type = self.type return nil unless type @@ -1138,9 +1143,9 @@ Generated on #{Time.now}. objects = [] path = self.value - unless path =~ /^#{File::SEPARATOR}/ - path = File.join(Dir.getwd, path) - end + + # Skip plain files that don't exist, since we won't be managing them anyway. + return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) or self.create obj = Puppet::TransObject.new(path, "file") # Only create directories, or files that are specifically marked to @@ -1157,7 +1162,7 @@ Generated on #{Time.now}. } # Only chown or chgrp when root - if Puppet::Util::SUIDManager.uid == 0 + if Puppet.features.root? [:group, :owner].each { |var| if value = self.send(var) obj[var] = value @@ -1187,7 +1192,7 @@ Generated on #{Time.now}. name = $1 unless @parent.include?(name) raise ArgumentError, - "Configuration parameter '%s' is undefined" % + "Settings parameter '%s' is undefined" % name end } @@ -1218,5 +1223,3 @@ Generated on #{Time.now}. end end end - -# $Id$ diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index a10183615..9e99057d9 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -46,7 +46,7 @@ class Puppet::Util::Storage self.init def self.load - Puppet.config.use(:main) + Puppet.settings.use(:main) unless File.exists?(Puppet[:statefile]) unless defined? @@state and ! @@state.nil? @@ -99,5 +99,3 @@ class Puppet::Util::Storage end end end - -# $Id$ |
