diff --git a/lib/puppet/indirector/catalog/compiler.rb b/lib/puppet/indirector/catalog/compiler.rb index f4c40812d..dced27868 100644 --- a/lib/puppet/indirector/catalog/compiler.rb +++ b/lib/puppet/indirector/catalog/compiler.rb @@ -1,171 +1,165 @@ require 'puppet/node' require 'puppet/resource/catalog' require 'puppet/indirector/code' require 'yaml' class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code desc "Puppet's catalog compilation interface, and its back-end is Puppet's compiler" include Puppet::Util attr_accessor :code def extract_facts_from_request(request) return unless text_facts = request.options[:facts] raise ArgumentError, "Facts but no fact format provided for #{request.name}" unless format = request.options[:facts_format] # If the facts were encoded as yaml, then the param reconstitution system # in Network::HTTP::Handler will automagically deserialize the value. if text_facts.is_a?(Puppet::Node::Facts) facts = text_facts else facts = Puppet::Node::Facts.convert_from(format, text_facts) end facts.add_timestamp Puppet::Node::Facts.indirection.save(facts) end # Compile a node's catalog. def find(request) extract_facts_from_request(request) node = node_from_request(request) if catalog = compile(node) return catalog else # This shouldn't actually happen; we should either return # a config or raise an exception. return nil end end # filter-out a catalog to remove exported resources def filter(catalog) return catalog.filter { |r| r.virtual? } if catalog.respond_to?(:filter) catalog end def initialize set_server_facts - setup_database_backend if Puppet[:storeconfigs] end # Is our compiler part of a network, or are we just local? def networked? Puppet.run_mode.master? 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 catalog. def compile(node) str = "Compiled catalog for #{node.name}" str += " in environment #{node.environment}" if node.environment config = nil loglevel = networked? ? :notice : :none benchmark(loglevel, str) do begin config = Puppet::Parser::Compiler.compile(node) rescue Puppet::Error => detail Puppet.err(detail.to_s) if networked? raise end end config end # Turn our host name into a node object. def find_node(name) begin return nil unless node = Puppet::Node.indirection.find(name) rescue => detail puts detail.backtrace if Puppet[:trace] raise Puppet::Error, "Failed when searching for node #{name}: #{detail}" end # Add any external data to the node. add_node_data(node) node end # Extract the node from the request, or use the request # to find the node. def node_from_request(request) if node = request.options[:use_node] return node end # We rely on our authorization system to determine whether the connected # node is allowed to compile the catalog's node referenced by key. # By default the REST authorization system makes sure only the connected node # can compile his catalog. # This allows for instance monitoring systems or puppet-load to check several # node's catalog with only one certificate and a modification to auth.conf # If no key is provided we can only compile the currently connected node. name = request.key || request.node if node = find_node(name) return node end raise ArgumentError, "Could not find node '#{name}'; cannot compile" 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 #{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 - def setup_database_backend - raise Puppet::Error, "Rails is missing; cannot store configurations" unless Puppet.features.rails? - Puppet::Rails.init - 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/spec/unit/indirector/catalog/compiler_spec.rb b/spec/unit/indirector/catalog/compiler_spec.rb index ea0e98e80..3c559e3bb 100755 --- a/spec/unit/indirector/catalog/compiler_spec.rb +++ b/spec/unit/indirector/catalog/compiler_spec.rb @@ -1,279 +1,261 @@ #!/usr/bin/env rspec require 'spec_helper' require 'puppet/indirector/catalog/compiler' require 'puppet/rails' describe Puppet::Resource::Catalog::Compiler do before do require 'puppet/rails' Puppet::Rails.stubs(:init) Facter.stubs(:to_hash).returns({}) Facter.stubs(:value).returns(Facter::Util::Fact.new("something")) end describe "when initializing" do before do Puppet.expects(:version).returns(1) Facter.expects(:value).with('fqdn').returns("my.server.com") Facter.expects(:value).with('ipaddress').returns("my.ip.address") end it "should gather data about itself" do Puppet::Resource::Catalog::Compiler.new end it "should cache the server metadata and reuse it" do compiler = Puppet::Resource::Catalog::Compiler.new node1 = stub 'node1', :merge => nil node2 = stub 'node2', :merge => nil compiler.stubs(:compile) Puppet::Node.indirection.stubs(:find).with('node1').returns(node1) Puppet::Node.indirection.stubs(:find).with('node2').returns(node2) compiler.find(stub('request', :key => 'node1', :node => 'node1', :options => {})) compiler.find(stub('node2request', :key => 'node2', :node => 'node2', :options => {})) end it "should provide a method for determining if the catalog is networked" do compiler = Puppet::Resource::Catalog::Compiler.new compiler.should respond_to(:networked?) end - - describe "and storeconfigs is enabled" do - before do - Puppet.settings.expects(:value).with(:storeconfigs).returns true - end - - it "should initialize Rails if it is available" do - Puppet.features.expects(:rails?).returns true - Puppet::Rails.expects(:init) - Puppet::Resource::Catalog::Compiler.new - end - - it "should fail if Rails is unavailable" do - Puppet.features.expects(:rails?).returns false - Puppet::Rails.expects(:init).never - lambda { Puppet::Resource::Catalog::Compiler.new }.should raise_error(Puppet::Error) - end - end end describe "when finding catalogs" do before do Facter.stubs(:value).returns("whatever") @compiler = Puppet::Resource::Catalog::Compiler.new @name = "me" @node = Puppet::Node.new @name @node.stubs(:merge) Puppet::Node.indirection.stubs(:find).returns @node @request = stub 'request', :key => @name, :node => @name, :options => {} end it "should directly use provided nodes" do Puppet::Node.indirection.expects(:find).never @compiler.expects(:compile).with(@node) @request.stubs(:options).returns(:use_node => @node) @compiler.find(@request) end it "should use the authenticated node name if no request key is provided" do @request.stubs(:key).returns(nil) Puppet::Node.indirection.expects(:find).with(@name).returns(@node) @compiler.expects(:compile).with(@node) @compiler.find(@request) end it "should use the provided node name by default" do @request.expects(:key).returns "my_node" Puppet::Node.indirection.expects(:find).with("my_node").returns @node @compiler.expects(:compile).with(@node) @compiler.find(@request) end it "should fail if no node is passed and none can be found" do Puppet::Node.indirection.stubs(:find).with(@name).returns(nil) proc { @compiler.find(@request) }.should raise_error(ArgumentError) end it "should fail intelligently when searching for a node raises an exception" do Puppet::Node.indirection.stubs(:find).with(@name).raises "eh" proc { @compiler.find(@request) }.should raise_error(Puppet::Error) end it "should pass the found node to the compiler for compiling" do Puppet::Node.indirection.expects(:find).with(@name).returns(@node) config = mock 'config' Puppet::Parser::Compiler.expects(:compile).with(@node) @compiler.find(@request) end it "should extract and save any facts from the request" do Puppet::Node.indirection.expects(:find).with(@name).returns @node @compiler.expects(:extract_facts_from_request).with(@request) Puppet::Parser::Compiler.stubs(:compile) @compiler.find(@request) end it "should return the results of compiling as the catalog" do Puppet::Node.indirection.stubs(:find).returns(@node) config = mock 'config' result = mock 'result' Puppet::Parser::Compiler.expects(:compile).returns result @compiler.find(@request).should equal(result) end it "should benchmark the compile process" do Puppet::Node.indirection.stubs(:find).returns(@node) @compiler.stubs(:networked?).returns(true) @compiler.expects(:benchmark).with do |level, message| level == :notice and message =~ /^Compiled catalog/ end Puppet::Parser::Compiler.stubs(:compile) @compiler.find(@request) end it "should log the benchmark result" do Puppet::Node.indirection.stubs(:find).returns(@node) @compiler.stubs(:networked?).returns(true) Puppet::Parser::Compiler.stubs(:compile) Puppet.expects(:notice).with { |msg| msg =~ /Compiled catalog/ } @compiler.find(@request) end end describe "when extracting facts from the request" do before do Facter.stubs(:value).returns "something" @compiler = Puppet::Resource::Catalog::Compiler.new @request = stub 'request', :options => {} @facts = Puppet::Node::Facts.new('hostname', "fact" => "value", "architecture" => "i386") Puppet::Node::Facts.indirection.stubs(:save).returns(nil) end it "should do nothing if no facts are provided" do Puppet::Node::Facts.indirection.expects(:convert_from).never @request.options[:facts] = nil @compiler.extract_facts_from_request(@request) end it "should use the Facts class to deserialize the provided facts and update the timestamp" do @request.options[:facts_format] = "foo" @request.options[:facts] = "bar" Puppet::Node::Facts.expects(:convert_from).returns @facts @facts.timestamp = Time.parse('2010-11-01') @now = Time.parse('2010-11-02') Time.expects(:now).returns(@now) @compiler.extract_facts_from_request(@request) @facts.timestamp.should == @now end it "should use the provided fact format" do @request.options[:facts_format] = "foo" @request.options[:facts] = "bar" Puppet::Node::Facts.expects(:convert_from).with { |format, text| format == "foo" }.returns @facts @compiler.extract_facts_from_request(@request) end it "should convert the facts into a fact instance and save it" do @request.options[:facts_format] = "foo" @request.options[:facts] = "bar" Puppet::Node::Facts.expects(:convert_from).returns @facts Puppet::Node::Facts.indirection.expects(:save).with(@facts) @compiler.extract_facts_from_request(@request) end end describe "when finding nodes" do before do Facter.stubs(:value).returns("whatever") @compiler = Puppet::Resource::Catalog::Compiler.new @name = "me" @node = mock 'node' @request = stub 'request', :key => @name, :options => {} @compiler.stubs(:compile) end it "should look node information up via the Node class with the provided key" do @node.stubs :merge Puppet::Node.indirection.expects(:find).with(@name).returns(@node) @compiler.find(@request) end end describe "after finding nodes" do before do Puppet.expects(:version).returns(1) Facter.expects(:value).with('fqdn').returns("my.server.com") Facter.expects(:value).with('ipaddress').returns("my.ip.address") @compiler = Puppet::Resource::Catalog::Compiler.new @name = "me" @node = mock 'node' @request = stub 'request', :key => @name, :options => {} @compiler.stubs(:compile) Puppet::Node.indirection.stubs(:find).with(@name).returns(@node) end it "should add the server's Puppet version to the node's parameters as 'serverversion'" do @node.expects(:merge).with { |args| args["serverversion"] == "1" } @compiler.find(@request) end it "should add the server's fqdn to the node's parameters as 'servername'" do @node.expects(:merge).with { |args| args["servername"] == "my.server.com" } @compiler.find(@request) end it "should add the server's IP address to the node's parameters as 'serverip'" do @node.expects(:merge).with { |args| args["serverip"] == "my.ip.address" } @compiler.find(@request) end end describe "when filtering resources" do before :each do Facter.stubs(:value) @compiler = Puppet::Resource::Catalog::Compiler.new @catalog = stub_everything 'catalog' @catalog.stubs(:respond_to?).with(:filter).returns(true) end it "should delegate to the catalog instance filtering" do @catalog.expects(:filter) @compiler.filter(@catalog) end it "should filter out virtual resources" do resource = mock 'resource', :virtual? => true @catalog.stubs(:filter).yields(resource) @compiler.filter(@catalog) end it "should return the same catalog if it doesn't support filtering" do @catalog.stubs(:respond_to?).with(:filter).returns(false) @compiler.filter(@catalog).should == @catalog end it "should return the filtered catalog" do catalog = stub 'filtered catalog' @catalog.stubs(:filter).returns(catalog) @compiler.filter(@catalog).should == catalog end end end