diff --git a/lib/puppet/indirector/node/exec.rb b/lib/puppet/indirector/node/exec.rb index 9fa3c07ad..d4faf74f4 100644 --- a/lib/puppet/indirector/node/exec.rb +++ b/lib/puppet/indirector/node/exec.rb @@ -1,77 +1,69 @@ require 'puppet/node' require 'puppet/indirector/exec' class Puppet::Node::Exec < Puppet::Indirector::Exec desc "Call an external program to get node information. See the [External Nodes](http://docs.puppetlabs.com/guides/external_nodes.html) page for more information." include Puppet::Util def command command = Puppet[:external_nodes] raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node terminus" unless command != "none" command.split end # Look for external node definitions. def find(request) output = super or return nil # Translate the output to ruby. result = translate(request.key, output) - # If defining a new environment then we check to see if - # the environment exists, if it does not then we raise - # an error instead of falling back to the 'production' - # environment. - if result[:environment] && !Puppet.lookup(:environments).get(result[:environment]) - raise Puppet::Environments::EnvironmentNotFound, result[:environment] - end - # Set the requested environment if it wasn't overridden # If we don't do this it gets set to the local default result[:environment] ||= request.environment.name create_node(request.key, result) end private # Proxy the execution, so it's easier to test. def execute(command, arguments) Puppet::Util::Execution.execute(command,arguments) end # Turn our outputted objects into a Puppet::Node instance. def create_node(name, result) node = Puppet::Node.new(name) set = false [:parameters, :classes, :environment].each do |param| if value = result[param] node.send(param.to_s + "=", value) set = true end end node.fact_merge node end # Translate the yaml string into Ruby objects. def translate(name, output) YAML.load(output).inject({}) do |hash, data| case data[0] when String hash[data[0].intern] = data[1] when Symbol hash[data[0]] = data[1] else raise Puppet::Error, "key is a #{data[0].class}, not a string or symbol" end hash end rescue => detail raise Puppet::Error, "Could not load external node results for #{name}: #{detail}", detail.backtrace end end diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index d61d49385..8f79224b1 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -1,184 +1,189 @@ require 'puppet/indirector' # A class for managing nodes, including their facts and environment. class Puppet::Node require 'puppet/node/facts' require 'puppet/node/environment' # 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, :terminus_setting => :node_terminus, :doc => "Where to find node information. A node is composed of its name, its facts, and its environment." attr_accessor :name, :classes, :source, :ipaddress, :parameters, :trusted_data, :environment_name attr_reader :time, :facts ::PSON.register_document_type('Node',self) def self.from_data_hash(data) raise ArgumentError, "No name provided in serialized data" unless name = data['name'] node = new(name) node.classes = data['classes'] node.parameters = data['parameters'] node.environment_name = data['environment'] node end def self.from_pson(pson) Puppet.deprecation_warning("from_pson is being removed in favour of from_data_hash.") self.from_data_hash(pson) end def to_data_hash result = { 'name' => name, 'environment' => environment.name, } result['classes'] = classes unless classes.empty? result['parameters'] = parameters unless parameters.empty? result end def to_pson_data_hash(*args) { 'document_type' => "Node", 'data' => to_data_hash, } end def to_pson(*args) to_pson_data_hash.to_pson(*args) end def environment if @environment @environment else if env = parameters["environment"] self.environment = env elsif environment_name self.environment = environment_name else # This should not be :current_environment, this is the default # for a node when it has not specified its environment # Tt will be used to establish what the current environment is. # self.environment = Puppet.lookup(:environments).get(Puppet[:environment]) end @environment end end def environment=(env) if env.is_a?(String) or env.is_a?(Symbol) + if !Puppet.lookup(:environments).get(env) + raise Puppet::Environments::EnvironmentNotFound, env + end @environment = Puppet.lookup(:environments).get(env) - else + elsif env.is_a?(Puppet::Node::Environment) @environment = env + else + raise Puppet::Environments::EnvironmentNotFound, env end end def has_environment_instance? !@environment.nil? end def initialize(name, options = {}) raise ArgumentError, "Node names cannot be nil" unless name @name = name if classes = options[:classes] if classes.is_a?(String) @classes = [classes] else @classes = classes end else @classes = [] end @parameters = options[:parameters] || {} @facts = options[:facts] if env = options[:environment] self.environment = env end @time = Time.now end # Merge the node facts with parameters from the node source. def fact_merge if @facts = Puppet::Node::Facts.indirection.find(name, :environment => environment) @facts.sanitize merge(@facts.values) end rescue => detail error = Puppet::Error.new("Could not retrieve facts for #{name}: #{detail}") error.set_backtrace(detail.backtrace) raise error 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 @parameters["environment"] ||= self.environment.name.to_s end # Calculate the list of names we might use for looking # up our node. This is only used for AST nodes. def names return [name] if Puppet.settings[:strict_hostname_checking] names = [] names += split_name(name) if name.include?(".") # First, get the fqdn unless fqdn = parameters["fqdn"] if parameters["hostname"] and parameters["domain"] fqdn = parameters["hostname"] + "." + parameters["domain"] else Puppet.warning "Host is missing hostname and/or domain: #{name}" end end # Now that we (might) have the fqdn, add each piece to the name # list to search, in order of longest to shortest. names += split_name(fqdn) if fqdn # And make sure the node name is first, since that's the most # likely usage. # The name is usually the Certificate CN, but it can be # set to the 'facter' hostname instead. if Puppet[:node_name] == 'cert' names.unshift name else names.unshift parameters["hostname"] end names.uniq end def split_name(name) list = name.split(".") tmp = [] list.each_with_index do |short, i| tmp << list[0..i].join(".") end tmp.reverse end # Ensures the data is frozen # def trusted_data=(data) Puppet.warning("Trusted node data modified for node #{name}") unless @trusted_data.nil? @trusted_data = data.freeze end end