summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2007-09-23 19:04:31 -0500
committerLuke Kanies <luke@madstop.com>2007-09-23 19:04:31 -0500
commitcdc8ea6e81c1b5eba5ea784bb7079c4c1f3965a4 (patch)
tree6dab031628ee4c8269c93cd96ed646bdd2874cc4 /lib
parentc40da335123ee839294b37134d1e6361000bf216 (diff)
downloadpuppet-cdc8ea6e81c1b5eba5ea784bb7079c4c1f3965a4.tar.gz
puppet-cdc8ea6e81c1b5eba5ea784bb7079c4c1f3965a4.tar.xz
puppet-cdc8ea6e81c1b5eba5ea784bb7079c4c1f3965a4.zip
Taking a first stab at moving configuration compiling
into the indirection system. There are still quite a few unanswered questions, the two most notable being embodied in unimplemented tests in the Configuration Code terminus. This also requires changing the behaviour in a few places. In particular, 'puppet' and the 'module_puppet' cfengine module need to store a Node object in memory with the appropriate classes, since that's now the only way to communicate with the compiler. That integration work has not yet been done, partially because the old configuration handler (which the soon-to-be-deprecated master handler now uses) still exists.
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet/indirector.rb2
-rw-r--r--lib/puppet/indirector/code.rb6
-rw-r--r--lib/puppet/indirector/code/configuration.rb171
-rw-r--r--lib/puppet/node/configuration.rb4
-rw-r--r--lib/puppet/util/settings.rb20
5 files changed, 193 insertions, 10 deletions
diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb
index bd2487e33..6ff2de1b4 100644
--- a/lib/puppet/indirector.rb
+++ b/lib/puppet/indirector.rb
@@ -16,7 +16,7 @@ module Puppet::Indirector
# evaluated at parse time, which is before the user has had a chance
# to override it.
def indirects(indirection)
- raise(ArgumentError, "Already handling indirection for %s; cannot also handle %s" % [@indirection.name, indirection]) if defined?(@indirection) and indirection
+ raise(ArgumentError, "Already handling indirection for %s; cannot also handle %s" % [@indirection.name, indirection]) if defined?(@indirection) and @indirection
# populate this class with the various new methods
extend ClassMethods
include InstanceMethods
diff --git a/lib/puppet/indirector/code.rb b/lib/puppet/indirector/code.rb
new file mode 100644
index 000000000..0c0ee146b
--- /dev/null
+++ b/lib/puppet/indirector/code.rb
@@ -0,0 +1,6 @@
+require 'puppet/indirector/terminus'
+
+# Do nothing, requiring that the back-end terminus do all
+# of the work.
+class Puppet::Indirector::Code < Puppet::Indirector::Terminus
+end
diff --git a/lib/puppet/indirector/code/configuration.rb b/lib/puppet/indirector/code/configuration.rb
new file mode 100644
index 000000000..6d0317204
--- /dev/null
+++ b/lib/puppet/indirector/code/configuration.rb
@@ -0,0 +1,171 @@
+require 'puppet/node'
+require 'puppet/node/configuration'
+require 'puppet/indirector/code'
+require 'puppet/parser/interpreter'
+require 'yaml'
+
+class Puppet::Indirector::Code::Configuration < Puppet::Indirector::Code
+ desc "Puppet's configuration compilation interface. Passed a node name
+ or other key, retrieves information about the node (using the ``node_source``)
+ and returns a compiled configuration."
+
+ include Puppet::Util
+
+ attr_accessor :code
+
+ # Compile a node's configuration.
+ def find(key, client = nil, clientip = nil)
+ # If we want to use the cert name as our key
+ if Puppet[:node_name] == 'cert' and client
+ key = client
+ end
+
+ # Note that this is reasonable, because either their node source should actually
+ # know about the node, or they should be using the ``none`` node source, which
+ # will always return data.
+ unless node = Puppet::Node.search(key)
+ raise Puppet::Error, "Could not find node '%s'" % key
+ end
+
+ # Add any external data to the node.
+ add_node_data(node)
+
+ configuration = compile(node)
+
+ return configuration
+ end
+
+ def initialize
+ set_server_facts
+ end
+
+ # Create/return our interpreter.
+ def interpreter
+ unless defined?(@interpreter) and @interpreter
+ @interpreter = create_interpreter
+ end
+ @interpreter
+ end
+
+ # Return the configuration version.
+ def version(client = nil, clientip = nil)
+ if client and node = Puppet::Node.search(client)
+ update_node_check(node)
+ return interpreter.configuration_version(node)
+ else
+ # Just return something that will always result in a recompile, because
+ # this is local.
+ return (Time.now + 1000).to_i
+ end
+ end
+
+ private
+
+ # Add any extra data necessary to the node.
+ def add_node_data(node)
+ # Merge in our server-side facts, so they can be used during compilation.
+ node.merge(@server_facts)
+ end
+
+ # Compile the actual configuration.
+ def compile(node)
+ # Ask the interpreter to compile the configuration.
+ str = "Compiled configuration for %s" % node.name
+ if node.environment
+ str += " in environment %s" % node.environment
+ end
+ config = nil
+
+ # LAK:FIXME This should log at :none when our client is
+ # local, since we don't want 'puppet' (vs. puppetmasterd) to
+ # log compile times.
+ benchmark(:notice, "Compiled configuration for %s" % node.name) do
+ begin
+ config = interpreter.compile(node)
+ rescue Puppet::Error => detail
+ if Puppet[:trace]
+ puts detail.backtrace
+ end
+ unless local?
+ Puppet.err detail.to_s
+ end
+ raise XMLRPC::FaultException.new(
+ 1, detail.to_s
+ )
+ end
+ end
+
+ return config
+ end
+
+ # Create our interpreter object.
+ def create_interpreter
+ args = {}
+
+ # Allow specification of a code snippet or of a file
+ if self.code
+ args[:Code] = self.code
+ end
+
+ # LAK:FIXME This needs to be handled somehow.
+ #if options.include?(:UseNodes)
+ # args[:UseNodes] = options[:UseNodes]
+ #elsif @local
+ # args[:UseNodes] = false
+ #end
+
+ return Puppet::Parser::Interpreter.new(args)
+ end
+
+ # Initialize our server fact hash; we add these to each client, and they
+ # won't change while we're running, so it's safe to cache the values.
+ def set_server_facts
+ @server_facts = {}
+
+ # Add our server version to the fact list
+ @server_facts["serverversion"] = Puppet.version.to_s
+
+ # And then add the server name and IP
+ {"servername" => "fqdn",
+ "serverip" => "ipaddress"
+ }.each do |var, fact|
+ if value = Facter.value(fact)
+ @server_facts[var] = value
+ else
+ Puppet.warning "Could not retrieve fact %s" % fact
+ end
+ end
+
+ if @server_facts["servername"].nil?
+ host = Facter.value(:hostname)
+ if domain = Facter.value(:domain)
+ @server_facts["servername"] = [host, domain].join(".")
+ else
+ @server_facts["servername"] = host
+ end
+ end
+ end
+
+ # Translate our configuration appropriately for sending back to a client.
+ # LAK:FIXME This method should probably be part of the protocol, but it
+ # shouldn't be here.
+ def translate(config)
+ if local?
+ config
+ else
+ CGI.escape(config.to_yaml(:UseBlock => true))
+ end
+ end
+
+ # Mark that the node has checked in. LAK:FIXME this needs to be moved into
+ # the Node class, or somewhere that's got abstract backends.
+ def update_node_check(node)
+ if Puppet.features.rails? and Puppet[:storeconfigs]
+ Puppet::Rails.connect
+
+ host = Puppet::Rails::Host.find_or_create_by_name(node.name)
+ host.last_freshcheck = Time.now
+ host.save
+ end
+ end
+end
diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb
index 0ae03a651..53f63d003 100644
--- a/lib/puppet/node/configuration.rb
+++ b/lib/puppet/node/configuration.rb
@@ -1,3 +1,4 @@
+require 'puppet/indirector'
require 'puppet/external/gratr/digraph'
# This class models a node configuration. It is the thing
@@ -5,6 +6,9 @@ require 'puppet/external/gratr/digraph'
# of the information in the configuration, including the resources
# and the relationships between them.
class Puppet::Node::Configuration < Puppet::PGraph
+ extend Puppet::Indirector
+ indirects :configuration
+
# The host name this is a configuration for.
attr_accessor :name
diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb
index f2af13dc2..1478cd8a5 100644
--- a/lib/puppet/util/settings.rb
+++ b/lib/puppet/util/settings.rb
@@ -45,15 +45,17 @@ class Puppet::Util::Settings
end
# A simplified equality operator.
- def ==(other)
- self.each { |myname, myobj|
- unless other[myname] == value(myname)
- return false
- end
- }
-
- return true
- end
+ # LAK: For some reason, this causes mocha to not be able to mock
+ # the 'value' method, and it's not used anywhere.
+# def ==(other)
+# self.each { |myname, myobj|
+# unless other[myname] == value(myname)
+# return false
+# end
+# }
+#
+# return true
+# end
# Generate the list of valid arguments, in a format that GetoptLong can
# understand, and add them to the passed option list.