diff options
| author | Luke Kanies <luke@madstop.com> | 2007-08-14 00:09:49 -0500 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2007-08-14 00:09:49 -0500 |
| commit | aab419b8c1ad84e51c6f58839290bbe5d1e7b28b (patch) | |
| tree | 2447b704e0b601ffe10562d9eb83e6c9280366ba /lib/puppet/network/handler | |
| parent | ab42534ae243c24c8c702e38195a954ab52eaed9 (diff) | |
An intermediate commit in the work towards adding multi-environment support.
This has required splitting the interpreter up considerably, which is much
cleaner but is a large project. There is now a 'nodes' handler, but it is
currently non-functional, although all the support structure is there. It just
needs to have the individual methods fleshed out, and it needs to be connected
to the 'facts' handler.
Diffstat (limited to 'lib/puppet/network/handler')
| -rw-r--r-- | lib/puppet/network/handler/configuration.rb | 226 | ||||
| -rw-r--r-- | lib/puppet/network/handler/node.rb | 146 |
2 files changed, 372 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..0c532144a --- /dev/null +++ b/lib/puppet/network/handler/node.rb @@ -0,0 +1,146 @@ +# 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 + + def initialize(options) + unless @name = options[:name] + raise ArgumentError, "Nodes require names" unless self.name + end + + if classes = options[:classes] + if classes.is_a?(String) + @classes = [classes] + else + @classes = classes + end + else + @classes = [] + end + + @parameters = options[:parameters] || {} + end + end + desc "Retrieve information about nodes." + + extend Puppet::Util::ClassGen + extend Puppet::Util::InstanceLoader + + module SourceBase + include Puppet::Util::Docs + end + + @interface = XMLRPC::Service::Interface.new("nodes") { |iface| + iface.add_method("string node(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' + + # Add a new node source. + def self.newnode_source(name, options = {}, &block) + name = symbolize(name) + + genmodule(name, :extend => SourceBase, :hash => instance_hash(:node_source), :block => block) + 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) + instance_hash(:node_source).delete(name) + end + + # Return a given node's classes. + def classes(key) + raise "look up classes" + end + + # Return a given node's environment. + def environment(key) + raise "look up environment" + if node = node(key) + node.environment + else + nil + end + end + + # Return an entire node configuration. + def node(key) + # Try to find our node... + nodes = nodes.collect { |n| n.to_s.downcase } + + method = "nodesearch_%s" % @nodesource + # Do an inverse sort on the length, so the longest match always + # wins + nodes.sort { |a,b| b.length <=> a.length }.each do |node| + node = node.to_s if node.is_a?(Symbol) + if obj = self.send(method, node) + if obj.is_a?(AST::Node) + nsource = obj.file + else + nsource = obj.source + end + Puppet.info "Found %s in %s" % [node, nsource] + return obj + end + end + + # If they made it this far, we haven't found anything, so look for a + # default node. + unless nodes.include?("default") + if defobj = self.nodesearch("default") + Puppet.notice "Using default node for %s" % [nodes[0]] + return defobj + end + end + + return nil + end + + def parameters(key) + raise "Look up parameters" + end + + private + def node_facts(key) + raise "Look up node facts" + end + + def node_names(key, facts = nil) + facts ||= node_facts(key) + raise "Calculate node names" + end +end |
