diff options
Diffstat (limited to 'lib/puppet')
-rw-r--r-- | lib/puppet/configuration.rb | 12 | ||||
-rw-r--r-- | lib/puppet/network/handler/configuration.rb | 226 | ||||
-rw-r--r-- | lib/puppet/network/handler/node.rb | 240 | ||||
-rw-r--r-- | lib/puppet/node_source/external.rb | 51 | ||||
-rw-r--r-- | lib/puppet/node_source/ldap.rb | 118 | ||||
-rw-r--r-- | lib/puppet/node_source/none.rb | 10 | ||||
-rw-r--r-- | lib/puppet/parser/ast/hostclass.rb | 7 | ||||
-rw-r--r-- | lib/puppet/parser/configuration.rb | 133 | ||||
-rw-r--r-- | lib/puppet/parser/interpreter.rb | 163 | ||||
-rw-r--r-- | lib/puppet/parser/scope.rb | 222 | ||||
-rw-r--r-- | lib/puppet/reference/node_sources.rb | 7 |
11 files changed, 840 insertions, 349 deletions
diff --git a/lib/puppet/configuration.rb b/lib/puppet/configuration.rb index 65e0d9fa8..a8f6e0c35 100644 --- a/lib/puppet/configuration.rb +++ b/lib/puppet/configuration.rb @@ -122,7 +122,11 @@ module Puppet "The configuration file that defines the rights to the different namespaces and methods. This can be used as a coarse-grained authorization system for both ``puppetd`` and ``puppetmasterd``." - ] + ], + :environment => ["", "The environment Puppet is running in. For clients (e.g., ``puppetd``) this + determines the environment itself, which is used to find modules and much more. For + servers (i.e., ``puppetmasterd``) this provides the default environment for nodes we + know nothing about."] ) hostname = Facter["hostname"].value @@ -544,7 +548,11 @@ module Puppet setdefaults(:parser, :typecheck => [true, "Whether to validate types during parsing."], - :paramcheck => [true, "Whether to validate parameters during parsing."] + :paramcheck => [true, "Whether to validate parameters during parsing."], + :node_source => ["none", "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."] ) setdefaults(:main, diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb new file mode 100644 index 000000000..7f81879ba --- /dev/null +++ b/lib/puppet/network/handler/configuration.rb @@ -0,0 +1,226 @@ +require 'openssl' +require 'puppet' +require 'puppet/parser/interpreter' +require 'puppet/sslcertificates' +require 'xmlrpc/server' +require 'yaml' + +class Puppet::Network::Handler + class Configuration < Handler + desc "Puppet's configuration compilation interface. Passed a node name + or other key, retrieves information about the node and returns a + compiled configuration." + + include Puppet::Util + + attr_accessor :ast, :local + attr_reader :ca + + @interface = XMLRPC::Service::Interface.new("configuration") { |iface| + iface.add_method("string configuration(string)") + iface.add_method("string version()") + } + + # FIXME At some point, this should be autodocumenting. + def addfacts(facts) + # Add our server version to the fact list + facts["serverversion"] = Puppet.version.to_s + + # And then add the server name and IP + {"servername" => "fqdn", + "serverip" => "ipaddress" + }.each do |var, fact| + if obj = Facter[fact] + facts[var] = obj.value + else + Puppet.warning "Could not retrieve fact %s" % fact + end + end + + if facts["servername"].nil? + host = Facter.value(:hostname) + if domain = Facter.value(:domain) + facts["servername"] = [host, domain].join(".") + else + facts["servername"] = host + end + end + end + + # Manipulate the client name as appropriate. + def clientname(name, ip, facts) + # Always use the hostname from Facter. + client = facts["hostname"] + clientip = facts["ipaddress"] + if Puppet[:node_name] == 'cert' + if name + client = name + end + if ip + clientip = ip + end + end + + return client, clientip + end + + # Tell a client whether there's a fresh config for it + def freshness(client = nil, clientip = nil) + if Puppet.features.rails? and Puppet[:storeconfigs] + Puppet::Rails.connect + + host = Puppet::Rails::Host.find_or_create_by_name(client) + host.last_freshcheck = Time.now + if clientip and (! host.ip or host.ip == "" or host.ip == "NULL") + host.ip = clientip + end + host.save + end + if defined? @interpreter + return @interpreter.parsedate + else + return 0 + end + end + + def initialize(hash = {}) + args = {} + + # Allow specification of a code snippet or of a file + if code = hash[:Code] + args[:Code] = code + else + args[:Manifest] = hash[:Manifest] || Puppet[:manifest] + end + + if hash[:Local] + @local = hash[:Local] + else + @local = false + end + + args[:Local] = @local + + if hash.include?(:CA) and hash[:CA] + @ca = Puppet::SSLCertificates::CA.new() + else + @ca = nil + end + + Puppet.debug("Creating interpreter") + + if hash.include?(:UseNodes) + args[:UseNodes] = hash[:UseNodes] + elsif @local + args[:UseNodes] = false + end + + # This is only used by the cfengine module, or if --loadclasses was + # specified in +puppet+. + if hash.include?(:Classes) + args[:Classes] = hash[:Classes] + end + + @interpreter = Puppet::Parser::Interpreter.new(args) + end + + def getconfig(facts, format = "marshal", client = nil, clientip = nil) + if @local + # we don't need to do anything, since we should already + # have raw objects + Puppet.debug "Our client is local" + else + Puppet.debug "Our client is remote" + + # XXX this should definitely be done in the protocol, somehow + case format + when "marshal": + Puppet.warning "You should upgrade your client. 'Marshal' will not be supported much longer." + begin + facts = Marshal::load(CGI.unescape(facts)) + rescue => detail + raise XMLRPC::FaultException.new( + 1, "Could not rebuild facts" + ) + end + when "yaml": + begin + facts = YAML.load(CGI.unescape(facts)) + rescue => detail + raise XMLRPC::FaultException.new( + 1, "Could not rebuild facts" + ) + end + else + raise XMLRPC::FaultException.new( + 1, "Unavailable config format %s" % format + ) + end + end + + client, clientip = clientname(client, clientip, facts) + + # Add any server-side facts to our server. + addfacts(facts) + + retobjects = nil + + # This is hackish, but there's no "silence" option for benchmarks + # right now + if @local + #begin + retobjects = @interpreter.run(client, facts) + #rescue Puppet::Error => detail + # Puppet.err detail + # raise XMLRPC::FaultException.new( + # 1, detail.to_s + # ) + #rescue => detail + # Puppet.err detail.to_s + # return "" + #end + else + benchmark(:notice, "Compiled configuration for %s" % client) do + begin + retobjects = @interpreter.run(client, facts) + rescue Puppet::Error => detail + Puppet.err detail + raise XMLRPC::FaultException.new( + 1, detail.to_s + ) + rescue => detail + Puppet.err detail.to_s + return "" + end + end + end + + if @local + return retobjects + else + str = nil + case format + when "marshal": + str = Marshal::dump(retobjects) + when "yaml": + str = retobjects.to_yaml(:UseBlock => true) + else + raise XMLRPC::FaultException.new( + 1, "Unavailable config format %s" % format + ) + end + return CGI.escape(str) + end + end + + def local? + if defined? @local and @local + return true + else + return false + end + end + end +end + +# $Id$ diff --git a/lib/puppet/network/handler/node.rb b/lib/puppet/network/handler/node.rb new file mode 100644 index 000000000..898db7c22 --- /dev/null +++ b/lib/puppet/network/handler/node.rb @@ -0,0 +1,240 @@ +# Created by Luke A. Kanies on 2007-08-13. +# Copyright (c) 2007. All rights reserved. + +require 'puppet/util' +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 + # A simplistic class for managing the node information itself. + class SimpleNode + attr_accessor :name, :classes, :parameters, :environment, :source, :ipaddress + + def initialize(name, options = {}) + @name = name + + if classes = options[:classes] + if classes.is_a?(String) + @classes = [classes] + else + @classes = classes + end + else + @classes = [] + end + + @parameters = options[:parameters] || {} + + unless @environment = options[:environment] + if env = Puppet[:environment] and env != "" + @environment = env + end + end + 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| + @parameters[name] = value unless @parameters.include?(name) + end + end + end + + desc "Retrieve information about nodes." + + 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. + autoload :node_source, 'puppet/node_source' + + attr_reader :source + + # 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 + + # 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) + 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) + 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 + + # Merge the facts into the parameters. + if fact_merge? + node.fact_merge(facts) + end + 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 + 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 + + # 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) + SimpleNode.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/node_source/external.rb b/lib/puppet/node_source/external.rb new file mode 100644 index 000000000..54111d924 --- /dev/null +++ b/lib/puppet/node_source/external.rb @@ -0,0 +1,51 @@ +Puppet::Network::Handler::Node.newnode_source(:external, :fact_merge => true) do + desc "Call an external program to get node information." + + include Puppet::Util + # Look for external node definitions. + def nodesearch(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/node_source/ldap.rb b/lib/puppet/node_source/ldap.rb new file mode 100644 index 000000000..9332fcb40 --- /dev/null +++ b/lib/puppet/node_source/ldap.rb @@ -0,0 +1,118 @@ +Puppet::Network::Handler::Node.newnode_source(:ldap, :fact_merge => true) do + desc "Search in LDAP for node configuration information." + + # Find the ldap node, return the class list and parent node specially, + # and everything else in a parameter hash. + def ldapsearch(node) + unless defined? @ldap and @ldap + setup_ldap() + unless @ldap + Puppet.info "Skipping ldap source; no ldap connection" + return nil + end + end + + 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 + setup_ldap() + 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 + + # Look for our node in ldap. + def nodesearch(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 +end diff --git a/lib/puppet/node_source/none.rb b/lib/puppet/node_source/none.rb new file mode 100644 index 000000000..ce188add5 --- /dev/null +++ b/lib/puppet/node_source/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/parser/ast/hostclass.rb b/lib/puppet/parser/ast/hostclass.rb index 642645824..d1ce370da 100644 --- a/lib/puppet/parser/ast/hostclass.rb +++ b/lib/puppet/parser/ast/hostclass.rb @@ -24,8 +24,11 @@ class Puppet::Parser::AST scope = hash[:scope] args = hash[:arguments] - # Verify that we haven't already been evaluated - if scope.class_scope(self) + # Verify that we haven't already been evaluated, and if we have been evaluated, + # make sure that we match the class. + if existing_scope = scope.class_scope(self) + raise "Fix this portion of the code -- check that the scopes match classes" + #if existing_scope.source.object_id == self.object_id Puppet.debug "%s class already evaluated" % @type return nil end diff --git a/lib/puppet/parser/configuration.rb b/lib/puppet/parser/configuration.rb new file mode 100644 index 000000000..c7979e51f --- /dev/null +++ b/lib/puppet/parser/configuration.rb @@ -0,0 +1,133 @@ +# Created by Luke A. Kanies on 2007-08-13. +# Copyright (c) 2007. All rights reserved. + +require 'puppet/external/gratr/digraph' +require 'puppet/external/gratr/import' +require 'puppet/external/gratr/dot' + +# Maintain a graph of scopes, along with a bunch of data +# about the individual configuration we're compiling. +class Puppet::Parser::Configuration + attr_reader :topscope, :interpreter, :host, :facts + + # Add a collection to the global list. + def add_collection(coll) + @collections << coll + end + + # Store the fact that we've evaluated a class, and store a reference to + # the scope in which it was evaluated, so that we can look it up later. + def class_set(name, scope) + @class_scopes[name] = scope + end + + # Return the scope associated with a class. This is just here so + # that subclasses can set their parent scopes to be the scope of + # their parent class, and it's also used when looking up qualified + # variables. + def class_scope(klass) + # They might pass in either the class or class name + if klass.respond_to?(:classname) + @class_scopes[klass.classname] + else + @class_scopes[klass] + end + end + + # Return a list of all of the defined classes. + def classlist + return @class_scopes.keys.reject { |k| k == "" } + end + + # Should the scopes behave declaratively? + def declarative? + true + end + + # Set up our configuration. We require an interpreter + # and a host name, and we normally are passed facts, too. + def initialize(options) + @interpreter = options[:interpreter] or + raise ArgumentError, "You must pass an interpreter to the configuration" + @facts = options[:facts] || {} + @host = options[:host] or + raise ArgumentError, "You must pass a host name to the configuration" + + # Call the setup methods from the base class. + super() + + initvars() + end + + # Create a new scope, with either a specified parent scope or + # using the top scope. Adds an edge between the scope and + # its parent to the graph. + def newscope(parent = nil) + parent ||= @topscope + scope = Puppet::Parser::Scope.new(:configuration => self) + @graph.add_edge!(parent, scope) + scope + end + + # Find the parent of a given scope. Assumes scopes only ever have + # one in edge, which will always be true. + def parent(scope) + if ary = @graph.adjacent(scope, :direction => :in) and ary.length > 0 + ary[0] + else + nil + end + end + + # Return an array of all of the unevaluated objects + def unevaluated + ary = @definedtable.find_all do |name, object| + ! object.builtin? and ! object.evaluated? + end.collect { |name, object| object } + + if ary.empty? + return nil + else + return ary + end + end + + private + + # Set up all of our internal variables. + def initvars + # The table for storing class singletons. This will only actually + # be used by top scopes and node scopes. + @class_scopes = {} + + # The table for all defined resources. + @resource_table = {} + + # The list of objects that will available for export. + @exported_resources = {} + + # The list of overrides. This is used to cache overrides on objects + # that don't exist yet. We store an array of each override. + @resource_overrides = Hash.new do |overs, ref| + overs[ref] = [] + end + + # The list of collections that have been created. This is a global list, + # but they each refer back to the scope that created them. + @collections = [] + + # Create our initial scope, our scope graph, and add the initial scope to the graph. + @topscope = Puppet::Parser::Scope.new(:configuration => self, :type => "main", :name => "top") + @graph = GRATR::Digraph.new + @graph.add_vertex!(@topscope) + end + + # Return the list of remaining overrides. + def overrides + @resource_overrides.values.flatten + end + + def resources + @resourcetable + end +end diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index 3ba9c0c7a..18bf31087 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -178,7 +178,6 @@ class Puppet::Parser::Interpreter # Evaluate all of the code we can find that's related to our client. def evaluate(client, facts) - scope = Puppet::Parser::Scope.new(:interp => self) # no parent scope scope.name = "top" scope.type = "main" @@ -327,101 +326,6 @@ class Puppet::Parser::Interpreter parsefiles end - # Find the ldap node, return the class list and parent node specially, - # and everything else in a parameter hash. - def ldapsearch(node) - unless defined? @ldap and @ldap - setup_ldap() - unless @ldap - Puppet.info "Skipping ldap source; no ldap connection" - return nil - end - end - - 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 - setup_ldap() - 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 - # Pass these methods through to the parser. [:newclass, :newdefine, :newnode].each do |name| define_method(name) do |*args| @@ -476,73 +380,6 @@ class Puppet::Parser::Interpreter def nodesearch_code(name) @parser.nodes[name] end - - # Look for external node definitions. - def nodesearch_external(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_args = {:source => "external node source", :name => name} - set = false - [:parameters, :classes].each do |param| - if value = result[param] - node_args[param] = value - set = true - end - end - - if set - return NodeDef.new(node_args) - else - return nil - end - end - - # Look for our node in ldap. - def nodesearch_ldap(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 NodeDef.new(:name => node, :classes => classes, :source => "ldap", :parameters => parameters) - end def parsedate parsefiles() diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index 6feeefc46..1fb4f6906 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -15,36 +15,18 @@ class Puppet::Parser::Scope include Enumerable include Puppet::Util::Errors - attr_accessor :parent, :level, :interp, :source, :host - attr_accessor :name, :type, :topscope, :base, :keyword - attr_accessor :top, :translated, :exported, :virtual + attr_accessor :parent, :level, :interp, :source + attr_accessor :name, :type, :base, :keyword + attr_accessor :top, :translated, :exported, :virtual, :configuration - # Whether we behave declaratively. Note that it's a class variable, - # so all scopes behave the same. - @@declarative = true - - # Retrieve and set the declarative setting. - def self.declarative - return @@declarative - end - - def self.declarative=(val) - @@declarative = val + # Proxy accessors + def host + @configuration.host end - - # This handles the shared tables that all scopes have. They're effectively - # global tables, except that they're only global for a single scope tree, - # which is why I can't use class variables for them. - def self.sharedtable(*names) - attr_accessor(*names) - @@sharedtables ||= [] - @@sharedtables += names + def interpreter + @configuration.interpreter end - # This is probably not all that good of an idea, but... - # This way a parent can share its tables with all of its children. - sharedtable :classtable, :definedtable, :exportable, :overridetable, :collecttable - # Is the value true? This allows us to control the definition of truth # in one place. def self.true?(value) @@ -111,44 +93,6 @@ class Puppet::Parser::Scope return true end - # Return the scope associated with a class. This is just here so - # that subclasses can set their parent scopes to be the scope of - # their parent class. - def class_scope(klass) - scope = if klass.respond_to?(:classname) - @classtable[klass.classname] - else - @classtable[klass] - end - - return nil unless scope - - if scope.nodescope? and ! klass.is_a?(AST::Node) - raise Puppet::ParseError, "Node %s has already been evaluated; cannot evaluate class with same name" % [klass.classname] - end - - scope - end - - # Return the list of collections. - def collections - @collecttable - end - - def declarative=(val) - self.class.declarative = val - end - - def declarative - self.class.declarative - end - - # Test whether a given scope is declarative. Even though it's - # a global value, the calling objects don't need to know that. - def declarative? - @@declarative - end - # Remove a specific child. def delete(child) @children.delete(child) @@ -171,14 +115,6 @@ class Puppet::Parser::Scope @level == 1 end - # Return a list of all of the defined classes. - def classlist - unless defined? @classtable - raise Puppet::DevError, "Scope did not receive class table" - end - return @classtable.keys.reject { |k| k == "" } - end - # Yield each child scope in turn def each @children.each { |child| @@ -239,9 +175,6 @@ class Puppet::Parser::Scope # Initialize our new scope. Defaults to having no parent and to # being declarative. def initialize(hash = {}) - @parent = nil - @type = nil - @name = nil @finished = false if hash.include?(:namespace) if n = hash[:namespace] @@ -262,26 +195,6 @@ class Puppet::Parser::Scope @tags = [] - if @parent.nil? - unless hash.include?(:declarative) - hash[:declarative] = true - end - self.istop(hash[:declarative]) - @inside = nil - else - # This is here, rather than in newchild(), so that all - # of the later variable initialization works. - @parent.child = self - - @level = @parent.level + 1 - @interp = @parent.interp - @source = hash[:source] || @parent.source - @topscope = @parent.topscope - #@inside = @parent.inside # Used for definition inheritance - @host = @parent.host - @type ||= @parent.type - end - # Our child scopes and objects @children = [] @@ -294,62 +207,6 @@ class Puppet::Parser::Scope @defaultstable = Hash.new { |dhash,type| dhash[type] = {} } - - unless @interp - raise Puppet::DevError, "Scopes require an interpreter" - end - end - - # Associate the object directly with the scope, so that contained objects - # can look up what container they're running within. - def inside(arg = nil) - return @inside unless arg - - old = @inside - @inside = arg - yield - ensure - #Puppet.warning "exiting %s" % @inside.name - @inside = old - end - - # Mark that we're the top scope, and set some hard-coded info. - def istop(declarative = true) - # the level is mostly used for debugging - @level = 1 - - # The table for storing class singletons. This will only actually - # be used by top scopes and node scopes. - @classtable = {} - - self.class.declarative = declarative - - # The table for all defined objects. - @definedtable = {} - - # The list of objects that will available for export. - @exportable = {} - - # The list of overrides. This is used to cache overrides on objects - # that don't exist yet. We store an array of each override. - @overridetable = Hash.new do |overs, ref| - overs[ref] = [] - end - - # Eventually, if we support sites, this will allow definitions - # of nodes with the same name in different sites. For now - # the top-level scope is always the only site scope. - @sitescope = true - - @namespaces = [""] - - # The list of collections that have been created. This is a global list, - # but they each refer back to the scope that created them. - @collecttable = [] - - @topscope = self - @type = "puppet" - @name = "top" end # Collect all of the defaults set at any higher scopes. @@ -360,8 +217,8 @@ class Puppet::Parser::Scope values = {} # first collect the values from the parents - unless @parent.nil? - @parent.lookupdefaults(type).each { |var,value| + unless parent.nil? + parent.lookupdefaults(type).each { |var,value| values[var] = value } end @@ -426,7 +283,7 @@ class Puppet::Parser::Scope return @symtable[name] end elsif self.parent - return @parent.lookupvar(name, usestring) + return parent.lookupvar(name, usestring) elsif usestring return "" else @@ -438,11 +295,6 @@ class Puppet::Parser::Scope @namespaces.dup end - # Add a collection to the global list. - def newcollection(coll) - @collecttable << coll - end - # Create a new scope. def newscope(hash = {}) hash[:parent] = self @@ -464,6 +316,25 @@ class Puppet::Parser::Scope @overridetable.values.flatten end + # We probably shouldn't cache this value... But it's a lot faster + # than doing lots of queries. + def parent + unless defined?(@parent) + @parent = configuration.parent(self) + end + @parent + end + + # Return the list of scopes up to the top scope, ordered with our own first. + # This is used for looking up variables and defaults. + def scope_path + if parent + [self, parent.scope_path].flatten.compact + else + [self] + end + end + def resources @definedtable.values end @@ -472,17 +343,17 @@ class Puppet::Parser::Scope # that gets inherited from the top scope down, rather than a global # hash. We store the object ID, not class name, so that we # can support multiple unrelated classes with the same name. - def setclass(obj) - if obj.is_a?(AST::HostClass) - unless obj.classname + def setclass(klass) + if klass.is_a?(AST::HostClass) + unless klass.classname raise Puppet::DevError, "Got a %s with no fully qualified name" % - obj.class + klass.class end - @classtable[obj.classname] = self + @configuration.class_set(klass.classname, self) else - raise Puppet::DevError, "Invalid class %s" % obj.inspect + raise Puppet::DevError, "Invalid class %s" % klass.inspect end - if obj.is_a?(AST::Node) + if klass.is_a?(AST::Node) @nodescope = true end nil @@ -665,9 +536,9 @@ class Puppet::Parser::Scope unless ! defined? @type or @type.nil? or @type == "" tmp << @type.to_s end - if @parent - #info "Looking for tags in %s" % @parent.type - @parent.tags.each { |tag| + if parent + #info "Looking for tags in %s" % parent.type + parent.tags.each { |tag| if tag.nil? or tag == "" Puppet.debug "parent returned tag %s" % tag.inspect next @@ -722,19 +593,6 @@ class Puppet::Parser::Scope end end - # Return an array of all of the unevaluated objects - def unevaluated - ary = @definedtable.find_all do |name, object| - ! object.builtin? and ! object.evaluated? - end.collect { |name, object| object } - - if ary.empty? - return nil - else - return ary - end - end - def virtual? self.virtual || self.exported? end diff --git a/lib/puppet/reference/node_sources.rb b/lib/puppet/reference/node_sources.rb new file mode 100644 index 000000000..33a4c539c --- /dev/null +++ b/lib/puppet/reference/node_sources.rb @@ -0,0 +1,7 @@ +noderef = Puppet::Util::Reference.newreference :node_source, :doc => "Sources of node configuration information" do + Puppet::Network::Handler.node.sourcedocs +end + +nodref.header = " +Nodes can be searched for in different locations. This document describes those different locations. +" |