summaryrefslogtreecommitdiffstats
path: root/lib/puppet/network
diff options
context:
space:
mode:
authorJeffrey J McCune <jeff.mccune@northstarlabs.net>2007-08-15 15:52:27 -0400
committerJeffrey J McCune <jeff.mccune@northstarlabs.net>2007-08-15 15:52:27 -0400
commit7d09d4624dd1d4d69cf5acd355762503e9cb448c (patch)
tree1a306f28c07992195c56fe581ea5494d6d6c8971 /lib/puppet/network
parentfb4ab97580f40d664e76aa7107e58d16097570b8 (diff)
parentaabad8e1e262fb2f63fa4eef0f0e6fc00cc4b01f (diff)
Merge commit 'aabad8e'
Diffstat (limited to 'lib/puppet/network')
-rw-r--r--lib/puppet/network/handler/configuration.rb226
-rw-r--r--lib/puppet/network/handler/node.rb240
2 files changed, 466 insertions, 0 deletions
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