summaryrefslogtreecommitdiffstats
path: root/lib/puppet
diff options
context:
space:
mode:
Diffstat (limited to 'lib/puppet')
-rw-r--r--lib/puppet/defaults.rb2
-rw-r--r--lib/puppet/indirector.rb109
-rw-r--r--lib/puppet/indirector/facts/yaml.rb41
-rw-r--r--lib/puppet/indirector/node/external.rb51
-rw-r--r--lib/puppet/indirector/node/ldap.rb138
-rw-r--r--lib/puppet/indirector/node/none.rb10
-rw-r--r--lib/puppet/node.rb121
-rwxr-xr-xlib/puppet/node/facts.rb36
-rwxr-xr-xlib/puppet/util/instance_loader.rb2
9 files changed, 482 insertions, 28 deletions
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 78364e786..4b442d094 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -495,7 +495,7 @@ module Puppet
)
self.setdefaults(:facts,
- :factstore => ["yaml",
+ :fact_store => ["yaml",
"The backend store to use for client facts."]
)
diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb
index 0ba538355..b8690d7d5 100644
--- a/lib/puppet/indirector.rb
+++ b/lib/puppet/indirector.rb
@@ -1,25 +1,55 @@
# 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
+ # LAK:FIXME We need to figure out how to handle documentation for the
+ # different indirection types.
+
+ # A simple class that can function as the base class for indirected types.
+ class Terminus
+ require 'puppet/util/docs'
+ extend Puppet::Util::Docs
+ end
+
+ # This handles creating the terminus classes.
+ require 'puppet/util/classgen'
+ extend Puppet::Util::ClassGen
+
# 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
+ extend Puppet::Util::InstanceLoader
+
+ # Register a given indirection type. The classes including this module
+ # handle creating terminus instances, but the module itself handles
+ # loading them and managing the classes.
+ def self.register_indirection(name)
+ # Set up autoloading of the appropriate termini.
+ instance_load name, "puppet/indirector/%s" % name
+ end
# 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)
+ # Note that the termini are being registered on the Indirector module, not
+ # on the classes including the module. This allows a given indirection to
+ # be used in multiple classes.
+ def self.register_terminus(indirection, terminus, options = {}, &block)
+ genclass(terminus,
+ :prefix => indirection.to_s.capitalize,
+ :hash => instance_hash(indirection),
+ :attributes => options,
+ :block => block,
+ :parent => options[:parent] || Terminus
+ )
end
# Retrieve a terminus class by indirection and name.
- def terminus(name)
- loaded_instance(name)
+ def self.terminus(indirection, terminus)
+ loaded_instance(indirection, terminus)
end
# Declare that the including class indirects its methods to
@@ -27,12 +57,27 @@ module Puppet::Indirector
# 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]
+ # Options are:
+ # +:to+: What parameter to use as the name of the indirection terminus.
+ def indirects(indirection, options = {})
+ if defined?(@indirection)
+ raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection[:name], indirection]
+ end
+ options[:name] = indirection
+ @indirection = options
+ # Validate the parameter. This requires that indirecting
+ # classes require 'puppet/defaults', because of ordering issues,
+ # but it makes problems much easier to debug.
+ if param_name = options[:to]
+ begin
+ name = Puppet[param_name]
+ rescue
+ raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, indirection]
+ end
+ end
# Set up autoloading of the appropriate termini.
- autoload "puppet/indirector/%s" % indirection
+ Puppet::Indirector.register_indirection indirection
end
# Define methods for each of the HTTP methods. These just point to the
@@ -47,30 +92,46 @@ module Puppet::Indirector
# 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
+ redirect(method_name, *args)
end
end
private
+
# Create a new terminus instance.
- def make_terminus(indirection)
+ def make_terminus(name)
# 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]
+ unless klass = Puppet::Indirector.terminus(@indirection[:name], name)
+ raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, indirection]
end
return klass.new
end
+ # Redirect a given HTTP method.
+ def redirect(method_name, *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
+
# Return the singleton terminus for this indirection.
- def terminus
- unless terminus = @termini[indirection.name]
- terminus = @termini[indirection.name] = make_terminus(indirection)
+ def terminus(name = nil)
+ @termini ||= {}
+ # Get the name of the terminus.
+ unless name
+ unless param_name = @indirection[:to]
+ raise ArgumentError, "You must specify an indirection terminus for indirection %s" % @indirection[:name]
+ end
+ name = Puppet[param_name]
+ name = name.intern if name.is_a?(String)
+ end
+
+ unless @termini[name]
+ @termini[name] = make_terminus(name)
end
- terminus
+ @termini[name]
end
end
diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb
new file mode 100644
index 000000000..87860012f
--- /dev/null
+++ b/lib/puppet/indirector/facts/yaml.rb
@@ -0,0 +1,41 @@
+Puppet::Indirector.register_terminus :facts, :yaml do
+ desc "Store client facts as flat files, serialized using YAML."
+
+ # Get a client's facts.
+ def get(node)
+ file = path(node)
+
+ return nil unless FileTest.exists?(file)
+
+ begin
+ values = YAML::load(File.read(file))
+ rescue => detail
+ Puppet.err "Could not load facts for %s: %s" % [node, detail]
+ end
+
+ Puppet::Node::Facts.new(node, values)
+ end
+
+ def initialize
+ Puppet.config.use(:yamlfacts)
+ end
+
+ # Store the facts to disk.
+ def put(facts)
+ File.open(path(facts.name), "w", 0600) do |f|
+ begin
+ f.print YAML::dump(facts.values)
+ rescue => detail
+ Puppet.err "Could not write facts for %s: %s" % [facts.name, detail]
+ end
+ end
+ nil
+ end
+
+ private
+
+ # Return the path to a given node's file.
+ def path(name)
+ File.join(Puppet[:yamlfactdir], name + ".yaml")
+ end
+end
diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb
new file mode 100644
index 000000000..70fc2505a
--- /dev/null
+++ b/lib/puppet/indirector/node/external.rb
@@ -0,0 +1,51 @@
+Puppet::Indirector.register_terminus :node, :external, :fact_merge => true do
+ desc "Call an external program to get node information."
+
+ include Puppet::Util
+ # Look for external node definitions.
+ def get(name)
+ return nil unless Puppet[:external_nodes] != "none"
+
+ # This is a very cheap way to do this, since it will break on
+ # commands that have spaces in the arguments. But it's good
+ # enough for most cases.
+ external_node_command = Puppet[:external_nodes].split
+ external_node_command << name
+ begin
+ output = Puppet::Util.execute(external_node_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 external node source" % name
+ return nil
+ end
+
+ begin
+ result = 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
+
+ node = newnode(name)
+ set = false
+ [:parameters, :classes].each do |param|
+ if value = result[param]
+ node.send(param.to_s + "=", value)
+ set = true
+ end
+ end
+
+ if set
+ return node
+ else
+ return nil
+ end
+ end
+end
diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb
new file mode 100644
index 000000000..75a912568
--- /dev/null
+++ b/lib/puppet/indirector/node/ldap.rb
@@ -0,0 +1,138 @@
+Puppet::Indirector.register_terminus :node, :ldap, :fact_merge => true do
+ desc "Search in LDAP for node configuration information."
+
+ # Look for our node in ldap.
+ def get(node)
+ unless ary = ldapsearch(node)
+ return nil
+ end
+ parent, classes, parameters = ary
+
+ while parent
+ parent, tmpclasses, tmpparams = ldapsearch(parent)
+ classes += tmpclasses if tmpclasses
+ tmpparams.each do |param, value|
+ # Specifically test for whether it's set, so false values are handled
+ # correctly.
+ parameters[param] = value unless parameters.include?(param)
+ end
+ end
+
+ return newnode(node, :classes => classes, :source => "ldap", :parameters => parameters)
+ end
+
+ # Find the ldap node, return the class list and parent node specially,
+ # and everything else in a parameter hash.
+ def ldapsearch(node)
+ filter = Puppet[:ldapstring]
+ classattrs = Puppet[:ldapclassattrs].split("\s*,\s*")
+ if Puppet[:ldapattrs] == "all"
+ # A nil value here causes all attributes to be returned.
+ search_attrs = nil
+ else
+ search_attrs = classattrs + Puppet[:ldapattrs].split("\s*,\s*")
+ end
+ pattr = nil
+ if pattr = Puppet[:ldapparentattr]
+ if pattr == ""
+ pattr = nil
+ else
+ search_attrs << pattr unless search_attrs.nil?
+ end
+ end
+
+ if filter =~ /%s/
+ filter = filter.gsub(/%s/, node)
+ end
+
+ parent = nil
+ classes = []
+ parameters = nil
+
+ found = false
+ count = 0
+
+ begin
+ # We're always doing a sub here; oh well.
+ ldap.search(Puppet[:ldapbase], 2, filter, search_attrs) do |entry|
+ found = true
+ if pattr
+ if values = entry.vals(pattr)
+ if values.length > 1
+ raise Puppet::Error,
+ "Node %s has more than one parent: %s" %
+ [node, values.inspect]
+ end
+ unless values.empty?
+ parent = values.shift
+ end
+ end
+ end
+
+ classattrs.each { |attr|
+ if values = entry.vals(attr)
+ values.each do |v| classes << v end
+ end
+ }
+
+ 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
+ end
+ rescue => detail
+ if count == 0
+ # Try reconnecting to ldap
+ @ldap = nil
+ retry
+ else
+ raise Puppet::Error, "LDAP Search failed: %s" % detail
+ end
+ end
+
+ classes.flatten!
+
+ if classes.empty?
+ classes = nil
+ end
+
+ if parent or classes or parameters
+ return parent, classes, parameters
+ else
+ return nil
+ end
+ end
+
+ private
+
+ # Create an ldap connection.
+ def ldap
+ unless defined? @ldap and @ldap
+ unless Puppet.features.ldap?
+ raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries"
+ end
+ begin
+ if Puppet[:ldapssl]
+ @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport])
+ elsif Puppet[:ldaptls]
+ @ldap = LDAP::SSLConn.new(
+ Puppet[:ldapserver], Puppet[:ldapport], true
+ )
+ else
+ @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport])
+ end
+ @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
+ @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON)
+ @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword])
+ rescue => detail
+ raise Puppet::Error, "Could not connect to LDAP: %s" % detail
+ end
+ end
+
+ return @ldap
+ end
+end
diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb
new file mode 100644
index 000000000..ce188add5
--- /dev/null
+++ b/lib/puppet/indirector/node/none.rb
@@ -0,0 +1,10 @@
+Puppet::Network::Handler::Node.newnode_source(:none, :fact_merge => true) do
+ 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 nodesearch(name)
+ newnode(name)
+ end
+end
diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb
index 2d3ac712e..f04de91c5 100644
--- a/lib/puppet/node.rb
+++ b/lib/puppet/node.rb
@@ -1,10 +1,129 @@
# A simplistic class for managing the node information itself.
class Puppet::Node
+ # Set up indirection, so that nodes can be looked for in
+ # the node sources.
+ require 'puppet/indirector'
+ extend Puppet::Indirector
+
+ # Use the node source as the indirection terminus.
+ indirects :node, :to => :node_source
+
+ # Retrieve a node from the node source, with some additional munging
+ # thrown in for kicks.
+ # LAK:FIXME Crap. This won't work, because we might have two copies of this class,
+ # one remote and one local, and we won't know which one should do all of the
+ # extra crap.
+ def self.get(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 = 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
+
+ private
+
+ # Store the node to make things a bit faster.
+ def self.cache(node)
+ @node_cache[node.name] = node
+ end
+
+ # If the node is cached, return it.
+ def self.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
+
+ # Look up the node facts from our fact handler.
+ def self.node_facts(key)
+ if facts = Puppet::Node::Facts.get(key)
+ facts.values
+ else
+ {}
+ end
+ end
+
+ # Calculate the list of node names we should use for looking
+ # up our node.
+ def self.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
+
+ public
+
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
diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb
new file mode 100755
index 000000000..eddf44def
--- /dev/null
+++ b/lib/puppet/node/facts.rb
@@ -0,0 +1,36 @@
+# 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.
+ require 'puppet/indirector'
+ extend Puppet::Indirector
+
+ # Use the node source as the indirection terminus.
+ indirects :facts, :to => :fact_store
+
+ attr_accessor :name, :values
+
+ def initialize(name, values = {})
+ @name = name
+ @values = values
+ end
+
+ private
+
+ # FIXME These methods are currently unused.
+
+ # 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
diff --git a/lib/puppet/util/instance_loader.rb b/lib/puppet/util/instance_loader.rb
index 1a64c9c69..bc0567866 100755
--- a/lib/puppet/util/instance_loader.rb
+++ b/lib/puppet/util/instance_loader.rb
@@ -70,5 +70,3 @@ module Puppet::Util::InstanceLoader
instances[name]
end
end
-
-# $Id$