From 5aa4440b6fb8c9199ee549bd8fe0e4afb296c259 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 11 Sep 2007 12:38:56 -0500 Subject: Doing an intermediate commit so rick can look at the work I have done so far. --- lib/puppet/defaults.rb | 2 +- lib/puppet/indirector.rb | 109 ++++++++++++++++++++------ lib/puppet/indirector/facts/yaml.rb | 41 ++++++++++ lib/puppet/indirector/node/external.rb | 51 ++++++++++++ lib/puppet/indirector/node/ldap.rb | 138 +++++++++++++++++++++++++++++++++ lib/puppet/indirector/node/none.rb | 10 +++ lib/puppet/node.rb | 121 ++++++++++++++++++++++++++++- lib/puppet/node/facts.rb | 36 +++++++++ lib/puppet/util/instance_loader.rb | 2 - spec/unit/indirector/facts/yaml.rb | 62 +++++++++++++++ spec/unit/indirector/indirector.rb | 79 +++++++++++++++++++ spec/unit/node/facts.rb | 25 ++++++ spec/unit/node/node.rb | 116 +++++++++++++++++++++++++++ test/lib/puppettest.rb | 16 ++-- 14 files changed, 774 insertions(+), 34 deletions(-) create mode 100644 lib/puppet/indirector/facts/yaml.rb create mode 100644 lib/puppet/indirector/node/external.rb create mode 100644 lib/puppet/indirector/node/ldap.rb create mode 100644 lib/puppet/indirector/node/none.rb create mode 100755 lib/puppet/node/facts.rb create mode 100755 spec/unit/indirector/facts/yaml.rb create mode 100755 spec/unit/indirector/indirector.rb create mode 100755 spec/unit/node/facts.rb create mode 100755 spec/unit/node/node.rb diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 78364e786..4b442d094 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -495,7 +495,7 @@ module Puppet ) self.setdefaults(:facts, - :factstore => ["yaml", + :fact_store => ["yaml", "The backend store to use for client facts."] ) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 0ba538355..b8690d7d5 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -1,25 +1,55 @@ # Manage indirections to termini. They are organized in terms of indirections - # - e.g., configuration, node, file, certificate -- and each indirection has one -# or more terminus types defined. The indirection must have its preferred terminus -# configured via a 'default' in the form of '_terminus'; e.g., -# 'node_terminus = ldap'. +# or more terminus types defined. The indirection is configured via the +# +indirects+ method, which will be called by the class extending itself +# with this module. module Puppet::Indirector + # LAK:FIXME We need to figure out how to handle documentation for the + # different indirection types. + + # A simple class that can function as the base class for indirected types. + class Terminus + require 'puppet/util/docs' + extend Puppet::Util::Docs + end + + # This handles creating the terminus classes. + require 'puppet/util/classgen' + extend Puppet::Util::ClassGen + # This manages reading in all of our files for us and then retrieving # loaded instances. We still have to define the 'newX' method, but this # does all of the rest -- loading, storing, and retrieving by name. require 'puppet/util/instance_loader' - include Puppet::Util::InstanceLoader + extend Puppet::Util::InstanceLoader + + # Register a given indirection type. The classes including this module + # handle creating terminus instances, but the module itself handles + # loading them and managing the classes. + def self.register_indirection(name) + # Set up autoloading of the appropriate termini. + instance_load name, "puppet/indirector/%s" % name + end # Define a new indirection terminus. This method is used by the individual # termini in their separate files. Again, the autoloader takes care of # actually loading these files. - def register_terminus(name, options = {}, &block) - genclass(name, :hash => instance_hash(indirection.name), :attributes => options, :block => block) + # Note that the termini are being registered on the Indirector module, not + # on the classes including the module. This allows a given indirection to + # be used in multiple classes. + def self.register_terminus(indirection, terminus, options = {}, &block) + genclass(terminus, + :prefix => indirection.to_s.capitalize, + :hash => instance_hash(indirection), + :attributes => options, + :block => block, + :parent => options[:parent] || Terminus + ) end # Retrieve a terminus class by indirection and name. - def terminus(name) - loaded_instance(name) + def self.terminus(indirection, terminus) + loaded_instance(indirection, terminus) end # Declare that the including class indirects its methods to @@ -27,12 +57,27 @@ module Puppet::Indirector # default, not the value -- if it's the value, then it gets # evaluated at parse time, which is before the user has had a chance # to override it. - def indirects(indirection, options) - @indirection = indirection - @indirect_terminus = options[:to] + # Options are: + # +:to+: What parameter to use as the name of the indirection terminus. + def indirects(indirection, options = {}) + if defined?(@indirection) + raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection[:name], indirection] + end + options[:name] = indirection + @indirection = options + # Validate the parameter. This requires that indirecting + # classes require 'puppet/defaults', because of ordering issues, + # but it makes problems much easier to debug. + if param_name = options[:to] + begin + name = Puppet[param_name] + rescue + raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, indirection] + end + end # Set up autoloading of the appropriate termini. - autoload "puppet/indirector/%s" % indirection + Puppet::Indirector.register_indirection indirection end # Define methods for each of the HTTP methods. These just point to the @@ -47,30 +92,46 @@ module Puppet::Indirector # those methods. [:get, :post, :put, :delete].each do |method_name| define_method(method_name) do |*args| - begin - terminus.send(method_name, *args) - rescue NoMethodError - raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name] - end + redirect(method_name, *args) end end private + # Create a new terminus instance. - def make_terminus(indirection) + def make_terminus(name) # Load our terminus class. - unless klass = self.class.terminus(indirection, indirection.default) - raise ArgumentError, "Could not find terminus %s for indirection %s" % [indirection.default, indirection] + unless klass = Puppet::Indirector.terminus(@indirection[:name], name) + raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, indirection] end return klass.new end + # Redirect a given HTTP method. + def redirect(method_name, *args) + begin + terminus.send(method_name, *args) + rescue NoMethodError + raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name] + end + end + # Return the singleton terminus for this indirection. - def terminus - unless terminus = @termini[indirection.name] - terminus = @termini[indirection.name] = make_terminus(indirection) + def terminus(name = nil) + @termini ||= {} + # Get the name of the terminus. + unless name + unless param_name = @indirection[:to] + raise ArgumentError, "You must specify an indirection terminus for indirection %s" % @indirection[:name] + end + name = Puppet[param_name] + name = name.intern if name.is_a?(String) + end + + unless @termini[name] + @termini[name] = make_terminus(name) end - terminus + @termini[name] end end diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb new file mode 100644 index 000000000..87860012f --- /dev/null +++ b/lib/puppet/indirector/facts/yaml.rb @@ -0,0 +1,41 @@ +Puppet::Indirector.register_terminus :facts, :yaml do + desc "Store client facts as flat files, serialized using YAML." + + # Get a client's facts. + def get(node) + file = path(node) + + return nil unless FileTest.exists?(file) + + begin + values = YAML::load(File.read(file)) + rescue => detail + Puppet.err "Could not load facts for %s: %s" % [node, detail] + end + + Puppet::Node::Facts.new(node, values) + end + + def initialize + Puppet.config.use(:yamlfacts) + end + + # Store the facts to disk. + def put(facts) + File.open(path(facts.name), "w", 0600) do |f| + begin + f.print YAML::dump(facts.values) + rescue => detail + Puppet.err "Could not write facts for %s: %s" % [facts.name, detail] + end + end + nil + end + + private + + # Return the path to a given node's file. + def path(name) + File.join(Puppet[:yamlfactdir], name + ".yaml") + end +end diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb new file mode 100644 index 000000000..70fc2505a --- /dev/null +++ b/lib/puppet/indirector/node/external.rb @@ -0,0 +1,51 @@ +Puppet::Indirector.register_terminus :node, :external, :fact_merge => true do + desc "Call an external program to get node information." + + include Puppet::Util + # Look for external node definitions. + def get(name) + return nil unless Puppet[:external_nodes] != "none" + + # This is a very cheap way to do this, since it will break on + # commands that have spaces in the arguments. But it's good + # enough for most cases. + external_node_command = Puppet[:external_nodes].split + external_node_command << name + begin + output = Puppet::Util.execute(external_node_command) + rescue Puppet::ExecutionFailure => detail + if $?.exitstatus == 1 + return nil + else + Puppet.err "Could not retrieve external node information for %s: %s" % [name, detail] + end + return nil + end + + if output =~ /\A\s*\Z/ # all whitespace + Puppet.debug "Empty response for %s from external node source" % name + return nil + end + + begin + result = YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } + rescue => detail + raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] + end + + node = newnode(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + if set + return node + else + return nil + end + end +end diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb new file mode 100644 index 000000000..75a912568 --- /dev/null +++ b/lib/puppet/indirector/node/ldap.rb @@ -0,0 +1,138 @@ +Puppet::Indirector.register_terminus :node, :ldap, :fact_merge => true do + desc "Search in LDAP for node configuration information." + + # Look for our node in ldap. + def get(node) + unless ary = ldapsearch(node) + return nil + end + parent, classes, parameters = ary + + while parent + parent, tmpclasses, tmpparams = ldapsearch(parent) + classes += tmpclasses if tmpclasses + tmpparams.each do |param, value| + # Specifically test for whether it's set, so false values are handled + # correctly. + parameters[param] = value unless parameters.include?(param) + end + end + + return newnode(node, :classes => classes, :source => "ldap", :parameters => parameters) + end + + # Find the ldap node, return the class list and parent node specially, + # and everything else in a parameter hash. + def ldapsearch(node) + filter = Puppet[:ldapstring] + classattrs = Puppet[:ldapclassattrs].split("\s*,\s*") + if Puppet[:ldapattrs] == "all" + # A nil value here causes all attributes to be returned. + search_attrs = nil + else + search_attrs = classattrs + Puppet[:ldapattrs].split("\s*,\s*") + end + pattr = nil + if pattr = Puppet[:ldapparentattr] + if pattr == "" + pattr = nil + else + search_attrs << pattr unless search_attrs.nil? + end + end + + if filter =~ /%s/ + filter = filter.gsub(/%s/, node) + end + + parent = nil + classes = [] + parameters = nil + + found = false + count = 0 + + begin + # We're always doing a sub here; oh well. + ldap.search(Puppet[:ldapbase], 2, filter, search_attrs) do |entry| + found = true + if pattr + if values = entry.vals(pattr) + if values.length > 1 + raise Puppet::Error, + "Node %s has more than one parent: %s" % + [node, values.inspect] + end + unless values.empty? + parent = values.shift + end + end + end + + classattrs.each { |attr| + if values = entry.vals(attr) + values.each do |v| classes << v end + end + } + + parameters = entry.to_hash.inject({}) do |hash, ary| + if ary[1].length == 1 + hash[ary[0]] = ary[1].shift + else + hash[ary[0]] = ary[1] + end + hash + end + end + rescue => detail + if count == 0 + # Try reconnecting to ldap + @ldap = nil + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end + + classes.flatten! + + if classes.empty? + classes = nil + end + + if parent or classes or parameters + return parent, classes, parameters + else + return nil + end + end + + private + + # Create an ldap connection. + def ldap + unless defined? @ldap and @ldap + unless Puppet.features.ldap? + raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" + end + begin + if Puppet[:ldapssl] + @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @ldap = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + return @ldap + end +end diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb new file mode 100644 index 000000000..ce188add5 --- /dev/null +++ b/lib/puppet/indirector/node/none.rb @@ -0,0 +1,10 @@ +Puppet::Network::Handler::Node.newnode_source(:none, :fact_merge => true) do + desc "Always return an empty node object. This is the node source you should + use when you don't have some other, functional source you want to use, + as the compiler will not work without this node information." + + # Just return an empty node. + def nodesearch(name) + newnode(name) + end +end diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 2d3ac712e..f04de91c5 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -1,10 +1,129 @@ # A simplistic class for managing the node information itself. class Puppet::Node + # Set up indirection, so that nodes can be looked for in + # the node sources. + require 'puppet/indirector' + extend Puppet::Indirector + + # Use the node source as the indirection terminus. + indirects :node, :to => :node_source + + # Retrieve a node from the node source, with some additional munging + # thrown in for kicks. + # LAK:FIXME Crap. This won't work, because we might have two copies of this class, + # one remote and one local, and we won't know which one should do all of the + # extra crap. + def self.get(key) + return nil unless key + if node = cached?(key) + return node + end + 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) and @source != "none" + 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 + node.names = names + + # Merge the facts into the parameters. + if fact_merge? + node.fact_merge(facts) + end + + cache(node) + + return node + else + return nil + end + end + + private + + # Store the node to make things a bit faster. + def self.cache(node) + @node_cache[node.name] = node + end + + # If the node is cached, return it. + def self.cached?(name) + # Don't use cache when the filetimeout is set to 0 + return false if [0, "0"].include?(Puppet[:filetimeout]) + + if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] + return node + else + return false + end + end + + # Look up the node facts from our fact handler. + def self.node_facts(key) + if facts = Puppet::Node::Facts.get(key) + facts.values + else + {} + end + end + + # Calculate the list of node names we should use for looking + # up our node. + def self.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 + + public + attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names attr_reader :time attr_writer :environment - # Do not return environments tha are empty string, and use + # Do not return environments that are the empty string, and use # explicitly set environments, then facts, then a central env # value. def environment diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb new file mode 100755 index 000000000..eddf44def --- /dev/null +++ b/lib/puppet/node/facts.rb @@ -0,0 +1,36 @@ +# Manage a given node's facts. This either accepts facts and stores them, or +# returns facts for a given node. +class Puppet::Node::Facts + # Set up indirection, so that nodes can be looked for in + # the node sources. + require 'puppet/indirector' + extend Puppet::Indirector + + # Use the node source as the indirection terminus. + indirects :facts, :to => :fact_store + + attr_accessor :name, :values + + def initialize(name, values = {}) + @name = name + @values = values + end + + private + + # FIXME These methods are currently unused. + + # Add internal data to the facts for storage. + def add_internal(facts) + facts = facts.dup + facts[:_puppet_timestamp] = Time.now + facts + end + + # Strip out that internal data. + def strip_internal(facts) + facts = facts.dup + facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } + facts + end +end diff --git a/lib/puppet/util/instance_loader.rb b/lib/puppet/util/instance_loader.rb index 1a64c9c69..bc0567866 100755 --- a/lib/puppet/util/instance_loader.rb +++ b/lib/puppet/util/instance_loader.rb @@ -70,5 +70,3 @@ module Puppet::Util::InstanceLoader instances[name] end end - -# $Id$ diff --git a/spec/unit/indirector/facts/yaml.rb b/spec/unit/indirector/facts/yaml.rb new file mode 100755 index 000000000..45c079a69 --- /dev/null +++ b/spec/unit/indirector/facts/yaml.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector' +require 'puppet/node/facts' +require 'puppettest' + +describe Puppet::Indirector.terminus(:facts, :yaml), " when managing facts" do + # For cleanup mechanisms. + include PuppetTest + + # LAK:FIXME It seems like I really do have to hit the filesystem + # here, since, like, that's what I'm testing. Is there another/better + # way to do this? + before do + @store = Puppet::Indirector.terminus(:facts, :yaml).new + setup # Grr, stupid rspec + Puppet[:yamlfactdir] = tempfile + Dir.mkdir(Puppet[:yamlfactdir]) + end + + it "should store facts in YAML in the yamlfactdir" do + values = {"one" => "two", "three" => "four"} + facts = Puppet::Node::Facts.new("node", values) + @store.put(facts) + + # Make sure the file exists + path = File.join(Puppet[:yamlfactdir], facts.name) + ".yaml" + File.exists?(path).should be_true + + # And make sure it's right + newvals = YAML.load(File.read(path)) + + # We iterate over them, because the store might add extra values. + values.each do |name, value| + newvals[name].should == value + end + end + + it "should retrieve values from disk" do + values = {"one" => "two", "three" => "four"} + + # Create the file. + path = File.join(Puppet[:yamlfactdir], "node") + ".yaml" + File.open(path, "w") do |f| + f.print values.to_yaml + end + + facts = Puppet::Node::Facts.get('node') + facts.should be_instance_of(Puppet::Node::Facts) + + # We iterate over them, because the store might add extra values. + values.each do |name, value| + facts.values[name].should == value + end + end + + after do + teardown + end +end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb new file mode 100755 index 000000000..c4221febb --- /dev/null +++ b/spec/unit/indirector/indirector.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/defaults' +require 'puppet/indirector' + +describe Puppet::Indirector, " when managing indirections" do + before do + @indirector = Object.new + @indirector.send(:extend, Puppet::Indirector) + end + + # LAK:FIXME This seems like multiple tests, but I don't really know how to test one at a time. + it "should accept specification of an indirection terminus via a configuration parameter" do + @indirector.indirects :test, :to => :node_source + Puppet[:node_source] = "test_source" + klass = mock 'terminus_class' + terminus = mock 'terminus' + klass.expects(:new).returns terminus + Puppet::Indirector.expects(:terminus).with(:test, :test_source).returns(klass) + @indirector.send(:terminus).should equal(terminus) + end + + it "should not allow more than one indirection in the same object" do + @indirector.indirects :test + proc { @indirector.indirects :else }.should raise_error(ArgumentError) + end + + it "should allow multiple classes to use the same indirection" do + @indirector.indirects :test + other = Object.new + other.send(:extend, Puppet::Indirector) + proc { other.indirects :test }.should_not raise_error + end +end + +describe Puppet::Indirector, " when managing termini" do + before do + @indirector = Object.new + @indirector.send(:extend, Puppet::Indirector) + end + + it "should should autoload termini from disk" do + Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") + @indirector.indirects :test + end +end + +describe Puppet::Indirector, " when performing indirections" do + before do + @indirector = Object.new + @indirector.send(:extend, Puppet::Indirector) + @indirector.indirects :test, :to => :node_source + + # Set up a fake terminus class that will just be used to spit out + # mock terminus objects. + @terminus_class = mock 'terminus_class' + Puppet::Indirector.stubs(:terminus).with(:test, :test_source).returns(@terminus_class) + Puppet[:node_source] = "test_source" + end + + it "should redirect http methods to the default terminus" do + terminus = mock 'terminus' + terminus.expects(:put).with("myargument") + @terminus_class.expects(:new).returns(terminus) + @indirector.put("myargument") + end + + # Make sure it caches the terminus. + it "should use the same terminus for all indirections" do + terminus = mock 'terminus' + terminus.expects(:put).with("myargument") + terminus.expects(:get).with("other_argument") + @terminus_class.expects(:new).returns(terminus) + @indirector.put("myargument") + @indirector.get("other_argument") + end +end diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb new file mode 100755 index 000000000..c7fc65f38 --- /dev/null +++ b/spec/unit/node/facts.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/node/facts' + +describe Puppet::Node::Facts, " when indirecting" do + before do + Puppet[:fact_store] = "test_store" + @terminus_class = mock 'terminus_class' + @terminus = mock 'terminus' + @terminus_class.expects(:new).returns(@terminus) + Puppet::Indirector.expects(:terminus).with(:facts, :test_store).returns(@terminus_class) + end + + it "should redirect to the specified fact store for retrieval" do + @terminus.expects(:get).with(:my_facts) + Puppet::Node::Facts.get(:my_facts) + end + + it "should redirect to the specified fact store for storage" do + @terminus.expects(:put).with(:my_facts) + Puppet::Node::Facts.put(:my_facts) + end +end diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb new file mode 100755 index 000000000..a6cc1e301 --- /dev/null +++ b/spec/unit/node/node.rb @@ -0,0 +1,116 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Node, " when initializing" do + before do + @node = Puppet::Node.new("testnode") + end + + it "should set the node name" do + @node.name.should == "testnode" + end + + it "should default to an empty parameter hash" do + @node.parameters.should == {} + end + + it "should default to an empty class array" do + @node.classes.should == [] + end + + it "should note its creation time" do + @node.time.should be_instance_of(Time) + end + + it "should accept parameters passed in during initialization" do + params = {"a" => "b"} + @node = Puppet::Node.new("testing", :parameters => params) + @node.parameters.should == params + end + + it "should accept classes passed in during initialization" do + classes = %w{one two} + @node = Puppet::Node.new("testing", :classes => classes) + @node.classes.should == classes + end + + it "should always return classes as an array" do + @node = Puppet::Node.new("testing", :classes => "myclass") + @node.classes.should == ["myclass"] + end + + it "should accept the environment during initialization" do + @node = Puppet::Node.new("testing", :environment => "myenv") + @node.environment.should == "myenv" + end + + it "should accept names passed in" do + @node = Puppet::Node.new("testing", :names => ["myenv"]) + @node.names.should == ["myenv"] + end +end + +describe Puppet::Node, " when returning the environment" do + before do + @node = Puppet::Node.new("testnode") + end + + it "should return the 'environment' fact if present and there is no explicit environment" do + @node.parameters = {"environment" => "myenv"} + @node.environment.should == "myenv" + end + + it "should return the central environment if there is no environment fact nor explicit environment" do + Puppet.config.expects(:[]).with(:environment).returns(:centralenv) + @node.environment.should == :centralenv + end + + it "should not use an explicit environment that is an empty string" do + @node.environment == "" + @node.environment.should be_nil + end + + it "should not use an environment fact that is an empty string" do + @node.parameters = {"environment" => ""} + @node.environment.should be_nil + end + + it "should not use an explicit environment that is an empty string" do + Puppet.config.expects(:[]).with(:environment).returns(nil) + @node.environment.should be_nil + end +end + +describe Puppet::Node, " when merging facts" do + before do + @node = Puppet::Node.new("testnode") + end + + it "should prefer parameters already set on the node over facts from the node" do + @node.parameters = {"one" => "a"} + @node.fact_merge("one" => "c") + @node.parameters["one"].should == "a" + end + + it "should add passed parameters to the parameter list" do + @node.parameters = {"one" => "a"} + @node.fact_merge("two" => "b") + @node.parameters["two"].should == "b" + end +end + +describe Puppet::Node, " when indirecting" do + before do + Puppet[:node_source] = :test_source + @terminus_class = mock 'terminus_class' + @terminus = mock 'terminus' + @terminus_class.expects(:new).returns(@terminus) + Puppet::Indirector.expects(:terminus).with(:node, :test_source).returns(@terminus_class) + end + + it "should redirect to the specified node source" do + @terminus.expects(:get).with(:my_node) + Puppet::Node.get(:my_node) + end +end diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index b56bc563e..45c5b2ed9 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -5,7 +5,12 @@ $LOAD_PATH.unshift(File.expand_path(File.join(File.dirname(__FILE__), '../../lib require 'puppet' require 'mocha' -require 'test/unit' + +# Only load the test/unit class if we're not in the spec directory. +# Else we get the bogus 'no tests, no failures' message. +unless Dir.getwd =~ /spec/ + require 'test/unit' +end # Yay; hackish but it works if ARGV.include?("-d") @@ -141,7 +146,7 @@ module PuppetTest end @configpath = File.join(tmpdir, - self.class.to_s + "configdir" + @@testcount.to_s + "/" + "configdir" + @@testcount.to_s + "/" ) unless defined? $user and $group @@ -197,8 +202,7 @@ module PuppetTest @@tmpfilenum = 1 end - f = File.join(self.tmpdir(), self.class.to_s + "_" + @method_name.to_s + - @@tmpfilenum.to_s) + f = File.join(self.tmpdir(), "tempfile_" + @@tmpfilenum.to_s) @@tmpfiles << f return f end @@ -223,11 +227,11 @@ module PuppetTest when "Darwin": "/private/tmp" when "SunOS": "/var/tmp" else - "/tmp" + "/tmp" end - @tmpdir = File.join(@tmpdir, "puppettesting") + @tmpdir = File.join(@tmpdir, "puppettesting" + Process.pid.to_s) unless File.exists?(@tmpdir) FileUtils.mkdir_p(@tmpdir) -- cgit From 65c1501504dd7e9166176661f9ed9f80300954db Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 11 Sep 2007 16:19:03 -0500 Subject: The Node handler is now obsolete. Node searching is handled through the indirector. I have not yet added the tests for the node handlers themselves, which is next. --- lib/puppet/indirector/node/external.rb | 8 +- lib/puppet/indirector/node/ldap.rb | 12 +- lib/puppet/indirector/node/none.rb | 10 +- lib/puppet/network/handler/configuration.rb | 12 +- lib/puppet/network/handler/node.rb | 242 ----------- lib/puppet/node.rb | 115 +---- lib/puppet/node/searching.rb | 106 +++++ spec/unit/node/searching.rb | 79 ++++ test/network/handler/configuration.rb | 22 +- test/network/handler/node.rb | 640 ---------------------------- 10 files changed, 215 insertions(+), 1031 deletions(-) delete mode 100644 lib/puppet/network/handler/node.rb create mode 100644 lib/puppet/node/searching.rb create mode 100755 spec/unit/node/searching.rb delete mode 100755 test/network/handler/node.rb diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb index 70fc2505a..ed2a8893e 100644 --- a/lib/puppet/indirector/node/external.rb +++ b/lib/puppet/indirector/node/external.rb @@ -1,4 +1,4 @@ -Puppet::Indirector.register_terminus :node, :external, :fact_merge => true do +Puppet::Indirector.register_terminus :node, :external do desc "Call an external program to get node information." include Puppet::Util @@ -33,7 +33,7 @@ Puppet::Indirector.register_terminus :node, :external, :fact_merge => true do raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] end - node = newnode(name) + node = Puppe::Node.new(name) set = false [:parameters, :classes].each do |param| if value = result[param] @@ -42,6 +42,10 @@ Puppet::Indirector.register_terminus :node, :external, :fact_merge => true do end end + if facts = Puppet::Node.facts(name) + node.fact_merge(facts) + end + if set return node else diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb index 75a912568..77be04126 100644 --- a/lib/puppet/indirector/node/ldap.rb +++ b/lib/puppet/indirector/node/ldap.rb @@ -1,9 +1,9 @@ -Puppet::Indirector.register_terminus :node, :ldap, :fact_merge => true do +Puppet::Indirector.register_terminus :node, :ldap do desc "Search in LDAP for node configuration information." # Look for our node in ldap. - def get(node) - unless ary = ldapsearch(node) + def get(name) + unless ary = ldapsearch(name) return nil end parent, classes, parameters = ary @@ -18,7 +18,11 @@ Puppet::Indirector.register_terminus :node, :ldap, :fact_merge => true do end end - return newnode(node, :classes => classes, :source => "ldap", :parameters => parameters) + node = Puppe::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) + if facts = Puppet::Node.facts(name) + node.fact_merge(facts) + end + return node end # Find the ldap node, return the class list and parent node specially, diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb index ce188add5..7143033d9 100644 --- a/lib/puppet/indirector/node/none.rb +++ b/lib/puppet/indirector/node/none.rb @@ -1,10 +1,14 @@ -Puppet::Network::Handler::Node.newnode_source(:none, :fact_merge => true) do +Puppet::Indirector.register_terminus :node, :none do desc "Always return an empty node object. This is the node source you should use when you don't have some other, functional source you want to use, as the compiler will not work without this node information." # Just return an empty node. - def nodesearch(name) - newnode(name) + def get(name) + node = Puppet::Node.new(name) + if facts = Puppet::Node.facts(name) + node.fact_merge(facts) + end + node end end diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb index 2c72d3d2b..372e80325 100644 --- a/lib/puppet/network/handler/configuration.rb +++ b/lib/puppet/network/handler/configuration.rb @@ -30,7 +30,7 @@ class Puppet::Network::Handler # 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 = node_handler.details(key) + unless node = Puppet::Node.search(key) raise Puppet::Error, "Could not find node '%s'" % key end @@ -64,7 +64,7 @@ class Puppet::Network::Handler # Return the configuration version. def version(client = nil, clientip = nil) - if client and node = node_handler.details(client) + if client and node = Puppet::Node.search(client) update_node_check(node) return interpreter.configuration_version(node) else @@ -159,14 +159,6 @@ class Puppet::Network::Handler @interpreter end - # Create a node handler instance for looking up our nodes. - def node_handler - unless defined?(@node_handler) - @node_handler = Puppet::Network::Handler.handler(:node).create - end - @node_handler - 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 diff --git a/lib/puppet/network/handler/node.rb b/lib/puppet/network/handler/node.rb deleted file mode 100644 index c6ccc2eb6..000000000 --- a/lib/puppet/network/handler/node.rb +++ /dev/null @@ -1,242 +0,0 @@ -# Created by Luke A. Kanies on 2007-08-13. -# Copyright (c) 2007. All rights reserved. - -require 'puppet/util' -require 'puppet/node' -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 - desc "Retrieve information about nodes." - - # Create a singleton node handler - def self.create - unless @handler - @handler = new - end - @handler - end - - # 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 - - 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. - instance_load :node_source, 'puppet/node_source' - - attr_reader :source - - # 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) - return nil unless key - if node = cached?(key) - return node - end - 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) and @source != "none" - 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 - node.names = names - - # Merge the facts into the parameters. - if fact_merge? - node.fact_merge(facts) - end - - cache(node) - - 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 - - # We cache node info for speed - @node_cache = {} - 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 - - # Store the node to make things a bit faster. - def cache(node) - @node_cache[node.name] = node - end - - # If the node is cached, return it. - def cached?(name) - # Don't use cache when the filetimeout is set to 0 - return false if [0, "0"].include?(Puppet[:filetimeout]) - - if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] - return node - else - return false - end - end - - # 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) - Puppet::Node.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 diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index f04de91c5..9c5d2d397 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -8,116 +8,11 @@ class Puppet::Node # Use the node source as the indirection terminus. indirects :node, :to => :node_source - # Retrieve a node from the node source, with some additional munging - # thrown in for kicks. - # LAK:FIXME Crap. This won't work, because we might have two copies of this class, - # one remote and one local, and we won't know which one should do all of the - # extra crap. - def self.get(key) - return nil unless key - if node = cached?(key) - return node - end - 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) and @source != "none" - 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 - node.names = names - - # Merge the facts into the parameters. - if fact_merge? - node.fact_merge(facts) - end - - cache(node) - - return node - else - return nil - end - end - - private - - # Store the node to make things a bit faster. - def self.cache(node) - @node_cache[node.name] = node - end - - # If the node is cached, return it. - def self.cached?(name) - # Don't use cache when the filetimeout is set to 0 - return false if [0, "0"].include?(Puppet[:filetimeout]) - - if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] - return node - else - return false - end - end - - # Look up the node facts from our fact handler. - def self.node_facts(key) - if facts = Puppet::Node::Facts.get(key) - facts.values - else - {} - end - end - - # Calculate the list of node names we should use for looking - # up our node. - def self.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 - - public + # Add the node-searching methods. This is what people will actually + # interact with that will find the node with the list of names or + # will search for a default node. + require 'puppet/node/searching' + extend Puppet::Node::Searching attr_accessor :name, :classes, :parameters, :source, :ipaddress, :names attr_reader :time diff --git a/lib/puppet/node/searching.rb b/lib/puppet/node/searching.rb new file mode 100644 index 000000000..3a632d50e --- /dev/null +++ b/lib/puppet/node/searching.rb @@ -0,0 +1,106 @@ +# The module that handles actually searching for nodes. This is only included +# in the Node class, but it's completely stand-alone functionality, so it's +# worth making it a separate module to simplify testing. +module Puppet::Node::Searching + # Retrieve a node from the node source, with some additional munging + # thrown in for kicks. + def search(key) + return nil unless key + if node = cached?(key) + return node + end + 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 = get(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 = get("default") + Puppet.notice "Using default node for %s" % key + end + end + + if node + node.names = names + + cache(node) + + return node + else + return nil + end + end + + private + + # Store the node to make things a bit faster. + def cache(node) + @node_cache ||= {} + @node_cache[node.name] = node + end + + # If the node is cached, return it. + def cached?(name) + # Don't use cache when the filetimeout is set to 0 + return false if [0, "0"].include?(Puppet[:filetimeout]) + @node_cache ||= {} + + if node = @node_cache[name] and Time.now - node.time < Puppet[:filetimeout] + return node + else + return false + end + end + + # Look up the node facts from our fact handler. + def node_facts(key) + if facts = Puppet::Node::Facts.get(key) + facts.values + 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 diff --git a/spec/unit/node/searching.rb b/spec/unit/node/searching.rb new file mode 100755 index 000000000..553822576 --- /dev/null +++ b/spec/unit/node/searching.rb @@ -0,0 +1,79 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/node/searching' +require 'puppet/node/facts' + +describe Puppet::Node::Searching, " when searching for nodes" do + before do + @searcher = Object.new + @searcher.extend(Puppet::Node::Searching) + @facts = Puppet::Node::Facts.new("foo", "hostname" => "yay", "domain" => "domain.com") + @node = Puppet::Node.new("foo") + Puppet::Node::Facts.stubs(:get).with("foo").returns(@facts) + end + + it "should search for the node by its key first" do + names = [] + @searcher.expects(:get).with do |name| + names << name + names == %w{foo} + end.returns(@node) + @searcher.search("foo").should equal(@node) + end + + it "should return the first node found using the generated list of names" do + names = [] + @searcher.expects(:get).with("foo").returns(nil) + @searcher.expects(:get).with("yay.domain.com").returns(@node) + @searcher.search("foo").should equal(@node) + end + + it "should search for the rest of the names inversely by length" do + names = [] + @facts.values["fqdn"] = "longer.than.the.normal.fqdn.com" + @searcher.stubs(:get).with do |name| + names << name + end + @searcher.search("foo") + # Strip off the key + names.shift + + # And the 'default' + names.pop + + length = 100 + names.each do |name| + (name.length < length).should be_true + length = name.length + end + end + + it "should attempt to find a default node if no names are found" do + names = [] + @searcher.stubs(:get).with do |name| + names << name + end.returns(nil) + @searcher.search("foo") + names[-1].should == "default" + end + + it "should cache the nodes" do + @searcher.expects(:get).with("foo").returns(@node) + @searcher.search("foo").should equal(@node) + @searcher.search("foo").should equal(@node) + end + + it "should flush the node cache using the :filetimeout parameter" do + node2 = Puppet::Node.new("foo2") + Puppet[:filetimeout] = -1 + # I couldn't get this to work with :expects + @searcher.stubs(:get).returns(@node, node2).then.raises(ArgumentError) + @searcher.search("foo").should equal(@node) + @searcher.search("foo").should equal(node2) + end + + after do + Puppet.config.clear + end +end diff --git a/test/network/handler/configuration.rb b/test/network/handler/configuration.rb index 072fdc053..0964a4c5e 100755 --- a/test/network/handler/configuration.rb +++ b/test/network/handler/configuration.rb @@ -20,19 +20,6 @@ class TestHandlerConfiguration < Test::Unit::TestCase assert(config.local?, "Config is not considered local after being started that way") end - # Make sure we create the node handler when necessary. - def test_node_handler - config = Config.new - handler = nil - assert_nothing_raised("Could not create node handler") do - handler = config.send(:node_handler) - end - assert_instance_of(Puppet::Network::Handler.handler(:node), handler, "Did not create node handler") - - # Now make sure we get the same object back again - assert_equal(handler.object_id, config.send(:node_handler).object_id, "Did not cache node handler") - end - # Test creation/returning of the interpreter def test_interpreter config = Config.new @@ -170,19 +157,14 @@ class TestHandlerConfiguration < Test::Unit::TestCase def test_version # First try the case where we can't look up the node config = Config.new - handler = Object.new - handler.expects(:details).with(:client).returns(false) - config.expects(:node_handler).returns(handler) + node = Object.new + Puppet::Node.stubs(:search).with(:client).returns(false, node) interp = Object.new assert_instance_of(Bignum, config.version(:client), "Did not return configuration version") # And then when we find the node. config = Config.new - node = Object.new - handler = Object.new - handler.expects(:details).with(:client).returns(node) config.expects(:update_node_check).with(node) - config.expects(:node_handler).returns(handler) interp = Object.new interp.expects(:configuration_version).returns(:version) config.expects(:interpreter).returns(interp) diff --git a/test/network/handler/node.rb b/test/network/handler/node.rb deleted file mode 100755 index 6b8ab9290..000000000 --- a/test/network/handler/node.rb +++ /dev/null @@ -1,640 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'mocha' -require 'puppettest' -require 'puppettest/resourcetesting' -require 'puppettest/parsertesting' -require 'puppettest/servertest' -require 'puppet/network/handler/node' - -module NodeTesting - include PuppetTest - Node = Puppet::Network::Handler::Node - SimpleNode = Puppet::Node - - def mk_node_mapper - # First, make sure our nodesearch command works as we expect - # Make a nodemapper - mapper = tempfile() - ruby = %x{which ruby}.chomp - File.open(mapper, "w") { |f| - f.puts "#!#{ruby} - require 'yaml' - name = ARGV.last.chomp - result = {} - - if name =~ /a/ - result[:parameters] = {'one' => ARGV.last + '1', 'two' => ARGV.last + '2'} - end - - if name =~ /p/ - result['classes'] = [1,2,3].collect { |n| ARGV.last + n.to_s } - end - - puts YAML.dump(result) - " - } - File.chmod(0755, mapper) - mapper - end - - def mk_searcher(name) - searcher = Object.new - searcher.extend(Node.node_source(name)) - searcher.meta_def(:newnode) do |name, *args| - SimpleNode.new(name, *args) - end - searcher - end - - def mk_node_source - @node_info = {} - @node_source = Node.newnode_source(:testing, :fact_merge => true) do - def nodesearch(key) - if info = @node_info[key] - SimpleNode.new(info) - else - nil - end - end - end - Puppet[:node_source] = "testing" - - cleanup { Node.rm_node_source(:testing) } - end -end - -class TestNodeHandler < Test::Unit::TestCase - include NodeTesting - - def setup - super - mk_node_source - end - - # Make sure that the handler includes the appropriate - # node source. - def test_initialize - # First try it when passing in the node source - handler = nil - assert_nothing_raised("Could not specify a node source") do - handler = Node.new(:Source => :testing) - end - assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source") - - # Now use the Puppet[:node_source] - Puppet[:node_source] = "testing" - assert_nothing_raised("Could not specify a node source") do - handler = Node.new() - end - assert(handler.metaclass.included_modules.include?(@node_source), "Handler did not include node source") - - # And make sure we throw an exception when an invalid node source is used - assert_raise(ArgumentError, "Accepted an invalid node source") do - handler = Node.new(:Source => "invalid") - end - end - - # Make sure we can find and we cache a fact handler. - def test_fact_handler - handler = Node.new - fhandler = nil - assert_nothing_raised("Could not retrieve the fact handler") do - fhandler = handler.send(:fact_handler) - end - assert_instance_of(Puppet::Network::Handler::Facts, fhandler, "Did not get a fact handler back") - - # Now call it again, making sure we're caching the value. - fhandler2 = nil - assert_nothing_raised("Could not retrieve the fact handler") do - fhandler2 = handler.send(:fact_handler) - end - assert_instance_of(Puppet::Network::Handler::Facts, fhandler2, "Did not get a fact handler on the second run") - assert_equal(fhandler.object_id, fhandler2.object_id, "Did not cache fact handler") - end - - # Make sure we can get node facts from the fact handler. - def test_node_facts - # Check the case where we find the node. - handler = Node.new - fhandler = handler.send(:fact_handler) - fhandler.expects(:get).with("present").returns("a" => "b") - - result = nil - assert_nothing_raised("Could not get facts from fact handler") do - result = handler.send(:node_facts, "present") - end - assert_equal({"a" => "b"}, result, "Did not get correct facts back") - - # Now try the case where the fact handler knows nothing about our host - fhandler.expects(:get).with('missing').returns(nil) - result = nil - assert_nothing_raised("Could not get facts from fact handler when host is missing") do - result = handler.send(:node_facts, "missing") - end - assert_equal({}, result, "Did not get empty hash when no facts are known") - end - - # Test our simple shorthand - def test_newnode - SimpleNode.expects(:new).with("stuff") - handler = Node.new - handler.send(:newnode, "stuff") - end - - # Make sure we can build up the correct node names to search for - def test_node_names - handler = Node.new - - # Verify that the handler asks for the facts if we don't pass them in - handler.expects(:node_facts).with("testing").returns({}) - handler.send(:node_names, "testing") - - handler = Node.new - # Test it first with no parameters - assert_equal(%w{testing}, handler.send(:node_names, "testing"), "Node names did not default to an array including just the node name") - - # Now test it with a fully qualified name - assert_equal(%w{testing.domain.com testing}, handler.send(:node_names, "testing.domain.com"), - "Fully qualified names did not get turned into multiple names, longest first") - - # And try it with a short name + domain fact - assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "hostname" => "host"), - "The domain fact was not used to build up an fqdn") - - # And with an fqdn - assert_equal(%w{testing host.domain.com host}, handler.send(:node_names, "testing", "fqdn" => "host.domain.com"), - "The fqdn was not used") - - # And make sure the fqdn beats the domain - assert_equal(%w{testing host.other.com host}, handler.send(:node_names, "testing", "domain" => "domain.com", "fqdn" => "host.other.com"), - "The domain was used in preference to the fqdn") - end - - # Make sure we can retrieve a whole node by name. - def test_details_when_we_find_nodes - handler = Node.new - - # Make sure we get the facts first - handler.expects(:node_facts).with("host").returns(:facts) - - # Find the node names - handler.expects(:node_names).with("host", :facts).returns(%w{a b c}) - - # Iterate across them - handler.expects(:nodesearch).with("a").returns(nil) - handler.expects(:nodesearch).with("b").returns(nil) - - # Create an example node to return - node = SimpleNode.new("host") - - # Make sure its source is set - node.expects(:source=).with(handler.source) - - # And that the names are retained - node.expects(:names=).with(%w{a b c}) - - # And make sure we actually get it back - handler.expects(:nodesearch).with("c").returns(node) - - handler.expects(:fact_merge?).returns(true) - - # Make sure we merge the facts with the node's parameters. - node.expects(:fact_merge).with(:facts) - - # Now call the method - result = nil - assert_nothing_raised("could not call 'details'") do - result = handler.details("host") - end - assert_equal(node, result, "Did not get correct node back") - end - - # But make sure we pass through to creating default nodes when appropriate. - def test_details_using_default_node - handler = Node.new - - # Make sure we get the facts first - handler.expects(:node_facts).with("host").returns(:facts) - - # Find the node names - handler.expects(:node_names).with("host", :facts).returns([]) - - # Create an example node to return - node = SimpleNode.new("host") - - # Make sure its source is set - node.expects(:source=).with(handler.source) - - # And make sure we actually get it back - handler.expects(:nodesearch).with("default").returns(node) - - # This time, have it return false - handler.expects(:fact_merge?).returns(false) - - # And because fact_merge was false, we don't merge them. - node.expects(:fact_merge).never - - # Now call the method - result = nil - assert_nothing_raised("could not call 'details'") do - result = handler.details("host") - end - assert_equal(node, result, "Did not get correct node back") - end - - # Make sure our handler behaves rationally when it comes to getting environment data. - def test_environment - # What happens when we can't find the node - handler = Node.new - handler.expects(:details).with("fake").returns(nil) - - result = nil - assert_nothing_raised("Could not call 'Node.environment'") do - result = handler.environment("fake") - end - assert_nil(result, "Got an environment for a node we could not find") - - # Now for nodes we can find - handler = Node.new - node = SimpleNode.new("fake") - handler.expects(:details).with("fake").returns(node) - node.expects(:environment).returns("dev") - - result = nil - assert_nothing_raised("Could not call 'Node.environment'") do - result = handler.environment("fake") - end - assert_equal("dev", result, "Did not get environment back") - end - - # Make sure our handler behaves rationally when it comes to getting parameter data. - def test_parameters - # What happens when we can't find the node - handler = Node.new - handler.expects(:details).with("fake").returns(nil) - - result = nil - assert_nothing_raised("Could not call 'Node.parameters'") do - result = handler.parameters("fake") - end - assert_nil(result, "Got parameters for a node we could not find") - - # Now for nodes we can find - handler = Node.new - node = SimpleNode.new("fake") - handler.expects(:details).with("fake").returns(node) - node.expects(:parameters).returns({"a" => "b"}) - - result = nil - assert_nothing_raised("Could not call 'Node.parameters'") do - result = handler.parameters("fake") - end - assert_equal({"a" => "b"}, result, "Did not get parameters back") - end - - def test_classes - # What happens when we can't find the node - handler = Node.new - handler.expects(:details).with("fake").returns(nil) - - result = nil - assert_nothing_raised("Could not call 'Node.classes'") do - result = handler.classes("fake") - end - assert_nil(result, "Got classes for a node we could not find") - - # Now for nodes we can find - handler = Node.new - node = SimpleNode.new("fake") - handler.expects(:details).with("fake").returns(node) - node.expects(:classes).returns(%w{yay foo}) - - result = nil - assert_nothing_raised("Could not call 'Node.classes'") do - result = handler.classes("fake") - end - assert_equal(%w{yay foo}, result, "Did not get classes back") - end - - # We reuse the filetimeout for the node caching timeout. - def test_node_caching - handler = Node.new - - node = Object.new - node.metaclass.instance_eval do - attr_accessor :time, :name - end - node.time = Time.now - node.name = "yay" - - # Make sure caching works normally - assert_nothing_raised("Could not cache node") do - handler.send(:cache, node) - end - assert_equal(node.object_id, handler.send(:cached?, "yay").object_id, "Did not get node back from the cache") - - # And that it's returned if we ask for it, instead of creating a new node. - assert_equal(node.object_id, handler.details("yay").object_id, "Did not use cached node") - - # Now set the node's time to be a long time ago - node.time = Time.now - 50000 - assert(! handler.send(:cached?, "yay"), "Timed-out node was returned from cache") - end -end - -# Test our configuration object. -class TestNodeSources < Test::Unit::TestCase - include NodeTesting - - def test_node_sources - mod = nil - assert_nothing_raised("Could not add new search type") do - mod = Node.newnode_source(:testing) do - def nodesearch(name) - end - end - end - assert_equal(mod, Node.node_source(:testing), "Did not get node_source back") - - cleanup do - Node.rm_node_source(:testing) - assert(! Node.const_defined?("Testing"), "Did not remove constant") - end - end - - def test_external_node_source - source = Node.node_source(:external) - assert(source, "Could not find external node source") - mapper = mk_node_mapper - searcher = mk_searcher(:external) - assert(searcher.fact_merge?, "External node source does not merge facts") - - # Make sure it gives the right response - assert_equal({'classes' => %w{apple1 apple2 apple3}, :parameters => {"one" => "apple1", "two" => "apple2"}}, - YAML.load(%x{#{mapper} apple})) - - # First make sure we get nil back by default - assert_nothing_raised { - assert_nil(searcher.nodesearch("apple"), - "Interp#nodesearch_external defaulted to a non-nil response") - } - assert_nothing_raised { Puppet[:external_nodes] = mapper } - - node = nil - # Both 'a' and 'p', so we get classes and parameters - assert_nothing_raised { node = searcher.nodesearch("apple") } - assert_equal("apple", node.name, "node name was not set correctly for apple") - assert_equal(%w{apple1 apple2 apple3}, node.classes, "node classes were not set correctly for apple") - assert_equal( {"one" => "apple1", "two" => "apple2"}, node.parameters, "node parameters were not set correctly for apple") - - # A 'p' but no 'a', so we only get classes - assert_nothing_raised { node = searcher.nodesearch("plum") } - assert_equal("plum", node.name, "node name was not set correctly for plum") - assert_equal(%w{plum1 plum2 plum3}, node.classes, "node classes were not set correctly for plum") - assert_equal({}, node.parameters, "node parameters were not set correctly for plum") - - # An 'a' but no 'p', so we only get parameters. - assert_nothing_raised { node = searcher.nodesearch("guava")} # no p's, thus no classes - assert_equal("guava", node.name, "node name was not set correctly for guava") - assert_equal([], node.classes, "node classes were not set correctly for guava") - assert_equal({"one" => "guava1", "two" => "guava2"}, node.parameters, "node parameters were not set correctly for guava") - - assert_nothing_raised { node = searcher.nodesearch("honeydew")} # neither, thus nil - assert_nil(node) - end - - # Make sure a nodesearch with arguments works - def test_nodesearch_external_arguments - mapper = mk_node_mapper - Puppet[:external_nodes] = "#{mapper} -s something -p somethingelse" - searcher = mk_searcher(:external) - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create node") - end - - # A wrapper test, to make sure we're correctly calling the external search method. - def test_nodesearch_external_functional - mapper = mk_node_mapper - searcher = mk_searcher(:external) - - Puppet[:external_nodes] = mapper - - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create node") - end - - # This can stay in the main test suite because it doesn't actually use ldapsearch, - # it just overrides the method so it behaves as though it were hitting ldap. - def test_ldap_nodesearch - source = Node.node_source(:ldap) - assert(source, "Could not find ldap node source") - searcher = mk_searcher(:ldap) - assert(searcher.fact_merge?, "LDAP node source does not merge facts") - - nodetable = {} - - # Override the ldapsearch definition, so we don't have to actually set it up. - searcher.meta_def(:ldapsearch) do |name| - nodetable[name] - end - - # Make sure we get nothing for nonexistent hosts - node = nil - assert_nothing_raised do - node = searcher.nodesearch("nosuchhost") - end - - assert_nil(node, "Got a node for a non-existent host") - - # Now add a base node with some classes and parameters - nodetable["base"] = [nil, %w{one two}, {"base" => "true"}] - - assert_nothing_raised do - node = searcher.nodesearch("base") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("base", node.name, "node name was not set") - - assert_equal(%w{one two}, node.classes, "node classes were not set") - assert_equal({"base" => "true"}, node.parameters, "node parameters were not set") - - # Now use a different with this as the base - nodetable["middle"] = ["base", %w{three}, {"center" => "boo"}] - assert_nothing_raised do - node = searcher.nodesearch("middle") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("middle", node.name, "node name was not set") - - assert_equal(%w{one two three}.sort, node.classes.sort, "node classes were not set correctly with a parent node") - assert_equal({"base" => "true", "center" => "boo"}, node.parameters, "node parameters were not set correctly with a parent node") - - # And one further, to make sure we fully recurse - nodetable["top"] = ["middle", %w{four five}, {"master" => "far"}] - assert_nothing_raised do - node = searcher.nodesearch("top") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("top", node.name, "node name was not set") - - assert_equal(%w{one two three four five}.sort, node.classes.sort, "node classes were not set correctly with the top node") - assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node") - end - - # Make sure we always get a node back from the 'none' nodesource. - def test_nodesource_none - source = Node.node_source(:none) - assert(source, "Could not find 'none' node source") - searcher = mk_searcher(:none) - assert(searcher.fact_merge?, "'none' node source does not merge facts") - - # Run a couple of node names through it - node = nil - %w{192.168.0.1 0:0:0:3:a:f host host.domain.com}.each do |name| - assert_nothing_raised("Could not create an empty node with name '%s'" % name) do - node = searcher.nodesearch(name) - end - assert_instance_of(SimpleNode, node, "Did not get a simple node back for %s" % name) - assert_equal(name, node.name, "Name was not set correctly") - end - end -end - -class LdapNodeTest < PuppetTest::TestCase - include NodeTesting - include PuppetTest::ServerTest - include PuppetTest::ParserTesting - include PuppetTest::ResourceTesting - AST = Puppet::Parser::AST - confine "LDAP is not available" => Puppet.features.ldap? - confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" - - def ldapconnect - - @ldap = LDAP::Conn.new("ldap", 389) - @ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) - @ldap.simple_bind("", "") - - return @ldap - end - - def ldaphost(name) - node = Puppet::Node.new(name) - parent = nil - found = false - @ldap.search( "ou=hosts, dc=madstop, dc=com", 2, - "(&(objectclass=puppetclient)(cn=%s))" % name - ) do |entry| - node.classes = entry.vals("puppetclass") || [] - node.parameters = entry.to_hash.inject({}) do |hash, ary| - if ary[1].length == 1 - hash[ary[0]] = ary[1].shift - else - hash[ary[0]] = ary[1] - end - hash - end - parent = node.parameters["parentnode"] - found = true - end - raise "Could not find node %s" % name unless found - - return node, parent - end - - def test_ldapsearch - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - - ldapconnect() - - # Make sure we get nil and nil back when we search for something missing - parent, classes, parameters = nil - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("nosuchhost") - end - - assert_nil(parent, "Got a parent for a non-existent host") - assert_nil(classes, "Got classes for a non-existent host") - - # Make sure we can find 'culain' in ldap - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("culain") - end - - node, realparent = ldaphost("culain") - assert_equal(realparent, parent, "did not get correct parent node from ldap") - assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") - assert_equal(node.parameters, parameters, "did not get correct ldap parameters from ldap") - - # Now compare when we specify the attributes to get. - Puppet[:ldapattrs] = "cn" - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("culain") - end - assert_equal(realparent, parent, "did not get correct parent node from ldap") - assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") - - list = %w{cn puppetclass parentnode dn} - should = node.parameters.inject({}) { |h, a| h[a[0]] = a[1] if list.include?(a[0]); h } - assert_equal(should, parameters, "did not get correct ldap parameters from ldap") - end -end - -class LdapReconnectTests < PuppetTest::TestCase - include NodeTesting - include PuppetTest::ServerTest - include PuppetTest::ParserTesting - include PuppetTest::ResourceTesting - AST = Puppet::Parser::AST - confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") - - def test_ldapreconnect - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - hostname = "culain.madstop.com" - - # look for our host - assert_nothing_raised { - parent, classes = searcher.nodesearch(hostname) - } - - # Now restart ldap - system("/etc/init.d/slapd restart 2>/dev/null >/dev/null") - sleep(1) - - # and look again - assert_nothing_raised { - parent, classes = searcher.nodesearch(hostname) - } - - # Now stop ldap - system("/etc/init.d/slapd stop 2>/dev/null >/dev/null") - cleanup do - system("/etc/init.d/slapd start 2>/dev/null >/dev/null") - end - - # And make sure we actually fail here - assert_raise(Puppet::Error) { - parent, classes = searcher.nodesearch(hostname) - } - end -end -- cgit From 3b3065bc73e409874a9e6f1be755754fb2b226bf Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 11 Sep 2007 18:48:30 -0500 Subject: Refactoring the feature support so it loads libraries when a feature is asked about, rather than when it is defined. --- lib/puppet/util/feature.rb | 53 +++++++++++++++++++++++++++------------------- test/util/features.rb | 2 -- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/puppet/util/feature.rb b/lib/puppet/util/feature.rb index 30c38e286..2669d1ab1 100644 --- a/lib/puppet/util/feature.rb +++ b/lib/puppet/util/feature.rb @@ -16,8 +16,7 @@ class Puppet::Util::Feature if self.class.respond_to?(method) raise ArgumentError, "Feature %s is already defined" % name end - - result = true + if block_given? begin result = yield @@ -25,33 +24,21 @@ class Puppet::Util::Feature warn "Failed to load feature test for %s: %s" % [name, detail] result = false end - end - - if ary = options[:libs] - ary = [ary] unless ary.is_a?(Array) - - ary.each do |lib| - unless lib.is_a?(String) - raise ArgumentError, "Libraries must be passed as strings not %s" % lib.class - end - - begin - require lib - rescue Exception - Puppet.debug "Failed to load library '%s' for feature '%s'" % [lib, name] - result = false - end - end + @results[name] = result end meta_def(method) do - result + unless @results.include?(name) + @results[name] = test(name, options) + end + @results[name] end end # Create a new feature collection. def initialize(path) @path = path + @results = {} @loader = Puppet::Util::Autoload.new(self, @path) end @@ -71,6 +58,28 @@ class Puppet::Util::Feature return false end end -end -# $Id$ + # Actually test whether the feature is present. We only want to test when + # someone asks for the feature, so we don't unnecessarily load + # files. + def test(name, options) + result = true + if ary = options[:libs] + ary = [ary] unless ary.is_a?(Array) + + ary.each do |lib| + unless lib.is_a?(String) + raise ArgumentError, "Libraries must be passed as strings not %s" % lib.class + end + + begin + require lib + rescue Exception + Puppet.debug "Failed to load library '%s' for feature '%s'" % [lib, name] + result = false + end + end + end + result + end +end diff --git a/test/util/features.rb b/test/util/features.rb index 14e93c537..1e5858877 100755 --- a/test/util/features.rb +++ b/test/util/features.rb @@ -93,5 +93,3 @@ class TestFeatures < Test::Unit::TestCase end end end - -# $Id$ -- cgit From 1459c507ddccff2a2a6fbadd4c880c023b5e9893 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 11 Sep 2007 19:21:49 -0500 Subject: Adding setup/teardown hooks to rspec, so we can use test/unit methods --- spec/spec_helper.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index aa56fd93e..a4171bb07 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -10,4 +10,11 @@ require 'puppettest' Spec::Runner.configure do |config| config.mock_with :mocha + config.prepend_before :each do + setup() if respond_to? :setup + end + + config.prepend_after :each do + teardown() if respond_to? :teardown + end end -- cgit From a6fe70054f4fb3efe4d558ffdd244917ca1c6f9c Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 12 Sep 2007 15:32:25 -0500 Subject: Another intermediate commit. The node and fact classes are now functional and are used instead of the network handlers, which have been removed. There are some failing tests as a result, but I want to get this code committed before I massage the rest of the system to make it work again. --- lib/puppet/indirector.rb | 69 +++----- lib/puppet/indirector/facts/yaml.rb | 2 +- lib/puppet/indirector/indirection.rb | 74 +++++++++ lib/puppet/indirector/node/external.rb | 78 ++++++--- lib/puppet/indirector/node/ldap.rb | 6 +- lib/puppet/indirector/node/none.rb | 6 +- lib/puppet/network/handler/configuration.rb | 2 +- lib/puppet/network/handler/facts.rb | 68 -------- lib/puppet/network/handler/fileserver.rb | 10 +- lib/puppet/network/handler/master.rb | 9 +- lib/puppet/node.rb | 17 +- lib/puppet/node/facts.rb | 22 +-- spec/Rakefile | 6 + spec/lib/spec/dsl/behaviour.rb | 6 +- spec/lib/spec/runner/behaviour_runner.rb | 2 + spec/spec_helper.rb | 2 +- spec/unit/indirector/facts/yaml.rb | 2 +- spec/unit/indirector/indirection.rb | 57 +++++++ spec/unit/indirector/indirector.rb | 34 +--- spec/unit/indirector/node/external.rb | 119 ++++++++++++++ spec/unit/indirector/node/ldap.rb | 243 ++++++++++++++++++++++++++++ spec/unit/indirector/node/none.rb | 32 ++++ spec/unit/node/facts.rb | 25 ++- spec/unit/node/node.rb | 20 ++- spec/unit/other/node.rb | 101 ------------ spec/unit/util/config.rb | 56 ++++--- test/language/snippets.rb | 7 +- test/lib/puppettest.rb | 1 + test/lib/puppettest/parsertesting.rb | 1 - test/lib/puppettest/runnable_test.rb | 30 ++++ test/lib/puppettest/testcase.rb | 23 +-- test/network/handler/configuration.rb | 4 +- test/network/handler/facts.rb | 112 ------------- test/network/handler/master.rb | 5 + 34 files changed, 776 insertions(+), 475 deletions(-) create mode 100644 lib/puppet/indirector/indirection.rb delete mode 100755 lib/puppet/network/handler/facts.rb create mode 100755 spec/unit/indirector/indirection.rb create mode 100755 spec/unit/indirector/node/external.rb create mode 100755 spec/unit/indirector/node/ldap.rb create mode 100755 spec/unit/indirector/node/none.rb delete mode 100755 spec/unit/other/node.rb create mode 100644 test/lib/puppettest/runnable_test.rb delete mode 100755 test/network/handler/facts.rb diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index b8690d7d5..704039abc 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -11,8 +11,20 @@ module Puppet::Indirector class Terminus require 'puppet/util/docs' extend Puppet::Util::Docs + + class << self + attr_accessor :name, :indirection + end + def name + self.class.name + end + def indirection + self.class.indirection + end end + require 'puppet/indirector/indirection' + # This handles creating the terminus classes. require 'puppet/util/classgen' extend Puppet::Util::ClassGen @@ -38,13 +50,15 @@ module Puppet::Indirector # on the classes including the module. This allows a given indirection to # be used in multiple classes. def self.register_terminus(indirection, terminus, options = {}, &block) - genclass(terminus, + klass = genclass(terminus, :prefix => indirection.to_s.capitalize, :hash => instance_hash(indirection), :attributes => options, :block => block, :parent => options[:parent] || Terminus ) + klass.indirection = indirection + klass.name = terminus end # Retrieve a terminus class by indirection and name. @@ -61,23 +75,14 @@ module Puppet::Indirector # +:to+: What parameter to use as the name of the indirection terminus. def indirects(indirection, options = {}) if defined?(@indirection) - raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection[:name], indirection] + raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection.name, indirection] end - options[:name] = indirection - @indirection = options + @indirection = Indirection.new(indirection, options) - # Validate the parameter. This requires that indirecting - # classes require 'puppet/defaults', because of ordering issues, - # but it makes problems much easier to debug. - if param_name = options[:to] - begin - name = Puppet[param_name] - rescue - raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, indirection] - end - end # Set up autoloading of the appropriate termini. Puppet::Indirector.register_indirection indirection + + return @indirection end # Define methods for each of the HTTP methods. These just point to the @@ -98,40 +103,16 @@ module Puppet::Indirector private - - # Create a new terminus instance. - def make_terminus(name) - # Load our terminus class. - unless klass = Puppet::Indirector.terminus(@indirection[:name], name) - raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, indirection] - end - return klass.new - end - # Redirect a given HTTP method. def redirect(method_name, *args) begin - terminus.send(method_name, *args) - rescue NoMethodError - raise ArgumentError, "Indirection category %s does not respond to REST method %s" % [indirection, method_name] - end - end - - # Return the singleton terminus for this indirection. - def terminus(name = nil) - @termini ||= {} - # Get the name of the terminus. - unless name - unless param_name = @indirection[:to] - raise ArgumentError, "You must specify an indirection terminus for indirection %s" % @indirection[:name] + @indirection.terminus.send(method_name, *args) + rescue NoMethodError => detail + if Puppet[:trace] + puts detail.backtrace end - name = Puppet[param_name] - name = name.intern if name.is_a?(String) - end - - unless @termini[name] - @termini[name] = make_terminus(name) + raise ArgumentError, "The %s terminus of the %s indirection failed to respond to %s: %s" % + [@indirection.terminus.name, @indirection.name, method_name, detail] end - @termini[name] end end diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb index 87860012f..f29ea8ebc 100644 --- a/lib/puppet/indirector/facts/yaml.rb +++ b/lib/puppet/indirector/facts/yaml.rb @@ -21,7 +21,7 @@ Puppet::Indirector.register_terminus :facts, :yaml do end # Store the facts to disk. - def put(facts) + def post(facts) File.open(path(facts.name), "w", 0600) do |f| begin f.print YAML::dump(facts.values) diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb new file mode 100644 index 000000000..7a4c4bd55 --- /dev/null +++ b/lib/puppet/indirector/indirection.rb @@ -0,0 +1,74 @@ +# An actual indirection. +class Puppet::Indirector::Indirection + @@indirections = [] + + # Clear all cached termini from all indirections. + def self.clear_cache + @@indirections.each { |ind| ind.clear_cache } + end + + attr_accessor :name, :termini + attr_reader :to + + # Clear our cached list of termini. + # This is only used for testing. + def clear_cache + @termini.clear + end + + # This is only used for testing. + def delete + @@indirections.delete(self) if @@indirections.include?(self) + end + + def initialize(name, options = {}) + @name = name + options.each do |name, value| + begin + send(name.to_s + "=", value) + rescue NoMethodError + raise ArgumentError, "%s is not a valid Indirection parameter" % name + end + end + @termini = {} + @@indirections << self + end + + # Return the singleton terminus for this indirection. + def terminus(name = nil) + # Get the name of the terminus. + unless name + unless param_name = self.to + raise ArgumentError, "You must specify an indirection terminus for indirection %s" % self.name + end + name = Puppet[param_name] + name = name.intern if name.is_a?(String) + end + + unless @termini[name] + @termini[name] = make_terminus(name) + end + @termini[name] + end + + # Validate the parameter. This requires that indirecting + # classes require 'puppet/defaults', because of ordering issues, + # but it makes problems much easier to debug. + def to=(param_name) + unless Puppet.config.valid?(param_name) + raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, self.name] + end + @to = param_name + end + + private + + # Create a new terminus instance. + def make_terminus(name) + # Load our terminus class. + unless klass = Puppet::Indirector.terminus(self.name, name) + raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, self.name] + end + return klass.new + end +end diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb index ed2a8893e..13cd265fb 100644 --- a/lib/puppet/indirector/node/external.rb +++ b/lib/puppet/indirector/node/external.rb @@ -1,18 +1,66 @@ +require 'puppet/node/facts' + Puppet::Indirector.register_terminus :node, :external do desc "Call an external program to get node information." include Puppet::Util + + # Proxy the execution, so it's easier to test. + def execute(command) + Puppet::Util.execute(command) + end + # Look for external node definitions. def get(name) - return nil unless Puppet[:external_nodes] != "none" + unless Puppet[:external_nodes] != "none" + raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node source" + end + unless Puppet[:external_nodes][0] == File::SEPARATOR[0] + raise ArgumentError, "You must set the 'external_nodes' parameter to a fully qualified command" + end + + # Run the command. + unless output = query(name) + return nil + end + + # Translate the output to ruby. + result = translate(name, output) + + return create_node(name, result) + end + + private + + # Turn our outputted objects into a Puppet::Node instance. + def create_node(name, result) + node = Puppet::Node.new(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + if set + node.fact_merge + return node + else + return nil + end + end + + # Call the external command and see if it returns our output. + def query(name) # This is a very cheap way to do this, since it will break on # commands that have spaces in the arguments. But it's good # enough for most cases. external_node_command = Puppet[:external_nodes].split external_node_command << name begin - output = Puppet::Util.execute(external_node_command) + output = execute(external_node_command) rescue Puppet::ExecutionFailure => detail if $?.exitstatus == 1 return nil @@ -25,31 +73,17 @@ Puppet::Indirector.register_terminus :node, :external do if output =~ /\A\s*\Z/ # all whitespace Puppet.debug "Empty response for %s from external node source" % name return nil + else + return output end + end + # Translate the yaml string into Ruby objects. + def translate(name, output) begin - result = YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } + YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } rescue => detail raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] end - - node = Puppe::Node.new(name) - set = false - [:parameters, :classes].each do |param| - if value = result[param] - node.send(param.to_s + "=", value) - set = true - end - end - - if facts = Puppet::Node.facts(name) - node.fact_merge(facts) - end - - if set - return node - else - return nil - end end end diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb index 77be04126..fb60cad31 100644 --- a/lib/puppet/indirector/node/ldap.rb +++ b/lib/puppet/indirector/node/ldap.rb @@ -18,10 +18,8 @@ Puppet::Indirector.register_terminus :node, :ldap do end end - node = Puppe::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) - if facts = Puppet::Node.facts(name) - node.fact_merge(facts) - end + node = Puppet::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) + node.fact_merge return node end diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb index 7143033d9..2b326968e 100644 --- a/lib/puppet/indirector/node/none.rb +++ b/lib/puppet/indirector/node/none.rb @@ -1,3 +1,5 @@ +require 'puppet/node/facts' + Puppet::Indirector.register_terminus :node, :none do desc "Always return an empty node object. This is the node source you should use when you don't have some other, functional source you want to use, @@ -6,9 +8,7 @@ Puppet::Indirector.register_terminus :node, :none do # Just return an empty node. def get(name) node = Puppet::Node.new(name) - if facts = Puppet::Node.facts(name) - node.fact_merge(facts) - end + node.fact_merge node end end diff --git a/lib/puppet/network/handler/configuration.rb b/lib/puppet/network/handler/configuration.rb index 372e80325..2df1b3ab4 100644 --- a/lib/puppet/network/handler/configuration.rb +++ b/lib/puppet/network/handler/configuration.rb @@ -79,7 +79,7 @@ class Puppet::Network::Handler # 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.fact_merge(@server_facts) + node.merge(@server_facts) # Add any specified classes to the node's class list. if classes = @options[:Classes] diff --git a/lib/puppet/network/handler/facts.rb b/lib/puppet/network/handler/facts.rb deleted file mode 100755 index 4767e8be4..000000000 --- a/lib/puppet/network/handler/facts.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'yaml' -require 'puppet/util/fact_store' - -class Puppet::Network::Handler - # Receive logs from remote hosts. - class Facts < Handler - desc "An interface for storing and retrieving client facts. Currently only - used internally by Puppet." - - @interface = XMLRPC::Service::Interface.new("facts") { |iface| - iface.add_method("void set(string, string)") - iface.add_method("string get(string)") - iface.add_method("integer store_date(string)") - } - - def initialize(hash = {}) - super - - backend = Puppet[:factstore] - - unless klass = Puppet::Util::FactStore.store(backend) - raise Puppet::Error, "Could not find fact store %s" % backend - end - - @backend = klass.new - end - - # Get the facts from our back end. - def get(node) - if facts = @backend.get(node) - return strip_internal(facts) - else - return nil - end - end - - # Set the facts in the backend. - def set(node, facts) - @backend.set(node, add_internal(facts)) - nil - end - - # Retrieve a client's storage date. - def store_date(node) - if facts = get(node) - facts[:_puppet_timestamp].to_i - else - nil - end - end - - private - - # Add internal data to the facts for storage. - def add_internal(facts) - facts = facts.dup - facts[:_puppet_timestamp] = Time.now - facts - end - - # Strip out that internal data. - def strip_internal(facts) - facts = facts.dup - facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } - facts - end - end -end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index a429412d2..993e9d51a 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -243,7 +243,7 @@ class Puppet::Network::Handler # the modules. def modules_mount(module_name, client) # Find our environment, if we have one. - if node = node_handler.details(client || Facter.value("hostname")) + if node = Puppet::Node.get(client || Facter.value("hostname")) env = node.environment else env = nil @@ -258,14 +258,6 @@ class Puppet::Network::Handler end end - # Create a node handler instance for looking up our nodes. - def node_handler - unless defined?(@node_handler) - @node_handler = Puppet::Network::Handler.handler(:node).create - end - @node_handler - end - # Read the configuration file. def readconfig(check = true) return if @noreadconfig diff --git a/lib/puppet/network/handler/master.rb b/lib/puppet/network/handler/master.rb index c8db277ba..9550dd550 100644 --- a/lib/puppet/network/handler/master.rb +++ b/lib/puppet/network/handler/master.rb @@ -74,7 +74,7 @@ class Puppet::Network::Handler client, clientip = clientname(client, clientip, facts) # Pass the facts to the fact handler - fact_handler.set(client, facts) + Puppet::Node::Facts.post(Puppet::Node::Facts.new(client, facts)) # And get the configuration from the config handler begin @@ -134,13 +134,6 @@ class Puppet::Network::Handler return facts end - def fact_handler - unless defined? @fact_handler - @fact_handler = Puppet::Network::Handler.handler(:facts).new :local => local? - end - @fact_handler - end - # Translate our configuration appropriately for sending back to a client. def translate(config) if local? diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 9c5d2d397..7ad7bc3b3 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -1,8 +1,11 @@ +require 'puppet/indirector' + # A simplistic class for managing the node information itself. class Puppet::Node + require 'puppet/node/facts' + # Set up indirection, so that nodes can be looked for in # the node sources. - require 'puppet/indirector' extend Puppet::Indirector # Use the node source as the indirection terminus. @@ -66,9 +69,15 @@ class Puppet::Node 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| + def fact_merge + if facts = Puppet::Node::Facts.get(name) + merge(facts.values) + end + 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 end diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb index eddf44def..e5774127b 100755 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -1,9 +1,11 @@ +require 'puppet/node' +require 'puppet/indirector' + # Manage a given node's facts. This either accepts facts and stores them, or # returns facts for a given node. class Puppet::Node::Facts # Set up indirection, so that nodes can be looked for in # the node sources. - require 'puppet/indirector' extend Puppet::Indirector # Use the node source as the indirection terminus. @@ -14,23 +16,21 @@ class Puppet::Node::Facts def initialize(name, values = {}) @name = name @values = values + + add_internal end private - # FIXME These methods are currently unused. - # Add internal data to the facts for storage. - def add_internal(facts) - facts = facts.dup - facts[:_puppet_timestamp] = Time.now - facts + def add_internal + self.values[:_timestamp] = Time.now end # Strip out that internal data. - def strip_internal(facts) - facts = facts.dup - facts.find_all { |name, value| name.to_s =~ /^_puppet_/ }.each { |name, value| facts.delete(name) } - facts + def strip_internal + newvals = values.dup + newvals.find_all { |name, value| name.to_s =~ /^_/ }.each { |name, value| newvals.delete(name) } + newvals end end diff --git a/spec/Rakefile b/spec/Rakefile index 40d107312..bb2a75de5 100644 --- a/spec/Rakefile +++ b/spec/Rakefile @@ -2,9 +2,15 @@ require File.join(File.dirname(__FILE__), "spec_helper.rb") require 'rake' require 'spec/rake/spectask' +basedir = File.dirname(__FILE__) +puppetlibdir = File.join(basedir, "../lib") +puppettestlibdir = File.join(basedir, "../test/lib") +speclibdir = File.join(basedir, "lib") + desc "Run all spec unit tests" Spec::Rake::SpecTask.new('unit') do |t| t.spec_files = FileList['unit/**/*.rb'] + t.libs = [puppetlibdir, puppettestlibdir, speclibdir] end task :default => [:unit] diff --git a/spec/lib/spec/dsl/behaviour.rb b/spec/lib/spec/dsl/behaviour.rb index 5158bb673..93a357f19 100644 --- a/spec/lib/spec/dsl/behaviour.rb +++ b/spec/lib/spec/dsl/behaviour.rb @@ -1,6 +1,10 @@ +require 'puppettest/runnable_test' + module Spec module DSL - class EvalModule < Module; end + class EvalModule < Module; + include PuppetTest::RunnableTest + end class Behaviour extend BehaviourCallbacks diff --git a/spec/lib/spec/runner/behaviour_runner.rb b/spec/lib/spec/runner/behaviour_runner.rb index 1ac891f3c..078490e92 100644 --- a/spec/lib/spec/runner/behaviour_runner.rb +++ b/spec/lib/spec/runner/behaviour_runner.rb @@ -55,6 +55,8 @@ module Spec def run_behaviours @behaviours.each do |behaviour| + # LAK:NOTE: this 'runnable' test is Puppet-specific. + next unless behaviour.runnable? behaviour.run(@options.reporter, @options.dry_run, @options.reverse, @options.timeout) end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a4171bb07..477842495 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,8 +5,8 @@ $:.unshift("#{dir}/lib").unshift("#{dir}/../lib") $:.unshift("#{dir}/../test/lib") require 'mocha' -require 'spec' require 'puppettest' +require 'spec' Spec::Runner.configure do |config| config.mock_with :mocha diff --git a/spec/unit/indirector/facts/yaml.rb b/spec/unit/indirector/facts/yaml.rb index 45c079a69..176a47f04 100755 --- a/spec/unit/indirector/facts/yaml.rb +++ b/spec/unit/indirector/facts/yaml.rb @@ -23,7 +23,7 @@ describe Puppet::Indirector.terminus(:facts, :yaml), " when managing facts" do it "should store facts in YAML in the yamlfactdir" do values = {"one" => "two", "three" => "four"} facts = Puppet::Node::Facts.new("node", values) - @store.put(facts) + @store.post(facts) # Make sure the file exists path = File.join(Puppet[:yamlfactdir], facts.name) + ".yaml" diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb new file mode 100755 index 000000000..e9b10c8c7 --- /dev/null +++ b/spec/unit/indirector/indirection.rb @@ -0,0 +1,57 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector' + +describe Puppet::Indirector::Indirection, " when initializing" do + it "should set the name" do + @indirection = Puppet::Indirector::Indirection.new(:myind) + @indirection.name.should == :myind + end + + it "should set any passed options" do + @indirection = Puppet::Indirector::Indirection.new(:myind, :to => :node_source) + @indirection.to.should == :node_source + end + + it "should only allow valid configuration parameters to be specified as :to targets" do + proc { Puppet::Indirector::Indirection.new(:myind, :to => :no_such_variable) }.should raise_error(ArgumentError) + end + + after do + if defined? @indirection + @indirection.delete + end + end +end + +describe Puppet::Indirector, " when managing termini" do + before do + @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source) + end + + it "should allow the clearance of cached termini" do + terminus1 = mock 'terminus1' + terminus2 = mock 'terminus2' + Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(terminus1, terminus2, ArgumentError) + @indirection.terminus.should equal(terminus1) + @indirection.class.clear_cache + @indirection.terminus.should equal(terminus2) + end + + # Make sure it caches the terminus. + it "should return the same terminus each time" do + @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source) + @terminus = mock 'new' + Puppet::Indirector.terminus(:node, Puppet[:node_source]).expects(:new).returns(@terminus) + + @indirection.terminus.should equal(@terminus) + @indirection.terminus.should equal(@terminus) + end + + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end +end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index c4221febb..312c60951 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -11,15 +11,10 @@ describe Puppet::Indirector, " when managing indirections" do @indirector.send(:extend, Puppet::Indirector) end - # LAK:FIXME This seems like multiple tests, but I don't really know how to test one at a time. - it "should accept specification of an indirection terminus via a configuration parameter" do - @indirector.indirects :test, :to => :node_source - Puppet[:node_source] = "test_source" - klass = mock 'terminus_class' - terminus = mock 'terminus' - klass.expects(:new).returns terminus - Puppet::Indirector.expects(:terminus).with(:test, :test_source).returns(klass) - @indirector.send(:terminus).should equal(terminus) + it "should create an indirection" do + indirection = @indirector.indirects :test, :to => :node_source + indirection.name.should == :test + indirection.to.should == :node_source end it "should not allow more than one indirection in the same object" do @@ -33,18 +28,15 @@ describe Puppet::Indirector, " when managing indirections" do other.send(:extend, Puppet::Indirector) proc { other.indirects :test }.should_not raise_error end -end - -describe Puppet::Indirector, " when managing termini" do - before do - @indirector = Object.new - @indirector.send(:extend, Puppet::Indirector) - end it "should should autoload termini from disk" do Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") @indirector.indirects :test end + + after do + Puppet.config.clear + end end describe Puppet::Indirector, " when performing indirections" do @@ -66,14 +58,4 @@ describe Puppet::Indirector, " when performing indirections" do @terminus_class.expects(:new).returns(terminus) @indirector.put("myargument") end - - # Make sure it caches the terminus. - it "should use the same terminus for all indirections" do - terminus = mock 'terminus' - terminus.expects(:put).with("myargument") - terminus.expects(:get).with("other_argument") - @terminus_class.expects(:new).returns(terminus) - @indirector.put("myargument") - @indirector.get("other_argument") - end end diff --git a/spec/unit/indirector/node/external.rb b/spec/unit/indirector/node/external.rb new file mode 100755 index 000000000..30b2f74c2 --- /dev/null +++ b/spec/unit/indirector/node/external.rb @@ -0,0 +1,119 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'yaml' +require 'puppet/indirector' + +describe Puppet::Indirector.terminus(:node, :external), " when searching for nodes" do + require 'puppet/node' + + before do + Puppet.config[:external_nodes] = "/yay/ness" + @searcher = Puppet::Indirector.terminus(:node, :external).new + + # Set the searcher up so that we do not need to actually call the + # external script. + @searcher.meta_def(:execute) do |command| + name = command.last.chomp + result = {} + + if name =~ /a/ + result[:parameters] = {'one' => command.last + '1', 'two' => command.last + '2'} + end + + if name =~ /p/ + result['classes'] = [1,2,3].collect { |n| command.last + n.to_s } + end + + return YAML.dump(result) + end + end + + it "should throw an exception if the node_source is external but no external node command is set" do + Puppet[:external_nodes] = "none" + proc { @searcher.get("foo") }.should raise_error(ArgumentError) + end + + it "should throw an exception if the external node source is not fully qualified" do + Puppet[:external_nodes] = "mycommand" + proc { @searcher.get("foo") }.should raise_error(ArgumentError) + end + + it "should execute the command with the node name as the only argument" do + command = [Puppet[:external_nodes], "yay"] + @searcher.expects(:execute).with(command).returns("") + @searcher.get("yay") + end + + it "should return a node object" do + @searcher.get("apple").should be_instance_of(Puppet::Node) + end + + it "should set the node's name" do + @searcher.get("apple").name.should == "apple" + end + + # If we use a name that has a 'p' but no 'a', then our test generator + # will return classes but no parameters. + it "should be able to configure a node's classes" do + node = @searcher.get("plum") + node.classes.should == %w{plum1 plum2 plum3} + node.parameters.should == {} + end + + # If we use a name that has an 'a' but no 'p', then our test generator + # will return parameters but no classes. + it "should be able to configure a node's parameters" do + node = @searcher.get("guava") + node.classes.should == [] + node.parameters.should == {"one" => "guava1", "two" => "guava2"} + end + + it "should be able to configure a node's classes and parameters" do + node = @searcher.get("apple") + node.classes.should == %w{apple1 apple2 apple3} + node.parameters.should == {"one" => "apple1", "two" => "apple2"} + end + + it "should merge node facts with returned parameters" do + facts = Puppet::Node::Facts.new("apple", "three" => "four") + Puppet::Node::Facts.expects(:get).with("apple").returns(facts) + node = @searcher.get("apple") + node.parameters["three"].should == "four" + end + + it "should return nil when it cannot find the node" do + @searcher.get("honeydew").should be_nil + end + + # Make sure a nodesearch with arguments works + def test_nodesearch_external_arguments + mapper = mk_node_mapper + Puppet[:external_nodes] = "#{mapper} -s something -p somethingelse" + searcher = mk_searcher(:external) + node = nil + assert_nothing_raised do + node = searcher.nodesearch("apple") + end + assert_instance_of(SimpleNode, node, "did not create node") + end + + # A wrapper test, to make sure we're correctly calling the external search method. + def test_nodesearch_external_functional + mapper = mk_node_mapper + searcher = mk_searcher(:external) + + Puppet[:external_nodes] = mapper + + node = nil + assert_nothing_raised do + node = searcher.nodesearch("apple") + end + assert_instance_of(SimpleNode, node, "did not create node") + end + + after do + Puppet.config.clear + end +end diff --git a/spec/unit/indirector/node/ldap.rb b/spec/unit/indirector/node/ldap.rb new file mode 100755 index 000000000..c6eb45ffc --- /dev/null +++ b/spec/unit/indirector/node/ldap.rb @@ -0,0 +1,243 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'yaml' +require 'puppet/indirector' + +describe Puppet::Indirector.terminus(:node, :ldap), " when searching for nodes" do + require 'puppet/node' + + def setup + Puppet.config[:external_nodes] = "/yay/ness" + @searcher = Puppet::Indirector.terminus(:node, :ldap).new + nodetable = {} + @nodetable = nodetable + # Override the ldapsearch definition, so we don't have to actually set it up. + @searcher.meta_def(:ldapsearch) do |name| + nodetable[name] + end + end + + it "should return nil for hosts that cannot be found" do + @searcher.get("foo").should be_nil + end + + it "should return Puppet::Node instances" do + @nodetable["foo"] = [nil, %w{}, {}] + @searcher.get("foo").should be_instance_of(Puppet::Node) + end + + it "should set the node name" do + @nodetable["foo"] = [nil, %w{}, {}] + @searcher.get("foo").name.should == "foo" + end + + it "should set the classes" do + @nodetable["foo"] = [nil, %w{one two}, {}] + @searcher.get("foo").classes.should == %w{one two} + end + + it "should set the parameters" do + @nodetable["foo"] = [nil, %w{}, {"one" => "two"}] + @searcher.get("foo").parameters.should == {"one" => "two"} + end + + it "should set classes and parameters from the parent node" do + @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] + @nodetable["middle"] = [nil, %w{three four}, {"three" => "four"}] + node = @searcher.get("foo") + node.classes.sort.should == %w{one two three four}.sort + node.parameters.should == {"one" => "two", "three" => "four"} + end + + it "should prefer child parameters to parent parameters" do + @nodetable["foo"] = ["middle", %w{}, {"one" => "two"}] + @nodetable["middle"] = [nil, %w{}, {"one" => "four"}] + @searcher.get("foo").parameters["one"].should == "two" + end + + it "should recurse indefinitely through parent relationships" do + @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] + @nodetable["middle"] = ["top", %w{three four}, {"three" => "four"}] + @nodetable["top"] = [nil, %w{five six}, {"five" => "six"}] + node = @searcher.get("foo") + node.parameters.should == {"one" => "two", "three" => "four", "five" => "six"} + node.classes.sort.should == %w{one two three four five six}.sort + end + + # This can stay in the main test suite because it doesn't actually use ldapsearch, + # it just overrides the method so it behaves as though it were hitting ldap. + def test_ldap_nodesearch + + # Make sure we get nothing for nonexistent hosts + node = nil + assert_nothing_raised do + node = searcher.nodesearch("nosuchhost") + end + + assert_nil(node, "Got a node for a non-existent host") + + # Now add a base node with some classes and parameters + nodetable["base"] = [nil, %w{one two}, {"base" => "true"}] + + assert_nothing_raised do + node = searcher.nodesearch("base") + end + + assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") + assert_equal("base", node.name, "node name was not set") + + assert_equal(%w{one two}, node.classes, "node classes were not set") + assert_equal({"base" => "true"}, node.parameters, "node parameters were not set") + + # Now use a different with this as the base + nodetable["middle"] = ["base", %w{three}, {"center" => "boo"}] + assert_nothing_raised do + node = searcher.nodesearch("middle") + end + + assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") + assert_equal("middle", node.name, "node name was not set") + + assert_equal(%w{one two three}.sort, node.classes.sort, "node classes were not set correctly with a parent node") + assert_equal({"base" => "true", "center" => "boo"}, node.parameters, "node parameters were not set correctly with a parent node") + + # And one further, to make sure we fully recurse + nodetable["top"] = ["middle", %w{four five}, {"master" => "far"}] + assert_nothing_raised do + node = searcher.nodesearch("top") + end + + assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") + assert_equal("top", node.name, "node name was not set") + + assert_equal(%w{one two three four five}.sort, node.classes.sort, "node classes were not set correctly with the top node") + assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node") + end +end + +describe Puppet::Indirector.terminus(:node, :ldap), " when interacting with ldap" do + confine "LDAP is not available" => Puppet.features.ldap? + confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" + + def ldapconnect + + @ldap = LDAP::Conn.new("ldap", 389) + @ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) + @ldap.simple_bind("", "") + + return @ldap + end + + def ldaphost(name) + node = Puppet::Node.new(name) + parent = nil + found = false + @ldap.search( "ou=hosts, dc=madstop, dc=com", 2, + "(&(objectclass=puppetclient)(cn=%s))" % name + ) do |entry| + node.classes = entry.vals("puppetclass") || [] + node.parameters = entry.to_hash.inject({}) do |hash, ary| + if ary[1].length == 1 + hash[ary[0]] = ary[1].shift + else + hash[ary[0]] = ary[1] + end + hash + end + parent = node.parameters["parentnode"] + found = true + end + raise "Could not find node %s" % name unless found + + return node, parent + end + + it "should have tests" do + raise ArgumentError + end + + def test_ldapsearch + Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" + Puppet[:ldapnodes] = true + + searcher = Object.new + searcher.extend(Node.node_source(:ldap)) + + ldapconnect() + + # Make sure we get nil and nil back when we search for something missing + parent, classes, parameters = nil + assert_nothing_raised do + parent, classes, parameters = searcher.ldapsearch("nosuchhost") + end + + assert_nil(parent, "Got a parent for a non-existent host") + assert_nil(classes, "Got classes for a non-existent host") + + # Make sure we can find 'culain' in ldap + assert_nothing_raised do + parent, classes, parameters = searcher.ldapsearch("culain") + end + + node, realparent = ldaphost("culain") + assert_equal(realparent, parent, "did not get correct parent node from ldap") + assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") + assert_equal(node.parameters, parameters, "did not get correct ldap parameters from ldap") + + # Now compare when we specify the attributes to get. + Puppet[:ldapattrs] = "cn" + assert_nothing_raised do + parent, classes, parameters = searcher.ldapsearch("culain") + end + assert_equal(realparent, parent, "did not get correct parent node from ldap") + assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") + + list = %w{cn puppetclass parentnode dn} + should = node.parameters.inject({}) { |h, a| h[a[0]] = a[1] if list.include?(a[0]); h } + assert_equal(should, parameters, "did not get correct ldap parameters from ldap") + end +end + +describe Puppet::Indirector.terminus(:node, :ldap), " when connecting to ldap" do + confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") + + it "should have tests" do + raise ArgumentError + end + + def test_ldapreconnect + Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" + Puppet[:ldapnodes] = true + + searcher = Object.new + searcher.extend(Node.node_source(:ldap)) + hostname = "culain.madstop.com" + + # look for our host + assert_nothing_raised { + parent, classes = searcher.nodesearch(hostname) + } + + # Now restart ldap + system("/etc/init.d/slapd restart 2>/dev/null >/dev/null") + sleep(1) + + # and look again + assert_nothing_raised { + parent, classes = searcher.nodesearch(hostname) + } + + # Now stop ldap + system("/etc/init.d/slapd stop 2>/dev/null >/dev/null") + cleanup do + system("/etc/init.d/slapd start 2>/dev/null >/dev/null") + end + + # And make sure we actually fail here + assert_raise(Puppet::Error) { + parent, classes = searcher.nodesearch(hostname) + } + end +end diff --git a/spec/unit/indirector/node/none.rb b/spec/unit/indirector/node/none.rb new file mode 100755 index 000000000..d52d7ca83 --- /dev/null +++ b/spec/unit/indirector/node/none.rb @@ -0,0 +1,32 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' +require 'puppet/indirector' +require 'puppet/node/facts' + +describe Puppet::Indirector.terminus(:node, :none), " when searching for nodes" do + before do + Puppet.config[:node_source] = "none" + @searcher = Puppet::Indirector.terminus(:node, :none).new + end + + it "should create a node instance" do + @searcher.get("yay").should be_instance_of(Puppet::Node) + end + + it "should create a new node with the correct name" do + @searcher.get("yay").name.should == "yay" + end + + it "should merge the node's facts" do + facts = Puppet::Node::Facts.new("yay", "one" => "two", "three" => "four") + Puppet::Node::Facts.expects(:get).with("yay").returns(facts) + node = @searcher.get("yay") + node.parameters["one"].should == "two" + node.parameters["three"].should == "four" + end + + after do + Puppet.config.clear + end +end diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb index c7fc65f38..c677c1d2e 100755 --- a/spec/unit/node/facts.rb +++ b/spec/unit/node/facts.rb @@ -6,11 +6,12 @@ require 'puppet/node/facts' describe Puppet::Node::Facts, " when indirecting" do before do - Puppet[:fact_store] = "test_store" - @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' - @terminus_class.expects(:new).returns(@terminus) - Puppet::Indirector.expects(:terminus).with(:facts, :test_store).returns(@terminus_class) + Puppet::Indirector.terminus(:facts, Puppet[:fact_store].intern).stubs(:new).returns(@terminus) + + # We have to clear the cache so that the facts ask for our terminus stub, + # instead of anything that might be cached. + Puppet::Indirector::Indirection.clear_cache end it "should redirect to the specified fact store for retrieval" do @@ -19,7 +20,19 @@ describe Puppet::Node::Facts, " when indirecting" do end it "should redirect to the specified fact store for storage" do - @terminus.expects(:put).with(:my_facts) - Puppet::Node::Facts.put(:my_facts) + @terminus.expects(:post).with(:my_facts) + Puppet::Node::Facts.post(:my_facts) + end + + after do + mocha_verify + Puppet::Indirector::Indirection.clear_cache + end +end + +describe Puppet::Node::Facts, " when storing and retrieving" do + it "should add metadata to the facts" do + facts = Puppet::Node::Facts.new("me", "one" => "two", "three" => "four") + facts.values[:_timestamp].should be_instance_of(Time) end end diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb index a6cc1e301..9342dc5ce 100755 --- a/spec/unit/node/node.rb +++ b/spec/unit/node/node.rb @@ -85,32 +85,40 @@ end describe Puppet::Node, " when merging facts" do before do @node = Puppet::Node.new("testnode") + Puppet::Node::Facts.stubs(:get).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end it "should prefer parameters already set on the node over facts from the node" do @node.parameters = {"one" => "a"} - @node.fact_merge("one" => "c") + @node.fact_merge @node.parameters["one"].should == "a" end it "should add passed parameters to the parameter list" do @node.parameters = {"one" => "a"} - @node.fact_merge("two" => "b") + @node.fact_merge @node.parameters["two"].should == "b" end + + it "should accept arbitrary parameters to merge into its parameters" do + @node.parameters = {"one" => "a"} + @node.merge "two" => "three" + @node.parameters["two"].should == "three" + end end describe Puppet::Node, " when indirecting" do before do - Puppet[:node_source] = :test_source - @terminus_class = mock 'terminus_class' @terminus = mock 'terminus' - @terminus_class.expects(:new).returns(@terminus) - Puppet::Indirector.expects(:terminus).with(:node, :test_source).returns(@terminus_class) + Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(@terminus) end it "should redirect to the specified node source" do @terminus.expects(:get).with(:my_node) Puppet::Node.get(:my_node) end + + after do + Puppet::Indirector::Indirection.clear_cache + end end diff --git a/spec/unit/other/node.rb b/spec/unit/other/node.rb deleted file mode 100755 index 66d5ba9d7..000000000 --- a/spec/unit/other/node.rb +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../spec_helper' - -describe Puppet::Node, " when initializing" do - before do - @node = Puppet::Node.new("testnode") - end - - it "should set the node name" do - @node.name.should == "testnode" - end - - it "should default to an empty parameter hash" do - @node.parameters.should == {} - end - - it "should default to an empty class array" do - @node.classes.should == [] - end - - it "should note its creation time" do - @node.time.should be_instance_of(Time) - end - - it "should accept parameters passed in during initialization" do - params = {"a" => "b"} - @node = Puppet::Node.new("testing", :parameters => params) - @node.parameters.should == params - end - - it "should accept classes passed in during initialization" do - classes = %w{one two} - @node = Puppet::Node.new("testing", :classes => classes) - @node.classes.should == classes - end - - it "should always return classes as an array" do - @node = Puppet::Node.new("testing", :classes => "myclass") - @node.classes.should == ["myclass"] - end - - it "should accept the environment during initialization" do - @node = Puppet::Node.new("testing", :environment => "myenv") - @node.environment.should == "myenv" - end - - it "should accept names passed in" do - @node = Puppet::Node.new("testing", :names => ["myenv"]) - @node.names.should == ["myenv"] - end -end - -describe Puppet::Node, " when returning the environment" do - before do - @node = Puppet::Node.new("testnode") - end - - it "should return the 'environment' fact if present and there is no explicit environment" do - @node.parameters = {"environment" => "myenv"} - @node.environment.should == "myenv" - end - - it "should return the central environment if there is no environment fact nor explicit environment" do - Puppet.config.expects(:[]).with(:environment).returns(:centralenv) - @node.environment.should == :centralenv - end - - it "should not use an explicit environment that is an empty string" do - @node.environment == "" - @node.environment.should be_nil - end - - it "should not use an environment fact that is an empty string" do - @node.parameters = {"environment" => ""} - @node.environment.should be_nil - end - - it "should not use an explicit environment that is an empty string" do - Puppet.config.expects(:[]).with(:environment).returns(nil) - @node.environment.should be_nil - end -end - -describe Puppet::Node, " when merging facts" do - before do - @node = Puppet::Node.new("testnode") - end - - it "should prefer parameters already set on the node over facts from the node" do - @node.parameters = {"one" => "a"} - @node.fact_merge("one" => "c") - @node.parameters["one"].should == "a" - end - - it "should add passed parameters to the parameter list" do - @node.parameters = {"one" => "a"} - @node.fact_merge("two" => "b") - @node.parameters["two"].should == "b" - end -end diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb index 348a54893..28ccb04d7 100755 --- a/spec/unit/util/config.rb +++ b/spec/unit/util/config.rb @@ -387,22 +387,40 @@ describe Puppet::Util::Config, " when reparsing its configuration" do end end -#describe Puppet::Util::Config, " when being used to manage the host machine" do -# it "should provide a method that writes files with the correct modes" -# -# it "should provide a method that creates directories with the correct modes" -# -# it "should provide a method to declare what directories should exist" -# -# it "should provide a method to trigger enforcing of file modes on existing files and directories" -# -# it "should provide a method to convert the file mode enforcement into a Puppet manifest" -# -# it "should provide an option to create needed users and groups" -# -# it "should provide a method to print out the current configuration" -# -# it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" -# -# it "should not attempt to manage files within /dev" -#end +describe Puppet::Util::Config, " when being used to manage the host machine" do + it "should provide a method that writes files with the correct modes" do + pending "Not converted from test/unit yet" + end + + it "should provide a method that creates directories with the correct modes" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to declare what directories should exist" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to trigger enforcing of file modes on existing files and directories" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to convert the file mode enforcement into a Puppet manifest" do + pending "Not converted from test/unit yet" + end + + it "should provide an option to create needed users and groups" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to print out the current configuration" do + pending "Not converted from test/unit yet" + end + + it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do + pending "Not converted from test/unit yet" + end + + it "should not attempt to manage files within /dev" do + pending "Not converted from test/unit yet" + end +end diff --git a/test/language/snippets.rb b/test/language/snippets.rb index 2c74543e7..ff8a09881 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -472,13 +472,14 @@ class TestSnippets < Test::Unit::TestCase :Manifest => snippet(file), :Local => true ) - server.send(:fact_handler).stubs(:set) - server.send(:fact_handler).stubs(:get).returns(facts) + facts = Puppet::Node::Facts.new("testhost", facts) + Puppet::Node::Facts.stubs(:post) + Puppet::Node::Facts.stubs(:get).returns(facts) client = Puppet::Network::Client.master.new( :Master => server, :Cache => false ) - client.class.stubs(:facts).returns(facts) + client.class.stubs(:facts).returns(facts.values) assert(client.local) assert_nothing_raised { diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 45c5b2ed9..06b85f147 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -264,6 +264,7 @@ module PuppetTest Puppet::Type.allclear Puppet::Util::Storage.clear Puppet.clear + Puppet.config.clear @memoryatend = Puppet::Util.memory diff = @memoryatend - @memoryatstart diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index eef0cd8bc..c4bd7dc2b 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -42,7 +42,6 @@ module PuppetTest::ParserTesting end def mkcompile(parser = nil) - require 'puppet/network/handler/node' parser ||= mkparser node = mknode return Compile.new(node, parser) diff --git a/test/lib/puppettest/runnable_test.rb b/test/lib/puppettest/runnable_test.rb new file mode 100644 index 000000000..e4b0f9033 --- /dev/null +++ b/test/lib/puppettest/runnable_test.rb @@ -0,0 +1,30 @@ +# Manage whether a test is runnable. +module PuppetTest + module RunnableTest + # Confine this test based on specified criteria. The keys of the + # hash should be the message to use if the test is not suitable, + # and the values should be either 'true' or 'false'; true values + # mean the test is suitable. + def confine(hash) + @confines ||= {} + hash.each do |message, result| + @confines[message] = result + end + end + + # Evaluate all of our tests to see if any of them are false + # and thus whether this test is considered not runnable. + def runnable? + @messages ||= [] + return false unless @messages.empty? + return true unless defined? @confines + @confines.find_all do |message, result| + ! result + end.each do |message, result| + @messages << message + end + + return @messages.empty? + end + end +end diff --git a/test/lib/puppettest/testcase.rb b/test/lib/puppettest/testcase.rb index cfedeee26..15c835854 100644 --- a/test/lib/puppettest/testcase.rb +++ b/test/lib/puppettest/testcase.rb @@ -4,28 +4,11 @@ # Copyright (c) 2007. All rights reserved. require 'puppettest' +require 'puppettest/runnable_test' class PuppetTest::TestCase < Test::Unit::TestCase include PuppetTest - def self.confine(hash) - @confines ||= {} - hash.each do |message, result| - @confines[message] = result - end - end - - def self.runnable? - @messages ||= [] - return false unless @messages.empty? - return true unless defined? @confines - @confines.find_all do |message, result| - ! result - end.each do |message, result| - @messages << message - end - - return @messages.empty? - end + extend PuppetTest::RunnableTest def self.suite # Always skip this parent class. It'd be nice if there were a @@ -44,5 +27,3 @@ class PuppetTest::TestCase < Test::Unit::TestCase end end end - -# $Id$ diff --git a/test/network/handler/configuration.rb b/test/network/handler/configuration.rb index 0964a4c5e..29a393769 100755 --- a/test/network/handler/configuration.rb +++ b/test/network/handler/configuration.rb @@ -63,14 +63,14 @@ class TestHandlerConfiguration < Test::Unit::TestCase fakenode = Object.new # Set the server facts to something config.instance_variable_set("@server_facts", :facts) - fakenode.expects(:fact_merge).with(:facts) + fakenode.expects(:merge).with(:facts) config.send(:add_node_data, fakenode) # Now try it with classes. config.instance_variable_set("@options", {:Classes => %w{a b}}) list = [] fakenode = Object.new - fakenode.expects(:fact_merge).with(:facts) + fakenode.expects(:merge).with(:facts) fakenode.expects(:classes).returns(list).times(2) config.send(:add_node_data, fakenode) assert_equal(%w{a b}, list, "Did not add classes to node") diff --git a/test/network/handler/facts.rb b/test/network/handler/facts.rb deleted file mode 100755 index 03327b8c4..000000000 --- a/test/network/handler/facts.rb +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'mocha' -require 'puppet/network/handler/facts' - -class TestFactsHandler < Test::Unit::TestCase - include PuppetTest::ServerTest - - def setup - super - - @class = Puppet::Network::Handler.handler(:facts) - - @@client_facts = {} - - unless Puppet::Util::FactStore.store(:testing) - Puppet::Util::FactStore.newstore(:testing) do - def get(node) - @@client_facts[node] - end - - def set(node, facts) - @@client_facts[node] = facts - end - end - end - - Puppet[:factstore] = :testing - - @handler = @class.new - - @facts = {:a => :b, :c => :d} - @name = "foo" - - @backend = @handler.instance_variable_get("@backend") - end - - def teardown - @@client_facts.clear - end - - def test_strip_internal - @facts[:_puppet_one] = "yay" - @facts[:_puppet_two] = "boo" - @facts[:_puppetthree] = "foo" - - newfacts = nil - assert_nothing_raised("Could not call strip_internal") do - newfacts = @handler.send(:strip_internal, @facts) - end - - [:_puppet_one, :_puppet_two, :_puppetthree].each do |name| - assert(@facts.include?(name), "%s was removed in strip_internal from original hash" % name) - end - [:_puppet_one, :_puppet_two].each do |name| - assert(! newfacts.include?(name), "%s was not removed in strip_internal" % name) - end - assert_equal("foo", newfacts[:_puppetthree], "_puppetthree was removed in strip_internal") - end - - def test_add_internal - newfacts = nil - assert_nothing_raised("Could not call strip_internal") do - newfacts = @handler.send(:add_internal, @facts) - end - - assert_instance_of(Time, newfacts[:_puppet_timestamp], "Did not set timestamp in add_internal") - assert(! @facts.include?(:_puppet_timestamp), "Modified original hash in add_internal") - end - - def test_set - newfacts = @facts.dup - newfacts[:_puppet_timestamp] = Time.now - @handler.expects(:add_internal).with(@facts).returns(newfacts) - @backend.expects(:set).with(@name, newfacts).returns(nil) - - assert_nothing_raised("Could not set facts") do - assert_nil(@handler.set(@name, @facts), "handler.set did not return nil") - end - end - - def test_get - prefacts = @facts.dup - prefacts[:_puppet_timestamp] = Time.now - @@client_facts[@name] = prefacts - @handler.expects(:strip_internal).with(prefacts).returns(@facts) - @backend.expects(:get).with(@name).returns(prefacts) - - assert_nothing_raised("Could not retrieve facts") do - assert_equal(@facts, @handler.get(@name), "did not get correct answer from handler.get") - end - - @handler = @class.new - assert_nothing_raised("Failed to call 'get' with no stored facts") do - @handler.get("nosuchname") - end - end - - def test_store_date - time = Time.now - @facts[:_puppet_timestamp] = time - - @handler.expects(:get).with(@name).returns(@facts) - - assert_equal(time.to_i, @handler.store_date(@name), "Did not retrieve timestamp correctly") - end -end - -# $Id$ diff --git a/test/network/handler/master.rb b/test/network/handler/master.rb index a976726ef..9749c7bdf 100755 --- a/test/network/handler/master.rb +++ b/test/network/handler/master.rb @@ -8,6 +8,11 @@ require 'puppet/network/handler/master' class TestMaster < Test::Unit::TestCase include PuppetTest::ServerTest + def teardown + super + Puppet::Indirector::Indirection.clear_cache + end + def test_defaultmanifest textfiles { |file| Puppet[:manifest] = file -- cgit From 43f22a2414048b180d2c0e2a421fa8d905c4d8eb Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 12 Sep 2007 18:32:26 -0500 Subject: Adding a to_graph method to TransBuckets, so that the buckets can directly generate a graph, rather than having to first convert to RAL types and then have them convert to a graph. This allows us to make it so components do not need a @children array at all. This was all done because I am having the "already a parent of" problem again, and I have gotten far enough that it is relatively easy to just make this problem go away once and for all. --- lib/puppet/dsl.rb | 3 +- lib/puppet/pgraph.rb | 28 +++- lib/puppet/transportable.rb | 50 ++++++- lib/puppet/type/component.rb | 4 + spec/unit/other/pgraph.rb | 310 +++++++++++++++++++++++++++++++++++++++++ spec/unit/other/transbucket.rb | 123 ++++++++++++++++ spec/unit/other/transobject.rb | 116 +++++++++++++++ test/lib/puppettest.rb | 1 + test/lib/puppettest/graph.rb | 41 ------ test/other/pgraph.rb | 290 -------------------------------------- 10 files changed, 629 insertions(+), 337 deletions(-) create mode 100755 spec/unit/other/pgraph.rb create mode 100755 spec/unit/other/transbucket.rb create mode 100755 spec/unit/other/transobject.rb delete mode 100644 test/lib/puppettest/graph.rb delete mode 100755 test/other/pgraph.rb diff --git a/lib/puppet/dsl.rb b/lib/puppet/dsl.rb index 793578bca..795358e83 100644 --- a/lib/puppet/dsl.rb +++ b/lib/puppet/dsl.rb @@ -255,8 +255,7 @@ module Puppet def scope unless defined?(@scope) @interp = Puppet::Parser::Interpreter.new :Code => "" - # Load the class, so the node object class is available. - require 'puppet/network/handler/node' + require 'puppet/node' @node = Puppet::Node.new(Facter.value(:hostname)) @node.parameters = Facter.to_hash @interp = Puppet::Parser::Interpreter.new :Code => "" diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index 07bdff4bb..ddecc731d 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -19,6 +19,20 @@ class Puppet::PGraph < GRATR::Digraph super end + # Add a resource to our graph and to our resource table. + def add_resource(resource) + unless resource.respond_to?(:ref) + raise ArgumentError, "Can only add objects that respond to :ref" + end + + ref = resource.ref + if @resource_table.include?(ref) + raise ArgumentError, "Resource %s is already defined" % ref + else + @resource_table[ref] = resource + end + end + def add_vertex!(*args) @reversal = nil super @@ -26,6 +40,7 @@ class Puppet::PGraph < GRATR::Digraph def clear @vertex_dict.clear + #@resource_table.clear if defined? @edge_number @edge_number.clear end @@ -71,6 +86,12 @@ class Puppet::PGraph < GRATR::Digraph def edge_class() Puppet::Relationship end + + def initialize(*args) + super + # Create a table to keep references to all of our resources. + @resource_table = {} + end # Determine all of the leaf nodes below a given vertex. def leaves(vertex, type = :dfs) @@ -97,6 +118,11 @@ class Puppet::PGraph < GRATR::Digraph end end.flatten end + + # Look a resource up by its reference (e.g., File["/etc/passwd"]). + def resource(ref) + @resource_table[ref] + end # Take container information from another graph and use it # to replace any container vertices with their respective leaves. @@ -209,5 +235,3 @@ class Puppet::PGraph < GRATR::Digraph end end end - -# $Id$ diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index aa7eb92f7..7a04e1f63 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -60,6 +60,17 @@ module Puppet instance_variables end + def to_ref + unless defined? @ref + if self.type and self.name + @ref = "%s[%s]" % [self.type, self.name] + else + @ref = nil + end + end + @ref + end + def to_type(parent = nil) retobj = nil if typeklass = Puppet::Type.type(self.type) @@ -188,6 +199,43 @@ module Puppet instance_variables end + # Create a resource graph from our structure. + def to_graph + graph = Puppet::PGraph.new + + delver = proc do |obj| + obj.each do |child| + unless container = graph.resource(obj.to_ref) + container = obj.to_type + graph.add_resource container + end + unless resource = graph.resource(child.to_ref) + resource = child.to_type + graph.add_resource resource + end + graph.add_edge!(container, resource) + if child.is_a?(self.class) + delver.call(child) + end + end + end + + delver.call(self) + + return graph + end + + def to_ref + unless defined? @ref + if self.type and self.name + @ref = "%s[%s]" % [self.type, self.name] + else + @ref = nil + end + end + @ref + end + def to_type(parent = nil) # this container will contain the equivalent of all objects at # this level @@ -287,7 +335,5 @@ module Puppet end end - #------------------------------------------------------------ end -# $Id$ diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 5905d85ab..79afa95a7 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -169,6 +169,10 @@ Puppet::Type.newtype(:component) do end end + def ref + title + end + # Remove an object. The argument determines whether the object's # subscriptions get eliminated, too. def remove(rmdeps = true) diff --git a/spec/unit/other/pgraph.rb b/spec/unit/other/pgraph.rb new file mode 100755 index 000000000..d4af87ba0 --- /dev/null +++ b/spec/unit/other/pgraph.rb @@ -0,0 +1,310 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-12. +# Copyright (c) 2006. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/util/graph' + +class Container + include Puppet::Util::Graph + include Enumerable + attr_accessor :name + def each + @children.each do |c| yield c end + end + + def initialize(name, ary) + @name = name + @children = ary + end + + def push(*ary) + ary.each { |c| @children.push(c)} + end + + def to_s + @name + end +end + +describe Puppet::PGraph do + before do + @graph = Puppet::PGraph.new + end + + it "should correctly clear vertices and edges when asked" do + @graph.add_edge!("a", "b") + @graph.add_vertex! "c" + @graph.clear + @graph.vertices.should be_empty + @graph.edges.should be_empty + end +end + +describe Puppet::PGraph, " when matching edges" do + before do + @graph = Puppet::PGraph.new + @event = Puppet::Event.new(:source => "a", :event => :yay) + @none = Puppet::Event.new(:source => "a", :event => :NONE) + + @edges = {} + @edges["a/b"] = Puppet::Relationship["a", "b", {:event => :yay, :callback => :refresh}] + @edges["a/c"] = Puppet::Relationship["a", "c", {:event => :yay, :callback => :refresh}] + @graph.add_edge!(@edges["a/b"]) + end + + it "should match edges whose source matches the source of the event" do + @graph.matching_edges([@event]).should == [@edges["a/b"]] + end + + it "should match always match nothing when the event is :NONE" do + @graph.matching_edges([@none]).should be_empty + end + + it "should match multiple edges" do + @graph.add_edge!(@edges["a/c"]) + @graph.matching_edges([@event]).sort.should == [@edges["a/b"], @edges["a/c"]].sort + end +end + +describe Puppet::PGraph, " when determining dependencies" do + before do + @graph = Puppet::PGraph.new + + @graph.add_edge!("a", "b") + @graph.add_edge!("a", "c") + @graph.add_edge!("b", "d") + end + + it "should find all dependents when they are on multiple levels" do + @graph.dependents("a").sort.should == %w{b c d}.sort + end + + it "should find single dependents" do + @graph.dependents("b").sort.should == %w{d}.sort + end + + it "should return an empty array when there are no dependents" do + @graph.dependents("c").sort.should == [].sort + end + + it "should find all dependencies when they are on multiple levels" do + @graph.dependencies("d").sort.should == %w{a b} + end + + it "should find single dependencies" do + @graph.dependencies("c").sort.should == %w{a} + end + + it "should return an empty array when there are no dependencies" do + @graph.dependencies("a").sort.should == [] + end +end + +describe Puppet::PGraph, " when splicing the relationship graph" do + def container_graph + @one = Container.new("one", %w{a b}) + @two = Container.new("two", ["c", "d"]) + @three = Container.new("three", ["i", "j"]) + @middle = Container.new("middle", ["e", "f", @two]) + @top = Container.new("top", ["g", "h", @middle, @one, @three]) + @empty = Container.new("empty", []) + + @contgraph = @top.to_graph + + # We have to add the container to the main graph, else it won't + # be spliced in the dependency graph. + @contgraph.add_vertex!(@empty) + end + + def dependency_graph + @depgraph = Puppet::PGraph.new + @contgraph.vertices.each do |v| + @depgraph.add_vertex(v) + end + + # We have to specify a relationship to our empty container, else it + # never makes it into the dep graph in the first place. + {@one => @two, "f" => "c", "h" => @middle, "c" => @empty}.each do |source, target| + @depgraph.add_edge!(source, target, :callback => :refresh) + end + end + + def splice + @depgraph.splice!(@contgraph, Container) + end + + before do + container_graph + dependency_graph + splice + end + + it "should not create a cyclic graph" do + @depgraph.should_not be_cyclic + end + + # This is the real heart of splicing -- replacing all containers in + # our relationship and exploding their relationships so that each + # relationship to a container gets copied to all of its children. + it "should remove all Container objects from the dependency graph" do + @depgraph.vertices.find_all { |v| v.is_a?(Container) }.should be_empty + end + + it "should add container relationships to contained objects" do + @contgraph.leaves(@middle).each do |leaf| + @depgraph.should be_edge("h", leaf) + end + end + + it "should explode container-to-container relationships, making edges between all respective contained objects" do + @one.each do |oobj| + @two.each do |tobj| + @depgraph.should be_edge(oobj, tobj) + end + end + end + + it "should no longer contain anything but the non-container objects" do + @depgraph.vertices.find_all { |v| ! v.is_a?(String) }.should be_empty + end + + it "should copy labels" do + @depgraph.edges.each do |edge| + edge.label.should == {:callback => :refresh} + end + end + + it "should not add labels to edges that have none" do + @depgraph.add_edge!(@two, @three) + splice + @depgraph.edge_label("c", "i").should == {} + end + + it "should copy labels over edges that have none" do + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + # And make sure the label got copied. + @depgraph.edge_label("c", "i").should == {:callback => :refresh} + end + + it "should not replace a label with a nil label" do + # Lastly, add some new label-less edges and make sure the label stays. + @depgraph.add_edge!(@middle, @three) + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + @depgraph.edge_label("c", "i").should == {:callback => :refresh} + end + + it "should copy labels to all created edges" do + @depgraph.add_edge!(@middle, @three) + @depgraph.add_edge!("c", @three, {:callback => :refresh}) + splice + @three.each do |child| + edge = @depgraph.edge_class.new("c", child) + @depgraph.should be_edge(edge) + @depgraph[edge].should == {:callback => :refresh} + end + end +end + +# Labels in this graph are used for managing relationships, +# including callbacks, so they're quite important. +describe Puppet::PGraph, " when managing labels" do + before do + @graph = Puppet::PGraph.new + @label = {:callback => :yay} + end + + it "should return nil for edges with no label" do + @graph.add_edge!(:a, :b) + @graph.edge_label(:a, :b).should be_nil + end + + it "should just return empty label hashes" do + @graph.add_edge!(:a, :b, {}) + @graph.edge_label(:a, :b).should == {} + end + + it "should consider empty label hashes to be nil when copying" do + @graph.add_edge!(:a, :b) + @graph.copy_label(:a, :b, {}) + @graph.edge_label(:a, :b).should be_nil + end + + it "should return label hashes" do + @graph.add_edge!(:a, :b, @label) + @graph.edge_label(:a, :b).should == @label + end + + it "should replace nil labels with real labels" do + @graph.add_edge!(:a, :b) + @graph.copy_label(:a, :b, @label) + @graph.edge_label(:a, :b).should == @label + end + + it "should not replace labels with nil labels" do + @graph.add_edge!(:a, :b, @label) + @graph.copy_label(:a, :b, {}) + @graph.edge_label(:a, :b).should == @label + end +end + +describe Puppet::PGraph, " when sorting the graph" do + before do + @graph = Puppet::PGraph.new + end + + def add_edges(hash) + hash.each do |a,b| + @graph.add_edge!(a, b) + end + end + + it "should fail on two-vertex loops" do + add_edges :a => :b, :b => :a + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should fail on multi-vertex loops" do + add_edges :a => :b, :b => :c, :c => :a + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should fail when a larger tree contains a small cycle" do + add_edges :a => :b, :b => :a, :c => :a, :d => :c + proc { @graph.topsort }.should raise_error(Puppet::Error) + end + + it "should succeed on trees with no cycles" do + add_edges :a => :b, :b => :e, :c => :a, :d => :c + proc { @graph.topsort }.should_not raise_error + end +end + +describe Puppet::PGraph, " functioning as a resource container" do + before do + @graph = Puppet::PGraph.new + @one = stub 'resource1', :ref => "Me[you]" + @two = stub 'resource2', :ref => "Me[him]" + @dupe = stub 'resource3', :ref => "Me[you]" + end + + it "should make all vertices available by resource reference" do + @graph.add_vertex!(@one) + @graph.resource(@one.ref).should equal(@one) + end + + it "should not allow two resources with the same resource reference" do + @graph.add_vertex!(@one) + proc { @graph.add_vertex!(@dupe) }.should raise_error(ArgumentError) + end + + it "should not store objects that do not respond to :ref" do + str = "thing" + @graph.add_vertex!(str) + @graph.resource(str).should be_nil + end +end diff --git a/spec/unit/other/transbucket.rb b/spec/unit/other/transbucket.rb new file mode 100755 index 000000000..c013973ee --- /dev/null +++ b/spec/unit/other/transbucket.rb @@ -0,0 +1,123 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::TransBucket do + before do + @bucket = Puppet::TransBucket.new + end + + it "should be able to produce a RAL component" do + @bucket.name = "luke" + @bucket.type = "user" + + resource = nil + proc { resource = @bucket.to_type }.should_not raise_error + resource.should be_instance_of(Puppet::Type::Component) + resource.title.should == "user[luke]" + end + + it "should accept TransObjects into its children list" do + object = Puppet::TransObject.new("luke", "user") + proc { @bucket.push(object) }.should_not raise_error + @bucket.each do |o| + o.should equal(object) + end + end + + it "should accept TransBuckets into its children list" do + object = Puppet::TransBucket.new() + proc { @bucket.push(object) }.should_not raise_error + @bucket.each do |o| + o.should equal(object) + end + end + + it "should refuse to accept any children that are not TransObjects or TransBuckets" do + proc { @bucket.push "a test" }.should raise_error + end + + it "should return nil as its reference when type or name is missing" do + @bucket.to_ref.should be_nil + end + + it "should return the title as its reference" do + @bucket.name = "luke" + @bucket.type = "user" + @bucket.to_ref.should == "user[luke]" + end +end + +describe Puppet::TransBucket, " when generating a resource graph" do + before do + @bottom = Puppet::TransBucket.new + @bottom.type = "fake" + @bottom.name = "bottom" + @bottomobj = Puppet::TransObject.new("bottom", "user") + @bottom.push @bottomobj + + @middle = Puppet::TransBucket.new + @middle.type = "fake" + @middle.name = "middle" + @middleobj = Puppet::TransObject.new("middle", "user") + @middle.push(@middleobj) + @middle.push(@bottom) + + @top = Puppet::TransBucket.new + @top.type = "fake" + @top.name = "top" + @topobj = Puppet::TransObject.new("top", "user") + @top.push(@topobj) + @top.push(@middle) + + @graph = @top.to_graph + + @users = %w{top middle bottom} + @fakes = %w{fake[bottom] fake[middle] fake[top]} + end + + it "should convert all transportable objects to RAL resources" do + @users.each do |name| + @graph.vertices.find { |r| r.class.name == :user and r.title == name }.should be_instance_of(Puppet::Type.type(:user)) + end + end + + it "should convert all transportable buckets to RAL components" do + @fakes.each do |name| + @graph.vertices.find { |r| r.class.name == :component and r.title == name }.should be_instance_of(Puppet::Type.type(:component)) + end + end + + it "should add all resources to the graph's resource table" do + @graph.resource("fake[top]").should equal(@top) + end + + after do + Puppet::Type.allclear + end +end + +describe Puppet::TransBucket, " when serializing" do + before do + @bucket = Puppet::TransBucket.new(%w{one two}) + @bucket.name = "one" + @bucket.type = "two" + end + + it "should be able to be dumped to yaml" do + proc { YAML.dump(@bucket) }.should_not raise_error + end + + it "should dump YAML that produces an equivalent object" do + result = YAML.dump(@bucket) + + newobj = YAML.load(result) + newobj.name.should == "one" + newobj.type.should == "two" + children = [] + newobj.each do |o| + children << o + end + children.should == %w{one two} + end +end diff --git a/spec/unit/other/transobject.rb b/spec/unit/other/transobject.rb new file mode 100755 index 000000000..07c9dc761 --- /dev/null +++ b/spec/unit/other/transobject.rb @@ -0,0 +1,116 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::TransObject, " when building its search path" do +end + +describe Puppet::TransObject, " when building its search path" do +end +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'puppet' +require 'puppet/transportable' +require 'puppettest' +require 'puppettest/parsertesting' +require 'yaml' + +class TestTransportable < Test::Unit::TestCase + include PuppetTest::ParserTesting + + def test_yamldumpobject + obj = mk_transobject + obj.to_yaml_properties + str = nil + assert_nothing_raised { + str = YAML.dump(obj) + } + + newobj = nil + assert_nothing_raised { + newobj = YAML.load(str) + } + + assert(newobj.name, "Object has no name") + assert(newobj.type, "Object has no type") + end + + def test_yamldumpbucket + objects = %w{/etc/passwd /etc /tmp /var /dev}.collect { |d| + mk_transobject(d) + } + bucket = mk_transbucket(*objects) + str = nil + assert_nothing_raised { + str = YAML.dump(bucket) + } + + newobj = nil + assert_nothing_raised { + newobj = YAML.load(str) + } + + assert(newobj.name, "Bucket has no name") + assert(newobj.type, "Bucket has no type") + end + + # Verify that we correctly strip out collectable objects, since they should + # not be sent to the client. + def test_collectstrip + top = mk_transtree do |object, depth, width| + if width % 2 == 1 + object.collectable = true + end + end + + assert(top.flatten.find_all { |o| o.collectable }.length > 0, + "Could not find any collectable objects") + + # Now strip out the collectable objects + top.collectstrip! + + # And make sure they're actually gone + assert_equal(0, top.flatten.find_all { |o| o.collectable }.length, + "Still found collectable objects") + end + + # Make sure our 'delve' command is working + def test_delve + top = mk_transtree do |object, depth, width| + if width % 2 == 1 + object.collectable = true + end + end + + objects = [] + buckets = [] + collectable = [] + + count = 0 + assert_nothing_raised { + top.delve do |object| + count += 1 + if object.is_a? Puppet::TransBucket + buckets << object + else + objects << object + if object.collectable + collectable << object + end + end + end + } + + top.flatten.each do |obj| + assert(objects.include?(obj), "Missing obj %s[%s]" % [obj.type, obj.name]) + end + + assert_equal(collectable.length, + top.flatten.find_all { |o| o.collectable }.length, + "Found incorrect number of collectable objects") + end +end + +# $Id$ diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 06b85f147..33e3b2daf 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -265,6 +265,7 @@ module PuppetTest Puppet::Util::Storage.clear Puppet.clear Puppet.config.clear + Puppet::Indirector::Indirection.clear_cache @memoryatend = Puppet::Util.memory diff = @memoryatend - @memoryatstart diff --git a/test/lib/puppettest/graph.rb b/test/lib/puppettest/graph.rb deleted file mode 100644 index db77889bd..000000000 --- a/test/lib/puppettest/graph.rb +++ /dev/null @@ -1,41 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke A. Kanies on 2006-11-24. -# Copyright (c) 2006. All rights reserved. - -require 'puppet/util/graph' - -class Container - include Puppet::Util::Graph - include Enumerable - attr_accessor :name - def each - @children.each do |c| yield c end - end - - def initialize(name, ary) - @name = name - @children = ary - end - - def push(*ary) - ary.each { |c| @children.push(c)} - end - - def to_s - @name - end -end - -module PuppetTest::Graph - def build_tree - one = Container.new("one", %w{a b}) - two = Container.new("two", ["c", "d"]) - three = Container.new("three", ["i", "j"]) - middle = Container.new("middle", ["e", "f", two]) - top = Container.new("top", ["g", "h", middle, one, three]) - return one, two, three, middle, top - end -end - -# $Id$ diff --git a/test/other/pgraph.rb b/test/other/pgraph.rb deleted file mode 100755 index 34ba0e18c..000000000 --- a/test/other/pgraph.rb +++ /dev/null @@ -1,290 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2006-11-16. -# Copyright (c) 2006. All rights reserved. - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppettest/graph' - -class TestPGraph < Test::Unit::TestCase - include PuppetTest - include PuppetTest::Graph - - Edge = Puppet::Relationship - - def test_clear - graph = Puppet::PGraph.new - graph.add_edge!("a", "b") - graph.add_vertex! "c" - assert_nothing_raised do - graph.clear - end - assert(graph.vertices.empty?, "Still have vertices after clear") - assert(graph.edges.empty?, "still have edges after clear") - end - - - def test_matching_edges - graph = Puppet::PGraph.new - - event = Puppet::Event.new(:source => "a", :event => :yay) - none = Puppet::Event.new(:source => "a", :event => :NONE) - - edges = {} - - edges["a/b"] = Edge["a", "b", {:event => :yay, :callback => :refresh}] - edges["a/c"] = Edge["a", "c", {:event => :yay, :callback => :refresh}] - - graph.add_edge!(edges["a/b"]) - - # Try it for the trivial case of one target and a matching event - assert_equal([edges["a/b"]], graph.matching_edges([event])) - - # Make sure we get nothing with a different event - assert_equal([], graph.matching_edges([none])) - - # Set up multiple targets and make sure we get them all back - graph.add_edge!(edges["a/c"]) - assert_equal([edges["a/b"], edges["a/c"]].sort, graph.matching_edges([event]).sort) - assert_equal([], graph.matching_edges([none])) - end - - def test_dependencies - graph = Puppet::PGraph.new - - graph.add_edge!("a", "b") - graph.add_edge!("a", "c") - graph.add_edge!("b", "d") - - assert_equal(%w{b c d}.sort, graph.dependents("a").sort) - assert_equal(%w{d}.sort, graph.dependents("b").sort) - assert_equal([].sort, graph.dependents("c").sort) - - assert_equal(%w{a b}, graph.dependencies("d").sort) - assert_equal(%w{a}, graph.dependencies("b").sort) - assert_equal(%w{a}, graph.dependencies("c").sort) - assert_equal([], graph.dependencies("a").sort) - - end - - # Test that we can take a containment graph and rearrange it by dependencies - def test_splice - one, two, three, middle, top = build_tree - empty = Container.new("empty", []) - # Also, add an empty container to top - top.push empty - - contgraph = top.to_graph - - # Now add a couple of child files, so that we can test whether all - # containers get spliced, rather than just components. - - # Now make a dependency graph - deps = Puppet::PGraph.new - - contgraph.vertices.each do |v| - deps.add_vertex(v) - end - - # We have to specify a relationship to our empty container, else it - # never makes it into the dep graph in the first place. - {one => two, "f" => "c", "h" => middle, "c" => empty}.each do |source, target| - deps.add_edge!(source, target, :callback => :refresh) - end - - #contgraph.to_jpg(File.expand_path("~/Desktop/pics"), "main") - #deps.to_jpg(File.expand_path("~/Desktop/pics"), "before") - assert_nothing_raised { deps.splice!(contgraph, Container) } - - assert(! deps.cyclic?, "Created a cyclic graph") - - # Make sure there are no container objects remaining - #deps.to_jpg(File.expand_path("~/Desktop/pics"), "after") - c = deps.vertices.find_all { |v| v.is_a?(Container) } - assert(c.empty?, "Still have containers %s" % c.inspect) - - # Now make sure the containers got spliced correctly. - contgraph.leaves(middle).each do |leaf| - assert(deps.edge?("h", leaf), "no edge for h => %s" % leaf) - end - one.each do |oobj| - two.each do |tobj| - assert(deps.edge?(oobj, tobj), "no %s => %s edge" % [oobj, tobj]) - end - end - - nons = deps.vertices.find_all { |v| ! v.is_a?(String) } - assert(nons.empty?, - "still contain non-strings %s" % nons.inspect) - - deps.edges.each do |edge| - assert_equal({:callback => :refresh}, edge.label, - "Label was not copied for %s => %s" % [edge.source, edge.target]) - end - - # Now add some relationships to three, but only add labels to one of - # the relationships. - - # Add a simple, label-less relationship - deps.add_edge!(two, three) - assert_nothing_raised { deps.splice!(contgraph, Container) } - - # And make sure it stuck, with no labels. - assert_equal({}, deps.edge_label("c", "i"), - "label was created for c => i") - - # Now add some edges with labels, in a way that should overwrite - deps.add_edge!("c", three, {:callback => :refresh}) - assert_nothing_raised { deps.splice!(contgraph, Container) } - - # And make sure the label got copied. - assert_equal({:callback => :refresh}, deps.edge_label("c", "i"), - "label was not copied for c => i") - - # Lastly, add some new label-less edges and make sure the label stays. - deps.add_edge!(middle, three) - assert_nothing_raised { deps.splice!(contgraph, Container) } - assert_equal({:callback => :refresh}, deps.edge_label("c", "i"), - "label was lost for c => i") - - # Now make sure the 'three' edges all have the label we've used. - # Note that this will not work when we support more than one type of - # subscription. - three.each do |child| - edge = deps.edge_class.new("c", child) - assert(deps.edge?(edge), "no c => %s edge" % child) - assert_equal({:callback => :refresh}, deps[edge], - "label was not retained for c => %s" % child) - end - end - - def test_copy_label - graph = Puppet::PGraph.new - - # First make an edge with no label - graph.add_edge!(:a, :b) - assert_nil(graph.edge_label(:a, :b), "Created a label") - - # Now try to copy an empty label in. - graph.copy_label(:a, :b, {}) - - # It should just do nothing, since we copied an empty label. - assert_nil(graph.edge_label(:a, :b), "Created a label") - - # Now copy in a real label. - graph.copy_label(:a, :b, {:callback => :yay}) - assert_equal({:callback => :yay}, - graph.edge_label(:a, :b), "Did not copy label") - - # Now copy in a nil label - graph.copy_label(:a, :b, nil) - assert_equal({:callback => :yay}, - graph.edge_label(:a, :b), "lost label") - - # And an empty one. - graph.copy_label(:a, :b, {}) - assert_equal({:callback => :yay}, - graph.edge_label(:a, :b), "lost label") - end - - def test_fail_on_cycle - { - {:a => :b, :b => :a, :c => :a, :d => :c} => true, # larger tree involving a smaller cycle - {:a => :b, :b => :c, :c => :a} => true, - {:a => :b, :b => :a, :c => :d, :d => :c} => true, - {:a => :b, :b => :c} => false, - }.each do |hash, result| - graph = Puppet::PGraph.new - hash.each do |a,b| - graph.add_edge!(a, b) - end - - if result - assert_raise(Puppet::Error, "%s did not fail" % hash.inspect) do - graph.topsort - end - else - assert_nothing_raised("%s failed" % hash.inspect) do - graph.topsort - end - end - end - end - - # This isn't really a unit test, it's just a way to do some graphing with - # tons of relationships so we can see how it performs. - def disabled_test_lots_of_relationships - containers = Puppet::PGraph.new - relationships = Puppet::PGraph.new - labels = %w{a b c d e} - conts = {} - vertices = {} - labels.each do |label| - vertices[label] = [] - end - num = 100 - num.times do |i| - labels.each do |label| - vertices[label] << ("%s%s" % [label, i]) - end - end - labels.each do |label| - conts[label] = Container.new(label, vertices[label]) - end - - conts.each do |label, cont| - cont.each do |child| - containers.add_edge!(cont, child) - end - end - prev = nil - labels.inject(nil) do |prev, label| - if prev - containers.add_edge!(conts[prev], conts[label]) - end - label - end - - containers.to_jpg(File.expand_path("~/Desktop/pics/lots"), "start") - - # Now create the relationship graph - - # Make everything in both b and c require d1 - %w{b c}.each do |label| - conts[label].each do |v| - relationships.add_edge!(v, "d1") - #relationships.add_edge!(v, conts["d"]) - end - end - - # Make most in b also require the appropriate thing in c - conts["b"].each do |v| - i = v.split('')[1] - - relationships.add_edge!(v, "c%s" % i) - end - - # And make d1 require most of e - num.times do |i| - relationships.add_edge!("d1", "e%s" % i) - end - - containers.vertices.each do |v| - relationships.add_vertex!(v) - end - relationships.to_jpg(File.expand_path("~/Desktop/pics/lots"), "relationships") - - time = Benchmark.realtime do - relationships.splice!(containers, Container) - end - relationships.to_jpg(File.expand_path("~/Desktop/pics/lots"), "final") - puts time - time = Benchmark.realtime do - relationships.topsort - end - end -end - -# $Id$ -- cgit From 3632926089cb27b93ff075c05ba21e2340a562ac Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 12 Sep 2007 18:40:54 -0500 Subject: Moving the resource container behaviour to the Configuration object, rather than the base PGraph class. I expect I will just do away with PGraph, but for now, I am at least going to keep configuration-related code in that class. --- lib/puppet/node/configuration.rb | 27 ++++++++++++++++++++++++++- lib/puppet/pgraph.rb | 26 -------------------------- lib/puppet/transportable.rb | 2 +- spec/unit/node/configuration.rb | 23 +++++++++++++++++++++++ spec/unit/other/pgraph.rb | 25 ------------------------- 5 files changed, 50 insertions(+), 53 deletions(-) diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 4f93fdbe5..be30ddb68 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -4,7 +4,7 @@ require 'puppet/external/gratr/digraph' # meant to be passed from server to client, and it contains all # of the information in the configuration, including the resources # and the relationships between them. -class Puppet::Node::Configuration < GRATR::Digraph +class Puppet::Node::Configuration < Puppet::PGraph attr_accessor :name, :version attr_reader :extraction_format @@ -18,6 +18,25 @@ class Puppet::Node::Configuration < GRATR::Digraph tag(*classes) end + # Add a resource to our graph and to our resource table. + def add_resource(resource) + unless resource.respond_to?(:ref) + raise ArgumentError, "Can only add objects that respond to :ref" + end + + ref = resource.ref + if @resource_table.include?(ref) + raise ArgumentError, "Resource %s is already defined" % ref + else + @resource_table[ref] = resource + end + end + + def clear + super + @resource_table.clear + end + def classes @classes.dup end @@ -100,6 +119,12 @@ class Puppet::Node::Configuration < GRATR::Digraph @extraction_format ||= :transportable @tags = [] @classes = [] + @resource_table = {} + end + + # Look a resource up by its reference (e.g., File[/etc/passwd]). + def resource(ref) + @resource_table[ref] end # Add a tag. diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index ddecc731d..7a3869dcf 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -19,20 +19,6 @@ class Puppet::PGraph < GRATR::Digraph super end - # Add a resource to our graph and to our resource table. - def add_resource(resource) - unless resource.respond_to?(:ref) - raise ArgumentError, "Can only add objects that respond to :ref" - end - - ref = resource.ref - if @resource_table.include?(ref) - raise ArgumentError, "Resource %s is already defined" % ref - else - @resource_table[ref] = resource - end - end - def add_vertex!(*args) @reversal = nil super @@ -40,7 +26,6 @@ class Puppet::PGraph < GRATR::Digraph def clear @vertex_dict.clear - #@resource_table.clear if defined? @edge_number @edge_number.clear end @@ -86,12 +71,6 @@ class Puppet::PGraph < GRATR::Digraph def edge_class() Puppet::Relationship end - - def initialize(*args) - super - # Create a table to keep references to all of our resources. - @resource_table = {} - end # Determine all of the leaf nodes below a given vertex. def leaves(vertex, type = :dfs) @@ -118,11 +97,6 @@ class Puppet::PGraph < GRATR::Digraph end end.flatten end - - # Look a resource up by its reference (e.g., File["/etc/passwd"]). - def resource(ref) - @resource_table[ref] - end # Take container information from another graph and use it # to replace any container vertices with their respective leaves. diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index 7a04e1f63..782ba2467 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -201,7 +201,7 @@ module Puppet # Create a resource graph from our structure. def to_graph - graph = Puppet::PGraph.new + graph = Puppet::Node::Configuration.new(Facter.value(:hostname)) delver = proc do |obj| obj.each do |child| diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index 4429fe3a3..774a1550f 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -133,3 +133,26 @@ describe Puppet::Node::Configuration, " when extracting transobjects" do botarray.include?(:botres).should be_true end end + +describe Puppet::Node::Configuration, " functioning as a resource container" do + before do + @graph = Puppet::Node::Configuration.new("host") + @one = stub 'resource1', :ref => "Me[you]" + @two = stub 'resource2', :ref => "Me[him]" + @dupe = stub 'resource3', :ref => "Me[you]" + end + + it "should make all vertices available by resource reference" do + @graph.add_resource(@one) + @graph.resource(@one.ref).should equal(@one) + end + + it "should not allow two resources with the same resource reference" do + @graph.add_resource(@one) + proc { @graph.add_resource(@dupe) }.should raise_error(ArgumentError) + end + + it "should not store objects that do not respond to :ref" do + proc { @graph.add_resource("thing") }.should raise_error(ArgumentError) + end +end diff --git a/spec/unit/other/pgraph.rb b/spec/unit/other/pgraph.rb index d4af87ba0..19809ac1e 100755 --- a/spec/unit/other/pgraph.rb +++ b/spec/unit/other/pgraph.rb @@ -283,28 +283,3 @@ describe Puppet::PGraph, " when sorting the graph" do proc { @graph.topsort }.should_not raise_error end end - -describe Puppet::PGraph, " functioning as a resource container" do - before do - @graph = Puppet::PGraph.new - @one = stub 'resource1', :ref => "Me[you]" - @two = stub 'resource2', :ref => "Me[him]" - @dupe = stub 'resource3', :ref => "Me[you]" - end - - it "should make all vertices available by resource reference" do - @graph.add_vertex!(@one) - @graph.resource(@one.ref).should equal(@one) - end - - it "should not allow two resources with the same resource reference" do - @graph.add_vertex!(@one) - proc { @graph.add_vertex!(@dupe) }.should raise_error(ArgumentError) - end - - it "should not store objects that do not respond to :ref" do - str = "thing" - @graph.add_vertex!(str) - @graph.resource(str).should be_nil - end -end -- cgit From 3ccf483f77b026dde8a53bd8e9dff6a5fd0f6722 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 13 Sep 2007 16:09:44 -0500 Subject: Removing the completely obsolete passwd2puppet and the obsolete component.rb --- ext/tools/passwd2puppet | 45 -------- lib/puppet/parser/ast/component.rb | 224 ------------------------------------- 2 files changed, 269 deletions(-) delete mode 100755 ext/tools/passwd2puppet delete mode 100644 lib/puppet/parser/ast/component.rb diff --git a/ext/tools/passwd2puppet b/ext/tools/passwd2puppet deleted file mode 100755 index 29ffdbf95..000000000 --- a/ext/tools/passwd2puppet +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/ruby -w - -#-------------------- -# Convert a passwd-format file to Puppet users -# - -require 'getoptlong' - -result = GetoptLong.new( - [ "--help", "-h", GetoptLong::NO_ARGUMENT ] -) - -result.each { |opt,arg| - case opt - when "--help" - puts "There is no help yet" - exit - else - raise "Invalid option '#{opt}'" - end -} - -fields = %w{uid gid comment home shell} - -puts "user {" -ARGV.each do |file| - File.open(file) do |of| - of.sort.each do |line| - next if line =~ /^\s*#/ - next if line =~ /^\s*$/ - - ary = line.chomp.split(":") - puts " " + ary.shift + ":" - ary.shift # get rid of that password field - - puts fields.zip(ary).collect { |field, val| - " %s => \"%s\"" % [field, val] - }.join(",\n") + ";" - - end - end -end -puts "}" - -# $Id$ diff --git a/lib/puppet/parser/ast/component.rb b/lib/puppet/parser/ast/component.rb deleted file mode 100644 index 1d7fe9cdd..000000000 --- a/lib/puppet/parser/ast/component.rb +++ /dev/null @@ -1,224 +0,0 @@ -require 'puppet/parser/ast/branch' - -class Puppet::Parser::AST - # Evaluate the stored parse tree for a given component. This will - # receive the arguments passed to the component and also the type and - # name of the component. - class Component < AST::Branch - include Puppet::Util - include Puppet::Util::Warnings - include Puppet::Util::MethodHelper - class << self - attr_accessor :name - end - - # The class name - @name = :definition - - attr_accessor :classname, :arguments, :code, :scope, :keyword - attr_accessor :exported, :namespace, :parser, :virtual - - # These are retrieved when looking up the superclass - attr_accessor :name - - attr_reader :parentclass - - def child_of?(klass) - false - end - - def evaluate_resource(hash) - origscope = hash[:scope] - title = hash[:title] - args = symbolize_options(hash[:arguments] || {}) - - name = args[:name] || title - - exported = hash[:exported] - virtual = hash[:virtual] - - pscope = origscope - scope = subscope(pscope, title) - - if virtual or origscope.virtual? - scope.virtual = true - end - - if exported or origscope.exported? - scope.exported = true - end - - # Additionally, add a tag for whatever kind of class - # we are - if @classname != "" and ! @classname.nil? - @classname.split(/::/).each { |tag| scope.tag(tag) } - end - - [name, title].each do |str| - unless str.nil? or str =~ /[^\w]/ or str == "" - scope.tag(str) - end - end - - # define all of the arguments in our local scope - if self.arguments - # Verify that all required arguments are either present or - # have been provided with defaults. - self.arguments.each { |arg, default| - arg = symbolize(arg) - unless args.include?(arg) - if defined? default and ! default.nil? - default = default.safeevaluate :scope => scope - args[arg] = default - #Puppet.debug "Got default %s for %s in %s" % - # [default.inspect, arg.inspect, @name.inspect] - else - parsefail "Must pass %s to %s of type %s" % - [arg,title,@classname] - end - end - } - end - - # Set each of the provided arguments as variables in the - # component's scope. - args.each { |arg,value| - unless validattr?(arg) - parsefail "%s does not accept attribute %s" % [@classname, arg] - end - - exceptwrap do - scope.setvar(arg.to_s,args[arg]) - end - } - - unless args.include? :title - scope.setvar("title",title) - end - - unless args.include? :name - scope.setvar("name",name) - end - - if self.code - return self.code.safeevaluate(:scope => scope) - else - return nil - end - end - - def initialize(hash = {}) - @arguments = nil - @parentclass = nil - super - - # Convert the arguments to a hash for ease of later use. - if @arguments - unless @arguments.is_a? Array - @arguments = [@arguments] - end - oldargs = @arguments - @arguments = {} - oldargs.each do |arg, val| - @arguments[arg] = val - end - else - @arguments = {} - end - - # Deal with metaparams in the argument list. - @arguments.each do |arg, defvalue| - next unless Puppet::Type.metaparamclass(arg) - if defvalue - warnonce "%s is a metaparam; this value will inherit to all contained elements" % arg - else - raise Puppet::ParseError, - "%s is a metaparameter; please choose another name" % - name - end - end - end - - def find_parentclass - @parser.findclass(namespace, parentclass) - end - - # Set our parent class, with a little check to avoid some potential - # weirdness. - def parentclass=(name) - if name == self.classname - parsefail "Parent classes must have dissimilar names" - end - - @parentclass = name - end - - # Hunt down our class object. - def parentobj - if @parentclass - # Cache our result, since it should never change. - unless defined?(@parentobj) - unless tmp = find_parentclass - parsefail "Could not find %s %s" % [self.class.name, @parentclass] - end - - if tmp == self - parsefail "Parent classes must have dissimilar names" - end - - @parentobj = tmp - end - @parentobj - else - nil - end - end - - # Create a new subscope in which to evaluate our code. - def subscope(scope, name = nil) - args = { - :type => self.classname, - :keyword => self.keyword, - :namespace => self.namespace - } - - args[:name] = name if name - scope = scope.newscope(args) - scope.source = self - - return scope - end - - def to_s - classname - end - - # Check whether a given argument is valid. Searches up through - # any parent classes that might exist. - def validattr?(param) - param = param.to_s - - if @arguments.include?(param) - # It's a valid arg for us - return true - elsif param == "name" - return true -# elsif defined? @parentclass and @parentclass -# # Else, check any existing parent -# if parent = @scope.lookuptype(@parentclass) and parent != [] -# return parent.validarg?(param) -# elsif builtin = Puppet::Type.type(@parentclass) -# return builtin.validattr?(param) -# else -# raise Puppet::Error, "Could not find parent class %s" % -# @parentclass -# end - elsif Puppet::Type.metaparam?(param) - return true - else - # Or just return false - return false - end - end - end -end -- cgit From f17f19dae941b17a56c1fc83ed3a89712b98c427 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 15 Sep 2007 22:17:20 -0600 Subject: The whole system now uses Configuration objects instead of ever converting the Transportable objects into a tree of components and then converting that into a graph. This is a significant step, and drastically simplifies the model of how to use a configuration. The old code might have looked something like this: file = Puppet::Type.create :path => "/whatever", ... comp = Puppet::Type.create :name => :whatever comp.push file transaction = comp.evaluate transaction.evaluate The new code looks like this: file = Puppet::Type.create :path => "/whatever", ... config = Puppet::Node::Configuration.new config.add_resource file config.apply I did not really intend to do this much refactoring, but I found I could not use a Configuration object to do work without refactoring a lot of the system. The primary problem was that the Client::Master and the Config classes determined how the transactions behaved; when I moved to using a Configuration, this distinction was lost, which meant that configurations were often needing to create other configurations, which resulted in a whole lot of infinite recursion (e.g., Config objects that create directories for Puppet use Configuration objects -- yes, I'm s/Config/Settings/g soon -- and these Configuration objects would need to create directories). Not everything is fixed, but it's very close. I am clearly over the hump, though, so I wanted to get a commit in. --- lib/puppet/defaults.rb | 9 +- lib/puppet/indirector/facts/yaml.rb | 4 - lib/puppet/network/client/master.rb | 174 ++++++------------------ lib/puppet/network/handler/fileserver.rb | 5 +- lib/puppet/node.rb | 3 + lib/puppet/node/configuration.rb | 81 ++++++++++- lib/puppet/provider/mount.rb | 2 - lib/puppet/transaction.rb | 98 ++++++++++---- lib/puppet/transportable.rb | 71 +++------- lib/puppet/type/component.rb | 1 + lib/puppet/util/config.rb | 185 ++++++++++++------------- lib/puppet/util/storage.rb | 2 - spec/unit/node/configuration.rb | 147 +++++++++++++++++++- spec/unit/node/node.rb | 4 + spec/unit/other/transbucket.rb | 20 ++- spec/unit/util/config.rb | 115 ++++++++++++++-- test/lib/puppettest/support/assertions.rb | 52 ++----- test/lib/puppettest/support/resources.rb | 34 ++--- test/lib/puppettest/support/utils.rb | 38 ++++-- test/network/client/master.rb | 52 +------ test/other/transactions.rb | 216 ++++++++++++++---------------- 21 files changed, 736 insertions(+), 577 deletions(-) diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 4b442d094..cb6e67a7b 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -494,12 +494,11 @@ module Puppet "The server through which to send email reports."] ) - self.setdefaults(:facts, + # This needs to be in main because it's used too early in the system, such that + # we get an infinite loop otherwise. + self.setdefaults(:main, :fact_store => ["yaml", - "The backend store to use for client facts."] - ) - - self.setdefaults(:yamlfacts, + "The backend store to use for client facts."], :yamlfactdir => ["$vardir/facts", "The directory in which client facts are stored when the yaml fact store is used."] ) diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb index f29ea8ebc..55f0d16ee 100644 --- a/lib/puppet/indirector/facts/yaml.rb +++ b/lib/puppet/indirector/facts/yaml.rb @@ -16,10 +16,6 @@ Puppet::Indirector.register_terminus :facts, :yaml do Puppet::Node::Facts.new(node, values) end - def initialize - Puppet.config.use(:yamlfacts) - end - # Store the facts to disk. def post(facts) File.open(path(facts.name), "w", 0600) do |f| diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index c6d7cd75d..b5824e6bd 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -7,7 +7,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client @@sync = Sync.new end - attr_accessor :objects + attr_accessor :configuration attr_reader :compile_time class << self @@ -49,50 +49,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.config[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase } end - # This method actually applies the configuration. - def apply(tags = nil, ignoreschedules = false) - unless defined? @objects - raise Puppet::Error, "Cannot apply; objects not defined" - end - - transaction = @objects.evaluate - - if tags - transaction.tags = tags - end - - if ignoreschedules - transaction.ignoreschedules = true - end - - transaction.addtimes :config_retrieval => @configtime - - begin - transaction.evaluate - rescue Puppet::Error => detail - Puppet.err "Could not apply complete configuration: %s" % - detail - rescue => detail - Puppet.err "Got an uncaught exception of type %s: %s" % - [detail.class, detail] - if Puppet[:trace] - puts detail.backtrace - end - ensure - Puppet::Util::Storage.store - end - - if Puppet[:report] or Puppet[:summarize] - report(transaction) - end - - return transaction - ensure - if defined? transaction and transaction - transaction.cleanup - end - end - # Cache the config def cache(text) Puppet.info "Caching configuration at %s" % self.cachefile @@ -111,10 +67,10 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end def clear - @objects.remove(true) if @objects + @configuration.clear(true) if @configuration Puppet::Type.allclear mkdefault_objects - @objects = nil + @configuration = nil end # Initialize and load storage @@ -183,15 +139,15 @@ class Puppet::Network::Client::Master < Puppet::Network::Client facts = self.class.facts end - if self.objects or FileTest.exists?(self.cachefile) + if self.configuration or FileTest.exists?(self.cachefile) if self.fresh?(facts) Puppet.info "Config is up to date" - if self.objects + if self.configuration return end if oldtext = self.retrievecache begin - @objects = YAML.load(oldtext).to_type + @configuration = YAML.load(oldtext).to_configuration rescue => detail Puppet.warning "Could not load cached configuration: %s" % detail end @@ -213,7 +169,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end unless objects = get_actual_config(facts) - @objects = nil + @configuration = nil return end @@ -225,21 +181,21 @@ class Puppet::Network::Client::Master < Puppet::Network::Client self.setclasses(objects.classes) # Clear all existing objects, so we can recreate our stack. - if self.objects + if self.configuration clear() end - # Now convert the objects to real Puppet objects - @objects = objects.to_type + # Now convert the objects to a puppet configuration graph. + @configuration = objects.to_configuration - if @objects.nil? + if @configuration.nil? raise Puppet::Error, "Configuration could not be processed" end - # and perform any necessary final actions before we evaluate. - @objects.finalize + # Keep the state database up to date. + @configuration.host_config = true - return @objects + return @configuration end # A simple proxy method, so it's easy to test. @@ -252,9 +208,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.config.use(:main, :ssl, :puppetd) super - # This might be nil - @configtime = 0 - self.class.instance = self @running = false @@ -297,7 +250,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end # The code that actually runs the configuration. - def run(tags = nil, ignoreschedules = false) + def run got_lock = false splay Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do @@ -307,19 +260,20 @@ class Puppet::Network::Client::Master < Puppet::Network::Client else got_lock = true begin - @configtime = thinmark do + duration = thinmark do self.getconfig end rescue => detail Puppet.err "Could not retrieve configuration: %s" % detail end - if defined? @objects and @objects + if defined? @configuration and @configuration + @configuration.retrieval_duration = duration unless @local Puppet.notice "Starting configuration run" end benchmark(:notice, "Finished configuration run") do - self.apply(tags, ignoreschedules) + @configuration.apply end end end @@ -366,9 +320,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Download files from the remote server, returning a list of all # changed files. def self.download(args) - objects = Puppet::Type.type(:component).create( - :name => "#{args[:name]}_collector" - ) hash = { :path => args[:dest], :recurse => true, @@ -383,18 +334,23 @@ class Puppet::Network::Client::Master < Puppet::Network::Client if args[:ignore] hash[:ignore] = args[:ignore].split(/\s+/) end - objects.push Puppet::Type.type(:file).create(hash) + downconfig = Puppet::Node::Configuration.new("downloading") + downconfig.add_resource Puppet::Type.type(:file).create(hash) Puppet.info "Retrieving #{args[:name]}s" noop = Puppet[:noop] Puppet[:noop] = false + files = [] begin - trans = objects.evaluate - trans.ignoretags = true Timeout::timeout(self.timeout) do - trans.evaluate + downconfig.apply do |trans| + trans.changed?.find_all do |resource| + yield resource if block_given? + files << resource[:path] + end + end end rescue Puppet::Error, Timeout::Error => detail if Puppet[:debug] @@ -403,18 +359,10 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.err "Could not retrieve #{args[:name]}s: %s" % detail end - # Now source all of the changed objects, but only source those - # that are top-level. - files = [] - trans.changed?.find_all do |object| - yield object if block_given? - files << object[:path] - end - trans.cleanup - # Now clean up after ourselves - objects.remove - files + downconfig.clear + + return files ensure # I can't imagine why this is necessary, but apparently at last one person has had problems with noop # being nil here. @@ -427,21 +375,21 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Retrieve facts from the central server. def self.getfacts - # Clear all existing definitions. - Facter.clear # Download the new facts path = Puppet[:factpath].split(":") files = [] download(:dest => Puppet[:factdest], :source => Puppet[:factsource], - :ignore => Puppet[:factsignore], :name => "fact") do |object| - - next unless path.include?(::File.dirname(object[:path])) + :ignore => Puppet[:factsignore], :name => "fact") do |resource| - files << object[:path] + next unless path.include?(::File.dirname(resource[:path])) + files << resource[:path] end ensure + # Clear all existing definitions. + Facter.clear + # Reload everything. if Facter.respond_to? :loadfacts Facter.loadfacts @@ -461,20 +409,20 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # changed plugins, because Puppet::Type loads plugins on demand. def self.getplugins download(:dest => Puppet[:plugindest], :source => Puppet[:pluginsource], - :ignore => Puppet[:pluginsignore], :name => "plugin") do |object| + :ignore => Puppet[:pluginsignore], :name => "plugin") do |resource| - next if FileTest.directory?(object[:path]) - path = object[:path].sub(Puppet[:plugindest], '').sub(/^\/+/, '') + next if FileTest.directory?(resource[:path]) + path = resource[:path].sub(Puppet[:plugindest], '').sub(/^\/+/, '') unless Puppet::Util::Autoload.loaded?(path) next end begin Puppet.info "Reloading downloaded file %s" % path - load object[:path] + load resource[:path] rescue => detail Puppet.warning "Could not reload downloaded file %s: %s" % - [object[:path], detail] + [resource[:path], detail] end end end @@ -517,42 +465,6 @@ class Puppet::Network::Client::Master < Puppet::Network::Client return timeout end - - # Send off the transaction report. - def report(transaction) - begin - report = transaction.generate_report() - rescue => detail - Puppet.err "Could not generate report: %s" % detail - return - end - - if Puppet[:rrdgraph] == true - report.graph() - end - - if Puppet[:summarize] - puts report.summary - end - - if Puppet[:report] - begin - reportclient().report(report) - rescue => detail - Puppet.err "Reporting failed: %s" % detail - end - end - end - - def reportclient - unless defined? @reportclient - @reportclient = Puppet::Network::Client.report.new( - :Server => Puppet[:reportserver] - ) - end - - @reportclient - end loadfacts() @@ -633,7 +545,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.err "Could not retrieve configuration: %s" % detail unless Puppet[:usecacheonfailure] - @objects = nil + @configuration = nil Puppet.warning "Not using cache on failed configuration" return end diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index 993e9d51a..022e0c1d4 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -243,7 +243,10 @@ class Puppet::Network::Handler # the modules. def modules_mount(module_name, client) # Find our environment, if we have one. - if node = Puppet::Node.get(client || Facter.value("hostname")) + unless hostname = (client || Facter.value("hostname")) + raise ArgumentError, "Could not find hostname" + end + if node = Puppet::Node.get(hostname) env = node.environment else env = nil diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 7ad7bc3b3..247cd0521 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -38,6 +38,9 @@ class Puppet::Node end def initialize(name, options = {}) + unless name + raise ArgumentError, "Node names cannot be nil" + end @name = name # Provide a default value. diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index be30ddb68..8cb6ed1f9 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -5,9 +5,26 @@ 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 - attr_accessor :name, :version + # The host name this is a configuration for. + attr_accessor :name + + # The configuration version. Used for testing whether a configuration + # is up to date. + attr_accessor :version + + # How long this configuration took to retrieve. Used for reporting stats. + attr_accessor :retrieval_duration + + # How we should extract the configuration for sending to the client. attr_reader :extraction_format + # Whether this is a host configuration, which behaves very differently. + # In particular, reports are sent, graphs are made, and state is + # stored in the state database. If this is set incorrectly, then you often + # end up in infinite loops, because configurations are used to make things + # that the host configuration needs. + attr_accessor :host_config + # Add classes to our class list. def add_class(*classes) classes.each do |klass| @@ -30,10 +47,50 @@ class Puppet::Node::Configuration < Puppet::PGraph else @resource_table[ref] = resource end + add_vertex!(resource) + end + + # Apply our configuration to the local host. + def apply + Puppet::Util::Storage.load if host_config? + transaction = Puppet::Transaction.new(self) + + transaction.addtimes :config_retrieval => @retrieval_duration + + begin + transaction.evaluate + rescue Puppet::Error => detail + Puppet.err "Could not apply complete configuration: %s" % detail + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Got an uncaught exception of type %s: %s" % [detail.class, detail] + ensure + # Don't try to store state unless we're a host config + # too recursive. + Puppet::Util::Storage.store if host_config? + end + + if block_given? + yield transaction + end + + if host_config and (Puppet[:report] or Puppet[:summarize]) + transaction.send_report + end + + return transaction + ensure + if defined? transaction and transaction + transaction.cleanup + end end - def clear - super + def clear(remove_resources = true) + super() + # We have to do this so that the resources clean themselves up. + @resource_table.values.each { |resource| resource.remove } if remove_resources @resource_table.clear end @@ -113,13 +170,23 @@ class Puppet::Node::Configuration < Puppet::PGraph return result end - def initialize(name) + # Make sure all of our resources are "finished". + def finalize + @resource_table.values.each { |resource| resource.finish } + end + + def initialize(name = nil) super() - @name = name + @name = name if name @extraction_format ||= :transportable @tags = [] @classes = [] @resource_table = {} + + if block_given? + yield(self) + finalize() + end end # Look a resource up by its reference (e.g., File[/etc/passwd]). @@ -127,6 +194,10 @@ class Puppet::Node::Configuration < Puppet::PGraph @resource_table[ref] end + def host_config? + host_config || false + end + # Add a tag. def tag(*names) names.each do |name| diff --git a/lib/puppet/provider/mount.rb b/lib/puppet/provider/mount.rb index 446ed641c..6a05d9826 100644 --- a/lib/puppet/provider/mount.rb +++ b/lib/puppet/provider/mount.rb @@ -50,5 +50,3 @@ module Puppet::Provider::Mount end end end - -# $Id$ diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 46bac3058..0d81b1e63 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -6,10 +6,14 @@ require 'puppet/propertychange' module Puppet class Transaction - attr_accessor :component, :resources, :ignoreschedules, :ignoretags + attr_accessor :component, :configuration, :ignoreschedules attr_accessor :relgraph, :sorted_resources, :configurator + # The report, once generated. attr_reader :report + + # The list of events generated in this transaction. + attr_reader :events attr_writer :tags @@ -129,6 +133,9 @@ class Transaction # Find all of the changed resources. def changed? @changes.find_all { |change| change.changed }.collect { |change| + unless change.property.resource + raise "No resource for %s" % change.inspect + end change.property.resource }.uniq end @@ -144,7 +151,6 @@ class Transaction if defined? @relgraph @relgraph.clear end - @resources.clear end # Copy an important relationships from the parent to the newly-generated @@ -283,7 +289,7 @@ class Transaction def evaluate @count = 0 - graph(@resources, :resources) + graph(@configuration, :resources) # Start logging. Puppet::Util::Log.newdestination(@report) @@ -347,7 +353,7 @@ class Transaction # Collect any dynamically generated resources. def generate - list = @resources.vertices + list = @configuration.vertices # Store a list of all generated resources, so that we can clean them up # after the transaction closes. @@ -369,7 +375,7 @@ class Transaction end made.uniq! made.each do |res| - @resources.add_vertex!(res) + @configuration.add_vertex!(res) newlist << res @generated << res res.finish @@ -412,8 +418,8 @@ class Transaction # Produce the graph files if requested. def graph(gr, name) - # We don't want to graph the configuration process. - return if self.configurator + # We only want to graph the main host configuration. + return unless @configuration.host_config? return unless Puppet[:graph] @@ -425,17 +431,24 @@ class Transaction } end + # Should we ignore tags? + def ignore_tags? + ! @configuration.host_config? + end + # this should only be called by a Puppet::Type::Component resource now # and it should only receive an array def initialize(resources) - if resources.is_a?(Puppet::PGraph) - @resources = resources + if resources.is_a?(Puppet::Node::Configuration) + @configuration = resources + elsif resources.is_a?(Puppet::PGraph) + raise "Transactions should get configurations now, not PGraph" else - @resources = resources.to_graph + raise "Transactions require configurations" end @resourcemetrics = { - :total => @resources.vertices.length, + :total => @configuration.vertices.length, :out_of_sync => 0, # The number of resources that had changes :applied => 0, # The number of resources fixed :skipped => 0, # The number of resources skipped @@ -474,7 +487,7 @@ class Transaction # types, just providers. def prefetch prefetchers = {} - @resources.each do |resource| + @configuration.each do |resource| if provider = resource.provider and provider.class.respond_to?(:prefetch) prefetchers[provider.class] ||= {} prefetchers[provider.class][resource.title] = resource @@ -514,7 +527,7 @@ class Transaction graph = Puppet::PGraph.new # First create the dependency graph - @resources.vertices.each do |vertex| + @configuration.vertices.each do |vertex| graph.add_vertex!(vertex) vertex.builddepends.each do |edge| graph.add_edge!(edge) @@ -538,12 +551,48 @@ class Transaction graph(graph, :relationships) # Then splice in the container information - graph.splice!(@resources, Puppet::Type::Component) + graph.splice!(@configuration, Puppet::Type::Component) graph(graph, :expanded_relationships) return graph end + + # Send off the transaction report. + def send_report + begin + report = generate_report() + rescue => detail + Puppet.err "Could not generate report: %s" % detail + return + end + + if Puppet[:rrdgraph] == true + report.graph() + end + + if Puppet[:summarize] + puts report.summary + end + + if Puppet[:report] + begin + reportclient().report(report) + rescue => detail + Puppet.err "Reporting failed: %s" % detail + end + end + end + + def reportclient + unless defined? @reportclient + @reportclient = Puppet::Network::Client.report.new( + :Server => Puppet[:reportserver] + ) + end + + @reportclient + end # Roll all completed changes back. def rollback @@ -606,7 +655,7 @@ class Transaction # Should this resource be skipped? def skip?(resource) skip = false - if ! tagged?(resource) + if missing_tags?(resource) resource.debug "Not tagged with %s" % tags.join(", ") elsif ! scheduled?(resource) resource.debug "Not scheduled" @@ -620,29 +669,22 @@ class Transaction # The tags we should be checking. def tags - # Allow the tags to be overridden unless defined? @tags - @tags = Puppet[:tags] - end - - unless defined? @processed_tags - if @tags.nil? or @tags == "" + tags = Puppet[:tags] + if tags.nil? or tags == "" @tags = [] else - @tags = [@tags] unless @tags.is_a? Array - @tags = @tags.collect do |tag| - tag.split(/\s*,\s*/) - end.flatten + @tags = tags.split(/\s*,\s*/) end - @processed_tags = true end @tags end # Is this resource tagged appropriately? - def tagged?(resource) - self.ignoretags or tags.empty? or resource.tagged?(tags) + def missing_tags?(resource) + return false if self.ignore_tags? or tags.empty? + return true unless resource.tagged?(tags) end # Are there any edges that target this resource? diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index 782ba2467..e4cde6e37 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -71,7 +71,7 @@ module Puppet @ref end - def to_type(parent = nil) + def to_type retobj = nil if typeklass = Puppet::Type.type(self.type) # FIXME This should really be done differently, but... @@ -88,10 +88,6 @@ module Puppet raise Puppet::Error.new("Could not find object type %s" % self.type) end - if parent - parent.push retobj - end - return retobj end end @@ -200,29 +196,29 @@ module Puppet end # Create a resource graph from our structure. - def to_graph - graph = Puppet::Node::Configuration.new(Facter.value(:hostname)) - - delver = proc do |obj| - obj.each do |child| - unless container = graph.resource(obj.to_ref) + def to_configuration + configuration = Puppet::Node::Configuration.new(Facter.value("hostname")) do |config| + delver = proc do |obj| + unless container = config.resource(obj.to_ref) container = obj.to_type - graph.add_resource container - end - unless resource = graph.resource(child.to_ref) - resource = child.to_type - graph.add_resource resource + config.add_resource container end - graph.add_edge!(container, resource) - if child.is_a?(self.class) - delver.call(child) + obj.each do |child| + unless resource = config.resource(child.to_ref) + next unless resource = child.to_type + config.add_resource resource + end + config.add_edge!(container, resource) + if child.is_a?(self.class) + delver.call(child) + end end end + + delver.call(self) end - delver.call(self) - - return graph + return configuration end def to_ref @@ -236,7 +232,7 @@ module Puppet @ref end - def to_type(parent = nil) + def to_type # this container will contain the equivalent of all objects at # this level #container = Puppet::Component.new(:name => @name, :type => @type) @@ -283,45 +279,16 @@ module Puppet #Puppet.debug "%s[%s] has no parameters" % [@type, @name] end - #if parent - # hash[:parent] = parent - #end container = Puppet::Type::Component.create(hash) end #Puppet.info container.inspect - if parent - parent.push container - end - # unless we successfully created the container, return an error unless container Puppet.warning "Got no container back" return nil end - self.each { |child| - # the fact that we descend here means that we are - # always going to execute depth-first - # which is _probably_ a good thing, but one never knows... - unless child.is_a?(Puppet::TransBucket) or - child.is_a?(Puppet::TransObject) - raise Puppet::DevError, - "TransBucket#to_type cannot handle objects of type %s" % - child.class - end - - # Now just call to_type on them with the container as a parent - begin - child.to_type(container) - rescue => detail - if Puppet[:trace] and ! detail.is_a?(Puppet::Error) - puts detail.backtrace - end - Puppet.err detail.to_s - end - } - # at this point, no objects at are level are still Transportable # objects return container diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 79afa95a7..1615abad8 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -52,6 +52,7 @@ Puppet::Type.newtype(:component) do # this is only called on one component over the whole system # this also won't work with scheduling, but eh def evaluate + raise "Component#evaluate is deprecated" self.finalize unless self.finalized? transaction = Puppet::Transaction.new(self) transaction.component = self diff --git a/lib/puppet/util/config.rb b/lib/puppet/util/config.rb index 9cdb4cfe3..5875f141b 100644 --- a/lib/puppet/util/config.rb +++ b/lib/puppet/util/config.rb @@ -69,14 +69,14 @@ class Puppet::Util::Config return options end - # Turn the config into a transaction and apply it + # Turn the config into a Puppet configuration and apply it def apply trans = self.to_transportable begin - comp = trans.to_type - trans = comp.evaluate - trans.evaluate - comp.remove + config = trans.to_configuration + config.store_state = false + config.apply + config.clear rescue => detail if Puppet[:trace] puts detail.backtrace @@ -476,57 +476,15 @@ class Puppet::Util::Config end # Convert a single section into transportable objects. - def section_to_transportable(section, done = nil, includefiles = true) + def section_to_transportable(section, done = nil) done ||= Hash.new { |hash, key| hash[key] = {} } objects = [] persection(section) do |obj| if @config[:mkusers] and value(:mkusers) - [:owner, :group].each do |attr| - type = nil - if attr == :owner - type = :user - else - type = attr - end - # If a user and/or group is set, then make sure we're - # managing that object - if obj.respond_to? attr and name = obj.send(attr) - # Skip root or wheel - next if %w{root wheel}.include?(name.to_s) - - # Skip owners and groups we've already done, but tag - # them with our section if necessary - if done[type].include?(name) - tags = done[type][name].tags - unless tags.include?(section) - done[type][name].tags = tags << section - end - elsif newobj = Puppet::Type.type(type)[name] - unless newobj.property(:ensure) - newobj[:ensure] = "present" - end - newobj.tag(section) - if type == :user - newobj[:comment] ||= "%s user" % name - end - else - newobj = Puppet::TransObject.new(name, type.to_s) - newobj.tags = ["puppet", "configuration", section] - newobj[:ensure] = "present" - if type == :user - newobj[:comment] ||= "%s user" % name - end - # Set the group appropriately for the user - if type == :user - newobj[:gid] = Puppet[:group] - end - done[type][name] = newobj - objects << newobj - end - end - end + objects += add_user_resources(section, obj, done) end + # Only files are convertable to transportable resources. if obj.respond_to? :to_transportable next if value(obj.name) =~ /^\/dev/ transobjects = obj.to_transportable @@ -544,7 +502,8 @@ class Puppet::Util::Config end bucket = Puppet::TransBucket.new - bucket.type = section + bucket.type = "Configuration" + bucket.name = section bucket.push(*objects) bucket.keyword = "class" @@ -634,7 +593,7 @@ Generated on #{Time.now}. end # Convert our configuration into a list of transportable objects. - def to_transportable + def to_transportable(*sections) done = Hash.new { |hash, key| hash[key] = {} } @@ -643,14 +602,23 @@ Generated on #{Time.now}. if defined? @file.file and @file.file topbucket.name = @file.file else - topbucket.name = "configtop" + topbucket.name = "top" end - topbucket.type = "puppetconfig" + topbucket.type = "Configuration" topbucket.top = true # Now iterate over each section - eachsection do |section| - topbucket.push section_to_transportable(section, done) + if sections.empty? + eachsection do |section| + sections << section + end + end + sections.each do |section| + obj = section_to_transportable(section, done) + #puts obj.to_manifest + #puts "%s: %s" % [section, obj.inspect] + topbucket.push obj + #topbucket.push section_to_transportable(section, done) end topbucket @@ -683,37 +651,31 @@ Generated on #{Time.now}. } return if runners.empty? - bucket = Puppet::TransBucket.new - bucket.type = "puppetconfig" - bucket.top = true - - # Create a hash to keep track of what we've done so far. - @done = Hash.new { |hash, key| hash[key] = {} } - runners.each do |section| - bucket.push section_to_transportable(section, @done, false) - end - - objects = bucket.to_type - - objects.finalize - tags = nil - if Puppet[:tags] - tags = Puppet[:tags] - Puppet[:tags] = "" - end - trans = objects.evaluate - trans.ignoretags = true - trans.configurator = true - trans.evaluate - if tags - Puppet[:tags] = tags - end - - # Remove is a recursive process, so it's sufficient to just call - # it on the component. - objects.remove(true) - - objects = nil + bucket = to_transportable(*sections) + + config = bucket.to_configuration + config.host_config = false + config.apply + config.clear + +# tags = nil +# if Puppet[:tags] +# tags = Puppet[:tags] +# Puppet[:tags] = "" +# end +# trans = objects.evaluate +# trans.ignoretags = true +# trans.configurator = true +# trans.evaluate +# if tags +# Puppet[:tags] = tags +# end +# +# # Remove is a recursive process, so it's sufficient to just call +# # it on the component. +# objects.remove(true) +# +# objects = nil runners.each { |s| @used << s } end @@ -845,6 +807,48 @@ Generated on #{Time.now}. private + # Create the transportable objects for users and groups. + def add_user_resources(section, obj, done) + resources = [] + [:owner, :group].each do |attr| + type = nil + if attr == :owner + type = :user + else + type = attr + end + # If a user and/or group is set, then make sure we're + # managing that object + if obj.respond_to? attr and name = obj.send(attr) + # Skip root or wheel + next if %w{root wheel}.include?(name.to_s) + + # Skip owners and groups we've already done, but tag + # them with our section if necessary + if done[type].include?(name) + tags = done[type][name].tags + unless tags.include?(section) + done[type][name].tags = tags << section + end + else + newobj = Puppet::TransObject.new(name, type.to_s) + newobj.tags = ["puppet", "configuration", section] + newobj[:ensure] = :present + if type == :user + newobj[:comment] ||= "%s user" % name + end + # Set the group appropriately for the user + if type == :user + newobj[:gid] = Puppet[:group] + end + done[type][name] = newobj + resources << newobj + end + end + end + resources + end + # Extract extra setting information for files. def extract_fileinfo(string) result = {} @@ -1141,6 +1145,9 @@ Generated on #{Time.now}. unless path =~ /^#{File::SEPARATOR}/ path = File.join(Dir.getwd, path) end + + # Skip plain files that don't exist, since we won't be managing them anyway. + return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) obj = Puppet::TransObject.new(path, "file") # Only create directories, or files that are specifically marked to @@ -1157,7 +1164,7 @@ Generated on #{Time.now}. } # Only chown or chgrp when root - if Puppet::Util::SUIDManager.uid == 0 + if Puppet.features.root? [:group, :owner].each { |var| if value = self.send(var) obj[var] = value @@ -1218,5 +1225,3 @@ Generated on #{Time.now}. end end end - -# $Id$ diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index a10183615..cd41aa572 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -99,5 +99,3 @@ class Puppet::Util::Storage end end end - -# $Id$ diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index 774a1550f..3c30d9b3e 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -134,25 +134,158 @@ describe Puppet::Node::Configuration, " when extracting transobjects" do end end -describe Puppet::Node::Configuration, " functioning as a resource container" do +describe Puppet::Node::Configuration, " when functioning as a resource container" do before do - @graph = Puppet::Node::Configuration.new("host") + @config = Puppet::Node::Configuration.new("host") @one = stub 'resource1', :ref => "Me[you]" @two = stub 'resource2', :ref => "Me[him]" @dupe = stub 'resource3', :ref => "Me[you]" end it "should make all vertices available by resource reference" do - @graph.add_resource(@one) - @graph.resource(@one.ref).should equal(@one) + @config.add_resource(@one) + @config.resource(@one.ref).should equal(@one) + @config.vertices.find { |r| r.ref == @one.ref }.should equal(@one) end it "should not allow two resources with the same resource reference" do - @graph.add_resource(@one) - proc { @graph.add_resource(@dupe) }.should raise_error(ArgumentError) + @config.add_resource(@one) + proc { @config.add_resource(@dupe) }.should raise_error(ArgumentError) end it "should not store objects that do not respond to :ref" do - proc { @graph.add_resource("thing") }.should raise_error(ArgumentError) + proc { @config.add_resource("thing") }.should raise_error(ArgumentError) end + + it "should remove all resources when asked" do + @config.add_resource @one + @config.add_resource @two + @one.expects :remove + @two.expects :remove + @config.clear(true) + end + + it "should support a mechanism for finishing resources" do + @one.expects :finish + @two.expects :finish + @config.add_resource @one + @config.add_resource @two + + @config.finalize + end + + it "should optionally support an initialization block and should finalize after such blocks" do + @one.expects :finish + @two.expects :finish + config = Puppet::Node::Configuration.new("host") do |conf| + conf.add_resource @one + conf.add_resource @two + end + end +end + +module ApplyingConfigurations + def setup + @config = Puppet::Node::Configuration.new("host") + + @config.retrieval_duration = Time.now + @transaction = mock 'transaction' + Puppet::Transaction.stubs(:new).returns(@transaction) + @transaction.stubs(:evaluate) + @transaction.stubs(:cleanup) + @transaction.stubs(:addtimes) + end +end + +describe Puppet::Node::Configuration, " when applying" do + include ApplyingConfigurations + + it "should create and evaluate a transaction" do + @transaction.expects(:evaluate) + @config.apply + end + + it "should provide the configuration time to the transaction" do + @transaction.expects(:addtimes).with do |arg| + arg[:config_retrieval].should be_instance_of(Time) + true + end + @config.apply + end + + it "should clean up the transaction" do + @transaction.expects :cleanup + @config.apply + end + + it "should return the transaction" do + @config.apply.should equal(@transaction) + end + + it "should yield the transaction if a block is provided" do + pending "the code works but is not tested" + end + + it "should default to not being a host configuration" do + @config.host_config.should be_nil + end +end + +describe Puppet::Node::Configuration, " when applying host configurations" do + include ApplyingConfigurations + + # super() doesn't work in the setup method for some reason + before do + @config.host_config = true + end + + it "should send a report if reporting is enabled" do + Puppet[:report] = true + @transaction.expects :send_report + @config.apply + end + + it "should send a report if report summaries are enabled" do + Puppet[:summarize] = true + @transaction.expects :send_report + @config.apply + end + + it "should initialize the state database before applying a configuration" do + Puppet::Util::Storage.expects(:load) + + # Short-circuit the apply, so we know we're loading before the transaction + Puppet::Transaction.expects(:new).raises ArgumentError + proc { @config.apply }.should raise_error(ArgumentError) + end + + it "should sync the state database after applying" do + Puppet::Util::Storage.expects(:store) + @config.apply + end + + after { Puppet.config.clear } +end + +describe Puppet::Node::Configuration, " when applying non-host configurations" do + include ApplyingConfigurations + + before do + @config.host_config = false + end + + it "should never send reports" do + Puppet[:report] = true + Puppet[:summarize] = true + @transaction.expects(:send_report).never + @config.apply + end + + it "should never modify the state database" do + Puppet::Util::Storage.expects(:load).never + Puppet::Util::Storage.expects(:store).never + @config.apply + end + + after { Puppet.config.clear } end diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb index 9342dc5ce..2f63c253d 100755 --- a/spec/unit/node/node.rb +++ b/spec/unit/node/node.rb @@ -11,6 +11,10 @@ describe Puppet::Node, " when initializing" do @node.name.should == "testnode" end + it "should not allow nil node names" do + proc { Puppet::Node.new(nil) }.should raise_error(ArgumentError) + end + it "should default to an empty parameter hash" do @node.parameters.should == {} end diff --git a/spec/unit/other/transbucket.rb b/spec/unit/other/transbucket.rb index c013973ee..8cb9abaa4 100755 --- a/spec/unit/other/transbucket.rb +++ b/spec/unit/other/transbucket.rb @@ -48,7 +48,7 @@ describe Puppet::TransBucket do end end -describe Puppet::TransBucket, " when generating a resource graph" do +describe Puppet::TransBucket, " when generating a configuration" do before do @bottom = Puppet::TransBucket.new @bottom.type = "fake" @@ -70,7 +70,7 @@ describe Puppet::TransBucket, " when generating a resource graph" do @top.push(@topobj) @top.push(@middle) - @graph = @top.to_graph + @config = @top.to_configuration @users = %w{top middle bottom} @fakes = %w{fake[bottom] fake[middle] fake[top]} @@ -78,18 +78,28 @@ describe Puppet::TransBucket, " when generating a resource graph" do it "should convert all transportable objects to RAL resources" do @users.each do |name| - @graph.vertices.find { |r| r.class.name == :user and r.title == name }.should be_instance_of(Puppet::Type.type(:user)) + @config.vertices.find { |r| r.class.name == :user and r.title == name }.should be_instance_of(Puppet::Type.type(:user)) end end it "should convert all transportable buckets to RAL components" do @fakes.each do |name| - @graph.vertices.find { |r| r.class.name == :component and r.title == name }.should be_instance_of(Puppet::Type.type(:component)) + @config.vertices.find { |r| r.class.name == :component and r.title == name }.should be_instance_of(Puppet::Type.type(:component)) end end it "should add all resources to the graph's resource table" do - @graph.resource("fake[top]").should equal(@top) + @config.resource("fake[top]").should equal(@top) + end + + it "should finalize all resources" do + @config.vertices.each do |vertex| vertex.should be_finalized end + end + + it "should only call to_type on each resource once" do + @topobj.expects(:to_type) + @bottomobj.expects(:to_type) + @top.to_configuration end after do diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb index 28ccb04d7..ff9e28d3b 100755 --- a/spec/unit/util/config.rb +++ b/spec/unit/util/config.rb @@ -299,7 +299,7 @@ describe Puppet::Util::Config, " when parsing its configuration" do end it "should support specifying file all metadata (owner, group, mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/my/file", "a"] + @config.setdefaults :section, :myfile => ["/myfile", "a"] text = "[main] myfile = /other/file {owner = luke, group = luke, mode = 644} @@ -312,7 +312,7 @@ describe Puppet::Util::Config, " when parsing its configuration" do end it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/my/file", "a"] + @config.setdefaults :section, :myfile => ["/myfile", "a"] text = "[main] myfile = /other/file {owner = luke} @@ -388,16 +388,34 @@ describe Puppet::Util::Config, " when reparsing its configuration" do end describe Puppet::Util::Config, " when being used to manage the host machine" do + before do + @config = Puppet::Util::Config.new + @config.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] + @config.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "luke", :group => "johnny", :mode => 0755} + @config.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755} + end + it "should provide a method that writes files with the correct modes" do pending "Not converted from test/unit yet" end it "should provide a method that creates directories with the correct modes" do - pending "Not converted from test/unit yet" + Puppet::Util::SUIDManager.expects(:asuser).with("luke", "johnny").yields + Dir.expects(:mkdir).with("/otherdir", 0755) + @config.mkdir(:otherdir) end - it "should provide a method to declare what directories should exist" do - pending "Not converted from test/unit yet" + it "should be able to create needed directories in a single section" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/seconddir") + @config.use(:main) + end + + it "should be able to create needed directories in multiple sections" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/otherdir", 0755) + Dir.expects(:mkdir).with("/seconddir") + @config.use(:main, :other) end it "should provide a method to trigger enforcing of file modes on existing files and directories" do @@ -408,13 +426,85 @@ describe Puppet::Util::Config, " when being used to manage the host machine" do pending "Not converted from test/unit yet" end - it "should provide an option to create needed users and groups" do + it "should provide a method to convert the file mode enforcement into transportable resources" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + File.stubs(:exist?).with("/myfile").returns(true) + trans = nil + trans = @config.to_transportable + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject } + %w{/maindir /seconddir /otherdir /myfile}.each do |path| + obj = resources.find { |r| r.type == "file" and r.name == path } + if path.include?("dir") + obj[:ensure].should == :directory + else + # Do not create the file, just manage mode + obj[:ensure].should be_nil + end + obj.should be_instance_of(Puppet::TransObject) + case path + when "/otherdir": + obj[:owner].should == "luke" + obj[:group].should == "johnny" + obj[:mode].should == 0755 + when "/myfile": + obj[:mode].should == 0755 + end + end + end + + it "should not try to manage user or group when not running as root" do + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @config.to_transportable(:other) + trans.delve do |obj| + next unless obj.is_a?(Puppet::TransObject) + obj[:owner].should be_nil + obj[:group].should be_nil + end + end + + it "should add needed users and groups to the manifest when asked" do + # This is how we enable user/group management + @config.setdefaults :main, :mkusers => [true, "w"] + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @config.to_transportable(:other) + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject and obj.type != "file" } + + user = resources.find { |r| r.type == "user" } + user.should be_instance_of(Puppet::TransObject) + user.name.should == "luke" + user[:ensure].should == :present + + # This should maybe be a separate test, but... + group = resources.find { |r| r.type == "group" } + group.should be_instance_of(Puppet::TransObject) + group.name.should == "johnny" + group[:ensure].should == :present + end + + it "should ignore tags and schedules when creating files and directories" + + it "should apply all resources in debug mode to reduce logging" + + it "should not try to manage absent files" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + trans = nil + trans = @config.to_transportable + file = nil + trans.delve { |obj| file = obj if obj.name == "/myfile" } + file.should be_nil + end + + it "should be able to turn the current configuration into a parseable manifest" do pending "Not converted from test/unit yet" end - it "should provide a method to print out the current configuration" do - pending "Not converted from test/unit yet" - end + it "should convert octal numbers correctly when producing a manifest" it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do pending "Not converted from test/unit yet" @@ -423,4 +513,11 @@ describe Puppet::Util::Config, " when being used to manage the host machine" do it "should not attempt to manage files within /dev" do pending "Not converted from test/unit yet" end + + it "should not modify the stored state database when managing resources" do + Puppet::Util::Storage.expects(:store).never + Puppet::Util::Storage.expects(:load).never + Dir.expects(:mkdir).with("/maindir") + @config.use(:main) + end end diff --git a/test/lib/puppettest/support/assertions.rb b/test/lib/puppettest/support/assertions.rb index 9369f17e7..7e3e5ca2b 100644 --- a/test/lib/puppettest/support/assertions.rb +++ b/test/lib/puppettest/support/assertions.rb @@ -38,7 +38,7 @@ module PuppetTest run_events(:rollback, trans, events, msg) end - def assert_events(events, *items) + def assert_events(events, *resources) trans = nil comp = nil msg = nil @@ -46,56 +46,26 @@ module PuppetTest unless events.is_a? Array raise Puppet::DevError, "Incorrect call of assert_events" end - if items[-1].is_a? String - msg = items.pop + if resources[-1].is_a? String + msg = resources.pop end - remove_comp = false - # They either passed a comp or a list of items. - if items[0].is_a? Puppet.type(:component) - comp = items.shift - else - comp = newcomp(items[0].title, *items) - remove_comp = true - end - msg ||= comp.title - assert_nothing_raised("Component %s failed" % [msg]) { - trans = comp.evaluate - } + config = resources2config(*resources) + transaction = Puppet::Transaction.new(config) - run_events(:evaluate, trans, events, msg) + run_events(:evaluate, transaction, events, msg) - if remove_comp - Puppet.type(:component).delete(comp) - end - - return trans + return transaction end # A simpler method that just applies what we have. - def assert_apply(*objects) - if objects[0].is_a?(Puppet.type(:component)) - comp = objects.shift - unless objects.empty? - objects.each { |o| comp.push o } - end - else - comp = newcomp(*objects) - end - trans = nil - - assert_nothing_raised("Failed to create transaction") { - trans = comp.evaluate - } + def assert_apply(*resources) + config = resources2config(*resources) events = nil - assert_nothing_raised("Failed to evaluate transaction") { - events = trans.evaluate.collect { |e| e.event } + assert_nothing_raised("Failed to evaluate") { + events = config.apply.events } - trans.cleanup - Puppet.type(:component).delete(comp) events end end - -# $Id$ diff --git a/test/lib/puppettest/support/resources.rb b/test/lib/puppettest/support/resources.rb index 45d89c5fb..18d7caa77 100755 --- a/test/lib/puppettest/support/resources.rb +++ b/test/lib/puppettest/support/resources.rb @@ -4,34 +4,34 @@ # Copyright (c) 2006. All rights reserved. module PuppetTest::Support::Resources - def treefile(name) - Puppet::Type.type(:file).create :path => "/tmp/#{name}", :mode => 0755 + def tree_resource(name) + Puppet::Type.type(:file).create :title => name, :path => "/tmp/#{name}", :mode => 0755 end - def treecomp(name) + def tree_container(name) Puppet::Type::Component.create :name => name, :type => "yay" end - def treenode(name, *children) - comp = treecomp name - children.each do |c| - if c.is_a?(String) - comp.push treefile(c) - else - comp.push c + def treenode(config, name, *resources) + comp = tree_container name + resources.each do |resource| + if resource.is_a?(String) + resource = tree_resource(resource) end + config.add_edge!(comp, resource) + config.add_resource resource unless config.resource(resource.ref) end return comp end def mktree - one = treenode("one", "a", "b") - two = treenode("two", "c", "d") - middle = treenode("middle", "e", "f", two) - top = treenode("top", "g", "h", middle, one) + configuration = Puppet::Node::Configuration.new do |config| + one = treenode(config, "one", "a", "b") + two = treenode(config, "two", "c", "d") + middle = treenode(config, "middle", "e", "f", two) + top = treenode(config, "top", "g", "h", middle, one) + end - return one, two, middle, top + return configuration end end - -# $Id$ \ No newline at end of file diff --git a/test/lib/puppettest/support/utils.rb b/test/lib/puppettest/support/utils.rb index c7d54d5e6..7f4260e31 100644 --- a/test/lib/puppettest/support/utils.rb +++ b/test/lib/puppettest/support/utils.rb @@ -19,6 +19,25 @@ module PuppetTest } end + # Turn a list of resources, or possibly a configuration and some resources, + # into a configuration object. + def resources2config(*resources) + if resources[0].is_a?(Puppet::Node::Configuration) + config = resources.shift + unless resources.empty? + resources.each { |r| config.add_resource r } + end + elsif resources[0].is_a?(Puppet.type(:component)) + raise ArgumentError, "resource2config() no longer accpts components" + comp = resources.shift + comp.delve + else + config = Puppet::Node::Configuration.new + resources.each { |res| config.add_resource res } + end + return config + end + # stop any services that might be hanging around def stopservices if stype = Puppet::Type.type(:service) @@ -127,20 +146,17 @@ module PuppetTest } end - def newcomp(*ary) - name = nil - if ary[0].is_a?(String) - name = ary.shift + def mk_configuration(*resources) + if resources[0].is_a?(String) + name = resources.shift else - name = ary[0].title + name = :testing + end + config = Puppet::Node::Configuration.new :testing do |conf| + resources.each { |resource| conf.add_resource resource } end - comp = Puppet.type(:component).create(:name => name) - ary.each { |item| - comp.push item - } - - return comp + return config end def setme diff --git a/test/network/client/master.rb b/test/network/client/master.rb index a29254d16..2e9ed2752 100755 --- a/test/network/client/master.rb +++ b/test/network/client/master.rb @@ -88,51 +88,6 @@ class TestMasterClient < Test::Unit::TestCase return master, objects end - def test_apply - master, objects = mk_fake_client - - check = Proc.new do |hash| - assert(objects.trans, "transaction was not created") - trans = objects.trans - hash[:yes].each do |m| - assert_equal(1, trans.send(m.to_s + "?"), "did not call #{m} enough times") - end - hash[:no].each do |m| - assert_equal(0, trans.send(m.to_s + "?"), "called #{m} too many times") - end - end - - # First try it with no arguments - assert_nothing_raised do - master.apply - end - check.call :yes => %w{evaluate cleanup addtimes}, :no => %w{report tags ignoreschedules} - assert_equal(0, master.reported, "master sent report with reports disabled") - - - # Now enable reporting and make sure the report method gets called - Puppet[:report] = true - assert_nothing_raised do - master.apply - end - check.call :yes => %w{evaluate cleanup addtimes}, :no => %w{tags ignoreschedules} - assert_equal(1, master.reported, "master did not send report") - - # Now try it with tags enabled - assert_nothing_raised do - master.apply("tags") - end - check.call :yes => %w{evaluate cleanup tags addtimes}, :no => %w{ignoreschedules} - assert_equal(2, master.reported, "master did not send report") - - # and ignoreschedules - assert_nothing_raised do - master.apply("tags", true) - end - check.call :yes => %w{evaluate cleanup tags ignoreschedules addtimes}, :no => %w{} - assert_equal(3, master.reported, "master did not send report") - end - def test_getconfig client = mkclient @@ -167,9 +122,8 @@ class TestMasterClient < Test::Unit::TestCase [:getplugins, :get_actual_config].each do |method| assert($methodsrun.include?(method), "method %s was not run" % method) end - - objects = client.objects - assert(objects.finalized?, "objects were not finalized") + + assert_instance_of(Puppet::Node::Configuration, client.configuration, "Configuration was not created") end def test_disable @@ -233,7 +187,7 @@ class TestMasterClient < Test::Unit::TestCase } end - # This method is supposed + # This method downloads files, and yields each file object if a block is given. def test_download source = tempfile() dest = tempfile() diff --git a/test/other/transactions.rb b/test/other/transactions.rb index bf5f65084..833ead662 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -7,8 +7,6 @@ require 'puppettest' require 'mocha' require 'puppettest/support/resources' -# $Id$ - class TestTransactions < Test::Unit::TestCase include PuppetTest::FileTesting include PuppetTest::Support::Resources @@ -133,7 +131,7 @@ class TestTransactions < Test::Unit::TestCase inst = type.create :name => "yay" # Create a transaction - trans = Puppet::Transaction.new(newcomp(inst)) + trans = Puppet::Transaction.new(mk_configuration(inst)) # Make sure prefetch works assert_nothing_raised do @@ -255,7 +253,7 @@ class TestTransactions < Test::Unit::TestCase } - component = newcomp("file",file) + component = mk_configuration("file",file) require 'etc' groupname = Etc.getgrgid(File.stat(file.name).gid).name assert_nothing_raised() { @@ -299,7 +297,7 @@ class TestTransactions < Test::Unit::TestCase @@tmpfiles << execfile - component = newcomp("both",file,exec) + component = mk_configuration("both",file,exec) # 'subscribe' expects an array of arrays exec[:subscribe] = [[file.class.name,file.name]] @@ -343,24 +341,30 @@ class TestTransactions < Test::Unit::TestCase file[:group] = @groups[0] assert_apply(file) - fcomp = newcomp("file",file) - ecomp = newcomp("exec",exec) + config = Puppet::Node::Configuration.new + fcomp = Puppet::Type.type(:component).create(:name => "file") + config.add_resource fcomp + config.add_resource file + config.add_edge!(fcomp, file) - component = newcomp("both",fcomp,ecomp) + ecomp = Puppet::Type.type(:component).create(:name => "exec") + config.add_resource ecomp + config.add_resource exec + config.add_edge!(ecomp, exec) # 'subscribe' expects an array of arrays #component[:require] = [[file.class.name,file.name]] ecomp[:subscribe] = fcomp exec[:refreshonly] = true - trans = assert_events([], component) + trans = assert_events([], config) assert_nothing_raised() { file[:group] = @groups[1] file[:mode] = "755" } - trans = assert_events([:file_changed, :file_changed, :triggered], component) + trans = assert_events([:file_changed, :file_changed, :triggered], config) end # Make sure that multiple subscriptions get triggered. @@ -437,11 +441,10 @@ class TestTransactions < Test::Unit::TestCase :subscribe => ["file", file.name] ) - comp = newcomp(file,exec) - comp.finalize + config = mk_configuration(file,exec) # Run it once - assert_apply(comp) + assert_apply(config) assert(FileTest.exists?(fname), "File did not get created") assert(!exec.scheduled?, "Exec is somehow scheduled") @@ -451,7 +454,7 @@ class TestTransactions < Test::Unit::TestCase file[:content] = "some content" - assert_events([:file_changed, :triggered], comp) + assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(fname), "File did not get recreated") # Now remove it, so it can get created again @@ -469,7 +472,7 @@ class TestTransactions < Test::Unit::TestCase assert(! file.insync?(file.retrieve), "Uh, file is in sync?") - assert_events([:file_changed, :triggered], comp) + assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(fname), "File did not get recreated") end @@ -493,11 +496,9 @@ class TestTransactions < Test::Unit::TestCase :ensure => :file ) - comp = newcomp(exec, file1, file2) - - comp.finalize + config = mk_configuration(exec, file1, file2) - assert_apply(comp) + assert_apply(config) assert(! FileTest.exists?(file1[:path]), "File got created even tho its dependency failed") @@ -506,24 +507,20 @@ class TestTransactions < Test::Unit::TestCase end end - def f(n) - Puppet::Type.type(:file)["/tmp/#{n.to_s}"] - end - def test_relationship_graph - one, two, middle, top = mktree + config = mktree + + config.meta_def(:f) do |name| + self.resource("File[%s]" % name) + end - {one => two, "f" => "c", "h" => middle}.each do |source, target| - if source.is_a?(String) - source = f(source) - end - if target.is_a?(String) - target = f(target) - end + {"one" => "two", "File[f]" => "File[c]", "File[h]" => "middle"}.each do |source_ref, target_ref| + source = config.resource(source_ref) or raise "Missing %s" % source_ref + target = config.resource(target_ref) or raise "Missing %s" % target_ref target[:require] = source end - trans = Puppet::Transaction.new(top) + trans = Puppet::Transaction.new(config) graph = nil assert_nothing_raised do @@ -544,13 +541,13 @@ class TestTransactions < Test::Unit::TestCase sorted = graph.topsort.reverse # Now make sure the appropriate edges are there and are in the right order - assert(graph.dependents(f(:f)).include?(f(:c)), + assert(graph.dependents(config.f(:f)).include?(config.f(:c)), "c not marked a dep of f") - assert(sorted.index(f(:c)) < sorted.index(f(:f)), + assert(sorted.index(config.f(:c)) < sorted.index(config.f(:f)), "c is not before f") - one.each do |o| - two.each do |t| + config.resource("one").each do |o| + config.resource("two").each do |t| assert(graph.dependents(o).include?(t), "%s not marked a dep of %s" % [t.ref, o.ref]) assert(sorted.index(t) < sorted.index(o), @@ -558,22 +555,22 @@ class TestTransactions < Test::Unit::TestCase end end - trans.resources.leaves(middle).each do |child| - assert(graph.dependents(f(:h)).include?(child), + trans.configuration.leaves(config.resource("middle")).each do |child| + assert(graph.dependents(config.f(:h)).include?(child), "%s not marked a dep of h" % [child.ref]) - assert(sorted.index(child) < sorted.index(f(:h)), + assert(sorted.index(child) < sorted.index(config.f(:h)), "%s is not before h" % child.ref) end # Lastly, make sure our 'g' vertex made it into the relationship # graph, since it's not involved in any relationships. - assert(graph.vertex?(f(:g)), + assert(graph.vertex?(config.f(:g)), "Lost vertexes with no relations") # Now make the reversal graph and make sure all of the vertices made it into that reverse = graph.reversal %w{a b c d e f g h}.each do |letter| - file = f(letter) + file = config.f(letter) assert(reverse.vertex?(file), "%s did not make it into reversal" % letter) end end @@ -594,15 +591,15 @@ class TestTransactions < Test::Unit::TestCase yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah" - comp = newcomp(yay, rah) - trans = comp.evaluate + config = mk_configuration(yay, rah) + trans = Puppet::Transaction.new(config) assert_nothing_raised do trans.generate end %w{ya ra y r}.each do |name| - assert(trans.resources.vertex?(Puppet::Type.type(:generator)[name]), + assert(trans.configuration.vertex?(Puppet::Type.type(:generator)[name]), "Generated %s was not a vertex" % name) assert($finished.include?(name), "%s was not finished" % name) end @@ -613,7 +610,7 @@ class TestTransactions < Test::Unit::TestCase end %w{ya ra y r}.each do |name| - assert(!trans.resources.vertex?(Puppet::Type.type(:generator)[name]), + assert(!trans.configuration.vertex?(Puppet::Type.type(:generator)[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(Puppet::Type.type(:generator)[name], "Generated vertex %s was not removed from class" % name) @@ -633,8 +630,8 @@ class TestTransactions < Test::Unit::TestCase yay = Puppet::Type.newgenerator :title => "yay" rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay - comp = newcomp(yay, rah) - trans = comp.evaluate + config = mk_configuration(yay, rah) + trans = Puppet::Transaction.new(config) trans.prepare @@ -702,7 +699,7 @@ class TestTransactions < Test::Unit::TestCase end # Now, start over and make sure that everything gets evaluated. - trans = comp.evaluate + trans = Puppet::Transaction.new(config) $evaluated.clear assert_nothing_raised do trans.evaluate @@ -712,53 +709,51 @@ class TestTransactions < Test::Unit::TestCase "Not all resources were evaluated or not in the right order") end + # Make sure tags on the transaction get copied to the configuration. def test_tags - res = Puppet::Type.newfile :path => tempfile() - comp = newcomp(res) - - # Make sure they default to none - assert_equal([], comp.evaluate.tags) - - # Make sure we get the main tags - Puppet[:tags] = %w{this is some tags} - assert_equal(%w{this is some tags}, comp.evaluate.tags) - - # And make sure they get processed correctly - Puppet[:tags] = ["one", "two,three", "four"] - assert_equal(%w{one two three four}, comp.evaluate.tags) - - # lastly, make sure we can override them - trans = comp.evaluate - trans.tags = ["one", "two,three", "four"] - assert_equal(%w{one two three four}, comp.evaluate.tags) + config = Puppet::Node::Configuration.new + transaction = Puppet::Transaction.new(config) + assert_equal([], transaction.tags, "Tags defaulted to non-empty") + + Puppet[:tags] = "one,two" + transaction = Puppet::Transaction.new(config) + assert_equal(%w{one two}, transaction.tags, "Tags were not copied from the central configuration") + end + + def test_ignore_tags? + config = Puppet::Node::Configuration.new + config.host_config = true + transaction = Puppet::Transaction.new(config) + assert(! transaction.ignore_tags?, "Ignoring tags when applying a host configuration") + + config.host_config = false + transaction = Puppet::Transaction.new(config) + assert(transaction.ignore_tags?, "Not ignoring tags when applying a non-host configuration") end - def test_tagged? - res = Puppet::Type.newfile :path => tempfile() - comp = newcomp(res) - trans = comp.evaluate - - assert(trans.tagged?(res), "tagged? defaulted to false") - - # Now set some tags - trans.tags = %w{some tags} - - # And make sure it's false - assert(! trans.tagged?(res), "matched invalid tags") - - # Set ignoretags and make sure it sticks - trans.ignoretags = true - assert(trans.tagged?(res), "tags were not ignored") - - # Now make sure we actually correctly match tags - res[:tag] = "mytag" - trans.ignoretags = false - trans.tags = %w{notag} - - assert(! trans.tagged?(res), "tags incorrectly matched") - - trans.tags = %w{mytag yaytag} - assert(trans.tagged?(res), "tags should have matched") + def test_missing_tags? + resource = stub 'resource', :tagged? => true + config = Puppet::Node::Configuration.new + + # Mark it as a host config so we don't care which test is first + config.host_config = true + transaction = Puppet::Transaction.new(config) + assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when none are set") + + # host configurations pay attention to tags, no one else does. + Puppet[:tags] = "three,four" + config.host_config = false + transaction = Puppet::Transaction.new(config) + assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when not running a host configuration") + + # + config.host_config = true + transaction = Puppet::Transaction.new(config) + assert(! transaction.missing_tags?(resource), "Considered a resource to be missing tags when running a host configuration and all tags are present") + + transaction = Puppet::Transaction.new(config) + resource.stubs :tagged? => false + assert(transaction.missing_tags?(resource), "Considered a resource not to be missing tags when running a host configuration and tags are missing") end # Make sure changes generated by eval_generated resources have proxies @@ -772,8 +767,8 @@ class TestTransactions < Test::Unit::TestCase end resource = type.create :name => "test" - comp = newcomp(resource) - trans = comp.evaluate + config = mk_configuration(resource) + trans = Puppet::Transaction.new(config) trans.prepare assert_nothing_raised do @@ -831,7 +826,7 @@ class TestTransactions < Test::Unit::TestCase end # Make a graph with some stuff in it. - graph = Puppet::PGraph.new + graph = Puppet::Node::Configuration.new # Add a non-triggering edge. a = trigger.new(:a) @@ -887,7 +882,8 @@ class TestTransactions < Test::Unit::TestCase def test_graph Puppet.config.use(:main) # Make a graph - graph = Puppet::PGraph.new + graph = Puppet::Node::Configuration.new + graph.host_config = true graph.add_edge!("a", "b") # Create our transaction @@ -908,27 +904,12 @@ class TestTransactions < Test::Unit::TestCase end assert(FileTest.exists?(dotfile), "Did not create graph.") end - - def test_created_graphs - FileUtils.mkdir_p(Puppet[:graphdir]) - file = Puppet::Type.newfile(:path => tempfile, :content => "yay") - exec = Puppet::Type.type(:exec).create(:command => "echo yay", :path => ENV['PATH'], - :require => file) - - Puppet[:graph] = true - assert_apply(file, exec) - - %w{resources relationships expanded_relationships}.each do |name| - file = File.join(Puppet[:graphdir], "%s.dot" % name) - assert(FileTest.exists?(file), "graph for %s was not created" % name) - end - end def test_set_target file = Puppet::Type.newfile(:path => tempfile(), :content => "yay") exec1 = Puppet::Type.type(:exec).create :command => "/bin/echo exec1" exec2 = Puppet::Type.type(:exec).create :command => "/bin/echo exec2" - trans = Puppet::Transaction.new(newcomp(file, exec1, exec2)) + trans = Puppet::Transaction.new(mk_configuration(file, exec1, exec2)) # First try it with an edge that has no callback edge = Puppet::Relationship.new(file, exec1) @@ -975,7 +956,8 @@ class TestTransactions < Test::Unit::TestCase one[:require] = two two[:require] = one - trans = newcomp(one, two).evaluate + config = mk_configuration(one, two) + trans = Puppet::Transaction.new(config) assert_raise(Puppet::Error) do trans.prepare end @@ -1042,8 +1024,8 @@ class TestTransactions < Test::Unit::TestCase rels[dir] = file rels.each do |after, before| - comp = newcomp(before, after) - trans = comp.evaluate + config = mk_configuration(before, after) + trans = Puppet::Transaction.new(config) str = "from %s to %s" % [before, after] assert_nothing_raised("Failed to create graph %s" % str) do @@ -1063,7 +1045,7 @@ class TestTransactions < Test::Unit::TestCase one[:require] = two one[:subscribe] = two - comp = newcomp(one, two) + comp = mk_configuration(one, two) trans = Puppet::Transaction.new(comp) graph = trans.relationship_graph @@ -1188,5 +1170,3 @@ class TestTransactions < Test::Unit::TestCase assert_equal(1, $flushed, "object was flushed in noop") end end - -# $Id$ -- cgit From b3c8cdb67d9a423a1d14764f1e58f677d7ef8d41 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 15 Sep 2007 22:25:54 -0600 Subject: Configurations now set a "configuration" instance variable in resources that are inside a configuration, so the resources can interact with the configuration to get things like relationships. --- lib/puppet/node/configuration.rb | 1 + lib/puppet/type.rb | 4 +++- spec/unit/node/configuration.rb | 15 +++++++++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 8cb6ed1f9..9cd23926e 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -47,6 +47,7 @@ class Puppet::Node::Configuration < Puppet::PGraph else @resource_table[ref] = resource end + resource.configuration = self add_vertex!(resource) end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index b4b6e3b18..03f571d4a 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -44,7 +44,9 @@ class Type # that it is clear whether it operates on all attributes (thus has 'attr' in # the method name, or whether it operates on a specific type of attributes. attr_accessor :file, :line - attr_reader :parent + + # The configuration that this resource is stored in. + attr_accessor :configuration attr_writer :title attr_writer :noop diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index 3c30d9b3e..071afc041 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -137,9 +137,9 @@ end describe Puppet::Node::Configuration, " when functioning as a resource container" do before do @config = Puppet::Node::Configuration.new("host") - @one = stub 'resource1', :ref => "Me[you]" - @two = stub 'resource2', :ref => "Me[him]" - @dupe = stub 'resource3', :ref => "Me[you]" + @one = stub 'resource1', :ref => "Me[you]", :configuration= => nil + @two = stub 'resource2', :ref => "Me[him]", :configuration= => nil + @dupe = stub 'resource3', :ref => "Me[you]", :configuration= => nil end it "should make all vertices available by resource reference" do @@ -182,6 +182,11 @@ describe Puppet::Node::Configuration, " when functioning as a resource container conf.add_resource @two end end + + it "should inform the resource that it is the resource's configuration" do + @one.expects(:configuration=).with(@config) + @config.add_resource @one + end end module ApplyingConfigurations @@ -223,7 +228,9 @@ describe Puppet::Node::Configuration, " when applying" do end it "should yield the transaction if a block is provided" do - pending "the code works but is not tested" + @config.apply do |trans| + trans.should equal(@transaction) + end end it "should default to not being a host configuration" do -- cgit From 19e049322879f5195b6fb653ae0c63e776e94835 Mon Sep 17 00:00:00 2001 From: Rick Bradley Date: Mon, 17 Sep 2007 16:49:53 -0500 Subject: Updates to indirection stuffs. Making a better spec and migrating to it. --- lib/puppet/indirector.rb | 33 +++++++++++--- lib/puppet/indirector/indirection.rb | 2 +- spec/bin/spec | 1 + spec/lib/spec/dsl/behaviour.rb | 2 +- spec/spec_helper.rb | 5 ++- spec/unit/indirector/indirector.rb | 83 ++++++++++++++++++++++++++++++++++-- 6 files changed, 113 insertions(+), 13 deletions(-) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 704039abc..13cf3991f 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -42,7 +42,7 @@ module Puppet::Indirector # Set up autoloading of the appropriate termini. instance_load name, "puppet/indirector/%s" % name end - + # Define a new indirection terminus. This method is used by the individual # termini in their separate files. Again, the autoloader takes care of # actually loading these files. @@ -77,14 +77,36 @@ module Puppet::Indirector if defined?(@indirection) raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection.name, indirection] end + + # JRB: this associates an indirection class with this class (e.g., Node.@indirection = Indirection.new(:node)) @indirection = Indirection.new(indirection, options) # Set up autoloading of the appropriate termini. Puppet::Indirector.register_indirection indirection - return @indirection + extend ClassMethods + include InstanceMethods end + module InstanceMethods + # these become instance methods + def save + end + end + + module ClassMethods + def find + end + + def destroy + end + + def search + end + end + + # JRB:FIXME: these methods to be deprecated: + # Define methods for each of the HTTP methods. These just point to the # termini, with consistent error-handling. Each method is called with # the first argument being the indirection type and the rest of the @@ -103,14 +125,13 @@ module Puppet::Indirector private - # Redirect a given HTTP method. + + # Redirect one of our methods to the corresponding method on the Terminus def redirect(method_name, *args) begin @indirection.terminus.send(method_name, *args) rescue NoMethodError => detail - if Puppet[:trace] - puts detail.backtrace - end + puts detail.backtrace if Puppet[:trace] raise ArgumentError, "The %s terminus of the %s indirection failed to respond to %s: %s" % [@indirection.terminus.name, @indirection.name, method_name, detail] end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 7a4c4bd55..67b8a9d48 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -6,7 +6,7 @@ class Puppet::Indirector::Indirection def self.clear_cache @@indirections.each { |ind| ind.clear_cache } end - + attr_accessor :name, :termini attr_reader :to diff --git a/spec/bin/spec b/spec/bin/spec index a7e6ce0cb..aaf320f34 100755 --- a/spec/bin/spec +++ b/spec/bin/spec @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'spec' ::Spec::Runner::CommandLine.run(ARGV, STDERR, STDOUT, true, true) diff --git a/spec/lib/spec/dsl/behaviour.rb b/spec/lib/spec/dsl/behaviour.rb index 93a357f19..159a0ba7e 100644 --- a/spec/lib/spec/dsl/behaviour.rb +++ b/spec/lib/spec/dsl/behaviour.rb @@ -1,4 +1,4 @@ -require 'puppettest/runnable_test' +require(File.expand_path(File.dirname(__FILE__) + '../../../../../test/lib/puppettest/runnable_test.rb')) module Spec module DSL diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 477842495..3017f272a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,6 @@ -dir = File.dirname(__FILE__) -$:.unshift("#{dir}/lib").unshift("#{dir}/../lib") +dir = File.expand_path(File.dirname(__FILE__)) +$:.unshift("#{dir}/lib") +$:.unshift("#{dir}/../lib") # Add the old test dir, so that we can still find mocha and spec $:.unshift("#{dir}/../test/lib") diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 312c60951..489ac57c4 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -5,9 +5,86 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/defaults' require 'puppet/indirector' +class TestThingie + extend Puppet::Indirector + indirects :thingie +end + +class TestNormalThingie +end + +describe Puppet::Indirector, " when included into a class" do + before do + @thingie = Class.new + @thingie.send(:extend, Puppet::Indirector) + end + + it "should provide the indirects method to the class" do + @thingie.should respond_to(:indirects) + end + + it "should require a name to register when indirecting" do + Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) + end + + it "should require each indirection to be registered under a unique name" do + @thingie.send(:indirects, :name) + Proc.new {@thingie.send(:indirects, :name)}.should raise_error(ArgumentError) + end + + it "should not allow a class to register multiple indirections" do + @thingie.send(:indirects, :first) + Proc.new {@thingie.send(:indirects, :second)}.should raise_error(ArgumentError) + end + + it "should provide a way to access the list of registered classes" + + it "should provide a way to find a class, given the registered name" + + it "should make a find method available on the registered class" do + @thingie.send(:indirects, :first) + @thingie.should respond_to(:find) + end + + it "should make a destroy method available on the registered class" do + @thingie.send(:indirects, :first) + @thingie.should respond_to(:destroy) + end + + it "should make a search method available on the registered class" do + @thingie.send(:indirects, :first) + @thingie.should respond_to(:search) + end + + it "should make a save method available on instances of the registered class" do + @thing = TestThingie.new + @thing.should respond_to(:save) + end + + + + # when dealing with Terminus methods + it "should look up the indirection configuration for the registered class when a new instance of that class is created" + + it "should use the Terminus described in the class configuration" + + it "should use the Terminus find method when calling find on the registered class" + it "should use the Terminus save method when calling save on the registered class" + it "should use the Terminus destroy method when calling destroy on the registered class" + it "should use the Terminus search method when calling search on the registered class" + + it "should allow a registered class to specify its own means of ..." +end + + + + + + + describe Puppet::Indirector, " when managing indirections" do before do - @indirector = Object.new + @indirector = Class.new @indirector.send(:extend, Puppet::Indirector) end @@ -24,7 +101,7 @@ describe Puppet::Indirector, " when managing indirections" do it "should allow multiple classes to use the same indirection" do @indirector.indirects :test - other = Object.new + other = Class.new other.send(:extend, Puppet::Indirector) proc { other.indirects :test }.should_not raise_error end @@ -41,7 +118,7 @@ end describe Puppet::Indirector, " when performing indirections" do before do - @indirector = Object.new + @indirector = Class.new @indirector.send(:extend, Puppet::Indirector) @indirector.indirects :test, :to => :node_source -- cgit From 9fa2628a844c75b8f554f283dfece01667f20594 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Mon, 17 Sep 2007 15:21:44 -0700 Subject: This is basically another intermediate commit. I feel like I've gone too far down the rabbit hole to turn back now, but the code is clearly getting more centralized around the Configuration class, which is the goal. Things are currently a bit muddy between recursion, dynamic resource generation, transactions, and the configuration, and I don't expect to be able to clear it up much until we rewrite all of the tests for the Transaction class, since that is when we'll actually be setting its behaviour. At this point, Files (which are currently the only resources that generate other resources) are responsible for adding their edges to the relationship graph. This puts them knowing more than I would like about how the relationship graph works, but it'll have to do for now. There are still failing tests, but files seem to work again. Now to go through the rest of the tests and make them work. --- bin/puppet | 4 +- ext/module_puppet | 4 +- lib/puppet/dsl.rb | 7 +- lib/puppet/metatype/closure.rb | 13 --- lib/puppet/metatype/container.rb | 8 -- lib/puppet/metatype/instances.rb | 7 +- lib/puppet/network/handler/resource.rb | 13 +-- lib/puppet/node/configuration.rb | 164 +++++++++++++++++++++++++++++---- lib/puppet/reports/store.rb | 2 +- lib/puppet/transaction.rb | 89 ++++-------------- lib/puppet/transportable.rb | 7 +- lib/puppet/type.rb | 13 +++ lib/puppet/type/component.rb | 24 +---- lib/puppet/type/pfile.rb | 18 ++-- lib/puppet/util/config.rb | 18 ++-- lib/puppet/util/graph.rb | 39 -------- spec/unit/node/configuration.rb | 153 ++++++++++++++++++++++++++++++ spec/unit/ral/type.rb | 25 +++++ spec/unit/util/config.rb | 16 +++- test/lib/puppettest/parsertesting.rb | 10 +- test/network/handler/master.rb | 9 +- test/other/dsl.rb | 2 - test/other/events.rb | 67 +++----------- test/other/overrides.rb | 6 +- test/other/relationships.rb | 2 +- test/other/report.rb | 14 +-- test/other/transactions.rb | 61 ++++-------- test/ral/manager/type.rb | 24 ++++- test/ral/providers/service.rb | 4 +- test/ral/types/basic.rb | 11 +-- test/ral/types/component.rb | 113 ----------------------- test/ral/types/cron.rb | 4 +- test/ral/types/exec.rb | 10 +- test/ral/types/file.rb | 30 ++++-- test/ral/types/file/target.rb | 6 +- test/ral/types/fileignoresource.rb | 48 ++-------- test/ral/types/filesources.rb | 61 ++---------- test/ral/types/group.rb | 4 +- test/ral/types/tidy.rb | 2 +- test/ral/types/user.rb | 18 ++-- test/util/config.rb | 148 +---------------------------- test/util/fact_store.rb | 67 -------------- test/util/graph.rb | 108 ---------------------- 43 files changed, 536 insertions(+), 917 deletions(-) delete mode 100644 lib/puppet/util/graph.rb create mode 100755 spec/unit/ral/type.rb delete mode 100755 test/ral/types/component.rb delete mode 100755 test/util/fact_store.rb delete mode 100755 test/util/graph.rb diff --git a/bin/puppet b/bin/puppet index 36f0fcd62..9520492a2 100755 --- a/bin/puppet +++ b/bin/puppet @@ -194,8 +194,8 @@ begin if Puppet[:parseonly] exit(0) end - client.getconfig - client.apply + config = client.getconfig + config.apply rescue => detail if detail.is_a?(XMLRPC::FaultException) $stderr.puts detail.message diff --git a/ext/module_puppet b/ext/module_puppet index cb03b6ef2..194f3fa09 100755 --- a/ext/module_puppet +++ b/ext/module_puppet @@ -191,8 +191,8 @@ if parseonly end begin - client.getconfig - client.apply + config = client.getconfig + config.apply rescue => detail Puppet.err detail exit(1) diff --git a/lib/puppet/dsl.rb b/lib/puppet/dsl.rb index 795358e83..3696cd9ee 100644 --- a/lib/puppet/dsl.rb +++ b/lib/puppet/dsl.rb @@ -67,11 +67,8 @@ module Puppet def apply bucket = export() - objects = bucket.to_type - master = Puppet::Network::Client.master.new :Master => "whatever" - master.objects = objects - - master.apply + configuration = bucket.to_configuration + configuration.apply end def export diff --git a/lib/puppet/metatype/closure.rb b/lib/puppet/metatype/closure.rb index efb4712c6..727bc6884 100644 --- a/lib/puppet/metatype/closure.rb +++ b/lib/puppet/metatype/closure.rb @@ -1,19 +1,6 @@ class Puppet::Type attr_writer :implicit - def self.implicitcreate(hash) - unless hash.include?(:implicit) - hash[:implicit] = true - end - if obj = self.create(hash) - obj.implicit = true - - return obj - else - return nil - end - end - # Is this type's name isomorphic with the object? That is, if the # name conflicts, does it necessarily mean that the objects conflict? # Defaults to true. diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb index 7c44a7def..7bbccf8a0 100644 --- a/lib/puppet/metatype/container.rb +++ b/lib/puppet/metatype/container.rb @@ -14,14 +14,6 @@ class Puppet::Type self.class.depthfirst? end - def parent=(parent) - if self.parentof?(parent) - devfail "%s[%s] is already the parent of %s[%s]" % - [self.class.name, self.title, parent.class.name, parent.title] - end - @parent = parent - end - # Add a hook for testing for recursion. def parentof?(child) if (self == child) diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb index f6c2fdd34..4fd72e85c 100644 --- a/lib/puppet/metatype/instances.rb +++ b/lib/puppet/metatype/instances.rb @@ -79,8 +79,7 @@ class Puppet::Type end # Force users to call this, so that we can merge objects if - # necessary. FIXME This method should be responsible for most of the - # error handling. + # necessary. def self.create(args) # Don't modify the original hash; instead, create a duplicate and modify it. # We have to dup and use the ! so that it stays a TransObject if it is @@ -308,8 +307,8 @@ class Puppet::Type # Create the path for logging and such. def pathbuilder - if defined? @parent and @parent - [@parent.pathbuilder, self.ref].flatten + if p = parent + [p.pathbuilder, self.ref].flatten else [self.ref] end diff --git a/lib/puppet/network/handler/resource.rb b/lib/puppet/network/handler/resource.rb index ac29dce53..7709b85fe 100755 --- a/lib/puppet/network/handler/resource.rb +++ b/lib/puppet/network/handler/resource.rb @@ -39,21 +39,14 @@ class Puppet::Network::Handler end end - component = bucket.to_type - - # Create a client, but specify the remote machine as the server - # because the class requires it, even though it's unused - client = Puppet::Network::Client.client(:Master).new(:Master => client||"localhost") - - # Set the objects - client.objects = component + config = bucket.to_configuration # And then apply the configuration. This way we're reusing all # the code in there. It should probably just be separated out, though. - transaction = client.apply + transaction = config.apply # And then clean up - component.remove + config.clear(true) # It'd be nice to return some kind of report, but... at this point # we have no such facility. diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 9cd23926e..c882e0ee1 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -35,20 +35,22 @@ class Puppet::Node::Configuration < Puppet::PGraph tag(*classes) end - # Add a resource to our graph and to our resource table. - def add_resource(resource) - unless resource.respond_to?(:ref) - raise ArgumentError, "Can only add objects that respond to :ref" - end + # Add one or more resources to our graph and to our resource table. + def add_resource(*resources) + resources.each do |resource| + unless resource.respond_to?(:ref) + raise ArgumentError, "Can only add objects that respond to :ref" + end - ref = resource.ref - if @resource_table.include?(ref) - raise ArgumentError, "Resource %s is already defined" % ref - else - @resource_table[ref] = resource + ref = resource.ref + if @resource_table.include?(ref) + raise ArgumentError, "Resource %s is already defined" % ref + else + @resource_table[ref] = resource + end + resource.configuration = self + add_vertex!(resource) end - resource.configuration = self - add_vertex!(resource) end # Apply our configuration to the local host. @@ -58,6 +60,8 @@ class Puppet::Node::Configuration < Puppet::PGraph transaction.addtimes :config_retrieval => @retrieval_duration + @applying = true + begin transaction.evaluate rescue Puppet::Error => detail @@ -83,22 +87,62 @@ class Puppet::Node::Configuration < Puppet::PGraph return transaction ensure + @applying = false + cleanup() if defined? transaction and transaction transaction.cleanup end end + + # Are we in the middle of applying the configuration? + def applying? + @applying + end def clear(remove_resources = true) super() # We have to do this so that the resources clean themselves up. @resource_table.values.each { |resource| resource.remove } if remove_resources @resource_table.clear + + if defined?(@relationship_graph) and @relationship_graph + @relationship_graph.clear(false) + @relationship_graph = nil + end end def classes @classes.dup end + # Create an implicit resource, meaning that it will lose out + # to any explicitly defined resources. This method often returns + # nil. + def create_implicit_resource(type, options) + unless options.include?(:implicit) + options[:implicit] = true + end + if resource = create_resource(type, options) + resource.implicit = true + + return resource + else + return nil + end + end + + # Create a new resource and register it in the configuration. + def create_resource(type, options) + unless klass = Puppet::Type.type(type) + raise ArgumentError, "Unknown resource type %s" % type + end + return unless resource = klass.create(options) + + @transient_resources << resource if applying? + add_resource(resource) + resource + end + # Make sure we support the requested extraction format. def extraction_format=(value) unless respond_to?("extract_to_%s" % value) @@ -174,6 +218,12 @@ class Puppet::Node::Configuration < Puppet::PGraph # Make sure all of our resources are "finished". def finalize @resource_table.values.each { |resource| resource.finish } + + write_graph(:resources) + end + + def host_config? + host_config || false end def initialize(name = nil) @@ -183,20 +233,76 @@ class Puppet::Node::Configuration < Puppet::PGraph @tags = [] @classes = [] @resource_table = {} + @transient_resources = [] + @applying = false if block_given? yield(self) finalize() end end + + # Create a graph of all of the relationships in our configuration. + def relationship_graph + unless defined? @relationship_graph and @relationship_graph + relationships = self.class.new + + # First create the dependency graph + self.vertices.each do |vertex| + relationships.add_resource vertex + vertex.builddepends.each do |edge| + relationships.add_edge!(edge) + end + end + + # Lastly, add in any autorequires + relationships.vertices.each do |vertex| + vertex.autorequire.each do |edge| + unless relationships.edge?(edge) + unless relationships.edge?(edge.target, edge.source) + vertex.debug "Autorequiring %s" % [edge.source] + relationships.add_edge!(edge) + else + vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) + end + end + end + end + + relationships.write_graph(:relationships) + + # Then splice in the container information + relationships.splice!(self, Puppet::Type::Component) + + relationships.write_graph(:expanded_relationships) + @relationship_graph = relationships + end + @relationship_graph + end - # Look a resource up by its reference (e.g., File[/etc/passwd]). - def resource(ref) - @resource_table[ref] + # Remove the resource from our configuration. Notice that we also call + # 'remove' on the resource, at least until resource classes no longer maintain + # references to the resource instances. + def remove_resource(*resources) + resources.each do |resource| + @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) + remove_vertex!(resource) if vertex?(resource) + resource.remove + end end - def host_config? - host_config || false + # Look a resource up by its reference (e.g., File[/etc/passwd]). + def resource(type, title = nil) + if title + ref = "%s[%s]" % [type.to_s.capitalize, title] + else + ref = type + end + if resource = @resource_table[ref] + return resource + elsif defined?(@relationship_graph) and @relationship_graph + @relationship_graph.resource(ref) + end end # Add a tag. @@ -217,4 +323,28 @@ class Puppet::Node::Configuration < Puppet::PGraph def tags @tags.dup end + + # Produce the graph files if requested. + def write_graph(name) + # We only want to graph the main host configuration. + return unless host_config? + + return unless Puppet[:graph] + + Puppet.config.use(:graphing) + + file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) + File.open(file, "w") { |f| + f.puts to_dot("name" => name.to_s.capitalize) + } + end + + private + + def cleanup + unless @transient_resources.empty? + remove_resource(*@transient_resources) + @transient_resources.clear + end + end end diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb index 8d6e11379..d51f50372 100644 --- a/lib/puppet/reports/store.rb +++ b/lib/puppet/reports/store.rb @@ -13,7 +13,7 @@ Puppet::Network::Handler.report.newreport(:store, :useyaml => true) do def mkclientdir(client, dir) config = Puppet::Util::Config.new config.setdefaults("reportclient-#{client}", - "clientdir-#{client}" => { :default => dir, + "client-#{client}-dir" => { :default => dir, :mode => 0750, :desc => "Client dir for %s" % client, :owner => Puppet[:user], diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 0d81b1e63..43889927c 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -7,7 +7,7 @@ require 'puppet/propertychange' module Puppet class Transaction attr_accessor :component, :configuration, :ignoreschedules - attr_accessor :relgraph, :sorted_resources, :configurator + attr_accessor :sorted_resources, :configurator # The report, once generated. attr_reader :report @@ -32,7 +32,7 @@ class Transaction # dependencies, then don't delete it unless it's implicit or the # dependency is itself being deleted. if resource.purging? and resource.deleting? - if deps = @relgraph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? } + if deps = relationship_graph.dependents(resource) and ! deps.empty? and deps.detect { |d| ! d.deleting? } resource.warning "%s still depend%s on me -- not purging" % [deps.collect { |r| r.ref }.join(","), deps.length > 1 ? "":"s"] return false @@ -144,12 +144,7 @@ class Transaction # contained resources might never get cleaned up. def cleanup if defined? @generated - @generated.each do |resource| - resource.remove - end - end - if defined? @relgraph - @relgraph.clear + relationship_graph.remove_resource(*@generated) end end @@ -164,10 +159,11 @@ class Transaction else edge = [resource, gen_child] end - unless @relgraph.edge?(edge[1], edge[0]) - @relgraph.add_edge!(*edge) + relationship_graph.add_resource(gen_child) unless relationship_graph.resource(gen_child.ref) + + unless relationship_graph.edge?(edge[1], edge[0]) + relationship_graph.add_edge!(*edge) else - @relgraph.add_vertex!(gen_child) resource.debug "Skipping automatic relationship to %s" % gen_child end end @@ -198,7 +194,8 @@ class Transaction children.each do |child| child.finish # Make sure that the vertex is in the relationship graph. - @relgraph.add_vertex!(child) + relationship_graph.add_resource(child) unless relationship_graph.resource(child.ref) + child.configuration = relationship_graph end @generated += children return children @@ -271,7 +268,7 @@ class Transaction # Collect the targets of any subscriptions to those events. We pass # the parent resource in so it will override the source in the events, # since eval_generated children can't have direct relationships. - @relgraph.matching_edges(events, resource).each do |edge| + relationship_graph.matching_edges(events, resource).each do |edge| edge = edge.dup label = edge.label label[:event] = events.collect { |e| e.event } @@ -289,8 +286,6 @@ class Transaction def evaluate @count = 0 - graph(@configuration, :resources) - # Start logging. Puppet::Util::Log.newdestination(@report) @@ -320,6 +315,7 @@ class Transaction Puppet.debug "Finishing transaction %s with %s changes" % [self.object_id, @count] + @events = allevents allevents end @@ -339,7 +335,7 @@ class Transaction # enough to check the immediate dependencies, which is why we use # a tree from the reversed graph. skip = false - deps = @relgraph.dependencies(resource) + deps = relationship_graph.dependencies(resource) deps.each do |dep| if fails = failed?(dep) resource.notice "Dependency %s[%s] has %s failures" % @@ -375,7 +371,8 @@ class Transaction end made.uniq! made.each do |res| - @configuration.add_vertex!(res) + @configuration.add_resource(res) + res.configuration = configuration newlist << res @generated << res res.finish @@ -416,21 +413,6 @@ class Transaction return @report end - # Produce the graph files if requested. - def graph(gr, name) - # We only want to graph the main host configuration. - return unless @configuration.host_config? - - return unless Puppet[:graph] - - Puppet.config.use(:graphing) - - file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) - File.open(file, "w") { |f| - f.puts gr.to_dot("name" => name.to_s.capitalize) - } - end - # Should we ignore tags? def ignore_tags? ! @configuration.host_config? @@ -514,48 +496,13 @@ class Transaction # Now add any dynamically generated resources generate() - - # Create a relationship graph from our resource graph - @relgraph = relationship_graph # This will throw an error if there are cycles in the graph. - @sorted_resources = @relgraph.topsort + @sorted_resources = relationship_graph.topsort end - - # Create a graph of all of the relationships in our resource graph. - def relationship_graph - graph = Puppet::PGraph.new - - # First create the dependency graph - @configuration.vertices.each do |vertex| - graph.add_vertex!(vertex) - vertex.builddepends.each do |edge| - graph.add_edge!(edge) - end - end - - # Lastly, add in any autorequires - graph.vertices.each do |vertex| - vertex.autorequire.each do |edge| - unless graph.edge?(edge) - unless graph.edge?(edge.target, edge.source) - vertex.debug "Autorequiring %s" % [edge.source] - graph.add_edge!(edge) - else - vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) - end - end - end - end - - graph(graph, :relationships) - - # Then splice in the container information - graph.splice!(@configuration, Puppet::Type::Component) - graph(graph, :expanded_relationships) - - return graph + def relationship_graph + configuration.relationship_graph end # Send off the transaction report. @@ -621,7 +568,7 @@ class Transaction end # FIXME This won't work right now. - @relgraph.matching_edges(events).each do |edge| + relationship_graph.matching_edges(events).each do |edge| @targets[edge.target] << edge end diff --git a/lib/puppet/transportable.rb b/lib/puppet/transportable.rb index e4cde6e37..ecd179ed7 100644 --- a/lib/puppet/transportable.rb +++ b/lib/puppet/transportable.rb @@ -174,12 +174,7 @@ module Puppet end end - str = nil - if self.top - str = "%s" - else - str = "#{@keyword} #{@type} {\n%s\n}" - end + str = "#{@keyword} #{@name} {\n%s\n}" str % @children.collect { |child| child.to_manifest }.collect { |str| diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 03f571d4a..1db435224 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -312,6 +312,19 @@ class Type return self[:name] end + # Look up our parent in the configuration, if we have one. + def parent + return nil unless configuration + + # We should never have more than one parent, so let's just ignore + # it if we happen to. + if parents = configuration.adjacent(self, :direction => :in) + return parents.shift + else + return nil + end + end + # Return the "type[name]" style reference. def ref "%s[%s]" % [self.class.name.to_s.capitalize, self.title] diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 1615abad8..1eadeb060 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -163,8 +163,8 @@ Puppet::Type.newtype(:component) do else myname = self.title end - if self.parent - return [@parent.pathbuilder, myname] + if p = self.parent + return [p.pathbuilder, myname] else return [myname] end @@ -215,24 +215,6 @@ Puppet::Type.newtype(:component) do end } end - - # Convert to a graph object with all of the container info. - def to_graph - graph = Puppet::PGraph.new - - delver = proc do |obj| - obj.each do |child| - graph.add_edge!(obj, child) - if child.is_a?(self.class) - delver.call(child) - end - end - end - - delver.call(self) - - return graph - end def to_s if self.title =~ /\[/ @@ -242,5 +224,3 @@ Puppet::Type.newtype(:component) do end end end - -# $Id$ diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 99b5a7435..da345e5d6 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -574,6 +574,9 @@ module Puppet # Create a new file or directory object as a child to the current # object. def newchild(path, local, hash = {}) + unless configuration + raise Puppet::DevError, "File recursion cannot happen without a configuration" + end # make local copy of arguments args = symbolize_options(@arghash) @@ -608,13 +611,12 @@ module Puppet } child = nil - klass = self.class # The child might already exist because 'localrecurse' runs # before 'sourcerecurse'. I could push the override stuff into # a separate method or something, but the work is the same other # than this last bit, so it doesn't really make sense. - if child = klass[path] + if child = configuration.resource(:file, path) unless child.parent.object_id == self.object_id self.debug "Not managing more explicit file %s" % path @@ -640,12 +642,7 @@ module Puppet #notice "Creating new file with args %s" % args.inspect args[:parent] = self begin - child = klass.implicitcreate(args) - - # implicit creation can return nil - if child.nil? - return nil - end + return nil unless child = configuration.create_implicit_resource(:file, args) rescue Puppet::Error => detail self.notice( "Cannot manage: %s" % @@ -661,6 +658,7 @@ module Puppet self.debug args.inspect child = nil end + configuration.relationship_graph.add_edge! self, child end return child end @@ -695,9 +693,7 @@ module Puppet # files. def recurse # are we at the end of the recursion? - unless self.recurse? - return - end + return unless self.recurse? recurse = self[:recurse] # we might have a string, rather than a number diff --git a/lib/puppet/util/config.rb b/lib/puppet/util/config.rb index 5875f141b..3988b3fe0 100644 --- a/lib/puppet/util/config.rb +++ b/lib/puppet/util/config.rb @@ -555,9 +555,9 @@ class Puppet::Util::Config end # Convert our list of objects into a component that can be applied. - def to_component + def to_configuration transport = self.to_transportable - return transport.to_type + return transport.to_configuration end # Convert our list of objects into a configuration file. @@ -615,10 +615,7 @@ Generated on #{Time.now}. end sections.each do |section| obj = section_to_transportable(section, done) - #puts obj.to_manifest - #puts "%s: %s" % [section, obj.inspect] topbucket.push obj - #topbucket.push section_to_transportable(section, done) end topbucket @@ -1109,6 +1106,11 @@ Generated on #{Time.now}. # Set the type appropriately. Yep, a hack. This supports either naming # the variable 'dir', or adding a slash at the end. def munge(value) + # If it's not a fully qualified path... + if value.is_a?(String) and value !~ /^\$/ and value !~ /^\// + # Make it one + value = File.join(Dir.getwd, value) + end if value.to_s =~ /\/$/ @type = :directory return value.sub(/\/$/, '') @@ -1131,9 +1133,6 @@ Generated on #{Time.now}. end # Convert the object to a TransObject instance. - # FIXME There's no dependency system in place right now; if you use - # a section that requires another section, there's nothing done to - # correct that for you, at the moment. def to_transportable type = self.type return nil unless type @@ -1142,9 +1141,6 @@ Generated on #{Time.now}. objects = [] path = self.value - unless path =~ /^#{File::SEPARATOR}/ - path = File.join(Dir.getwd, path) - end # Skip plain files that don't exist, since we won't be managing them anyway. return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) diff --git a/lib/puppet/util/graph.rb b/lib/puppet/util/graph.rb deleted file mode 100644 index e51b8e25a..000000000 --- a/lib/puppet/util/graph.rb +++ /dev/null @@ -1,39 +0,0 @@ -# Created by Luke Kanies on 2006-11-16. -# Copyright (c) 2006. All rights reserved. - -require 'puppet' -require 'puppet/pgraph' - -# A module that handles the small amount of graph stuff in Puppet. -module Puppet::Util::Graph - # Make a graph where each of our children gets converted to - # the receiving end of an edge. Call the same thing on all - # of our children, optionally using a block - def to_graph(graph = nil, &block) - # Allow our calling function to send in a graph, so that we - # can call this recursively with one graph. - graph ||= Puppet::PGraph.new - - self.each do |child| - unless block_given? and ! yield(child) - graph.add_edge!(self, child) - - if graph.cyclic? - raise Puppet::Error, "%s created a cyclic graph" % self - end - - if child.respond_to?(:to_graph) - child.to_graph(graph, &block) - end - end - end - - if graph.cyclic? - raise Puppet::Error, "%s created a cyclic graph" % self - end - - graph - end -end - -# $Id$ diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index 071afc041..ecf311948 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -142,6 +142,12 @@ describe Puppet::Node::Configuration, " when functioning as a resource container @dupe = stub 'resource3', :ref => "Me[you]", :configuration= => nil end + it "should provide a method to add one or more resources" do + @config.add_resource @one, @two + @config.resource(@one.ref).should equal(@one) + @config.resource(@two.ref).should equal(@two) + end + it "should make all vertices available by resource reference" do @config.add_resource(@one) @config.resource(@one.ref).should equal(@one) @@ -187,6 +193,24 @@ describe Puppet::Node::Configuration, " when functioning as a resource container @one.expects(:configuration=).with(@config) @config.add_resource @one end + + it "should be able to find resources by reference" do + @config.add_resource @one + @config.resource(@one.ref).should equal(@one) + end + + it "should be able to find resources by reference or by type/title tuple" do + @config.add_resource @one + @config.resource("me", "you").should equal(@one) + end + + it "should have a mechanism for removing resources" do + @config.add_resource @one + @one.expects :remove + @config.remove_resource(@one) + @config.resource(@one.ref).should be_nil + @config.vertex?(@one).should be_false + end end module ApplyingConfigurations @@ -296,3 +320,132 @@ describe Puppet::Node::Configuration, " when applying non-host configurations" d after { Puppet.config.clear } end + +describe Puppet::Node::Configuration, " when creating a relationship graph" do + before do + @config = Puppet::Node::Configuration.new("host") + @compone = Puppet::Type::Component.create :name => "one" + @comptwo = Puppet::Type::Component.create :name => "two", :require => ["class", "one"] + @file = Puppet::Type.type(:file) + @one = @file.create :path => "/one" + @two = @file.create :path => "/two" + @config.add_edge! @compone, @one + @config.add_edge! @comptwo, @two + + @three = @file.create :path => "/three" + @four = @file.create :path => "/four", :require => ["file", "/three"] + @config.add_resource @compone, @comptwo, @one, @two, @three, @four + @relationships = @config.relationship_graph + end + + it "should be able to create a resource graph" do + @relationships.should be_instance_of(Puppet::Node::Configuration) + end + + it "should not have any components" do + @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil + end + + it "should have all non-component resources from the configuration" do + # The failures print out too much info, so i just do a class comparison + @relationships.resource(@one.ref).should be_instance_of(@one.class) + @relationships.resource(@three.ref).should be_instance_of(@three.class) + end + + it "should have all resource relationships set as edges" do + @relationships.edge?(@three, @four).should be_true + end + + it "should copy component relationships to all contained resources" do + @relationships.edge?(@one, @two).should be_true + end + + it "should get removed when the configuration is cleaned up" do + @relationships.expects(:clear).with(false) + @config.clear + @config.instance_variable_get("@relationship_graph").should be_nil + end + + it "should create a new relationship graph after clearing the old one" do + @relationships.expects(:clear).with(false) + @config.clear + @config.relationship_graph.should be_instance_of(Puppet::Node::Configuration) + end + + it "should look up resources in the relationship graph if not found in the main configuration" do + five = stub 'five', :ref => "File[five]", :configuration= => nil + @relationships.add_resource five + @config.resource(five.ref).should equal(five) + end + + it "should provide a method to create additional resources that also registers the resource" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + @config.create_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + + it "should provide a mechanism for creating implicit resources" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + resource.expects(:implicit=).with(true) + @config.create_implicit_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + + it "should remove resources created mid-transaction" do + args = {:name => "/yay", :ensure => :file} + resource = stub 'file', :ref => "File[/yay]", :configuration= => @config + @transaction = mock 'transaction' + Puppet::Transaction.stubs(:new).returns(@transaction) + @transaction.stubs(:evaluate) + @transaction.stubs(:cleanup) + @transaction.stubs(:addtimes) + Puppet::Type.type(:file).expects(:create).with(args).returns(resource) + resource.expects :remove + @config.apply do |trans| + @config.create_resource :file, args + @config.resource("File[/yay]").should equal(resource) + end + @config.resource("File[/yay]").should be_nil + end + + after do + Puppet::Type.allclear + end +end + +describe Puppet::Node::Configuration, " when writing dot files" do + before do + @config = Puppet::Node::Configuration.new("host") + @name = :test + @file = File.join(Puppet[:graphdir], @name.to_s + ".dot") + end + it "should only write when it is a host configuration" do + File.expects(:open).with(@file).never + @config.host_config = false + Puppet[:graph] = true + @config.write_graph(@name) + end + + it "should only write when graphing is enabled" do + File.expects(:open).with(@file).never + @config.host_config = true + Puppet[:graph] = false + @config.write_graph(@name) + end + + it "should write a dot file based on the passed name" do + File.expects(:open).with(@file, "w").yields(stub("file", :puts => nil)) + @config.expects(:to_dot).with("name" => @name.to_s.capitalize) + @config.host_config = true + Puppet[:graph] = true + @config.write_graph(@name) + end + + after do + Puppet.config.clear + end +end diff --git a/spec/unit/ral/type.rb b/spec/unit/ral/type.rb new file mode 100755 index 000000000..c8bf8c9b4 --- /dev/null +++ b/spec/unit/ral/type.rb @@ -0,0 +1,25 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Type, " when in a configuration" do + before do + @configuration = Puppet::Node::Configuration.new + @container = Puppet::Type.type(:component).create(:name => "container") + @one = Puppet::Type.type(:file).create(:path => "/file/one") + @two = Puppet::Type.type(:file).create(:path => "/file/two") + @configuration.add_resource @container + @configuration.add_resource @one + @configuration.add_resource @two + @configuration.add_edge! @container, @one + @configuration.add_edge! @container, @two + end + + it "should have no parent if there is no in edge" do + @container.parent.should be_nil + end + + it "should set its parent to its in edge" do + @one.parent.ref.should equal(@container.ref) + end +end diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb index ff9e28d3b..353bc2bcb 100755 --- a/spec/unit/util/config.rb +++ b/spec/unit/util/config.rb @@ -500,9 +500,7 @@ describe Puppet::Util::Config, " when being used to manage the host machine" do file.should be_nil end - it "should be able to turn the current configuration into a parseable manifest" do - pending "Not converted from test/unit yet" - end + it "should be able to turn the current configuration into a parseable manifest" it "should convert octal numbers correctly when producing a manifest" @@ -520,4 +518,16 @@ describe Puppet::Util::Config, " when being used to manage the host machine" do Dir.expects(:mkdir).with("/maindir") @config.use(:main) end + + it "should convert all relative paths to fully-qualified paths (#795)" do + @config[:myfile] = "unqualified" + dir = Dir.getwd + @config[:myfile].should == File.join(dir, "unqualified") + end + + it "should support a method for re-using all currently used sections" do + Dir.expects(:mkdir).with(@config[:otherdir], 0755).times(2) + @config.use(:other) + @config.reuse + end end diff --git a/test/lib/puppettest/parsertesting.rb b/test/lib/puppettest/parsertesting.rb index c4bd7dc2b..62fa7213e 100644 --- a/test/lib/puppettest/parsertesting.rb +++ b/test/lib/puppettest/parsertesting.rb @@ -308,17 +308,17 @@ module PuppetTest::ParserTesting ) } - config = nil + trans = nil assert_nothing_raised { - config = interp.compile(mknode) + trans = interp.compile(mknode) } - comp = nil + config = nil assert_nothing_raised { - comp = config.extract.to_type + config = trans.extract.to_configuration } - assert_apply(comp) + config.apply files.each do |file| assert(FileTest.exists?(file), "Did not create %s" % file) diff --git a/test/network/handler/master.rb b/test/network/handler/master.rb index 9749c7bdf..df946fa33 100755 --- a/test/network/handler/master.rb +++ b/test/network/handler/master.rb @@ -76,9 +76,10 @@ class TestMaster < Test::Unit::TestCase "Client is incorrectly up to date") Puppet.config.use(:main) + config = nil assert_nothing_raised { - client.getconfig - client.apply + config = client.getconfig + config.apply } # Now it should be up to date @@ -113,8 +114,8 @@ class TestMaster < Test::Unit::TestCase # Retrieve and apply the new config assert_nothing_raised { - client.getconfig - client.apply + config = client.getconfig + config.apply } assert(client.fresh?(facts), "Client is not up to date") diff --git a/test/other/dsl.rb b/test/other/dsl.rb index f1fd1a1e9..59610cd0f 100755 --- a/test/other/dsl.rb +++ b/test/other/dsl.rb @@ -214,5 +214,3 @@ class TestDSL < Test::Unit::TestCase assert_instance_of(Puppet::Parser::Resource, file) end end - -# $Id$ diff --git a/test/other/events.rb b/test/other/events.rb index 802a701a3..b67ea05a1 100755 --- a/test/other/events.rb +++ b/test/other/events.rb @@ -23,7 +23,7 @@ class TestEvents < Test::Unit::TestCase :subscribe => [[file.class.name, file.name]] ) - comp = newcomp("eventtesting", file, exec) + comp = mk_configuration("eventtesting", file, exec) trans = assert_events([:file_created, :triggered], comp) @@ -44,56 +44,16 @@ class TestEvents < Test::Unit::TestCase ) - comp = Puppet.type(:component).create( - :name => "eventtesting" - ) - comp.push exec - trans = comp.evaluate - events = nil - assert_nothing_raised { - events = trans.evaluate - } + config = mk_configuration + config.add_resource file + config.add_resource exec + trans = config.apply - assert_equal(1, events.length) + assert_equal(1, trans.events.length) assert_equal(0, trans.triggered?(exec, :refresh)) end - # Verify that one component can subscribe to another component and the "right" - # thing happens - def test_ladderrequire - comps = {} - objects = {} - fname = tempfile() - file = Puppet.type(:file).create( - :name => tempfile(), - :ensure => "file" - ) - - exec = Puppet.type(:exec).create( - :name => "touch %s" % fname, - :path => "/usr/bin:/bin", - :refreshonly => true - ) - - fcomp = newcomp(file) - ecomp = newcomp(exec) - comp = newcomp("laddercomp", fcomp, ecomp) - - ecomp[:subscribe] = [[fcomp.class.name, fcomp.name]] - - comp.finalize - - trans = comp.evaluate - events = nil - assert_nothing_raised { - events = trans.evaluate - } - - assert(FileTest.exists?(fname), "#{fname} does not exist") - #assert_equal(events.length, trans.triggered?(objects[:b], :refresh)) - end - def test_multiplerefreshes files = [] @@ -115,7 +75,7 @@ class TestEvents < Test::Unit::TestCase ["file", f.name] } - comp = newcomp(exec, *files) + comp = mk_configuration(exec, *files) assert_apply(comp) assert(FileTest.exists?(fname), "Exec file did not get created") @@ -147,17 +107,16 @@ class TestEvents < Test::Unit::TestCase ) execs = [exec1, exec2, exec3] - comp = newcomp(exec1,exec2,exec3) + config = mk_configuration(exec1,exec2,exec3) - trans = comp.evaluate - execs.each do |e| assert(trans.resources.vertex?(e), "%s is not in graph" % e.title) end + trans = Puppet::Transaction.new(config) + execs.each do |e| assert(config.vertex?(e), "%s is not in graph" % e.title) end trans.prepare - execs.each do |e| assert(trans.relgraph.vertex?(e), "%s is not in relgraph" % e.title) end - reverse = trans.relgraph.reversal + execs.each do |e| assert(config.vertex?(e), "%s is not in relgraph" % e.title) end + reverse = trans.relationship_graph.reversal execs.each do |e| assert(reverse.vertex?(e), "%s is not in reversed graph" % e.title) end - - assert_apply(comp) + config.apply assert(FileTest.exists?(file), "File does not exist") diff --git a/test/other/overrides.rb b/test/other/overrides.rb index 2bc443980..9a7c4b8ba 100755 --- a/test/other/overrides.rb +++ b/test/other/overrides.rb @@ -90,12 +90,10 @@ class TestOverrides < Test::Unit::TestCase } } - comp = newcomp("overrides", baseobj) - children.each { |child| comp.push child } + config = mk_configuration(baseobj, *children) assert_nothing_raised("Could not eval component") { - trans = comp.evaluate - trans.evaluate + config.apply } files.each { |path, mode| diff --git a/test/other/relationships.rb b/test/other/relationships.rb index a5c9db5dc..f463b4e5a 100755 --- a/test/other/relationships.rb +++ b/test/other/relationships.rb @@ -174,7 +174,7 @@ class TestRelationships < Test::Unit::TestCase # Now make sure that these relationships are added to the transaction's # relgraph - trans = Puppet::Transaction.new(newcomp(file, exec)) + trans = Puppet::Transaction.new(mk_configuration(file, exec)) assert_nothing_raised do trans.evaluate end diff --git a/test/other/report.rb b/test/other/report.rb index c59881f72..7499c51e2 100755 --- a/test/other/report.rb +++ b/test/other/report.rb @@ -26,16 +26,10 @@ class TestReports < Test::Unit::TestCase ) end - comp = newcomp(*objects) - - trans = nil - assert_nothing_raised("Failed to create transaction") { - trans = comp.evaluate - } - - assert_nothing_raised("Failed to evaluate transaction") { - trans.evaluate - } + config = mk_configuration(*objects) + # So the report works out. + config.retrieval_duration = 0.001 + trans = config.apply return trans.generate_report end diff --git a/test/other/transactions.rb b/test/other/transactions.rb index 833ead662..ecf0361fd 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -293,11 +293,13 @@ class TestTransactions < Test::Unit::TestCase file[:check] = check file[:group] = @groups[0] - assert_apply(file) + config = mk_configuration(file) + config.apply + config.clear(false) @@tmpfiles << execfile - component = mk_configuration("both",file,exec) + config.add_resource exec # 'subscribe' expects an array of arrays exec[:subscribe] = [[file.class.name,file.name]] @@ -315,7 +317,8 @@ class TestTransactions < Test::Unit::TestCase file[:mode] = "755" } - trans = assert_events([:file_changed, :triggered], component) + trans = assert_events([:file_changed, :triggered], config) + config.clear(false) assert(FileTest.exists?(execfile), "Execfile does not exist") File.unlink(execfile) @@ -323,7 +326,7 @@ class TestTransactions < Test::Unit::TestCase file[:group] = @groups[1] } - trans = assert_events([:file_changed, :triggered], component) + trans = assert_events([:file_changed, :triggered], config) assert(FileTest.exists?(execfile), "Execfile does not exist") end @@ -526,8 +529,8 @@ class TestTransactions < Test::Unit::TestCase assert_nothing_raised do graph = trans.relationship_graph end - - assert_instance_of(Puppet::PGraph, graph, + + assert_instance_of(Puppet::Node::Configuration, graph, "Did not get relationship graph") # Make sure all of the components are gone @@ -642,13 +645,13 @@ class TestTransactions < Test::Unit::TestCase end ya = type["ya"] assert(ya, "Did not generate ya") - assert(trans.relgraph.vertex?(ya), + assert(trans.relationship_graph.vertex?(ya), "Did not add ya to rel_graph") # Now make sure the appropriate relationships were added - assert(trans.relgraph.edge?(yay, ya), + assert(trans.relationship_graph.edge?(yay, ya), "parent was not required by child") - assert(! trans.relgraph.edge?(ya, rah), + assert(! trans.relationship_graph.edge?(ya, rah), "generated child ya inherited depencency on rah") # Now make sure it in turn eval_generates appropriately @@ -659,7 +662,7 @@ class TestTransactions < Test::Unit::TestCase %w{y}.each do |name| res = type[name] assert(res, "Did not generate %s" % name) - assert(trans.relgraph.vertex?(res), + assert(trans.relationship_graph.vertex?(res), "Did not add %s to rel_graph" % name) assert($finished.include?("y"), "y was not finished") end @@ -667,7 +670,7 @@ class TestTransactions < Test::Unit::TestCase assert_nothing_raised("failed to eval_generate with nil response") do trans.eval_resource(type["y"]) end - assert(trans.relgraph.edge?(yay, ya), "no edge was created for ya => yay") + assert(trans.relationship_graph.edge?(yay, ya), "no edge was created for ya => yay") assert_nothing_raised("failed to apply rah") do trans.eval_resource(rah) @@ -675,15 +678,15 @@ class TestTransactions < Test::Unit::TestCase ra = type["ra"] assert(ra, "Did not generate ra") - assert(trans.relgraph.vertex?(ra), + assert(trans.relationship_graph.vertex?(ra), "Did not add ra to rel_graph" % name) assert($finished.include?("ra"), "y was not finished") # Now make sure this generated resource has the same relationships as # the generating resource - assert(! trans.relgraph.edge?(yay, ra), + assert(! trans.relationship_graph.edge?(yay, ra), "rah passed its dependencies on to its children") - assert(! trans.relgraph.edge?(ya, ra), + assert(! trans.relationship_graph.edge?(ya, ra), "children have a direct relationship") # Now make sure that cleanup gets rid of those generated types. @@ -692,7 +695,7 @@ class TestTransactions < Test::Unit::TestCase end %w{ya ra y r}.each do |name| - assert(!trans.relgraph.vertex?(type[name]), + assert(!trans.relationship_graph.vertex?(type[name]), "Generated vertex %s was not removed from graph" % name) assert_nil(type[name], "Generated vertex %s was not removed from class" % name) @@ -878,32 +881,6 @@ class TestTransactions < Test::Unit::TestCase assert(trans.triggered?(c, :refresh), "Transaction did not store the trigger") end - - def test_graph - Puppet.config.use(:main) - # Make a graph - graph = Puppet::Node::Configuration.new - graph.host_config = true - graph.add_edge!("a", "b") - - # Create our transaction - trans = Puppet::Transaction.new(graph) - - assert_nothing_raised do - trans.graph(graph, :testing) - end - - dotfile = File.join(Puppet[:graphdir], "testing.dot") - assert(! FileTest.exists?(dotfile), "Enabled graphing even tho disabled") - - # Now enable graphing - Puppet[:graph] = true - - assert_nothing_raised do - trans.graph(graph, :testing) - end - assert(FileTest.exists?(dotfile), "Did not create graph.") - end def test_set_target file = Puppet::Type.newfile(:path => tempfile(), :content => "yay") @@ -1032,7 +1009,7 @@ class TestTransactions < Test::Unit::TestCase trans.prepare end - graph = trans.relgraph + graph = trans.relationship_graph assert(graph.edge?(before, after), "did not create manual relationship %s" % str) assert(! graph.edge?(after, before), "created automatic relationship %s" % str) end diff --git a/test/ral/manager/type.rb b/test/ral/manager/type.rb index 534c35759..4f2db4805 100755 --- a/test/ral/manager/type.rb +++ b/test/ral/manager/type.rb @@ -174,7 +174,7 @@ class TestType < Test::Unit::TestCase ) } - comp = newcomp(twoobj, oneobj) + comp = mk_configuration(twoobj, oneobj) assert_nothing_raised { comp.finalize @@ -732,12 +732,22 @@ class TestType < Test::Unit::TestCase end def test_path + config = mk_configuration + # Check that our paths are built correctly. Just pick a random, "normal" type. type = Puppet::Type.type(:exec) mk = Proc.new do |i, hash| hash[:title] = "exec%s" % i hash[:command] = "/bin/echo" - type.create(hash) + if parent = hash[:parent] + hash.delete(:parent) + end + res = type.create(hash) + config.add_resource res + if parent + config.add_edge!(parent, res) + end + res end exec = mk.call(1, {}) @@ -745,25 +755,31 @@ class TestType < Test::Unit::TestCase assert_equal("/Exec[exec1]", exec.path) comp = Puppet::Type.newcomponent :title => "My[component]", :type => "Yay" + config.add_resource comp exec = mk.call(2, :parent => comp) assert_equal("/My[component]/Exec[exec2]", exec.path) comp = Puppet::Type.newcomponent :name => "Other[thing]" + config.add_resource comp exec = mk.call(3, :parent => comp) assert_equal("/Other[thing]/Exec[exec3]", exec.path) comp = Puppet::Type.newcomponent :type => "server", :name => "server" + config.add_resource comp exec = mk.call(4, :parent => comp) assert_equal("/server/Exec[exec4]", exec.path) comp = Puppet::Type.newcomponent :type => "whatever", :name => "class[main]" + config.add_resource comp exec = mk.call(5, :parent => comp) assert_equal("//Exec[exec5]", exec.path) - comp = Puppet::Type.newcomponent :type => "yay", :name => "Good[bad]", :parent => comp - exec = mk.call(6, :parent => comp) + newcomp = Puppet::Type.newcomponent :type => "yay", :name => "Good[bad]" + config.add_resource newcomp + config.add_edge! comp, newcomp + exec = mk.call(6, :parent => newcomp) assert_equal("//Good[bad]/Exec[exec6]", exec.path) end diff --git a/test/ral/providers/service.rb b/test/ral/providers/service.rb index d21298162..5a4a6c1c2 100755 --- a/test/ral/providers/service.rb +++ b/test/ral/providers/service.rb @@ -54,7 +54,7 @@ class TestLocalService < Test::Unit::TestCase service.retrieve } - comp = newcomp("servicetst", service) + comp = mk_configuration("servicetst", service) service[:ensure] = :running Puppet.info "Starting %s" % service.name @@ -105,7 +105,7 @@ class TestLocalService < Test::Unit::TestCase service.retrieve } - comp = newcomp("servicetst", service) + comp = mk_configuration("servicetst", service) service[:enable] = true Puppet.info "Enabling %s" % service.name diff --git a/test/ral/types/basic.rb b/test/ral/types/basic.rb index 5d09a5183..2802f3440 100755 --- a/test/ral/types/basic.rb +++ b/test/ral/types/basic.rb @@ -35,12 +35,9 @@ class TestBasic < Test::Unit::TestCase :path => ENV["PATH"] ) } - assert_nothing_raised() { - @component.push( - @configfile, - @command - ) - } + @config = mk_configuration(@component, @configfile, @command) + @config.add_edge! @component, @configfile + @config.add_edge! @component, @command end def teardown @@ -86,5 +83,3 @@ class TestBasic < Test::Unit::TestCase } end end - -# $Id$ diff --git a/test/ral/types/component.rb b/test/ral/types/component.rb deleted file mode 100755 index 06c32dd01..000000000 --- a/test/ral/types/component.rb +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppettest/support/resources' - -# $Id$ - -class TestComponent < Test::Unit::TestCase - include PuppetTest - include PuppetTest::Support::Resources - def setup - super - @@used = {} - @type = Puppet::Type::Component - @file = Puppet::Type.type(:file) - end - - def randnum(limit) - num = nil - looped = 0 - loop do - looped += 1 - if looped > 2000 - raise "Reached limit of looping" - break - end - num = rand(limit) - unless @@used.include?(num) - @@used[num] = true - break - end - end - - num - end - - def mkfile(num = nil) - unless num - num = randnum(1000) - end - name = tempfile() + num.to_s - - file = Puppet.type(:file).create( - :path => name, - :checksum => "md5" - ) - @@tmpfiles << name - file - end - - def mkcomp - Puppet.type(:component).create(:name => "component_" + randnum(1000).to_s) - end - - def mkrandcomp(numfiles, numdivs) - comp = mkcomp - hash = {} - found = 0 - - divs = {} - - numdivs.times { |i| - num = i + 2 - divs[num] = nil - } - while found < numfiles - num = randnum(numfiles) - found += 1 - f = mkfile(num) - hash[f.name] = f - reqd = [] - divs.each { |n,obj| - if rand(50) % n == 0 - if obj - unless reqd.include?(obj.object_id) - f[:require] = [[obj.class.name, obj.name]] - reqd << obj.object_id - end - end - end - - divs[n] = f - } - end - - hash.each { |name, obj| - comp.push obj - } - - comp.finalize - comp - end - - def test_to_graph - one, two, middle, top = mktree - - graph = nil - assert_nothing_raised do - graph = top.to_graph - end - - assert(graph.is_a?(Puppet::PGraph), "result is not a pgraph") - - [one, two, middle, top].each do |comp| - comp.each do |child| - assert(graph.edge?(comp, child), - "Did not create edge from %s => %s" % [comp.name, child.name]) - end - end - end -end diff --git a/test/ral/types/cron.rb b/test/ral/types/cron.rb index 7b2e770f0..1695befac 100755 --- a/test/ral/types/cron.rb +++ b/test/ral/types/cron.rb @@ -94,7 +94,7 @@ class TestCron < Test::Unit::TestCase text = obj.read name = cron.name - comp = newcomp(name, cron) + comp = mk_configuration(name, cron) assert_events([:cron_created], comp) cron.provider.class.prefetch @@ -157,7 +157,7 @@ class TestCron < Test::Unit::TestCase def test_makeandretrievecron %w{storeandretrieve a-name another-name more_naming SomeName}.each do |name| cron = mkcron(name) - comp = newcomp(name, cron) + comp = mk_configuration(name, cron) trans = assert_events([:cron_created], comp, name) cron.provider.class.prefetch diff --git a/test/ral/types/exec.rb b/test/ral/types/exec.rb index ede6361cd..0c7cc1d90 100755 --- a/test/ral/types/exec.rb +++ b/test/ral/types/exec.rb @@ -179,7 +179,7 @@ class TestExec < Test::Unit::TestCase ) } - comp = newcomp("createstest", exec) + comp = mk_configuration("createstest", exec) assert_events([:executed_command], comp, "creates") assert_events([], comp, "creates") end @@ -202,7 +202,7 @@ class TestExec < Test::Unit::TestCase :require => [:file, oexe] ) - comp = newcomp("Testing", file, exec) + comp = mk_configuration("Testing", file, exec) assert_events([:file_created, :executed_command], comp) end @@ -299,7 +299,7 @@ class TestExec < Test::Unit::TestCase :path => ENV['PATH'] ) } - comp = newcomp(exec) + comp = mk_configuration(exec) assert_events([:executed_command], comp) assert_events([:executed_command], comp) @@ -344,7 +344,7 @@ class TestExec < Test::Unit::TestCase exec = Puppet.type(:exec).create(args) } - comp = newcomp("usertest", exec) + comp = mk_configuration("usertest", exec) assert_events([:executed_command], comp, "usertest") assert(FileTest.exists?(file), "File does not exist") @@ -425,7 +425,7 @@ class TestExec < Test::Unit::TestCase ) } - comp = newcomp(file, exec) + comp = mk_configuration(file, exec) comp.finalize assert_events([:executed_command, :file_changed], comp) diff --git a/test/ral/types/file.rb b/test/ral/types/file.rb index f3e1a562d..47fb87afd 100755 --- a/test/ral/types/file.rb +++ b/test/ral/types/file.rb @@ -122,7 +122,7 @@ class TestFile < Test::Unit::TestCase ) } - comp = newcomp("createusertest", file) + comp = mk_configuration("createusertest", file) assert_events([:file_created], comp) end @@ -397,6 +397,8 @@ class TestFile < Test::Unit::TestCase events = assert_apply(file) + assert(events) + assert(! events.include?(:file_changed), "File incorrectly changed") assert_events([], file) @@ -493,6 +495,7 @@ class TestFile < Test::Unit::TestCase # Create a test directory path = tempfile() dir = @file.create :path => path, :mode => 0755, :recurse => true + config = mk_configuration(dir) Dir.mkdir(path) @@ -673,6 +676,7 @@ class TestFile < Test::Unit::TestCase :check => %w{owner mode group} ) } + mk_configuration dir children = nil @@ -754,6 +758,7 @@ class TestFile < Test::Unit::TestCase :check => %w{owner mode group} ) } + mk_configuration dir assert_nothing_raised { dir.eval_generate @@ -796,6 +801,7 @@ class TestFile < Test::Unit::TestCase :check => %w{mode owner group} ) } + mk_configuration dirobj assert_nothing_raised { dirobj.eval_generate @@ -884,7 +890,7 @@ class TestFile < Test::Unit::TestCase ) } - comp = newcomp("yay", file) + comp = mk_configuration("yay", file) comp.finalize assert_apply(comp) #assert_events([:directory_created], comp) @@ -1274,26 +1280,29 @@ class TestFile < Test::Unit::TestCase lfobj = Puppet::Type.newfile( :title => "localfile", :path => localfile, - :content => "rahtest" + :content => "rahtest", + :backup => false ) + Puppet[:evaltrace] = true destobj = Puppet::Type.newfile(:title => "destdir", :path => destdir, :source => sourcedir, + :backup => false, :recurse => true) - comp = newcomp(lfobj, destobj) - assert_apply(comp) + config = mk_configuration(lfobj, destobj) + config.apply assert(FileTest.exists?(dsourcefile), "File did not get copied") assert(FileTest.exists?(localfile), "File did not get created") assert(FileTest.exists?(purgee), "File got prematurely purged") assert_nothing_raised { destobj[:purge] = true } - assert_apply(comp) + config.apply - assert(FileTest.exists?(dsourcefile), "Source file got purged") assert(FileTest.exists?(localfile), "Local file got purged") + assert(FileTest.exists?(dsourcefile), "Source file got purged") assert(! FileTest.exists?(purgee), "File did not get purged") end @@ -1333,7 +1342,7 @@ class TestFile < Test::Unit::TestCase group = Puppet.type(:group).create( :name => "pptestg" ) - comp = newcomp(user, group, home) + comp = mk_configuration(user, group, home) } # Now make sure we get a relationship for each of these @@ -1629,6 +1638,7 @@ class TestFile < Test::Unit::TestCase file = File.join(dir, "file") File.open(file, "w") { |f| f.puts "" } obj = Puppet::Type.newfile :path => dir, :recurse => true, :mode => 0755 + mk_configuration obj assert_equal("/%s" % obj.ref, obj.path) @@ -1739,14 +1749,17 @@ class TestFile < Test::Unit::TestCase File.open(file, "w") { |f| f.puts "yay" } File.chmod(0644, file) obj = Puppet::Type.newfile(:path => dir, :mode => 0750, :recurse => "2") + config = mk_configuration(obj) children = nil assert_nothing_raised("Failure when recursing") do children = obj.eval_generate end + config.add_resource(*children) assert(obj.class[subdir], "did not create subdir object") children.each do |c| assert_nothing_raised("Failure when recursing on %s" % c) do + c.configuration = config others = c.eval_generate end end @@ -1780,6 +1793,7 @@ class TestFile < Test::Unit::TestCase obj = Puppet::Type.newfile(:path => dir, :ensure => :directory, :recurse => true) + config = mk_configuration(obj) children = nil assert_nothing_raised do children = obj.eval_generate diff --git a/test/ral/types/file/target.rb b/test/ral/types/file/target.rb index 1e62f07ac..c4597cedf 100755 --- a/test/ral/types/file/target.rb +++ b/test/ral/types/file/target.rb @@ -44,6 +44,7 @@ class TestFileTarget < Test::Unit::TestCase def test_linkrecurse dest = tempfile() link = @file.create :path => tempfile(), :recurse => true, :ensure => dest + mk_configuration link ret = nil @@ -317,9 +318,8 @@ class TestFileTarget < Test::Unit::TestCase :source => dirs["source"], :recurse => true ) - - - trans = assert_apply(obj) + config = mk_configuration obj + config.apply newfile = File.join(dirs["target"], "sourcefile") diff --git a/test/ral/types/fileignoresource.rb b/test/ral/types/fileignoresource.rb index fa01aecdc..153946770 100755 --- a/test/ral/types/fileignoresource.rb +++ b/test/ral/types/fileignoresource.rb @@ -73,19 +73,8 @@ class TestFileIgnoreSources < Test::Unit::TestCase ) } - #make a component and adds the file - comp = Puppet.type(:component).create( - :name => "component" - ) - comp.push tofile - - #make, evaluate transaction and sync the component - assert_nothing_raised { - trans = comp.evaluate - } - assert_nothing_raised { - trans.evaluate - } + config = mk_configuration(tofile) + config.apply #topath should exist as a directory with sourcedir as a directory @@ -150,19 +139,8 @@ class TestFileIgnoreSources < Test::Unit::TestCase ) } - #make a component and adds the file - comp = Puppet.type(:component).create( - :name => "component" - ) - comp.push tofile - - #make, evaluate transaction and sync the component - assert_nothing_raised { - trans = comp.evaluate - } - assert_nothing_raised { - trans.evaluate - } + config = mk_configuration(tofile) + config.apply #topath should exist as a directory with sourcedir as a directory @@ -170,6 +148,7 @@ class TestFileIgnoreSources < Test::Unit::TestCase assert(FileTest.exists?(File.join(topath,sourcefile1))) assert(FileTest.exists?(File.join(topath,subdir))) assert(FileTest.exists?(File.join(File.join(topath,subdir),sourcefile1))) + #This file should not assert(!(FileTest.exists?(File.join(topath,sourcefile2)))) assert(!(FileTest.exists?(File.join(topath,subdir2)))) @@ -235,19 +214,8 @@ class TestFileIgnoreSources < Test::Unit::TestCase ) } - #make a component and adds the file - comp = Puppet.type(:component).create( - :name => "component" - ) - comp.push tofile - - #make, evaluate transaction and sync the component - assert_nothing_raised { - trans = comp.evaluate - } - assert_nothing_raised { - trans.evaluate - } + config = mk_configuration(tofile) + config.apply #topath should exist as a directory with sourcedir as a directory @@ -273,5 +241,3 @@ class TestFileIgnoreSources < Test::Unit::TestCase end end - -# $Id$ diff --git a/test/ral/types/filesources.rb b/test/ral/types/filesources.rb index b257fd935..046d46c1e 100755 --- a/test/ral/types/filesources.rb +++ b/test/ral/types/filesources.rb @@ -62,6 +62,7 @@ class TestFileSources < Test::Unit::TestCase :name => path ) } + config = mk_configuration(file) child = nil assert_nothing_raised { child = file.newchild("childtest", true) @@ -275,6 +276,7 @@ class TestFileSources < Test::Unit::TestCase # The sourcerecurse method will only ever get called when we're # recursing, so we go ahead and set it. obj = Puppet::Type.newfile :source => source, :path => dest, :recurse => true + config = mk_configuration(obj) result = nil sourced = nil @@ -577,51 +579,6 @@ class TestFileSources < Test::Unit::TestCase } end - def test_networkSourcesWithoutService - server = nil - - Puppet[:autosign] = true - Puppet[:masterport] = 8765 - - serverpid = nil - assert_nothing_raised() { - server = Puppet::Network::Server::WEBrick.new( - :Handlers => { - :CA => {}, # so that certs autogenerate - } - ) - - } - serverpid = fork { - assert_nothing_raised() { - #trap(:INT) { server.shutdown; Kernel.exit! } - trap(:INT) { server.shutdown } - server.start - } - } - @@tmppids << serverpid - - sleep(1) - - name = File.join(tmpdir(), "nosourcefile") - file = Puppet.type(:file).create( - :source => "puppet://localhost/dist/file", - :name => name - ) - - assert_nothing_raised { - file.retrieve - } - - comp = newcomp("nosource", file) - - assert_nothing_raised { - comp.evaluate - } - - assert(!FileTest.exists?(name), "File with no source exists anyway") - end - def test_unmountedNetworkSources server = nil mounts = { @@ -669,11 +626,8 @@ class TestFileSources < Test::Unit::TestCase file.retrieve } - comp = newcomp("nosource", file) - - assert_nothing_raised { - comp.evaluate - } + comp = mk_configuration(file) + comp.apply assert(!FileTest.exists?(name), "File with no source exists anyway") end @@ -722,7 +676,7 @@ class TestFileSources < Test::Unit::TestCase ) } - comp = newcomp(file) + comp = mk_configuration(file) assert_events([:file_created], comp) assert(File.exists?(to), "File does not exist") @@ -808,9 +762,8 @@ class TestFileSources < Test::Unit::TestCase trans = nil assert_nothing_raised { file[:links] = :manage - comp = newcomp(file) - trans = comp.evaluate - trans.evaluate + comp = mk_configuration(file) + trans = comp.apply } assert(trans.failed?(file), "Object did not fail to copy links") diff --git a/test/ral/types/group.rb b/test/ral/types/group.rb index 9870d533a..5189c63a1 100755 --- a/test/ral/types/group.rb +++ b/test/ral/types/group.rb @@ -65,7 +65,7 @@ class TestGroup < Test::Unit::TestCase def attrtest_ensure(group) group[:ensure] = :absent - comp = newcomp("ensuretest", group) + comp = mk_configuration("ensuretest", group) assert_apply(comp) assert_equal(:absent, group.provider.ensure, "Group is still present") group[:ensure] = :present @@ -91,7 +91,7 @@ class TestGroup < Test::Unit::TestCase assert_equal(15, group.should(:gid), "Did not convert gid to number") - comp = newcomp(group) + comp = mk_configuration(group) trans = assert_events([:group_modified], comp, "group") assert_equal(15, group.provider.gid, "GID was not changed") diff --git a/test/ral/types/tidy.rb b/test/ral/types/tidy.rb index b8d576b9a..8fada1adb 100755 --- a/test/ral/types/tidy.rb +++ b/test/ral/types/tidy.rb @@ -55,7 +55,7 @@ class TestTidy < Test::Unit::TestCase assert_nothing_raised { link = newlink(:target => source, :recurse => true) } - comp = newcomp("linktest",link) + comp = mk_configuration("linktest",link) cycle(comp) path = link.name diff --git a/test/ral/types/user.rb b/test/ral/types/user.rb index 121ac9cf0..9b24cc74a 100755 --- a/test/ral/types/user.rb +++ b/test/ral/types/user.rb @@ -82,7 +82,7 @@ class TestUser < Test::Unit::TestCase old = user.provider.ensure user[:ensure] = :absent - comp = newcomp("ensuretest", user) + comp = mk_configuration("ensuretest", user) assert_apply(user) assert(!user.provider.exists?, "User is still present") user[:ensure] = :present @@ -102,7 +102,7 @@ class TestUser < Test::Unit::TestCase old = user.provider.comment user[:comment] = "A different comment" - comp = newcomp("commenttest", user) + comp = mk_configuration("commenttest", user) trans = assert_events([:user_changed], comp, "user") @@ -117,7 +117,7 @@ class TestUser < Test::Unit::TestCase def attrtest_home(user) obj = nil - comp = newcomp("hometest", user) + comp = mk_configuration("hometest", user) old = user.provider.home user[:home] = old @@ -137,7 +137,7 @@ class TestUser < Test::Unit::TestCase def attrtest_shell(user) old = user.provider.shell - comp = newcomp("shelltest", user) + comp = mk_configuration("shelltest", user) user[:shell] = old @@ -167,7 +167,7 @@ class TestUser < Test::Unit::TestCase def attrtest_gid(user) obj = nil old = user.provider.gid - comp = newcomp("gidtest", user) + comp = mk_configuration("gidtest", user) user.retrieve @@ -216,7 +216,7 @@ class TestUser < Test::Unit::TestCase def attrtest_uid(user) obj = nil - comp = newcomp("uidtest", user) + comp = mk_configuration("uidtest", user) user.provider.uid = 1 @@ -387,7 +387,7 @@ class TestUser < Test::Unit::TestCase ogroup = Puppet.type(:group).create( :name => "yayness" ) - comp = newcomp(user, group, home, ogroup) + comp = mk_configuration(user, group, home, ogroup) } rels = nil @@ -404,7 +404,7 @@ class TestUser < Test::Unit::TestCase user = mkuser(name) - comp = newcomp("usercomp", user) + comp = mk_configuration("usercomp", user) trans = assert_events([:user_created], comp, "user") @@ -424,7 +424,7 @@ class TestUser < Test::Unit::TestCase assert(! user.provider.exists?, "User %s is present" % name) - comp = newcomp("usercomp", user) + comp = mk_configuration("usercomp", user) trans = assert_events([:user_created], comp, "user") diff --git a/test/util/config.rb b/test/util/config.rb index f99ad54b4..cbd02b4f9 100755 --- a/test/util/config.rb +++ b/test/util/config.rb @@ -59,57 +59,6 @@ class TestConfig < Test::Unit::TestCase } end - # #795 - when --config=relative, we want to fully expand file paths. - def test_relative_paths_when_to_transportable - config = mkconfig - config.setdefaults :yay, :transtest => ["/what/ever", "yo"] - file = config.element(:transtest) - - # Now override it with a relative path name - config[:transtest] = "here" - - should = File.join(Dir.getwd, "here") - - object = file.to_transportable[0] - assert_equal(should, object.name, "Did not translate relative pathnames to full path names") - end - - def test_to_manifest - set_configs - manifest = nil - assert_nothing_raised("Could not convert to a manifest") { - manifest = @config.to_manifest - } - - Puppet[:parseonly] = true - - interp = nil - assert_nothing_raised do - interp = mkinterp :Code => manifest, :UseNodes => false - end - - trans = nil - node = Puppet::Node.new("node") - assert_nothing_raised do - trans = interp.compile(node) - end - assert_nothing_raised("Could not instantiate objects") { - trans.extract.to_type - } - end - - def test_to_comp - set_configs - comp = nil - assert_nothing_raised("Could not convert to a component") { - comp = @config.to_component - } - - assert_nothing_raised("Could not retrieve component") { - comp.retrieve - } - end - def test_to_config set_configs @@ -668,91 +617,6 @@ yay = /a/path assert_equal("/my/file", @config[:b], "Values are not equal") end - def test_reuse - c = mkconfig - - file = tempfile() - section = "testing" - assert_nothing_raised { - @config.setdefaults(section, - :myfile => {:default => file, :create => true, :desc => "yay"} - ) - } - - assert_nothing_raised("Could not use a section") { - @config.use(section) - } - - assert(FileTest.exists?(file), "Did not create file") - - assert(! Puppet::Type.type(:file)[file], "File obj still exists") - - File.unlink(file) - - @config.reuse - assert(FileTest.exists?(file), "Did not create file") - end - - def test_mkusers - c = mkconfig - - file = tempfile() - section = "testing" - assert_nothing_raised { - @config.setdefaults(section, - :mkusers => [false, "yay"], - :myfile => { - :default => file, - :owner => "pptest", - :group => "pptest", - :desc => "yay", - :create => true - } - ) - } - - comp = nil - assert_nothing_raised { - comp = @config.to_component - } - - [:user, :group].each do |type| - # The objects might get created internally by Puppet::Util; just - # make sure they're not being managed - if obj = Puppet.type(type)["pptest"] - assert(! obj.managed?, "%s objectis managed" % type) - end - end - comp.each { |o| o.remove } - - @config[:mkusers] = true - - assert_nothing_raised { - @config.to_component - } - - user = Puppet.type(:user)["pptest"] - assert(user, "User object did not get created") - assert(user.managed?, "User object is not managed.") - assert(user.should(:comment), "user does not have a comment set") - - group = Puppet.type(:group)["pptest"] - assert(group, "Group object did not get created") - assert(group.managed?, - "Group object is not managed." - ) - - if Process.uid == 0 - cleanup do - user[:ensure] = :absent - group[:ensure] = :absent - assert_apply(user, group) - end - - assert_apply(user, group) - end - end - def test_notmanagingdev c = mkconfig path = "/dev/testing" @@ -764,11 +628,9 @@ yay = /a/path } ) - assert_nothing_raised { - @config.to_component - } + config = @config.to_configuration - assert(! Puppet.type(:file)["/dev/testing"], "Created dev file") + assert(! config.resource(:file, "/dev/testing"), "Created dev file") end def test_groupsetting @@ -1119,12 +981,12 @@ yay = /a/path # Now enable it so they'll be added config[:mkusers] = true - comp = config.to_component + comp = config.to_configuration - Puppet::Type.type(:user).each do |u| + comp.vertices.find_all { |r| r.class.name == :user }.each do |u| assert(u.name != "root", "Tried to manage root user") end - Puppet::Type.type(:group).each do |u| + comp.vertices.find_all { |r| r.class.name == :group }.each do |u| assert(u.name != "root", "Tried to manage root group") assert(u.name != "wheel", "Tried to manage wheel group") end diff --git a/test/util/fact_store.rb b/test/util/fact_store.rb deleted file mode 100755 index 5b04d1374..000000000 --- a/test/util/fact_store.rb +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2007-05-02. -# Copyright (c) 2007. All rights reserved. - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppet/util/fact_store' - -class TestFactStore < Test::Unit::TestCase - include PuppetTest - - def test_new_fact_store - klass = nil - assert_nothing_raised("Could not create fact store") do - klass = Puppet::Util::FactStore.newstore(:yay) do - end - end - - assert_equal(klass, Puppet::Util::FactStore.store(:yay), "Did not get created store back by name") - end - - def test_yaml_store - yaml = Puppet::Util::FactStore.store(:yaml) - assert(yaml, "Could not retrieve yaml store") - - name = "node" - facts = {"a" => :b, :c => "d", :e => :f, "g" => "h"} - - store = nil - assert_nothing_raised("Could not create YAML store instance") do - store = yaml.new - end - - assert_nothing_raised("Could not store host facts") do - store.set(name, facts) - end - - dir = Puppet[:yamlfactdir] - - file = File.join(dir, name + ".yaml") - assert(FileTest.exists?(file), "Did not create yaml file for node") - - text = File.read(file) - newfacts = nil - assert_nothing_raised("Could not deserialize yaml") do - newfacts = YAML::load(text) - end - - # Don't directly compare the hashes, because there might be extra - # data stored in the client hash - facts.each do |var, value| - assert_equal(value, newfacts[var], "Value for %s changed during storage" % var) - end - - # Now make sure the facts get retrieved correctly - assert_nothing_raised("Could not retrieve facts") do - newfacts = store.get(name) - end - - # Now make sure the hashes are equal, since internal facts should not be returned. - assert_equal(facts, newfacts, "Retrieved facts are not equal") - end -end - -# $Id$ diff --git a/test/util/graph.rb b/test/util/graph.rb deleted file mode 100755 index 875fd0ec3..000000000 --- a/test/util/graph.rb +++ /dev/null @@ -1,108 +0,0 @@ -#!/usr/bin/env ruby -# -# Created by Luke Kanies on 2006-11-16. -# Copyright (c) 2006. All rights reserved. - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'puppettest' -require 'puppettest/graph' -require 'puppet/util/graph' - -class TestUtilGraph < Test::Unit::TestCase - include PuppetTest - include PuppetTest::Graph - - def test_to_graph - children = %w{a b c d} - list = Container.new("yay", children) - - graph = nil - assert_nothing_raised do - graph = list.to_graph - end - - assert(graph.vertices.include?(list), "wtf?") - - ([list] + children).each do |thing| - assert(graph.vertex?(thing), "%s is not a vertex" % thing) - end - children.each do |child| - assert(graph.edge?(list, child), - "%s/%s was not added as an edge" % ["yay", child]) - end - end - - def test_recursive_to_graph - one, two, three, middle, top = build_tree - - graph = nil - assert_nothing_raised do - graph = top.to_graph - end - - (%w{a b c d e f g h} + [one, two, middle, top]).each do |v| - assert(graph.vertex?(v), "%s is not a vertex" % v) - end - - [one, two, middle, top].each do |con| - con.each do |child| - assert(graph.edge?(con, child), "%s/%s is not an edge" % [con, child]) - end - end - - # Now make sure we correctly retrieve the leaves from each container - {top => %w{a b c d e f g h i j}, - one => %w{a b}, - two => %w{c d}, - three => %w{i j}, - middle => %w{c d e f}}.each do |cont, list| - leaves = nil - assert_nothing_raised do - leaves = graph.leaves(cont) - end - leaves = leaves.sort - assert_equal(list.sort, leaves.sort, - "Got incorrect leaf list for %s" % cont.name) - %w{a b c d e f g h}.each do |letter| - unless list.include?(letter) - assert(!leaves.include?(letter), - "incorrectly got %s as a leaf of %s" % - [letter, cont.to_s]) - end - end - end - end - - def test_to_graph_with_block - middle = Container.new "middle", ["c", "d", 3, 4] - top = Container.new "top", ["a", "b", middle, 1, 2] - - graph = nil - assert_nothing_raised() { - graph = top.to_graph { |c| c.is_a?(String) or c.is_a?(Container) } - } - - %w{a b c d}.each do |child| - assert(graph.vertex?(child), "%s was not added as a vertex" % child) - end - - [1, 2, 3, 4].each do |child| - assert(! graph.vertex?(child), "%s is a vertex" % child) - end - end - - def test_cyclic_graphs - one = Container.new "one", %w{a b} - two = Container.new "two", %w{c d} - - one.push(two) - two.push(one) - - assert_raise(Puppet::Error, "did not fail on cyclic graph") do - one.to_graph - end - end -end - -# $Id$ -- cgit From a6c4041a201dd8984cec89a96a194e96479f6657 Mon Sep 17 00:00:00 2001 From: Rick Bradley Date: Wed, 19 Sep 2007 23:02:48 -0500 Subject: Reworking the Indirector code. Continuing to fight the classgen and instance_loader "utilities". --- lib/puppet/indirector.rb | 138 +++++++++++++++++-------- spec/unit/indirector/indirector.rb | 205 +++++++++++++++++++++++-------------- 2 files changed, 225 insertions(+), 118 deletions(-) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 13cf3991f..d30f8a63d 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -7,14 +7,18 @@ module Puppet::Indirector # LAK:FIXME We need to figure out how to handle documentation for the # different indirection types. +# JRB:TODO factor this out into its own class, with specs, and require it here +# require 'puppet/indirector/terminus' + # A simple class that can function as the base class for indirected types. class Terminus require 'puppet/util/docs' extend Puppet::Util::Docs - + class << self attr_accessor :name, :indirection end + def name self.class.name end @@ -35,14 +39,18 @@ module Puppet::Indirector require 'puppet/util/instance_loader' extend Puppet::Util::InstanceLoader +# JRB:TODO - where did this come from, re: the specs? also, shouldn't this be protected/private? + # Register a given indirection type. The classes including this module # handle creating terminus instances, but the module itself handles # loading them and managing the classes. - def self.register_indirection(name) + def self.enable_autoloading_indirection(indirection) # Set up autoloading of the appropriate termini. - instance_load name, "puppet/indirector/%s" % name + instance_load indirection, "puppet/indirector/%s" % indirection end +# JRB:TODO -- where did this come from, re: the specs? also, any way to make this protected/private? + # Define a new indirection terminus. This method is used by the individual # termini in their separate files. Again, the autoloader takes care of # actually loading these files. @@ -55,56 +63,99 @@ module Puppet::Indirector :hash => instance_hash(indirection), :attributes => options, :block => block, - :parent => options[:parent] || Terminus + :parent => options[:parent] || Terminus, +# JRB:FIXME -- why do I have to use overwrite here? + :overwrite => 'please do motherfucker' ) klass.indirection = indirection klass.name = terminus end +# JRB:TODO where did this come from, re: the specs? also, shouldn't this be protected/private? # Retrieve a terminus class by indirection and name. +# JRB:FIXME -- should be protected/private def self.terminus(indirection, terminus) loaded_instance(indirection, terminus) end + + # clear out the list of known indirections + def self.reset + @indirections = {} + @class_indirections = {} + end + + # return a hash of registered indirections, keys are indirection names, values are classes which handle the indirections + def self.indirections + @indirections ||= {} + @indirections + end + + # associate an indirection name with the class which handles the indirection + def self.register_indirection(name, klass) + @indirections ||= {} + @class_indirections ||= {} + + raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [name, klass.name] if @indirections[name] + raise ArgumentError, "Class %s is already redirecting to %s; cannot redirect to %s" % + [klass.name, @class_indirections[klass.name], name] if @class_indirections[klass.name] + @class_indirections[klass.name] = name + @indirections[name] = klass + end + + def self.terminus_for_indirection(name) +# JRB:TODO make this do something useful, aka look something up in a .yml file + :ldap + end # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets # evaluated at parse time, which is before the user has had a chance # to override it. - # Options are: - # +:to+: What parameter to use as the name of the indirection terminus. def indirects(indirection, options = {}) - if defined?(@indirection) - raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [@indirection.name, indirection] - end +#JRB:TODO remove options hash ^^^ - # JRB: this associates an indirection class with this class (e.g., Node.@indirection = Indirection.new(:node)) - @indirection = Indirection.new(indirection, options) - - # Set up autoloading of the appropriate termini. - Puppet::Indirector.register_indirection indirection + # associate the name :node, with this class, Node + # also, do error checking (already registered, etc.) + Puppet::Indirector.register_indirection(indirection, self) + # populate this registered class with the various new methods extend ClassMethods include InstanceMethods - end - module InstanceMethods - # these become instance methods - def save - end + # look up the type of Terminus for this name (:node => :ldap) + terminus = Puppet::Indirector.terminus_for_indirection(indirection) + + # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) + # & hook the instantiated Terminus into this registered class (Node: @indirection = terminus) + Puppet::Indirector.enable_autoloading_indirection indirection + @indirection = Puppet::Indirector.terminus(indirection, terminus) end - - module ClassMethods - def find + + module ClassMethods + attr_reader :indirection + + def find(*args) + self.indirection.find(args) + # JRB:TODO look up the indirection, and call its .find method end - def destroy + def destroy(*args) + self.indirection.destroy(args) end - def search + def search(*args) + self.indirection.search(args) end end + module InstanceMethods + # these become instance methods + def save(*args) + self.class.indirection.save(args) + end + end + # JRB:FIXME: these methods to be deprecated: # Define methods for each of the HTTP methods. These just point to the @@ -117,23 +168,24 @@ module Puppet::Indirector # the method in question. We should probably require that indirections # declare supported methods, and then verify that termini implement all of # those methods. - [:get, :post, :put, :delete].each do |method_name| - define_method(method_name) do |*args| - redirect(method_name, *args) - end - end - - private - - - # Redirect one of our methods to the corresponding method on the Terminus - def redirect(method_name, *args) - begin - @indirection.terminus.send(method_name, *args) - rescue NoMethodError => detail - puts detail.backtrace if Puppet[:trace] - raise ArgumentError, "The %s terminus of the %s indirection failed to respond to %s: %s" % - [@indirection.terminus.name, @indirection.name, method_name, detail] - end - end + # [:get, :post, :put, :delete].each do |method_name| + # define_method(method_name) do |*args| + # redirect(method_name, *args) + # end + # end + # + # private + # + # + # # JRB:TODO this needs to be renamed, as it actually ends up on the model class, where it might conflict with something + # # Redirect one of our methods to the corresponding method on the Terminus + # def redirect(method_name, *args) + # begin + # @indirection.terminus.send(method_name, *args) + # rescue NoMethodError => detail + # puts detail.backtrace if Puppet[:trace] + # raise ArgumentError, "The %s terminus of the %s indirection failed to respond to %s: %s" % + # [@indirection.terminus.name, @indirection.name, method_name, detail] + # end + # end end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 489ac57c4..3a51fedf2 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -1,28 +1,48 @@ -#!/usr/bin/env ruby - require File.dirname(__FILE__) + '/../../spec_helper' - require 'puppet/defaults' require 'puppet/indirector' -class TestThingie - extend Puppet::Indirector - indirects :thingie +describe Puppet::Indirector do + it "should provide a way to clear all registered classes" do + Puppet::Indirector.should respond_to(:reset) + end + + it "should provide a way to access a list of registered classes" do + Puppet::Indirector.should respond_to(:indirections) + end end -class TestNormalThingie +describe Puppet::Indirector, "when no classes are registered" do + before do + Puppet::Indirector.reset + end + + it "should provide an empty list of registered classes" do + Puppet::Indirector.indirections.should == {} + end end describe Puppet::Indirector, " when included into a class" do before do - @thingie = Class.new - @thingie.send(:extend, Puppet::Indirector) + @thingie = Class.new do + extend Puppet::Indirector + end end it "should provide the indirects method to the class" do @thingie.should respond_to(:indirects) end - +end + +describe Puppet::Indirector, "when registering an indirection" do + before do + Puppet::Indirector.reset + @thingie = Class.new do + extend Puppet::Indirector + end + Puppet::Indirector.stubs(:terminus_for_indirection).returns(:ldap) + end + it "should require a name to register when indirecting" do Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) end @@ -37,10 +57,6 @@ describe Puppet::Indirector, " when included into a class" do Proc.new {@thingie.send(:indirects, :second)}.should raise_error(ArgumentError) end - it "should provide a way to access the list of registered classes" - - it "should provide a way to find a class, given the registered name" - it "should make a find method available on the registered class" do @thingie.send(:indirects, :first) @thingie.should respond_to(:find) @@ -56,83 +72,122 @@ describe Puppet::Indirector, " when included into a class" do @thingie.should respond_to(:search) end + it "should make available the indirection used for a registered class" do + @thingie.send(:indirects, :node) + @thingie.indirection.indirection.should == :node + @thingie.indirection.name.should == :ldap + end + it "should make a save method available on instances of the registered class" do - @thing = TestThingie.new + @thing = Class.new do + extend Puppet::Indirector + indirects :thing + end.new @thing.should respond_to(:save) end - - - # when dealing with Terminus methods - it "should look up the indirection configuration for the registered class when a new instance of that class is created" + it "should include the registered class in the list of all registered classes" do + @thingie.send(:indirects, :name) + Puppet::Indirector.indirections[:name].should == @thingie + end - it "should use the Terminus described in the class configuration" + # when dealing with Terminus methods + it "should look up the indirection configuration for the registered class when a new instance of that class is created" do + Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) + @thingie.send(:indirects, :node) + end + + it "should use the Terminus described in the class configuration" do + Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) + @thingie.send(:indirects, :node) + @thingie.indirection.indirection.should == :node + @thingie.indirection.name.should == :ldap + end it "should use the Terminus find method when calling find on the registered class" it "should use the Terminus save method when calling save on the registered class" it "should use the Terminus destroy method when calling destroy on the registered class" it "should use the Terminus search method when calling search on the registered class" - it "should allow a registered class to specify its own means of ..." + it "should allow a registered class to specify variations in behavior for a given Terminus" end - - - -describe Puppet::Indirector, " when managing indirections" do - before do - @indirector = Class.new - @indirector.send(:extend, Puppet::Indirector) - end - - it "should create an indirection" do - indirection = @indirector.indirects :test, :to => :node_source - indirection.name.should == :test - indirection.to.should == :node_source - end - - it "should not allow more than one indirection in the same object" do - @indirector.indirects :test - proc { @indirector.indirects :else }.should raise_error(ArgumentError) - end - - it "should allow multiple classes to use the same indirection" do - @indirector.indirects :test - other = Class.new - other.send(:extend, Puppet::Indirector) - proc { other.indirects :test }.should_not raise_error - end - - it "should should autoload termini from disk" do - Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") - @indirector.indirects :test - end - - after do - Puppet.config.clear - end +describe Puppet::Indirector::Terminus do + it "should register itself" # ??? + + it "should allow for finding an object from a collection" + it "should allow for finding matching objects from a collection" + it "should allow for destroying an object in a collection" + it "should allow an object to be saved to a collection" + it "should allow an object class to pre-process its arguments" + it "should allow an object class to be in a read-only collection" + + it "should look up the appropriate decorator for the class" + it "should call " end -describe Puppet::Indirector, " when performing indirections" do - before do - @indirector = Class.new - @indirector.send(:extend, Puppet::Indirector) - @indirector.indirects :test, :to => :node_source - - # Set up a fake terminus class that will just be used to spit out - # mock terminus objects. - @terminus_class = mock 'terminus_class' - Puppet::Indirector.stubs(:terminus).with(:test, :test_source).returns(@terminus_class) - Puppet[:node_source] = "test_source" - end - it "should redirect http methods to the default terminus" do - terminus = mock 'terminus' - terminus.expects(:put).with("myargument") - @terminus_class.expects(:new).returns(terminus) - @indirector.put("myargument") - end -end +# describe Puppet::Indirector::Decorator do +# it "should register itself" # ??? +# end + + + + +# describe Puppet::Indirector, " when managing indirections" do +# before do +# @indirector = Class.new +# @indirector.send(:extend, Puppet::Indirector) +# end +# +# it "should create an indirection" do +# indirection = @indirector.indirects :test, :to => :node_source +# indirection.name.should == :test +# indirection.to.should == :node_source +# end +# +# it "should not allow more than one indirection in the same object" do +# @indirector.indirects :test +# proc { @indirector.indirects :else }.should raise_error(ArgumentError) +# end +# +# it "should allow multiple classes to use the same indirection" do +# @indirector.indirects :test +# other = Class.new +# other.send(:extend, Puppet::Indirector) +# proc { other.indirects :test }.should_not raise_error +# end +# +# it "should should autoload termini from disk" do +# Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") +# @indirector.indirects :test +# end +# +# after do +# Puppet.config.clear +# end +# end +# +# describe Puppet::Indirector, " when performing indirections" do +# before do +# @indirector = Class.new +# @indirector.send(:extend, Puppet::Indirector) +# @indirector.indirects :test, :to => :node_source +# +# # Set up a fake terminus class that will just be used to spit out +# # mock terminus objects. +# @terminus_class = mock 'terminus_class' +# Puppet::Indirector.stubs(:terminus).with(:test, :test_source).returns(@terminus_class) +# Puppet[:node_source] = "test_source" +# end +# +# it "should redirect http methods to the default terminus" do +# terminus = mock 'terminus' +# terminus.expects(:put).with("myargument") +# @terminus_class.expects(:new).returns(terminus) +# @indirector.put("myargument") +# end +# end -- cgit From 129cce8b63c827d16a49c5ff1f6f25daf2906f47 Mon Sep 17 00:00:00 2001 From: Rick Bradley Date: Wed, 19 Sep 2007 23:13:30 -0500 Subject: Finally, some progress. Closing the loops and delegating registered class calls out to the actual Terminus. --- spec/unit/indirector/indirector.rb | 47 ++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 3a51fedf2..a44ef8814 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -73,9 +73,11 @@ describe Puppet::Indirector, "when registering an indirection" do end it "should make available the indirection used for a registered class" do + mock_terminus = mock('Terminus') + Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) + Puppet::Indirector.expects(:terminus).returns(mock_terminus) @thingie.send(:indirects, :node) - @thingie.indirection.indirection.should == :node - @thingie.indirection.name.should == :ldap + @thingie.indirection.should == mock_terminus end it "should make a save method available on instances of the registered class" do @@ -98,16 +100,43 @@ describe Puppet::Indirector, "when registering an indirection" do end it "should use the Terminus described in the class configuration" do - Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) + mock_terminus = mock('Terminus') + Puppet::Indirector.expects(:terminus_for_indirection).with(:foo).returns(:bar) + Puppet::Indirector.expects(:terminus).with(:foo, :bar).returns(mock_terminus) + @thingie.send(:indirects, :foo) + end + + it "should delegate to the Terminus find method when calling find on the registered class" do + @thingie.send(:indirects, :node) + mock_terminus = mock('Terminus') + mock_terminus.expects(:find) + @thingie.expects(:indirection).returns(mock_terminus) + @thingie.find + end + + it "should delegate to the Terminus destroy method when calling destroy on the registered class" do + @thingie.send(:indirects, :node) + mock_terminus = mock('Terminus') + mock_terminus.expects(:destroy) + @thingie.expects(:indirection).returns(mock_terminus) + @thingie.destroy + end + + it "should delegate to the Terminus search method when calling search on the registered class" do @thingie.send(:indirects, :node) - @thingie.indirection.indirection.should == :node - @thingie.indirection.name.should == :ldap + mock_terminus = mock('Terminus') + mock_terminus.expects(:search) + @thingie.expects(:indirection).returns(mock_terminus) + @thingie.search end - it "should use the Terminus find method when calling find on the registered class" - it "should use the Terminus save method when calling save on the registered class" - it "should use the Terminus destroy method when calling destroy on the registered class" - it "should use the Terminus search method when calling search on the registered class" + it "should delegate to the Terminus save method when calling save on the registered class" do + @thingie.send(:indirects, :node) + mock_terminus = mock('Terminus') + mock_terminus.expects(:save) + @thingie.expects(:indirection).returns(mock_terminus) + @thingie.new.save + end it "should allow a registered class to specify variations in behavior for a given Terminus" end -- cgit From e90a51ff599c823372ddcf4e1ea821cc3f99638c Mon Sep 17 00:00:00 2001 From: Rick Bradley Date: Thu, 20 Sep 2007 12:55:12 -0500 Subject: More spec and indirector updates. --- lib/puppet/indirector.rb | 2 + spec/unit/indirector/indirector.rb | 149 +++++++++++-------------------------- 2 files changed, 46 insertions(+), 105 deletions(-) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index d30f8a63d..ca1b4b673 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -79,6 +79,7 @@ module Puppet::Indirector end # clear out the list of known indirections +#JRB:TODO -- I would prefer to get rid of this altogether, but it's implicated in testing, given the class loader def self.reset @indirections = {} @class_indirections = {} @@ -104,6 +105,7 @@ module Puppet::Indirector def self.terminus_for_indirection(name) # JRB:TODO make this do something useful, aka look something up in a .yml file + # JRB:TODO look up name + '_source' in standard configuration :ldap end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index a44ef8814..3bddc9520 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -3,33 +3,33 @@ require 'puppet/defaults' require 'puppet/indirector' describe Puppet::Indirector do - it "should provide a way to clear all registered classes" do + it "should provide a way to clear all registrations" do Puppet::Indirector.should respond_to(:reset) end - it "should provide a way to access a list of registered classes" do + it "should provide a way to access a list of all registered models" do Puppet::Indirector.should respond_to(:indirections) end end -describe Puppet::Indirector, "when no classes are registered" do +describe Puppet::Indirector, "when no models are registered" do before do Puppet::Indirector.reset end - it "should provide an empty list of registered classes" do + it "should provide an empty list of registered models" do Puppet::Indirector.indirections.should == {} end end -describe Puppet::Indirector, " when included into a class" do +describe Puppet::Indirector, " when available to a model" do before do @thingie = Class.new do extend Puppet::Indirector end end - it "should provide the indirects method to the class" do + it "should provide a way for the model to register an indirection under a name" do @thingie.should respond_to(:indirects) end end @@ -43,70 +43,70 @@ describe Puppet::Indirector, "when registering an indirection" do Puppet::Indirector.stubs(:terminus_for_indirection).returns(:ldap) end - it "should require a name to register when indirecting" do + it "should require a name when registering a model" do Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) end - it "should require each indirection to be registered under a unique name" do + it "should require each model to be registered under a unique name" do @thingie.send(:indirects, :name) Proc.new {@thingie.send(:indirects, :name)}.should raise_error(ArgumentError) end - it "should not allow a class to register multiple indirections" do + it "should not allow a model to register under multiple names" do @thingie.send(:indirects, :first) Proc.new {@thingie.send(:indirects, :second)}.should raise_error(ArgumentError) end - it "should make a find method available on the registered class" do + it "should allow finding an instance of a model in a collection" do @thingie.send(:indirects, :first) @thingie.should respond_to(:find) end - it "should make a destroy method available on the registered class" do + it "should allow removing an instance of a model from a collection" do @thingie.send(:indirects, :first) @thingie.should respond_to(:destroy) end - it "should make a search method available on the registered class" do + it "should allow finding all matching model instances in a collection" do @thingie.send(:indirects, :first) @thingie.should respond_to(:search) end - it "should make available the indirection used for a registered class" do - mock_terminus = mock('Terminus') - Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) - Puppet::Indirector.expects(:terminus).returns(mock_terminus) - @thingie.send(:indirects, :node) - @thingie.indirection.should == mock_terminus - end - - it "should make a save method available on instances of the registered class" do + it "should allow for storing a model instance in a collection" do @thing = Class.new do extend Puppet::Indirector indirects :thing end.new @thing.should respond_to(:save) end + + it "should provide a way to get a handle to the terminus for a model" do + mock_terminus = mock('Terminus') + Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) + Puppet::Indirector.expects(:terminus).returns(mock_terminus) + @thingie.send(:indirects, :node) + @thingie.indirection.should == mock_terminus + end - it "should include the registered class in the list of all registered classes" do + it "should list the model in a list of known indirections" do @thingie.send(:indirects, :name) Puppet::Indirector.indirections[:name].should == @thingie end # when dealing with Terminus methods - it "should look up the indirection configuration for the registered class when a new instance of that class is created" do + it "should consult a per-model configuration to determine what kind of collection a model is being stored in" do Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) @thingie.send(:indirects, :node) end - it "should use the Terminus described in the class configuration" do + it "should use the collection type described in the per-model configuration" do mock_terminus = mock('Terminus') Puppet::Indirector.expects(:terminus_for_indirection).with(:foo).returns(:bar) Puppet::Indirector.expects(:terminus).with(:foo, :bar).returns(mock_terminus) @thingie.send(:indirects, :foo) end - it "should delegate to the Terminus find method when calling find on the registered class" do + it "should handle lookups of a model instance by letting the terminus perform the lookup" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:find) @@ -114,7 +114,7 @@ describe Puppet::Indirector, "when registering an indirection" do @thingie.find end - it "should delegate to the Terminus destroy method when calling destroy on the registered class" do + it "should handle removing model instances from a collection letting the terminus remove the instance" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:destroy) @@ -122,7 +122,7 @@ describe Puppet::Indirector, "when registering an indirection" do @thingie.destroy end - it "should delegate to the Terminus search method when calling search on the registered class" do + it "should handle searching for model instances by letting the terminus find the matching instances" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:search) @@ -130,93 +130,32 @@ describe Puppet::Indirector, "when registering an indirection" do @thingie.search end - it "should delegate to the Terminus save method when calling save on the registered class" do + it "should handle storing a model instance by letting the terminus store the instance" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:save) @thingie.expects(:indirection).returns(mock_terminus) @thingie.new.save end - - it "should allow a registered class to specify variations in behavior for a given Terminus" -end - - - - -describe Puppet::Indirector::Terminus do - it "should register itself" # ??? - - it "should allow for finding an object from a collection" - it "should allow for finding matching objects from a collection" - it "should allow for destroying an object in a collection" - it "should allow an object to be saved to a collection" - it "should allow an object class to pre-process its arguments" - it "should allow an object class to be in a read-only collection" - it "should look up the appropriate decorator for the class" - it "should call " -end - - -# describe Puppet::Indirector::Decorator do -# it "should register itself" # ??? -# end - + it "should provide the same terminus for a given registered model" + it "should not access the collection for a registered model until that collection is actually needed" +# TODO: node lookup retries/searching +end -# describe Puppet::Indirector, " when managing indirections" do -# before do -# @indirector = Class.new -# @indirector.send(:extend, Puppet::Indirector) -# end # -# it "should create an indirection" do -# indirection = @indirector.indirects :test, :to => :node_source -# indirection.name.should == :test -# indirection.to.should == :node_source -# end -# -# it "should not allow more than one indirection in the same object" do -# @indirector.indirects :test -# proc { @indirector.indirects :else }.should raise_error(ArgumentError) -# end -# -# it "should allow multiple classes to use the same indirection" do -# @indirector.indirects :test -# other = Class.new -# other.send(:extend, Puppet::Indirector) -# proc { other.indirects :test }.should_not raise_error -# end -# -# it "should should autoload termini from disk" do -# Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") -# @indirector.indirects :test -# end -# -# after do -# Puppet.config.clear -# end -# end -# -# describe Puppet::Indirector, " when performing indirections" do -# before do -# @indirector = Class.new -# @indirector.send(:extend, Puppet::Indirector) -# @indirector.indirects :test, :to => :node_source -# -# # Set up a fake terminus class that will just be used to spit out -# # mock terminus objects. -# @terminus_class = mock 'terminus_class' -# Puppet::Indirector.stubs(:terminus).with(:test, :test_source).returns(@terminus_class) -# Puppet[:node_source] = "test_source" -# end -# -# it "should redirect http methods to the default terminus" do -# terminus = mock 'terminus' -# terminus.expects(:put).with("myargument") -# @terminus_class.expects(:new).returns(terminus) -# @indirector.put("myargument") -# end +# describe Puppet::Indirector::Terminus do +# it "should register itself" # ??? +# +# it "should allow for finding an object from a collection" +# it "should allow for finding matching objects from a collection" +# it "should allow for destroying an object in a collection" +# it "should allow an object to be saved to a collection" +# it "should allow an object class to pre-process its arguments" +# it "should allow an object class to be in a read-only collection" +# +# it "should look up the appropriate decorator for the class" +# it "should call " # end -- cgit From 46d69068fa7b2f3448294c5d3da21c69cef73d2f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 20 Sep 2007 12:57:57 -0500 Subject: An intermediate commit so I can start working on a different branch. The file recursion code actually works for the first time in a painful while, but there are still some quirks and design issues to resolve, particularly around creating implicit resources that then fail (i.e., the behaviour of the create_implicit_resource method in Configuration). --- lib/puppet/node/configuration.rb | 13 +++++++++---- lib/puppet/pgraph.rb | 2 +- lib/puppet/type.rb | 2 +- lib/puppet/type/pfile.rb | 24 +++++++++--------------- lib/puppet/util/config.rb | 2 +- spec/bin/spec | 1 + spec/unit/indirector/indirection.rb | 2 +- spec/unit/indirector/indirector.rb | 2 ++ spec/unit/node/configuration.rb | 19 +++++++++++++++---- spec/unit/other/modules.rb | 4 ++-- spec/unit/util/config.rb | 2 ++ test/ral/types/file.rb | 4 +--- 12 files changed, 45 insertions(+), 32 deletions(-) diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index c882e0ee1..de6e776c5 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -55,13 +55,13 @@ class Puppet::Node::Configuration < Puppet::PGraph # Apply our configuration to the local host. def apply + @applying = true + Puppet::Util::Storage.load if host_config? transaction = Puppet::Transaction.new(self) transaction.addtimes :config_retrieval => @retrieval_duration - @applying = true - begin transaction.evaluate rescue Puppet::Error => detail @@ -122,6 +122,7 @@ class Puppet::Node::Configuration < Puppet::PGraph unless options.include?(:implicit) options[:implicit] = true end + # LAK:FIXME catch exceptions here and return nil when problems if resource = create_resource(type, options) resource.implicit = true @@ -235,6 +236,7 @@ class Puppet::Node::Configuration < Puppet::PGraph @resource_table = {} @transient_resources = [] @applying = false + @relationship_graph = nil if block_given? yield(self) @@ -245,11 +247,12 @@ class Puppet::Node::Configuration < Puppet::PGraph # Create a graph of all of the relationships in our configuration. def relationship_graph unless defined? @relationship_graph and @relationship_graph - relationships = self.class.new + relationships = Puppet::Node::Configuration.new + relationships.host_config = host_config? # First create the dependency graph self.vertices.each do |vertex| - relationships.add_resource vertex + relationships.add_vertex! vertex vertex.builddepends.each do |edge| relationships.add_edge!(edge) end @@ -287,6 +290,7 @@ class Puppet::Node::Configuration < Puppet::PGraph resources.each do |resource| @resource_table.delete(resource.ref) if @resource_table.include?(resource.ref) remove_vertex!(resource) if vertex?(resource) + @relationship_graph.remove_vertex!(resource) if @relationship_graph and @relationship_graph.vertex?(resource) resource.remove end end @@ -345,6 +349,7 @@ class Puppet::Node::Configuration < Puppet::PGraph unless @transient_resources.empty? remove_resource(*@transient_resources) @transient_resources.clear + @relationship_graph = nil end end end diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index 7a3869dcf..70b466225 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -95,7 +95,7 @@ class Puppet::PGraph < GRATR::Digraph adjacent(source, :direction => :out, :type => :edges).find_all do |edge| edge.match?(event.event) end - end.flatten + end.compact.flatten end # Take container information from another graph and use it diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 1db435224..e55873c2b 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -318,7 +318,7 @@ class Type # We should never have more than one parent, so let's just ignore # it if we happen to. - if parents = configuration.adjacent(self, :direction => :in) + if parents = configuration.relationship_graph.adjacent(self, :direction => :in) return parents.shift else return nil diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index da345e5d6..f2fa6a14d 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -611,13 +611,14 @@ module Puppet } child = nil - + # The child might already exist because 'localrecurse' runs # before 'sourcerecurse'. I could push the override stuff into # a separate method or something, but the work is the same other # than this last bit, so it doesn't really make sense. if child = configuration.resource(:file, path) unless child.parent.object_id == self.object_id + puts("Parent is %s, I am %s" % [child.parent.ref, self.ref]) if child.parent self.debug "Not managing more explicit file %s" % path return nil @@ -643,22 +644,15 @@ module Puppet args[:parent] = self begin return nil unless child = configuration.create_implicit_resource(:file, args) - rescue Puppet::Error => detail - self.notice( - "Cannot manage: %s" % - [detail.message] - ) - self.debug args.inspect - child = nil rescue => detail - self.notice( - "Cannot manage: %s" % - [detail] - ) - self.debug args.inspect - child = nil + puts detail.backtrace + self.notice "Cannot manage: %s" % [detail] + return nil end - configuration.relationship_graph.add_edge! self, child + end + configuration.relationship_graph.add_edge! self, child + unless child.parent + raise "Did not set parent of %s" % child.ref end return child end diff --git a/lib/puppet/util/config.rb b/lib/puppet/util/config.rb index 3988b3fe0..4b6ae9e5b 100644 --- a/lib/puppet/util/config.rb +++ b/lib/puppet/util/config.rb @@ -1143,7 +1143,7 @@ Generated on #{Time.now}. path = self.value # Skip plain files that don't exist, since we won't be managing them anyway. - return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) + return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) or self.create obj = Puppet::TransObject.new(path, "file") # Only create directories, or files that are specifically marked to diff --git a/spec/bin/spec b/spec/bin/spec index a7e6ce0cb..aaf320f34 100755 --- a/spec/bin/spec +++ b/spec/bin/spec @@ -1,3 +1,4 @@ +#!/usr/bin/env ruby $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/../lib")) require 'spec' ::Spec::Runner::CommandLine.run(ARGV, STDERR, STDOUT, true, true) diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index e9b10c8c7..c9f15382e 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -26,7 +26,7 @@ describe Puppet::Indirector::Indirection, " when initializing" do end end -describe Puppet::Indirector, " when managing termini" do +describe Puppet::Indirector::Indirection, " when managing termini" do before do @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source) end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 312c60951..5453d30a7 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -11,6 +11,8 @@ describe Puppet::Indirector, " when managing indirections" do @indirector.send(:extend, Puppet::Indirector) end + it "should require a name" + it "should create an indirection" do indirection = @indirector.indirects :test, :to => :node_source indirection.name.should == :test diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index ecf311948..eb4eaa418 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -334,22 +334,28 @@ describe Puppet::Node::Configuration, " when creating a relationship graph" do @three = @file.create :path => "/three" @four = @file.create :path => "/four", :require => ["file", "/three"] - @config.add_resource @compone, @comptwo, @one, @two, @three, @four + @five = @file.create :path => "/five" + @config.add_resource @compone, @comptwo, @one, @two, @three, @four, @five @relationships = @config.relationship_graph end - it "should be able to create a resource graph" do + it "should be able to create a relationship graph" do @relationships.should be_instance_of(Puppet::Node::Configuration) end + it "should copy its host_config setting to the relationship graph" do + config = Puppet::Node::Configuration.new + config.host_config = true + config.relationship_graph.host_config.should be_true + end + it "should not have any components" do @relationships.vertices.find { |r| r.instance_of?(Puppet::Type::Component) }.should be_nil end it "should have all non-component resources from the configuration" do # The failures print out too much info, so i just do a class comparison - @relationships.resource(@one.ref).should be_instance_of(@one.class) - @relationships.resource(@three.ref).should be_instance_of(@three.class) + @relationships.vertex?(@five).should be_true end it "should have all resource relationships set as edges" do @@ -412,6 +418,11 @@ describe Puppet::Node::Configuration, " when creating a relationship graph" do @config.resource("File[/yay]").should be_nil end + it "should remove resources from the relationship graph if it exists" do + @config.remove_resource(@one) + @config.relationship_graph.vertex?(@one).should be_false + end + after do Puppet::Type.allclear end diff --git a/spec/unit/other/modules.rb b/spec/unit/other/modules.rb index 0ab37aa9e..dbd091d9a 100755 --- a/spec/unit/other/modules.rb +++ b/spec/unit/other/modules.rb @@ -5,10 +5,10 @@ require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Module, " when building its search path" do include PuppetTest - it "should ignore unqualified paths in the search path" do + it "should fully qualify unqualified paths in the search path" do Puppet[:modulepath] = "something:/my/something" File.stubs(:directory?).returns(true) - Puppet::Module.modulepath.should == %w{/my/something} + Puppet::Module.modulepath.should == [File.join(Dir.getwd, 'something'), "/my/something"] end it "should ignore paths that do not exist" do diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb index 353bc2bcb..8114acb8e 100755 --- a/spec/unit/util/config.rb +++ b/spec/unit/util/config.rb @@ -426,6 +426,8 @@ describe Puppet::Util::Config, " when being used to manage the host machine" do pending "Not converted from test/unit yet" end + it "should create files when configured to do so with the :create parameter" + it "should provide a method to convert the file mode enforcement into transportable resources" do # Make it think we're root so it tries to manage user and group. Puppet.features.stubs(:root?).returns(true) diff --git a/test/ral/types/file.rb b/test/ral/types/file.rb index 47fb87afd..b58814a7f 100755 --- a/test/ral/types/file.rb +++ b/test/ral/types/file.rb @@ -1283,8 +1283,6 @@ class TestFile < Test::Unit::TestCase :content => "rahtest", :backup => false ) - - Puppet[:evaltrace] = true destobj = Puppet::Type.newfile(:title => "destdir", :path => destdir, :source => sourcedir, @@ -1299,6 +1297,7 @@ class TestFile < Test::Unit::TestCase assert(FileTest.exists?(purgee), "File got prematurely purged") assert_nothing_raised { destobj[:purge] = true } + Puppet.err :yay config.apply assert(FileTest.exists?(localfile), "Local file got purged") @@ -1755,7 +1754,6 @@ class TestFile < Test::Unit::TestCase assert_nothing_raised("Failure when recursing") do children = obj.eval_generate end - config.add_resource(*children) assert(obj.class[subdir], "did not create subdir object") children.each do |c| assert_nothing_raised("Failure when recursing on %s" % c) do -- cgit From 944cd0eecfde0179eb594378640c7df84020cedf Mon Sep 17 00:00:00 2001 From: Rick Bradley Date: Thu, 20 Sep 2007 13:05:48 -0500 Subject: Whitespace and comment commit. --- spec/unit/indirector/indirector.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 3bddc9520..78f1c7f73 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -145,7 +145,9 @@ describe Puppet::Indirector, "when registering an indirection" do # TODO: node lookup retries/searching end -# + + + # describe Puppet::Indirector::Terminus do # it "should register itself" # ??? # -- cgit From 8212f88ce3ad2ddbc7e1e713111d9478171c42b8 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 20 Sep 2007 15:24:20 -0500 Subject: Fixing all existing spec tests so that they now pass given the redesign that Rick implemented. This was mostly a question of fixing the method names and the mocks. --- lib/puppet/indirector.rb | 17 +++++++++++------ lib/puppet/indirector/facts/yaml.rb | 4 ++-- lib/puppet/indirector/node/external.rb | 2 +- lib/puppet/indirector/node/ldap.rb | 2 +- lib/puppet/indirector/node/none.rb | 2 +- lib/puppet/node.rb | 2 +- lib/puppet/node/searching.rb | 6 +++--- spec/unit/indirector/facts/yaml.rb | 4 ++-- spec/unit/indirector/indirector.rb | 15 ++++++++++----- spec/unit/indirector/node/external.rb | 22 +++++++++++----------- spec/unit/indirector/node/ldap.rb | 16 ++++++++-------- spec/unit/indirector/node/none.rb | 8 ++++---- spec/unit/node/facts.rb | 11 ++++++----- spec/unit/node/node.rb | 8 ++++---- spec/unit/node/searching.rb | 16 ++++++++-------- 15 files changed, 73 insertions(+), 62 deletions(-) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index ca1b4b673..cdfd28908 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -106,7 +106,12 @@ module Puppet::Indirector def self.terminus_for_indirection(name) # JRB:TODO make this do something useful, aka look something up in a .yml file # JRB:TODO look up name + '_source' in standard configuration - :ldap + case name + when :node: :none + when :facts: :yaml + else + raise ArgumentError, "Unknown indirection" + end end # Declare that the including class indirects its methods to @@ -131,30 +136,30 @@ module Puppet::Indirector # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) # & hook the instantiated Terminus into this registered class (Node: @indirection = terminus) Puppet::Indirector.enable_autoloading_indirection indirection - @indirection = Puppet::Indirector.terminus(indirection, terminus) + raise("No Terminus %s for %s" % [terminus, indirection]) unless @indirection = Puppet::Indirector.terminus(indirection, terminus).new end module ClassMethods attr_reader :indirection def find(*args) - self.indirection.find(args) + self.indirection.find(*args) # JRB:TODO look up the indirection, and call its .find method end def destroy(*args) - self.indirection.destroy(args) + self.indirection.destroy(*args) end def search(*args) - self.indirection.search(args) + self.indirection.search(*args) end end module InstanceMethods # these become instance methods def save(*args) - self.class.indirection.save(args) + self.class.indirection.save(self, *args) end end diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb index f29ea8ebc..cb02f05c6 100644 --- a/lib/puppet/indirector/facts/yaml.rb +++ b/lib/puppet/indirector/facts/yaml.rb @@ -2,7 +2,7 @@ Puppet::Indirector.register_terminus :facts, :yaml do desc "Store client facts as flat files, serialized using YAML." # Get a client's facts. - def get(node) + def find(node) file = path(node) return nil unless FileTest.exists?(file) @@ -21,7 +21,7 @@ Puppet::Indirector.register_terminus :facts, :yaml do end # Store the facts to disk. - def post(facts) + def save(facts) File.open(path(facts.name), "w", 0600) do |f| begin f.print YAML::dump(facts.values) diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb index 13cd265fb..66aca6048 100644 --- a/lib/puppet/indirector/node/external.rb +++ b/lib/puppet/indirector/node/external.rb @@ -11,7 +11,7 @@ Puppet::Indirector.register_terminus :node, :external do end # Look for external node definitions. - def get(name) + def find(name) unless Puppet[:external_nodes] != "none" raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node source" end diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb index fb60cad31..230ca2467 100644 --- a/lib/puppet/indirector/node/ldap.rb +++ b/lib/puppet/indirector/node/ldap.rb @@ -2,7 +2,7 @@ Puppet::Indirector.register_terminus :node, :ldap do desc "Search in LDAP for node configuration information." # Look for our node in ldap. - def get(name) + def find(name) unless ary = ldapsearch(name) return nil end diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb index 2b326968e..034c0b349 100644 --- a/lib/puppet/indirector/node/none.rb +++ b/lib/puppet/indirector/node/none.rb @@ -6,7 +6,7 @@ Puppet::Indirector.register_terminus :node, :none do as the compiler will not work without this node information." # Just return an empty node. - def get(name) + def find(name) node = Puppet::Node.new(name) node.fact_merge node diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 7ad7bc3b3..852f98a99 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -70,7 +70,7 @@ class Puppet::Node # Merge the node facts with parameters from the node source. def fact_merge - if facts = Puppet::Node::Facts.get(name) + if facts = Puppet::Node::Facts.find(name) merge(facts.values) end end diff --git a/lib/puppet/node/searching.rb b/lib/puppet/node/searching.rb index 3a632d50e..10dd588ab 100644 --- a/lib/puppet/node/searching.rb +++ b/lib/puppet/node/searching.rb @@ -14,7 +14,7 @@ module Puppet::Node::Searching names = node_names(key, facts) names.each do |name| name = name.to_s if name.is_a?(Symbol) - if node = get(name) + if node = find(name) #Puppet.info "Found %s in %s" % [name, @source] break end @@ -23,7 +23,7 @@ module Puppet::Node::Searching # 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 = get("default") + if node = find("default") Puppet.notice "Using default node for %s" % key end end @@ -62,7 +62,7 @@ module Puppet::Node::Searching # Look up the node facts from our fact handler. def node_facts(key) - if facts = Puppet::Node::Facts.get(key) + if facts = Puppet::Node::Facts.find(key) facts.values else {} diff --git a/spec/unit/indirector/facts/yaml.rb b/spec/unit/indirector/facts/yaml.rb index 176a47f04..49b361a95 100755 --- a/spec/unit/indirector/facts/yaml.rb +++ b/spec/unit/indirector/facts/yaml.rb @@ -23,7 +23,7 @@ describe Puppet::Indirector.terminus(:facts, :yaml), " when managing facts" do it "should store facts in YAML in the yamlfactdir" do values = {"one" => "two", "three" => "four"} facts = Puppet::Node::Facts.new("node", values) - @store.post(facts) + @store.save(facts) # Make sure the file exists path = File.join(Puppet[:yamlfactdir], facts.name) + ".yaml" @@ -47,7 +47,7 @@ describe Puppet::Indirector.terminus(:facts, :yaml), " when managing facts" do f.print values.to_yaml end - facts = Puppet::Node::Facts.get('node') + facts = Puppet::Node::Facts.find('node') facts.should be_instance_of(Puppet::Node::Facts) # We iterate over them, because the store might add extra values. diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 78f1c7f73..b5a70cd8f 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -41,6 +41,9 @@ describe Puppet::Indirector, "when registering an indirection" do extend Puppet::Indirector end Puppet::Indirector.stubs(:terminus_for_indirection).returns(:ldap) + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus + Puppet::Indirector.stubs(:terminus).returns(@terminus_class) end it "should require a name when registering a model" do @@ -81,11 +84,12 @@ describe Puppet::Indirector, "when registering an indirection" do end it "should provide a way to get a handle to the terminus for a model" do - mock_terminus = mock('Terminus') Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) - Puppet::Indirector.expects(:terminus).returns(mock_terminus) + terminus = mock 'terminus' + terminus_class = stub 'terminus class', :new => terminus + Puppet::Indirector.expects(:terminus).returns(terminus_class) @thingie.send(:indirects, :node) - @thingie.indirection.should == mock_terminus + @thingie.indirection.should == terminus end it "should list the model in a list of known indirections" do @@ -100,9 +104,10 @@ describe Puppet::Indirector, "when registering an indirection" do end it "should use the collection type described in the per-model configuration" do - mock_terminus = mock('Terminus') + terminus = mock 'terminus' + terminus_class = stub 'terminus class', :new => terminus Puppet::Indirector.expects(:terminus_for_indirection).with(:foo).returns(:bar) - Puppet::Indirector.expects(:terminus).with(:foo, :bar).returns(mock_terminus) + Puppet::Indirector.expects(:terminus).with(:foo, :bar).returns(terminus_class) @thingie.send(:indirects, :foo) end diff --git a/spec/unit/indirector/node/external.rb b/spec/unit/indirector/node/external.rb index 30b2f74c2..c64a6f6e2 100755 --- a/spec/unit/indirector/node/external.rb +++ b/spec/unit/indirector/node/external.rb @@ -32,32 +32,32 @@ describe Puppet::Indirector.terminus(:node, :external), " when searching for nod it "should throw an exception if the node_source is external but no external node command is set" do Puppet[:external_nodes] = "none" - proc { @searcher.get("foo") }.should raise_error(ArgumentError) + proc { @searcher.find("foo") }.should raise_error(ArgumentError) end it "should throw an exception if the external node source is not fully qualified" do Puppet[:external_nodes] = "mycommand" - proc { @searcher.get("foo") }.should raise_error(ArgumentError) + proc { @searcher.find("foo") }.should raise_error(ArgumentError) end it "should execute the command with the node name as the only argument" do command = [Puppet[:external_nodes], "yay"] @searcher.expects(:execute).with(command).returns("") - @searcher.get("yay") + @searcher.find("yay") end it "should return a node object" do - @searcher.get("apple").should be_instance_of(Puppet::Node) + @searcher.find("apple").should be_instance_of(Puppet::Node) end it "should set the node's name" do - @searcher.get("apple").name.should == "apple" + @searcher.find("apple").name.should == "apple" end # If we use a name that has a 'p' but no 'a', then our test generator # will return classes but no parameters. it "should be able to configure a node's classes" do - node = @searcher.get("plum") + node = @searcher.find("plum") node.classes.should == %w{plum1 plum2 plum3} node.parameters.should == {} end @@ -65,26 +65,26 @@ describe Puppet::Indirector.terminus(:node, :external), " when searching for nod # If we use a name that has an 'a' but no 'p', then our test generator # will return parameters but no classes. it "should be able to configure a node's parameters" do - node = @searcher.get("guava") + node = @searcher.find("guava") node.classes.should == [] node.parameters.should == {"one" => "guava1", "two" => "guava2"} end it "should be able to configure a node's classes and parameters" do - node = @searcher.get("apple") + node = @searcher.find("apple") node.classes.should == %w{apple1 apple2 apple3} node.parameters.should == {"one" => "apple1", "two" => "apple2"} end it "should merge node facts with returned parameters" do facts = Puppet::Node::Facts.new("apple", "three" => "four") - Puppet::Node::Facts.expects(:get).with("apple").returns(facts) - node = @searcher.get("apple") + Puppet::Node::Facts.expects(:find).with("apple").returns(facts) + node = @searcher.find("apple") node.parameters["three"].should == "four" end it "should return nil when it cannot find the node" do - @searcher.get("honeydew").should be_nil + @searcher.find("honeydew").should be_nil end # Make sure a nodesearch with arguments works diff --git a/spec/unit/indirector/node/ldap.rb b/spec/unit/indirector/node/ldap.rb index c6eb45ffc..e4b0cd7d4 100755 --- a/spec/unit/indirector/node/ldap.rb +++ b/spec/unit/indirector/node/ldap.rb @@ -20,33 +20,33 @@ describe Puppet::Indirector.terminus(:node, :ldap), " when searching for nodes" end it "should return nil for hosts that cannot be found" do - @searcher.get("foo").should be_nil + @searcher.find("foo").should be_nil end it "should return Puppet::Node instances" do @nodetable["foo"] = [nil, %w{}, {}] - @searcher.get("foo").should be_instance_of(Puppet::Node) + @searcher.find("foo").should be_instance_of(Puppet::Node) end it "should set the node name" do @nodetable["foo"] = [nil, %w{}, {}] - @searcher.get("foo").name.should == "foo" + @searcher.find("foo").name.should == "foo" end it "should set the classes" do @nodetable["foo"] = [nil, %w{one two}, {}] - @searcher.get("foo").classes.should == %w{one two} + @searcher.find("foo").classes.should == %w{one two} end it "should set the parameters" do @nodetable["foo"] = [nil, %w{}, {"one" => "two"}] - @searcher.get("foo").parameters.should == {"one" => "two"} + @searcher.find("foo").parameters.should == {"one" => "two"} end it "should set classes and parameters from the parent node" do @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] @nodetable["middle"] = [nil, %w{three four}, {"three" => "four"}] - node = @searcher.get("foo") + node = @searcher.find("foo") node.classes.sort.should == %w{one two three four}.sort node.parameters.should == {"one" => "two", "three" => "four"} end @@ -54,14 +54,14 @@ describe Puppet::Indirector.terminus(:node, :ldap), " when searching for nodes" it "should prefer child parameters to parent parameters" do @nodetable["foo"] = ["middle", %w{}, {"one" => "two"}] @nodetable["middle"] = [nil, %w{}, {"one" => "four"}] - @searcher.get("foo").parameters["one"].should == "two" + @searcher.find("foo").parameters["one"].should == "two" end it "should recurse indefinitely through parent relationships" do @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] @nodetable["middle"] = ["top", %w{three four}, {"three" => "four"}] @nodetable["top"] = [nil, %w{five six}, {"five" => "six"}] - node = @searcher.get("foo") + node = @searcher.find("foo") node.parameters.should == {"one" => "two", "three" => "four", "five" => "six"} node.classes.sort.should == %w{one two three four five six}.sort end diff --git a/spec/unit/indirector/node/none.rb b/spec/unit/indirector/node/none.rb index d52d7ca83..2329cdfbb 100755 --- a/spec/unit/indirector/node/none.rb +++ b/spec/unit/indirector/node/none.rb @@ -11,17 +11,17 @@ describe Puppet::Indirector.terminus(:node, :none), " when searching for nodes" end it "should create a node instance" do - @searcher.get("yay").should be_instance_of(Puppet::Node) + @searcher.find("yay").should be_instance_of(Puppet::Node) end it "should create a new node with the correct name" do - @searcher.get("yay").name.should == "yay" + @searcher.find("yay").name.should == "yay" end it "should merge the node's facts" do facts = Puppet::Node::Facts.new("yay", "one" => "two", "three" => "four") - Puppet::Node::Facts.expects(:get).with("yay").returns(facts) - node = @searcher.get("yay") + Puppet::Node::Facts.expects(:find).with("yay").returns(facts) + node = @searcher.find("yay") node.parameters["one"].should == "two" node.parameters["three"].should == "four" end diff --git a/spec/unit/node/facts.rb b/spec/unit/node/facts.rb index c677c1d2e..61f05a2b2 100755 --- a/spec/unit/node/facts.rb +++ b/spec/unit/node/facts.rb @@ -7,21 +7,22 @@ require 'puppet/node/facts' describe Puppet::Node::Facts, " when indirecting" do before do @terminus = mock 'terminus' - Puppet::Indirector.terminus(:facts, Puppet[:fact_store].intern).stubs(:new).returns(@terminus) + Puppet::Node::Facts.stubs(:indirection).returns(@terminus) # We have to clear the cache so that the facts ask for our terminus stub, # instead of anything that might be cached. Puppet::Indirector::Indirection.clear_cache + @facts = Puppet::Node::Facts.new("me", "one" => "two") end it "should redirect to the specified fact store for retrieval" do - @terminus.expects(:get).with(:my_facts) - Puppet::Node::Facts.get(:my_facts) + @terminus.expects(:find).with(:my_facts) + Puppet::Node::Facts.find(:my_facts) end it "should redirect to the specified fact store for storage" do - @terminus.expects(:post).with(:my_facts) - Puppet::Node::Facts.post(:my_facts) + @terminus.expects(:save).with(@facts) + @facts.save end after do diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb index 9342dc5ce..899d81ac7 100755 --- a/spec/unit/node/node.rb +++ b/spec/unit/node/node.rb @@ -85,7 +85,7 @@ end describe Puppet::Node, " when merging facts" do before do @node = Puppet::Node.new("testnode") - Puppet::Node::Facts.stubs(:get).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) + Puppet::Node::Facts.stubs(:find).with(@node.name).returns(Puppet::Node::Facts.new(@node.name, "one" => "c", "two" => "b")) end it "should prefer parameters already set on the node over facts from the node" do @@ -110,12 +110,12 @@ end describe Puppet::Node, " when indirecting" do before do @terminus = mock 'terminus' - Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(@terminus) + Puppet::Node.stubs(:indirection).returns(@terminus) end it "should redirect to the specified node source" do - @terminus.expects(:get).with(:my_node) - Puppet::Node.get(:my_node) + @terminus.expects(:find).with(:my_node.to_s) + Puppet::Node.find(:my_node.to_s) end after do diff --git a/spec/unit/node/searching.rb b/spec/unit/node/searching.rb index 553822576..b7105050a 100755 --- a/spec/unit/node/searching.rb +++ b/spec/unit/node/searching.rb @@ -10,12 +10,12 @@ describe Puppet::Node::Searching, " when searching for nodes" do @searcher.extend(Puppet::Node::Searching) @facts = Puppet::Node::Facts.new("foo", "hostname" => "yay", "domain" => "domain.com") @node = Puppet::Node.new("foo") - Puppet::Node::Facts.stubs(:get).with("foo").returns(@facts) + Puppet::Node::Facts.stubs(:find).with("foo").returns(@facts) end it "should search for the node by its key first" do names = [] - @searcher.expects(:get).with do |name| + @searcher.expects(:find).with do |name| names << name names == %w{foo} end.returns(@node) @@ -24,15 +24,15 @@ describe Puppet::Node::Searching, " when searching for nodes" do it "should return the first node found using the generated list of names" do names = [] - @searcher.expects(:get).with("foo").returns(nil) - @searcher.expects(:get).with("yay.domain.com").returns(@node) + @searcher.expects(:find).with("foo").returns(nil) + @searcher.expects(:find).with("yay.domain.com").returns(@node) @searcher.search("foo").should equal(@node) end it "should search for the rest of the names inversely by length" do names = [] @facts.values["fqdn"] = "longer.than.the.normal.fqdn.com" - @searcher.stubs(:get).with do |name| + @searcher.stubs(:find).with do |name| names << name end @searcher.search("foo") @@ -51,7 +51,7 @@ describe Puppet::Node::Searching, " when searching for nodes" do it "should attempt to find a default node if no names are found" do names = [] - @searcher.stubs(:get).with do |name| + @searcher.stubs(:find).with do |name| names << name end.returns(nil) @searcher.search("foo") @@ -59,7 +59,7 @@ describe Puppet::Node::Searching, " when searching for nodes" do end it "should cache the nodes" do - @searcher.expects(:get).with("foo").returns(@node) + @searcher.expects(:find).with("foo").returns(@node) @searcher.search("foo").should equal(@node) @searcher.search("foo").should equal(@node) end @@ -68,7 +68,7 @@ describe Puppet::Node::Searching, " when searching for nodes" do node2 = Puppet::Node.new("foo2") Puppet[:filetimeout] = -1 # I couldn't get this to work with :expects - @searcher.stubs(:get).returns(@node, node2).then.raises(ArgumentError) + @searcher.stubs(:find).returns(@node, node2).then.raises(ArgumentError) @searcher.search("foo").should equal(@node) @searcher.search("foo").should equal(node2) end -- cgit From 4e8b6712b688b23d6cb33f5829d3b15fe9d6833c Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 20 Sep 2007 17:31:03 -0500 Subject: The unit tests for the newly-resurrected indirection class now work; all we need do is fix the indirector module tests. --- lib/puppet/indirector/indirection.rb | 47 ++++++++----- spec/unit/indirector/indirection.rb | 122 ++++++++++++++++++++++++++------ spec/unit/indirector/indirector.rb | 133 +++++++---------------------------- 3 files changed, 157 insertions(+), 145 deletions(-) diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 67b8a9d48..7128346ac 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -8,7 +8,6 @@ class Puppet::Indirector::Indirection end attr_accessor :name, :termini - attr_reader :to # Clear our cached list of termini. # This is only used for testing. @@ -31,34 +30,44 @@ class Puppet::Indirector::Indirection end end @termini = {} + raise(ArgumentError, "Indirection %s is already defined" % @name) if @@indirections.find { |i| i.name == @name } @@indirections << self end # Return the singleton terminus for this indirection. - def terminus(name = nil) + def terminus(terminus_name = nil) # Get the name of the terminus. - unless name - unless param_name = self.to - raise ArgumentError, "You must specify an indirection terminus for indirection %s" % self.name + unless terminus_name + param_name = "%s_terminus" % self.name + if Puppet.config.valid?(param_name) + terminus_name = Puppet.config[param_name] + else + terminus_name = Puppet[:default_terminus] end - name = Puppet[param_name] - name = name.intern if name.is_a?(String) + unless terminus_name and terminus_name.to_s != "" + raise ArgumentError, "Invalid terminus name %s" % terminus_name.inspect + end + terminus_name = terminus_name.intern if terminus_name.is_a?(String) end - unless @termini[name] - @termini[name] = make_terminus(name) - end - @termini[name] + return @termini[terminus_name] ||= make_terminus(terminus_name) end - # Validate the parameter. This requires that indirecting - # classes require 'puppet/defaults', because of ordering issues, - # but it makes problems much easier to debug. - def to=(param_name) - unless Puppet.config.valid?(param_name) - raise ArgumentError, "Configuration parameter '%s' for indirection '%s' does not exist'" % [param_name, self.name] - end - @to = param_name + def find(*args) + terminus.find(*args) + end + + def destroy(*args) + terminus.destroy(*args) + end + + def search(*args) + terminus.search(*args) + end + + # these become instance methods + def save(*args) + terminus.save(*args) end private diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index e9b10c8c7..2aead4abf 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -10,44 +10,93 @@ describe Puppet::Indirector::Indirection, " when initializing" do @indirection.name.should == :myind end - it "should set any passed options" do - @indirection = Puppet::Indirector::Indirection.new(:myind, :to => :node_source) - @indirection.to.should == :node_source + it "should require indirections to have unique names" do + @indirection = Puppet::Indirector::Indirection.new(:test) + proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) end - it "should only allow valid configuration parameters to be specified as :to targets" do - proc { Puppet::Indirector::Indirection.new(:myind, :to => :no_such_variable) }.should raise_error(ArgumentError) + after do + @indirection.delete if defined? @indirection + end +end + +describe Puppet::Indirector::Indirection, " when choosing terminus types" do + before do + @indirection = Puppet::Indirector::Indirection.new(:test) + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus + end + + it "should follow a convention on using per-model configuration parameters to determine the terminus class" do + Puppet.config.expects(:valid?).with('test_terminus').returns(true) + Puppet.config.expects(:value).with('test_terminus').returns(:foo) + Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(@terminus_class) + @indirection.terminus.should equal(@terminus) + end + + it "should use a default system-wide configuration parameter parameter to determine the terminus class when no + per-model configuration parameter is available" do + Puppet.config.expects(:valid?).with('test_terminus').returns(false) + Puppet.config.expects(:value).with(:default_terminus).returns(:foo) + Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(@terminus_class) + @indirection.terminus.should equal(@terminus) + end + + it "should select the specified terminus class if a name is provided" do + Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(@terminus_class) + @indirection.terminus(:foo).should equal(@terminus) + end + + it "should fail when the terminus class name is an empty string" do + proc { @indirection.terminus("") }.should raise_error(ArgumentError) + end + + it "should fail when the terminus class name is nil" do + proc { @indirection.terminus(nil) }.should raise_error(ArgumentError) + end + + it "should fail when the specified terminus class cannot be found" do + Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(nil) + proc { @indirection.terminus(:foo) }.should raise_error(ArgumentError) end after do - if defined? @indirection - @indirection.delete - end + @indirection.delete if defined? @indirection end end -describe Puppet::Indirector, " when managing termini" do +describe Puppet::Indirector::Indirection, " when managing terminus instances" do before do - @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source) + @indirection = Puppet::Indirector::Indirection.new(:test) + @terminus = mock 'terminus' + @terminus_class = mock 'terminus class' + Puppet::Indirector.stubs(:terminus).with(:test, :foo).returns(@terminus_class) + end + + it "should create an instance of the chosen terminus class" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) end - it "should allow the clearance of cached termini" do + it "should allow the clearance of cached terminus instances" do terminus1 = mock 'terminus1' terminus2 = mock 'terminus2' - Puppet::Indirector.terminus(:node, Puppet[:node_source]).stubs(:new).returns(terminus1, terminus2, ArgumentError) - @indirection.terminus.should equal(terminus1) + @terminus_class.stubs(:new).returns(terminus1, terminus2, ArgumentError) + @indirection.terminus(:foo).should equal(terminus1) @indirection.class.clear_cache - @indirection.terminus.should equal(terminus2) + @indirection.terminus(:foo).should equal(terminus2) end # Make sure it caches the terminus. - it "should return the same terminus each time" do - @indirection = Puppet::Indirector::Indirection.new(:node, :to => :node_source) - @terminus = mock 'new' - Puppet::Indirector.terminus(:node, Puppet[:node_source]).expects(:new).returns(@terminus) + it "should return the same terminus instance each time for a given name" do + @terminus_class.stubs(:new).returns(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + @indirection.terminus(:foo).should equal(@terminus) + end - @indirection.terminus.should equal(@terminus) - @indirection.terminus.should equal(@terminus) + it "should not create a terminus instance until one is actually needed" do + Puppet::Indirector.expects(:terminus).never + indirection = Puppet::Indirector::Indirection.new(:lazytest) end after do @@ -55,3 +104,36 @@ describe Puppet::Indirector, " when managing termini" do Puppet::Indirector::Indirection.clear_cache end end + +describe Puppet::Indirector::Indirection do + before do + @indirection = Puppet::Indirector::Indirection.new(:test) + @terminus = mock 'terminus' + @indirection.stubs(:terminus).returns(@terminus) + end + + it "should handle lookups of a model instance by letting the appropriate terminus perform the lookup" do + @terminus.expects(:find).with(:mything).returns(:whev) + @indirection.find(:mything).should == :whev + end + + it "should handle removing model instances from a terminus letting the appropriate terminus remove the instance" do + @terminus.expects(:destroy).with(:mything).returns(:whev) + @indirection.destroy(:mything).should == :whev + end + + it "should handle searching for model instances by letting the appropriate terminus find the matching instances" do + @terminus.expects(:search).with(:mything).returns(:whev) + @indirection.search(:mything).should == :whev + end + + it "should handle storing a model instance by letting the appropriate terminus store the instance" do + @terminus.expects(:save).with(:mything).returns(:whev) + @indirection.save(:mything).should == :whev + end + + after do + @indirection.delete + Puppet::Indirector::Indirection.clear_cache + end +end diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index b5a70cd8f..1a9704e5e 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -2,26 +2,6 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/defaults' require 'puppet/indirector' -describe Puppet::Indirector do - it "should provide a way to clear all registrations" do - Puppet::Indirector.should respond_to(:reset) - end - - it "should provide a way to access a list of all registered models" do - Puppet::Indirector.should respond_to(:indirections) - end -end - -describe Puppet::Indirector, "when no models are registered" do - before do - Puppet::Indirector.reset - end - - it "should provide an empty list of registered models" do - Puppet::Indirector.indirections.should == {} - end -end - describe Puppet::Indirector, " when available to a model" do before do @thingie = Class.new do @@ -32,86 +12,8 @@ describe Puppet::Indirector, " when available to a model" do it "should provide a way for the model to register an indirection under a name" do @thingie.should respond_to(:indirects) end -end - -describe Puppet::Indirector, "when registering an indirection" do - before do - Puppet::Indirector.reset - @thingie = Class.new do - extend Puppet::Indirector - end - Puppet::Indirector.stubs(:terminus_for_indirection).returns(:ldap) - @terminus = mock 'terminus' - @terminus_class = stub 'terminus class', :new => @terminus - Puppet::Indirector.stubs(:terminus).returns(@terminus_class) - end - - it "should require a name when registering a model" do - Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) - end - - it "should require each model to be registered under a unique name" do - @thingie.send(:indirects, :name) - Proc.new {@thingie.send(:indirects, :name)}.should raise_error(ArgumentError) - end - - it "should not allow a model to register under multiple names" do - @thingie.send(:indirects, :first) - Proc.new {@thingie.send(:indirects, :second)}.should raise_error(ArgumentError) - end - - it "should allow finding an instance of a model in a collection" do - @thingie.send(:indirects, :first) - @thingie.should respond_to(:find) - end - - it "should allow removing an instance of a model from a collection" do - @thingie.send(:indirects, :first) - @thingie.should respond_to(:destroy) - end - it "should allow finding all matching model instances in a collection" do - @thingie.send(:indirects, :first) - @thingie.should respond_to(:search) - end - - it "should allow for storing a model instance in a collection" do - @thing = Class.new do - extend Puppet::Indirector - indirects :thing - end.new - @thing.should respond_to(:save) - end - - it "should provide a way to get a handle to the terminus for a model" do - Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) - terminus = mock 'terminus' - terminus_class = stub 'terminus class', :new => terminus - Puppet::Indirector.expects(:terminus).returns(terminus_class) - @thingie.send(:indirects, :node) - @thingie.indirection.should == terminus - end - - it "should list the model in a list of known indirections" do - @thingie.send(:indirects, :name) - Puppet::Indirector.indirections[:name].should == @thingie - end - - # when dealing with Terminus methods - it "should consult a per-model configuration to determine what kind of collection a model is being stored in" do - Puppet::Indirector.expects(:terminus_for_indirection).with(:node).returns(:ldap) - @thingie.send(:indirects, :node) - end - - it "should use the collection type described in the per-model configuration" do - terminus = mock 'terminus' - terminus_class = stub 'terminus class', :new => terminus - Puppet::Indirector.expects(:terminus_for_indirection).with(:foo).returns(:bar) - Puppet::Indirector.expects(:terminus).with(:foo, :bar).returns(terminus_class) - @thingie.send(:indirects, :foo) - end - - it "should handle lookups of a model instance by letting the terminus perform the lookup" do + it "should give model the ability to lookup a model instance by letting the indirection perform the lookup" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:find) @@ -119,7 +21,7 @@ describe Puppet::Indirector, "when registering an indirection" do @thingie.find end - it "should handle removing model instances from a collection letting the terminus remove the instance" do + it "should give model the ability to remove model instances from a terminus by letting the indirection remove the instance" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:destroy) @@ -127,7 +29,7 @@ describe Puppet::Indirector, "when registering an indirection" do @thingie.destroy end - it "should handle searching for model instances by letting the terminus find the matching instances" do + it "should give model the ability to search for model instances by letting the indirection find the matching instances" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:search) @@ -135,24 +37,43 @@ describe Puppet::Indirector, "when registering an indirection" do @thingie.search end - it "should handle storing a model instance by letting the terminus store the instance" do + it "should give model the ability to store a model instance by letting the indirection store the instance" do @thingie.send(:indirects, :node) mock_terminus = mock('Terminus') mock_terminus.expects(:save) @thingie.expects(:indirection).returns(mock_terminus) @thingie.new.save end - - it "should provide the same terminus for a given registered model" +end - it "should not access the collection for a registered model until that collection is actually needed" +describe Puppet::Indirector, "when registering an indirection" do + before do + Puppet::Indirector.reset + @thingie = Class.new do + extend Puppet::Indirector + end + Puppet::Indirector.stubs(:terminus_for_indirection).returns(:ldap) + @terminus = mock 'terminus' + @terminus_class = stub 'terminus class', :new => @terminus + Puppet::Indirector.stubs(:terminus).returns(@terminus_class) + end + + it "should require a name when registering a model" do + Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) + end + + it "should not allow a model to register under multiple names" do + @thingie.send(:indirects, :first) + Proc.new {@thingie.send(:indirects, :second)}.should raise_error(ArgumentError) + end + + it "should create an indirection instance to manage each indirecting model" # TODO: node lookup retries/searching end - # describe Puppet::Indirector::Terminus do # it "should register itself" # ??? # -- cgit From 7740cd497f936859fa5213c34dae485b5b70ba60 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 21 Sep 2007 13:07:34 -0500 Subject: The indirector specs now all pass. I think I need to add a few more specs, though. --- lib/puppet/indirector.rb | 95 ++++--------------------------------- lib/puppet/node.rb | 2 +- lib/puppet/node/facts.rb | 2 +- spec/unit/indirector/indirector.rb | 97 ++++++++++++++++++++------------------ 4 files changed, 63 insertions(+), 133 deletions(-) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index cdfd28908..90f4d3d33 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -22,6 +22,7 @@ module Puppet::Indirector def name self.class.name end + def indirection self.class.indirection end @@ -77,82 +78,37 @@ module Puppet::Indirector def self.terminus(indirection, terminus) loaded_instance(indirection, terminus) end - - # clear out the list of known indirections -#JRB:TODO -- I would prefer to get rid of this altogether, but it's implicated in testing, given the class loader - def self.reset - @indirections = {} - @class_indirections = {} - end - - # return a hash of registered indirections, keys are indirection names, values are classes which handle the indirections - def self.indirections - @indirections ||= {} - @indirections - end - - # associate an indirection name with the class which handles the indirection - def self.register_indirection(name, klass) - @indirections ||= {} - @class_indirections ||= {} - - raise ArgumentError, "Already performing an indirection of %s; cannot redirect %s" % [name, klass.name] if @indirections[name] - raise ArgumentError, "Class %s is already redirecting to %s; cannot redirect to %s" % - [klass.name, @class_indirections[klass.name], name] if @class_indirections[klass.name] - @class_indirections[klass.name] = name - @indirections[name] = klass - end - - def self.terminus_for_indirection(name) -# JRB:TODO make this do something useful, aka look something up in a .yml file - # JRB:TODO look up name + '_source' in standard configuration - case name - when :node: :none - when :facts: :yaml - else - raise ArgumentError, "Unknown indirection" - end - end # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets # evaluated at parse time, which is before the user has had a chance # to override it. - def indirects(indirection, options = {}) -#JRB:TODO remove options hash ^^^ - - # associate the name :node, with this class, Node - # also, do error checking (already registered, etc.) - Puppet::Indirector.register_indirection(indirection, self) - - # populate this registered class with the various new methods + def indirects(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 - # look up the type of Terminus for this name (:node => :ldap) - terminus = Puppet::Indirector.terminus_for_indirection(indirection) - # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) - # & hook the instantiated Terminus into this registered class (Node: @indirection = terminus) + # & hook the instantiated Terminus into this class (Node: @indirection = terminus) Puppet::Indirector.enable_autoloading_indirection indirection - raise("No Terminus %s for %s" % [terminus, indirection]) unless @indirection = Puppet::Indirector.terminus(indirection, terminus).new + @indirection = Puppet::Indirector::Indirection.new(indirection) end module ClassMethods attr_reader :indirection def find(*args) - self.indirection.find(*args) - # JRB:TODO look up the indirection, and call its .find method + indirection.find(*args) end def destroy(*args) - self.indirection.destroy(*args) + indirection.destroy(*args) end def search(*args) - self.indirection.search(*args) + indirection.search(*args) end end @@ -162,37 +118,4 @@ module Puppet::Indirector self.class.indirection.save(self, *args) end end - - # JRB:FIXME: these methods to be deprecated: - - # Define methods for each of the HTTP methods. These just point to the - # termini, with consistent error-handling. Each method is called with - # the first argument being the indirection type and the rest of the - # arguments passed directly on to the indirection terminus. There is - # currently no attempt to standardize around what the rest of the arguments - # should allow or include or whatever. - # There is also no attempt to pre-validate that a given indirection supports - # the method in question. We should probably require that indirections - # declare supported methods, and then verify that termini implement all of - # those methods. - # [:get, :post, :put, :delete].each do |method_name| - # define_method(method_name) do |*args| - # redirect(method_name, *args) - # end - # end - # - # private - # - # - # # JRB:TODO this needs to be renamed, as it actually ends up on the model class, where it might conflict with something - # # Redirect one of our methods to the corresponding method on the Terminus - # def redirect(method_name, *args) - # begin - # @indirection.terminus.send(method_name, *args) - # rescue NoMethodError => detail - # puts detail.backtrace if Puppet[:trace] - # raise ArgumentError, "The %s terminus of the %s indirection failed to respond to %s: %s" % - # [@indirection.terminus.name, @indirection.name, method_name, detail] - # end - # end end diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index 852f98a99..f8ede1858 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -9,7 +9,7 @@ class Puppet::Node extend Puppet::Indirector # Use the node source as the indirection terminus. - indirects :node, :to => :node_source + indirects :node # Add the node-searching methods. This is what people will actually # interact with that will find the node with the list of names or diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb index e5774127b..a2e6d9c04 100755 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -9,7 +9,7 @@ class Puppet::Node::Facts extend Puppet::Indirector # Use the node source as the indirection terminus. - indirects :facts, :to => :fact_store + indirects :facts attr_accessor :name, :values diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 1a9704e5e..2e22090b0 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -3,73 +3,80 @@ require 'puppet/defaults' require 'puppet/indirector' describe Puppet::Indirector, " when available to a model" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + end + + it "should provide a way for the model to register an indirection under a name" do + @thingie.should respond_to(:indirects) + end +end + +describe Puppet::Indirector, "when registering an indirection" do + before do + @thingie = Class.new do + extend Puppet::Indirector + end + end + + it "should require a name when registering a model" do + Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) + end + + it "should create an indirection instance to manage each indirecting model" do + @indirection = @thingie.indirects(:test) + @indirection.should be_instance_of(Puppet::Indirector::Indirection) + end + + it "should not allow a model to register under multiple names" do + # Keep track of the indirection instance so we can delete it on cleanup + @indirection = @thingie.indirects :first + Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) + end + + after do + @indirection.delete if @indirection + end + +# TODO: node lookup retries/searching +end + +describe Puppet::Indirector, " when redirecting model" do before do @thingie = Class.new do extend Puppet::Indirector end - end - - it "should provide a way for the model to register an indirection under a name" do - @thingie.should respond_to(:indirects) + @mock_terminus = mock('Terminus') + @indirection = @thingie.send(:indirects, :test) + @thingie.expects(:indirection).returns(@mock_terminus) end it "should give model the ability to lookup a model instance by letting the indirection perform the lookup" do - @thingie.send(:indirects, :node) - mock_terminus = mock('Terminus') - mock_terminus.expects(:find) - @thingie.expects(:indirection).returns(mock_terminus) + @mock_terminus.expects(:find) @thingie.find end it "should give model the ability to remove model instances from a terminus by letting the indirection remove the instance" do - @thingie.send(:indirects, :node) - mock_terminus = mock('Terminus') - mock_terminus.expects(:destroy) - @thingie.expects(:indirection).returns(mock_terminus) + @mock_terminus.expects(:destroy) @thingie.destroy end it "should give model the ability to search for model instances by letting the indirection find the matching instances" do - @thingie.send(:indirects, :node) - mock_terminus = mock('Terminus') - mock_terminus.expects(:search) - @thingie.expects(:indirection).returns(mock_terminus) + @mock_terminus.expects(:search) @thingie.search end it "should give model the ability to store a model instance by letting the indirection store the instance" do - @thingie.send(:indirects, :node) - mock_terminus = mock('Terminus') - mock_terminus.expects(:save) - @thingie.expects(:indirection).returns(mock_terminus) - @thingie.new.save - end -end - -describe Puppet::Indirector, "when registering an indirection" do - before do - Puppet::Indirector.reset - @thingie = Class.new do - extend Puppet::Indirector - end - Puppet::Indirector.stubs(:terminus_for_indirection).returns(:ldap) - @terminus = mock 'terminus' - @terminus_class = stub 'terminus class', :new => @terminus - Puppet::Indirector.stubs(:terminus).returns(@terminus_class) + thing = @thingie.new + @mock_terminus.expects(:save).with(thing) + thing.save end - it "should require a name when registering a model" do - Proc.new {@thingie.send(:indirects) }.should raise_error(ArgumentError) + after do + @indirection.delete end - - it "should not allow a model to register under multiple names" do - @thingie.send(:indirects, :first) - Proc.new {@thingie.send(:indirects, :second)}.should raise_error(ArgumentError) - end - - it "should create an indirection instance to manage each indirecting model" - -# TODO: node lookup retries/searching end -- cgit From 7e2ff4b39404ad9b0bdbc40d92c80bcb8c76fcf6 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 21 Sep 2007 13:18:51 -0500 Subject: Adding a couple more tests to the indirector, talking about terminus registration. I am about to change how loading is handled, so that individual termini are declared as normal Ruby classes. --- lib/puppet/indirector.rb | 2 -- spec/unit/indirector/indirector.rb | 13 +++++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 90f4d3d33..bb7006431 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -39,8 +39,6 @@ module Puppet::Indirector # does all of the rest -- loading, storing, and retrieving by name. require 'puppet/util/instance_loader' extend Puppet::Util::InstanceLoader - -# JRB:TODO - where did this come from, re: the specs? also, shouldn't this be protected/private? # Register a given indirection type. The classes including this module # handle creating terminus instances, but the module itself handles diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 2e22090b0..23637ec7c 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -36,6 +36,11 @@ describe Puppet::Indirector, "when registering an indirection" do Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) end + it "should set up instance loading for the indirection" do + Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") + @indirection = @thingie.indirects(:test) + end + after do @indirection.delete if @indirection end @@ -79,6 +84,14 @@ describe Puppet::Indirector, " when redirecting model" do end end +describe Puppet::Indirector, " when retrieving terminus classes" do + it "should allow terminus classes to register themselves" + + it "should provide a method to retrieve a terminus class by name and indirection" do + Puppet::Indirector.expects(:loaded_instance).with(:indirection, :terminus) + Puppet::Indirector.terminus(:indirection, :terminus) + end +end # describe Puppet::Indirector::Terminus do -- cgit From 0a48e5f5bf5885353edc20f020ae27eb682176f7 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 21 Sep 2007 13:42:39 -0500 Subject: Moving the Puppet::Indirector::Terminus class into its own file and adding a spec for it. --- lib/puppet/indirector.rb | 22 +--------------------- lib/puppet/indirector/terminus.rb | 19 +++++++++++++++++++ spec/unit/indirector/terminus.rb | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 21 deletions(-) create mode 100644 lib/puppet/indirector/terminus.rb create mode 100755 spec/unit/indirector/terminus.rb diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index bb7006431..4d7d9d92d 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -7,28 +7,8 @@ module Puppet::Indirector # LAK:FIXME We need to figure out how to handle documentation for the # different indirection types. -# JRB:TODO factor this out into its own class, with specs, and require it here -# require 'puppet/indirector/terminus' - - # A simple class that can function as the base class for indirected types. - class Terminus - require 'puppet/util/docs' - extend Puppet::Util::Docs - - class << self - attr_accessor :name, :indirection - end - - def name - self.class.name - end - - def indirection - self.class.indirection - end - end - require 'puppet/indirector/indirection' + require 'puppet/indirector/terminus' # This handles creating the terminus classes. require 'puppet/util/classgen' diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb new file mode 100644 index 000000000..d311eb60b --- /dev/null +++ b/lib/puppet/indirector/terminus.rb @@ -0,0 +1,19 @@ +require 'puppet/indirector' + +# A simple class that can function as the base class for indirected types. +class Puppet::Indirector::Terminus + require 'puppet/util/docs' + extend Puppet::Util::Docs + + class << self + attr_accessor :name, :indirection + end + + def name + self.class.name + end + + def indirection + self.class.indirection + end +end diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb new file mode 100755 index 000000000..9632aa22f --- /dev/null +++ b/spec/unit/indirector/terminus.rb @@ -0,0 +1,39 @@ +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/defaults' +require 'puppet/indirector' + +describe Puppet::Indirector::Terminus do + it "should support the documentation methods" do + Puppet::Indirector::Terminus.should respond_to(:desc) + end + + # LAK:FIXME I don't really know the best way to test this kind of + # requirement. + it "should support a class-level name attribute" do + Puppet::Indirector::Terminus.should respond_to(:name) + Puppet::Indirector::Terminus.should respond_to(:name=) + end + + it "should support a class-level indirection attribute" do + Puppet::Indirector::Terminus.should respond_to(:indirection) + Puppet::Indirector::Terminus.should respond_to(:indirection=) + end +end + +describe Puppet::Indirector::Terminus, " when a terminus instance" do + before do + @terminus_class = Class.new(Puppet::Indirector::Terminus) do + @name = :test + @indirection = :whatever + end + @terminus = @terminus_class.new + end + + it "should return the class's name as its name" do + @terminus.name.should == :test + end + + it "should return the class's indirection as its indirection" do + @terminus.indirection.should == :whatever + end +end -- cgit From da0555d948b837e7c16bdca164780c9e71353e4a Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 21 Sep 2007 15:15:40 -0500 Subject: Adding the first top-level terminus (yaml). It works and is tested, so now it is time to migrate the Facts YAML Terminus to use the / file structure instead of /. In this case, that means that I am moving the functionality in lib/puppet/indirector/facts/yaml.rb to lib/puppet/indirector/yaml/facts.rb, and the class in that new file will subclass Puppet::Indirector::Yaml. --- lib/puppet/defaults.rb | 6 +- lib/puppet/indirector/indirection.rb | 6 ++ lib/puppet/indirector/terminus.rb | 21 ++++++- lib/puppet/indirector/yaml.rb | 47 +++++++++++++++ spec/unit/indirector/indirection.rb | 15 +++++ spec/unit/indirector/terminus.rb | 48 +++++++++++---- spec/unit/indirector/yaml.rb | 113 +++++++++++++++++++++++++++++++++++ 7 files changed, 242 insertions(+), 14 deletions(-) create mode 100644 lib/puppet/indirector/yaml.rb create mode 100755 spec/unit/indirector/yaml.rb diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 4b442d094..fa119bb5a 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -499,9 +499,9 @@ module Puppet "The backend store to use for client facts."] ) - self.setdefaults(:yamlfacts, - :yamlfactdir => ["$vardir/facts", - "The directory in which client facts are stored when the yaml fact store is used."] + self.setdefaults(:yaml, + :yamldir => ["$vardir/yaml", + "The directory in which YAML data is stored, usually in a subdirectory."] ) self.setdefaults(:rails, diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 7128346ac..0cf8c7a27 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -6,6 +6,12 @@ class Puppet::Indirector::Indirection def self.clear_cache @@indirections.each { |ind| ind.clear_cache } end + + # Find an indirection by name. This is provided so that Terminus classes + # can specifically hook up with the indirections they are associated with. + def self.instance(name) + @@indirections.find { |i| i.name == name } + end attr_accessor :name, :termini diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb index d311eb60b..4d550e9ba 100644 --- a/lib/puppet/indirector/terminus.rb +++ b/lib/puppet/indirector/terminus.rb @@ -1,4 +1,5 @@ require 'puppet/indirector' +require 'puppet/indirector/indirection' # A simple class that can function as the base class for indirected types. class Puppet::Indirector::Terminus @@ -6,7 +7,25 @@ class Puppet::Indirector::Terminus extend Puppet::Util::Docs class << self - attr_accessor :name, :indirection + attr_accessor :name + attr_reader :indirection + + # Look up the indirection if we were only provided a name. + def indirection=(name) + if name.is_a?(Puppet::Indirector::Indirection) + @indirection = name + elsif ind = Puppet::Indirector::Indirection.instance(name) + @indirection = ind + else + raise ArgumentError, "Could not find indirection instance %s" % name + end + end + end + + def initialize + unless indirection + raise Puppet::DevError, "Indirection termini cannot be used without an associated indirection" + end end def name diff --git a/lib/puppet/indirector/yaml.rb b/lib/puppet/indirector/yaml.rb new file mode 100644 index 000000000..f7f7d290f --- /dev/null +++ b/lib/puppet/indirector/yaml.rb @@ -0,0 +1,47 @@ +require 'puppet/indirector/terminus' + +# The base class for YAML indirection termini. +class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus + def initialize + super + + # Make sure our base directory exists. + Puppet.config.use(:yaml) + end + + # Read a given name's file in and convert it from YAML. + def find(name) + raise ArgumentError.new("You must specify the name of the object to retrieve") unless name + file = path(name) + return nil unless FileTest.exist?(file) + + begin + return YAML.load(File.read(file)) + rescue => detail + raise Puppet::Error, "Could not read YAML data for %s(%s): %s" % [indirection.name, name, detail] + end + end + + # Convert our object to YAML and store it to the disk. + def save(object) + raise ArgumentError.new("You can only save objects that respond to :name") unless object.respond_to?(:name) + + file = path(object.name) + + basedir = File.dirname(file) + + # This is quite likely a bad idea, since we're not managing ownership or modes. + unless FileTest.exist?(basedir) + Dir.mkdir(basedir) + end + + File.open(file, "w", 0660) { |f| f.print YAML.dump(object) } + end + + private + + # Return the path to a given node's file. + def path(name) + File.join(Puppet[:yamldir], indirection.name.to_s, name.to_s + ".yaml") + end +end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index 2aead4abf..cd5379ee6 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -20,6 +20,21 @@ describe Puppet::Indirector::Indirection, " when initializing" do end end +describe Puppet::Indirector::Indirection, " when managing indirection instances" do + it "should allow an indirection to be retrieved by name" do + @indirection = Puppet::Indirector::Indirection.new(:test) + Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) + end + + it "should return nil when the named indirection has not been created" do + Puppet::Indirector::Indirection.instance(:test).should be_nil + end + + after do + @indirection.delete if defined? @indirection + end +end + describe Puppet::Indirector::Indirection, " when choosing terminus types" do before do @indirection = Puppet::Indirector::Indirection.new(:test) diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb index 9632aa22f..1660315ca 100755 --- a/spec/unit/indirector/terminus.rb +++ b/spec/unit/indirector/terminus.rb @@ -3,29 +3,52 @@ require 'puppet/defaults' require 'puppet/indirector' describe Puppet::Indirector::Terminus do + before do + @terminus = Class.new(Puppet::Indirector::Terminus) + end + it "should support the documentation methods" do - Puppet::Indirector::Terminus.should respond_to(:desc) + @terminus.should respond_to(:desc) end # LAK:FIXME I don't really know the best way to test this kind of # requirement. it "should support a class-level name attribute" do - Puppet::Indirector::Terminus.should respond_to(:name) - Puppet::Indirector::Terminus.should respond_to(:name=) + @terminus.should respond_to(:name) + @terminus.should respond_to(:name=) end it "should support a class-level indirection attribute" do - Puppet::Indirector::Terminus.should respond_to(:indirection) - Puppet::Indirector::Terminus.should respond_to(:indirection=) + @terminus.should respond_to(:indirection) + @terminus.should respond_to(:indirection=) + end + + it "should accept indirection instances as its indirection" do + indirection = stub 'indirection', :is_a? => true + proc { @terminus.indirection = indirection }.should_not raise_error + @terminus.indirection.should equal(indirection) + end + + it "should look up indirection instances when only a name has been provided" do + indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(indirection) + @terminus.indirection = :myind + @terminus.indirection.should equal(indirection) + end + + it "should fail when provided a name that does not resolve to an indirection" do + Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(nil) + proc { @terminus.indirection = :myind }.should raise_error(ArgumentError) + @terminus.indirection.should be_nil end end describe Puppet::Indirector::Terminus, " when a terminus instance" do before do - @terminus_class = Class.new(Puppet::Indirector::Terminus) do - @name = :test - @indirection = :whatever - end + @indirection = stub 'indirection', :name => :myyaml + @terminus_class = Class.new(Puppet::Indirector::Terminus) + @terminus_class.name = :test + @terminus_class.stubs(:indirection).returns(@indirection) @terminus = @terminus_class.new end @@ -34,6 +57,11 @@ describe Puppet::Indirector::Terminus, " when a terminus instance" do end it "should return the class's indirection as its indirection" do - @terminus.indirection.should == :whatever + @terminus.indirection.should equal(@indirection) + end + + it "should require an associated indirection" do + @terminus_class.expects(:indirection).returns(nil) + proc { @terminus_class.new }.should raise_error(Puppet::DevError) end end diff --git a/spec/unit/indirector/yaml.rb b/spec/unit/indirector/yaml.rb new file mode 100755 index 000000000..93af048f9 --- /dev/null +++ b/spec/unit/indirector/yaml.rb @@ -0,0 +1,113 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/yaml' + +module YamlTesting + def setup + @store_class = Class.new(Puppet::Indirector::Yaml) + @indirection = stub 'indirection', :name => :myyaml + @store_class.stubs(:indirection).returns(@indirection) + @store = @store_class.new + + @subject = Object.new + @subject.metaclass.send(:attr_accessor, :name) + @subject.name = :me + + @dir = "/what/ever" + Puppet.config.stubs(:use) + Puppet.config.stubs(:value).with(:yamldir).returns(@dir) + end +end + +describe Puppet::Indirector::Yaml, " when initializing" do + before do + @store_class = Class.new(Puppet::Indirector::Yaml) + end + + # The superclass tests for the same thing, but it doesn't hurt to + # leave this in the spec. + it "should require an associated indirection" do + @store_class.expects(:indirection).returns(nil) + proc { @store_class.new }.should raise_error(Puppet::DevError) + end +end + +describe Puppet::Indirector::Yaml, " when choosing file location" do + include YamlTesting + + it "should store all files in a single file root set in the Puppet defaults" do + @store.send(:path, :me).should =~ %r{^#{@dir}} + end + + it "should use the indirection name for choosing the subdirectory" do + @store.send(:path, :me).should =~ %r{^#{@dir}/myyaml} + end + + it "should use the object's name to determine the file name" do + @store.send(:path, :me).should =~ %r{me.yaml$} + end +end + +describe Puppet::Indirector::Yaml, " when storing objects as YAML" do + include YamlTesting + + it "should only store objects that respond to :name" do + proc { @store.save(Object.new) }.should raise_error(ArgumentError) + end + + it "should convert Ruby objects to YAML and write them to disk" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + FileTest.expects(:exist?).with(File.dirname(path)).returns(true) + File.expects(:open).with(path, "w", 0660).yields(file) + file.expects(:print).with(yaml) + + @store.save(@subject) + end + + it "should create the indirection subdirectory if it does not exist" do + yaml = @subject.to_yaml + file = mock 'file' + path = @store.send(:path, @subject.name) + dir = File.dirname(path) + FileTest.expects(:exist?).with(dir).returns(false) + Dir.expects(:mkdir).with(dir) + File.expects(:open).with(path, "w", 0660).yields(file) + file.expects(:print).with(yaml) + + @store.save(@subject) + end +end + +describe Puppet::Indirector::Yaml, " when retrieving YAML" do + include YamlTesting + + it "should require the name of the object to retrieve" do + proc { @store.find(nil) }.should raise_error(ArgumentError) + end + + it "should read YAML in from disk and convert it to Ruby objects" do + path = @store.send(:path, @subject.name) + + yaml = @subject.to_yaml + FileTest.expects(:exist?).with(path).returns(true) + File.expects(:read).with(path).returns(yaml) + + @store.find(@subject.name).instance_variable_get("@name").should == :me + end + + it "should fail coherently when the stored YAML is invalid" do + path = @store.send(:path, @subject.name) + + # Something that will fail in yaml + yaml = "--- !ruby/object:Hash" + + FileTest.expects(:exist?).with(path).returns(true) + File.expects(:read).with(path).returns(yaml) + + proc { @store.find(@subject.name) }.should raise_error(Puppet::Error) + end +end -- cgit From 02275f0b29cee70e3f02d6d8f911eaaf3fad579d Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 21 Sep 2007 15:49:25 -0500 Subject: Adding automatic association between terminus subclasses and the indirection they're working with. It looks like I'll be moving terminus registration to the indirection rather than the top-level Indirector. --- lib/puppet/indirector/facts/yaml.rb | 41 ------------------------ lib/puppet/indirector/terminus.rb | 23 ++++++++++++++ lib/puppet/indirector/yaml/facts.rb | 7 +++++ spec/unit/indirector/terminus.rb | 62 ++++++++++++++++++++++++++++++++++--- 4 files changed, 88 insertions(+), 45 deletions(-) delete mode 100644 lib/puppet/indirector/facts/yaml.rb create mode 100644 lib/puppet/indirector/yaml/facts.rb diff --git a/lib/puppet/indirector/facts/yaml.rb b/lib/puppet/indirector/facts/yaml.rb deleted file mode 100644 index cb02f05c6..000000000 --- a/lib/puppet/indirector/facts/yaml.rb +++ /dev/null @@ -1,41 +0,0 @@ -Puppet::Indirector.register_terminus :facts, :yaml do - desc "Store client facts as flat files, serialized using YAML." - - # Get a client's facts. - def find(node) - file = path(node) - - return nil unless FileTest.exists?(file) - - begin - values = YAML::load(File.read(file)) - rescue => detail - Puppet.err "Could not load facts for %s: %s" % [node, detail] - end - - Puppet::Node::Facts.new(node, values) - end - - def initialize - Puppet.config.use(:yamlfacts) - end - - # Store the facts to disk. - def save(facts) - File.open(path(facts.name), "w", 0600) do |f| - begin - f.print YAML::dump(facts.values) - rescue => detail - Puppet.err "Could not write facts for %s: %s" % [facts.name, detail] - end - end - nil - end - - private - - # Return the path to a given node's file. - def path(name) - File.join(Puppet[:yamlfactdir], name + ".yaml") - end -end diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb index 4d550e9ba..a98f6dff0 100644 --- a/lib/puppet/indirector/terminus.rb +++ b/lib/puppet/indirector/terminus.rb @@ -20,6 +20,29 @@ class Puppet::Indirector::Terminus raise ArgumentError, "Could not find indirection instance %s" % name end end + + # Register our subclass with the appropriate indirection. + # This follows the convention that our terminus is named after the + # indirection. + def inherited(subclass) + longname = subclass.to_s + if longname =~ /# :mystuff + Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) + @terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Terminus::Type::MyStuff" + end + end end it "should support the documentation methods" do @@ -39,16 +45,64 @@ describe Puppet::Indirector::Terminus do it "should fail when provided a name that does not resolve to an indirection" do Puppet::Indirector::Indirection.expects(:instance).with(:myind).returns(nil) proc { @terminus.indirection = :myind }.should raise_error(ArgumentError) - @terminus.indirection.should be_nil + + # It shouldn't overwrite our existing one (or, more normally, it shouldn't set + # anything). + @terminus.indirection.should equal(@indirection) + end +end + +describe Puppet::Indirector::Terminus, " when being subclassed" do + it "should associate the subclass with an indirection based on the subclass constant" do + indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(indirection) + + klass = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Puppet::Indirector::Terminus::MyIndirection" + end + end + + klass.indirection.should equal(indirection) + end + + it "should fail when the terminus subclass does not include its parent class in the constant path" do + indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(indirection) + + proc { + klass = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "MyIndirection" + end + end + }.should raise_error(ArgumentError) + end + + it "should set the subclass's name to the terminus type" do + indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(indirection) + + klass = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Puppet::Indirector::Terminus::Yaml::MyIndirection" + end + end + + klass.name.should == :yaml end end describe Puppet::Indirector::Terminus, " when a terminus instance" do before do @indirection = stub 'indirection', :name => :myyaml - @terminus_class = Class.new(Puppet::Indirector::Terminus) + Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) + @terminus_class = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Terminus::Type::MyStuff" + end + end @terminus_class.name = :test - @terminus_class.stubs(:indirection).returns(@indirection) @terminus = @terminus_class.new end -- cgit From b9dc6cb22f087f419b328cafa945c9604043b22f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Fri, 21 Sep 2007 19:28:01 -0500 Subject: It looks like the new indirection setup is complete. I only need to port the node indirection termini over. --- lib/puppet/indirector.rb | 48 ----------- lib/puppet/indirector/indirection.rb | 3 +- lib/puppet/indirector/terminus.rb | 74 +++++++++++++--- lib/puppet/indirector/yaml.rb | 4 +- lib/puppet/indirector/yaml/facts.rb | 2 - lib/puppet/util/instance_loader.rb | 8 +- spec/unit/indirector/facts/yaml.rb | 62 -------------- spec/unit/indirector/indirection.rb | 10 +-- spec/unit/indirector/indirector.rb | 31 ------- spec/unit/indirector/terminus.rb | 159 +++++++++++++++++++++++++++-------- spec/unit/indirector/yaml.rb | 25 ++---- spec/unit/indirector/yaml/facts.rb | 26 ++++++ 12 files changed, 235 insertions(+), 217 deletions(-) delete mode 100755 spec/unit/indirector/facts/yaml.rb create mode 100755 spec/unit/indirector/yaml/facts.rb diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index 4d7d9d92d..b89017c9a 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -10,53 +10,6 @@ module Puppet::Indirector require 'puppet/indirector/indirection' require 'puppet/indirector/terminus' - # This handles creating the terminus classes. - require 'puppet/util/classgen' - extend Puppet::Util::ClassGen - - # This manages reading in all of our files for us and then retrieving - # loaded instances. We still have to define the 'newX' method, but this - # does all of the rest -- loading, storing, and retrieving by name. - require 'puppet/util/instance_loader' - extend Puppet::Util::InstanceLoader - - # Register a given indirection type. The classes including this module - # handle creating terminus instances, but the module itself handles - # loading them and managing the classes. - def self.enable_autoloading_indirection(indirection) - # Set up autoloading of the appropriate termini. - instance_load indirection, "puppet/indirector/%s" % indirection - end - -# JRB:TODO -- where did this come from, re: the specs? also, any way to make this protected/private? - - # Define a new indirection terminus. This method is used by the individual - # termini in their separate files. Again, the autoloader takes care of - # actually loading these files. - # Note that the termini are being registered on the Indirector module, not - # on the classes including the module. This allows a given indirection to - # be used in multiple classes. - def self.register_terminus(indirection, terminus, options = {}, &block) - klass = genclass(terminus, - :prefix => indirection.to_s.capitalize, - :hash => instance_hash(indirection), - :attributes => options, - :block => block, - :parent => options[:parent] || Terminus, -# JRB:FIXME -- why do I have to use overwrite here? - :overwrite => 'please do motherfucker' - ) - klass.indirection = indirection - klass.name = terminus - end - -# JRB:TODO where did this come from, re: the specs? also, shouldn't this be protected/private? - # Retrieve a terminus class by indirection and name. -# JRB:FIXME -- should be protected/private - def self.terminus(indirection, terminus) - loaded_instance(indirection, terminus) - end - # Declare that the including class indirects its methods to # this terminus. The terminus name must be the name of a Puppet # default, not the value -- if it's the value, then it gets @@ -70,7 +23,6 @@ module Puppet::Indirector # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) # & hook the instantiated Terminus into this class (Node: @indirection = terminus) - Puppet::Indirector.enable_autoloading_indirection indirection @indirection = Puppet::Indirector::Indirection.new(indirection) end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 0cf8c7a27..31b9d68a5 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -36,6 +36,7 @@ class Puppet::Indirector::Indirection end end @termini = {} + @terminus_types = {} raise(ArgumentError, "Indirection %s is already defined" % @name) if @@indirections.find { |i| i.name == @name } @@indirections << self end @@ -81,7 +82,7 @@ class Puppet::Indirector::Indirection # Create a new terminus instance. def make_terminus(name) # Load our terminus class. - unless klass = Puppet::Indirector.terminus(self.name, name) + unless klass = Puppet::Indirector::Terminus.terminus_class(self.name, name) raise ArgumentError, "Could not find terminus %s for indirection %s" % [name, self.name] end return klass.new diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb index a98f6dff0..f3797f471 100644 --- a/lib/puppet/indirector/terminus.rb +++ b/lib/puppet/indirector/terminus.rb @@ -1,14 +1,23 @@ require 'puppet/indirector' require 'puppet/indirector/indirection' +require 'puppet/util/instance_loader' # A simple class that can function as the base class for indirected types. class Puppet::Indirector::Terminus require 'puppet/util/docs' extend Puppet::Util::Docs - + class << self - attr_accessor :name - attr_reader :indirection + include Puppet::Util::InstanceLoader + + attr_accessor :name, :terminus_type + attr_reader :abstract_terminus, :indirection + + # Are we an abstract terminus type, rather than an instance with an + # associated indirection? + def abstract_terminus? + abstract_terminus + end # Look up the indirection if we were only provided a name. def indirection=(name) @@ -30,27 +39,66 @@ class Puppet::Indirector::Terminus raise ArgumentError, "Terminus subclasses must have associated constants" end names = longname.split("::") + name = names.pop.downcase.intern - # First figure out the indirection - shortname = names.pop.downcase.intern - subclass.indirection = shortname + subclass.name = name - if names.empty? - raise ArgumentError, "Terminus subclasses need to have their constants include the parent class for setting the terminus name" + # Short-circuit the abstract types, which are those that directly subclass + # the Terminus class. + if self == Puppet::Indirector::Terminus + subclass.mark_as_abstract_terminus + return end - # And then the name (e.g., ldap or yaml) - termtype = names.pop.downcase.intern - subclass.name = termtype + # Set the terminus type to be the name of the abstract terminus type. + # Yay, class/instance confusion. + subclass.terminus_type = self.name + + # This will throw an exception if the indirection instance cannot be found. + # Do this last, because it also registers the terminus type with the indirection, + # which needs the above information. + subclass.indirection = name + + # And add this instance to the instance hash. + Puppet::Indirector::Terminus.register_terminus_class(subclass) + end + + # Mark that this instance is abstract. + def mark_as_abstract_terminus + @abstract_terminus = true + end + + # Register a class, probably autoloaded. + def register_terminus_class(klass) + setup_instance_loading klass.terminus_type + instance_hash(klass.terminus_type)[klass.name] = klass + end + + # Return a terminus by name, using the autoloader. + def terminus_class(type, name) + setup_instance_loading type + loaded_instance(type, name) + end + + private + + def setup_instance_loading(type) + unless instance_loading?(type) + instance_load type, "puppet/indirector/%s" % type + end end end def initialize - unless indirection - raise Puppet::DevError, "Indirection termini cannot be used without an associated indirection" + if self.class.abstract_terminus? + raise Puppet::DevError, "Cannot create instances of abstract terminus types" end end + def terminus_type + self.class.terminus_type + end + def name self.class.name end diff --git a/lib/puppet/indirector/yaml.rb b/lib/puppet/indirector/yaml.rb index f7f7d290f..42f1bb703 100644 --- a/lib/puppet/indirector/yaml.rb +++ b/lib/puppet/indirector/yaml.rb @@ -3,8 +3,6 @@ require 'puppet/indirector/terminus' # The base class for YAML indirection termini. class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus def initialize - super - # Make sure our base directory exists. Puppet.config.use(:yaml) end @@ -42,6 +40,6 @@ class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus # Return the path to a given node's file. def path(name) - File.join(Puppet[:yamldir], indirection.name.to_s, name.to_s + ".yaml") + File.join(Puppet[:yamldir], self.name.to_s, name.to_s + ".yaml") end end diff --git a/lib/puppet/indirector/yaml/facts.rb b/lib/puppet/indirector/yaml/facts.rb index c09eaeab1..754b0d5a6 100644 --- a/lib/puppet/indirector/yaml/facts.rb +++ b/lib/puppet/indirector/yaml/facts.rb @@ -2,6 +2,4 @@ require 'puppet/indirector/yaml' class Puppet::Indirector::Yaml::Facts < Puppet::Indirector::Yaml desc "Store client facts as flat files, serialized using YAML." - - register_terminus end diff --git a/lib/puppet/util/instance_loader.rb b/lib/puppet/util/instance_loader.rb index bc0567866..f280014eb 100755 --- a/lib/puppet/util/instance_loader.rb +++ b/lib/puppet/util/instance_loader.rb @@ -5,6 +5,12 @@ require 'puppet/util' # of Puppet::Util::Autoload module Puppet::Util::InstanceLoader include Puppet::Util + + # Are we instance-loading this type? + def instance_loading?(type) + defined?(@autoloaders) and @autoloaders.include?(symbolize(type)) + end + # Define a new type of autoloading. def instance_load(type, path, options = {}) @autoloaders ||= {} @@ -54,7 +60,7 @@ module Puppet::Util::InstanceLoader # Retrieve an alread-loaded instance, or attempt to load our instance. def loaded_instance(type, name) name = symbolize(name) - instances = instance_hash(type) + return nil unless instances = instance_hash(type) unless instances.include? name if instance_loader(type).load(name) unless instances.include? name diff --git a/spec/unit/indirector/facts/yaml.rb b/spec/unit/indirector/facts/yaml.rb deleted file mode 100755 index 49b361a95..000000000 --- a/spec/unit/indirector/facts/yaml.rb +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'puppet/indirector' -require 'puppet/node/facts' -require 'puppettest' - -describe Puppet::Indirector.terminus(:facts, :yaml), " when managing facts" do - # For cleanup mechanisms. - include PuppetTest - - # LAK:FIXME It seems like I really do have to hit the filesystem - # here, since, like, that's what I'm testing. Is there another/better - # way to do this? - before do - @store = Puppet::Indirector.terminus(:facts, :yaml).new - setup # Grr, stupid rspec - Puppet[:yamlfactdir] = tempfile - Dir.mkdir(Puppet[:yamlfactdir]) - end - - it "should store facts in YAML in the yamlfactdir" do - values = {"one" => "two", "three" => "four"} - facts = Puppet::Node::Facts.new("node", values) - @store.save(facts) - - # Make sure the file exists - path = File.join(Puppet[:yamlfactdir], facts.name) + ".yaml" - File.exists?(path).should be_true - - # And make sure it's right - newvals = YAML.load(File.read(path)) - - # We iterate over them, because the store might add extra values. - values.each do |name, value| - newvals[name].should == value - end - end - - it "should retrieve values from disk" do - values = {"one" => "two", "three" => "four"} - - # Create the file. - path = File.join(Puppet[:yamlfactdir], "node") + ".yaml" - File.open(path, "w") do |f| - f.print values.to_yaml - end - - facts = Puppet::Node::Facts.find('node') - facts.should be_instance_of(Puppet::Node::Facts) - - # We iterate over them, because the store might add extra values. - values.each do |name, value| - facts.values[name].should == value - end - end - - after do - teardown - end -end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index cd5379ee6..5866f2d5f 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -45,7 +45,7 @@ describe Puppet::Indirector::Indirection, " when choosing terminus types" do it "should follow a convention on using per-model configuration parameters to determine the terminus class" do Puppet.config.expects(:valid?).with('test_terminus').returns(true) Puppet.config.expects(:value).with('test_terminus').returns(:foo) - Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(@terminus_class) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus.should equal(@terminus) end @@ -53,12 +53,12 @@ describe Puppet::Indirector::Indirection, " when choosing terminus types" do per-model configuration parameter is available" do Puppet.config.expects(:valid?).with('test_terminus').returns(false) Puppet.config.expects(:value).with(:default_terminus).returns(:foo) - Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(@terminus_class) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus.should equal(@terminus) end it "should select the specified terminus class if a name is provided" do - Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(@terminus_class) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(@terminus_class) @indirection.terminus(:foo).should equal(@terminus) end @@ -71,7 +71,7 @@ describe Puppet::Indirector::Indirection, " when choosing terminus types" do end it "should fail when the specified terminus class cannot be found" do - Puppet::Indirector.expects(:terminus).with(:test, :foo).returns(nil) + Puppet::Indirector::Terminus.expects(:terminus_class).with(:test, :foo).returns(nil) proc { @indirection.terminus(:foo) }.should raise_error(ArgumentError) end @@ -85,7 +85,7 @@ describe Puppet::Indirector::Indirection, " when managing terminus instances" do @indirection = Puppet::Indirector::Indirection.new(:test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' - Puppet::Indirector.stubs(:terminus).with(:test, :foo).returns(@terminus_class) + Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) end it "should create an instance of the chosen terminus class" do diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 23637ec7c..47e93cbaa 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -36,16 +36,9 @@ describe Puppet::Indirector, "when registering an indirection" do Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) end - it "should set up instance loading for the indirection" do - Puppet::Indirector.expects(:instance_load).with(:test, "puppet/indirector/test") - @indirection = @thingie.indirects(:test) - end - after do @indirection.delete if @indirection end - -# TODO: node lookup retries/searching end describe Puppet::Indirector, " when redirecting model" do @@ -83,27 +76,3 @@ describe Puppet::Indirector, " when redirecting model" do @indirection.delete end end - -describe Puppet::Indirector, " when retrieving terminus classes" do - it "should allow terminus classes to register themselves" - - it "should provide a method to retrieve a terminus class by name and indirection" do - Puppet::Indirector.expects(:loaded_instance).with(:indirection, :terminus) - Puppet::Indirector.terminus(:indirection, :terminus) - end -end - - -# describe Puppet::Indirector::Terminus do -# it "should register itself" # ??? -# -# it "should allow for finding an object from a collection" -# it "should allow for finding matching objects from a collection" -# it "should allow for destroying an object in a collection" -# it "should allow an object to be saved to a collection" -# it "should allow an object class to pre-process its arguments" -# it "should allow an object class to be in a read-only collection" -# -# it "should look up the appropriate decorator for the class" -# it "should call " -# end diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb index c5f5c064a..08e7e6ccb 100755 --- a/spec/unit/indirector/terminus.rb +++ b/spec/unit/indirector/terminus.rb @@ -4,33 +4,40 @@ require 'puppet/indirector' describe Puppet::Indirector::Terminus do before do - @indirection = stub 'indirection', :name => :mystuff + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) - @terminus = Class.new(Puppet::Indirector::Terminus) do + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus = Class.new(@abstract_terminus) do def self.to_s "Terminus::Type::MyStuff" end end end - it "should support the documentation methods" do + it "should provide a method for setting terminus class documentation" do @terminus.should respond_to(:desc) end - # LAK:FIXME I don't really know the best way to test this kind of - # requirement. it "should support a class-level name attribute" do @terminus.should respond_to(:name) - @terminus.should respond_to(:name=) end it "should support a class-level indirection attribute" do @terminus.should respond_to(:indirection) - @terminus.should respond_to(:indirection=) + end + + it "should support a class-level terminus-type attribute" do + @terminus.should respond_to(:terminus_type) end it "should accept indirection instances as its indirection" do - indirection = stub 'indirection', :is_a? => true + indirection = stub 'indirection', :is_a? => true, :register_terminus_type => nil proc { @terminus.indirection = indirection }.should_not raise_error @terminus.indirection.should equal(indirection) end @@ -52,26 +59,43 @@ describe Puppet::Indirector::Terminus do end end -describe Puppet::Indirector::Terminus, " when being subclassed" do - it "should associate the subclass with an indirection based on the subclass constant" do - indirection = mock 'indirection' - Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(indirection) +# LAK: This could reasonably be in the Indirection instances, too. It doesn't make +# a whole heckuva lot of difference, except that with the instance loading in +# the Terminus base class, we have to have a check to see if we're already +# instance-loading a given terminus class type. +describe Puppet::Indirector::Terminus, " when managing terminus classes" do + it "should provide a method for registering terminus classes" do + Puppet::Indirector::Terminus.should respond_to(:register_terminus_class) + end - klass = Class.new(Puppet::Indirector::Terminus) do - def self.to_s - "Puppet::Indirector::Terminus::MyIndirection" - end - end + it "should provide a method for returning terminus classes by name and type" do + terminus = stub 'terminus_type', :terminus_type => :abstract, :name => :whatever + Puppet::Indirector::Terminus.register_terminus_class(terminus) + Puppet::Indirector::Terminus.terminus_class(:abstract, :whatever).should equal(terminus) + end - klass.indirection.should equal(indirection) + it "should set up autoloading for any terminus class types requested" do + Puppet::Indirector::Terminus.expects(:instance_load).with(:test2, "puppet/indirector/test2") + Puppet::Indirector::Terminus.terminus_class(:test2, :whatever) end - it "should fail when the terminus subclass does not include its parent class in the constant path" do - indirection = mock 'indirection' - Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(indirection) + it "should load terminus classes that are not found" do + # Set up instance loading; it would normally happen automatically + Puppet::Indirector::Terminus.instance_load :test1, "puppet/indirector/test1" + Puppet::Indirector::Terminus.instance_loader(:test1).expects(:load).with(:yay) + Puppet::Indirector::Terminus.terminus_class(:test1, :yay) + end + + it "should fail when no indirection can be found" do + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(nil) + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end proc { - klass = Class.new(Puppet::Indirector::Terminus) do + @terminus = Class.new(@abstract_terminus) do def self.to_s "MyIndirection" end @@ -79,27 +103,95 @@ describe Puppet::Indirector::Terminus, " when being subclassed" do }.should raise_error(ArgumentError) end - it "should set the subclass's name to the terminus type" do - indirection = mock 'indirection' - Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(indirection) + it "should register the terminus class with the terminus base class" do + Puppet::Indirector::Terminus.expects(:register_terminus_class).with do |type| + type.terminus_type == :abstract and type.name == :myindirection + end + @indirection = stub 'indirection', :name => :myind, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(@indirection) - klass = Class.new(Puppet::Indirector::Terminus) do + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do def self.to_s - "Puppet::Indirector::Terminus::Yaml::MyIndirection" + "Abstract" end end - klass.name.should == :yaml + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + end +end + +describe Puppet::Indirector::Terminus, " when creating terminus class types" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @subclass = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Puppet::Indirector::Terminus::MyTermType" + end + end + end + + it "should set the name of the abstract subclass to be its class constant" do + @subclass.name.should equal(:mytermtype) + end + + it "should mark abstract terminus types as such" do + @subclass.should be_abstract_terminus + end + + it "should not allow instances of abstract subclasses to be created" do + proc { @subclass.new }.should raise_error(Puppet::DevError) + end +end + +describe Puppet::Indirector::Terminus, " when creating terminus classes" do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + + @indirection = stub 'indirection', :name => :myind, :register_terminus_type => nil + Puppet::Indirector::Indirection.expects(:instance).with(:myindirection).returns(@indirection) + + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do + def self.to_s + "Abstract" + end + end + @terminus = Class.new(@abstract_terminus) do + def self.to_s + "MyIndirection" + end + end + end + + it "should associate the subclass with an indirection based on the subclass constant" do + @terminus.indirection.should equal(@indirection) + end + + it "should set the subclass's type to the abstract terminus name" do + @terminus.terminus_type.should == :abstract + end + + it "should set the subclass's name to the indirection name" do + @terminus.name.should == :myindirection end end describe Puppet::Indirector::Terminus, " when a terminus instance" do before do - @indirection = stub 'indirection', :name => :myyaml + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @indirection = stub 'indirection', :name => :myyaml, :register_terminus_type => nil Puppet::Indirector::Indirection.stubs(:instance).with(:mystuff).returns(@indirection) - @terminus_class = Class.new(Puppet::Indirector::Terminus) do + @abstract_terminus = Class.new(Puppet::Indirector::Terminus) do def self.to_s - "Terminus::Type::MyStuff" + "Abstract" + end + end + @terminus_class = Class.new(@abstract_terminus) do + def self.to_s + "MyStuff" end end @terminus_class.name = :test @@ -114,8 +206,7 @@ describe Puppet::Indirector::Terminus, " when a terminus instance" do @terminus.indirection.should equal(@indirection) end - it "should require an associated indirection" do - @terminus_class.expects(:indirection).returns(nil) - proc { @terminus_class.new }.should raise_error(Puppet::DevError) + it "should set the instances's type to the abstract terminus type's name" do + @terminus.terminus_type.should == :abstract end end diff --git a/spec/unit/indirector/yaml.rb b/spec/unit/indirector/yaml.rb index 93af048f9..339a2acfb 100755 --- a/spec/unit/indirector/yaml.rb +++ b/spec/unit/indirector/yaml.rb @@ -6,9 +6,13 @@ require 'puppet/indirector/yaml' module YamlTesting def setup - @store_class = Class.new(Puppet::Indirector::Yaml) - @indirection = stub 'indirection', :name => :myyaml - @store_class.stubs(:indirection).returns(@indirection) + @indirection = stub 'indirection', :name => :myyaml, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:myyaml).returns(@indirection) + @store_class = Class.new(Puppet::Indirector::Yaml) do + def self.to_s + "MyYaml" + end + end @store = @store_class.new @subject = Object.new @@ -21,19 +25,6 @@ module YamlTesting end end -describe Puppet::Indirector::Yaml, " when initializing" do - before do - @store_class = Class.new(Puppet::Indirector::Yaml) - end - - # The superclass tests for the same thing, but it doesn't hurt to - # leave this in the spec. - it "should require an associated indirection" do - @store_class.expects(:indirection).returns(nil) - proc { @store_class.new }.should raise_error(Puppet::DevError) - end -end - describe Puppet::Indirector::Yaml, " when choosing file location" do include YamlTesting @@ -41,7 +32,7 @@ describe Puppet::Indirector::Yaml, " when choosing file location" do @store.send(:path, :me).should =~ %r{^#{@dir}} end - it "should use the indirection name for choosing the subdirectory" do + it "should use the terminus name for choosing the subdirectory" do @store.send(:path, :me).should =~ %r{^#{@dir}/myyaml} end diff --git a/spec/unit/indirector/yaml/facts.rb b/spec/unit/indirector/yaml/facts.rb new file mode 100755 index 000000000..f1256cfa4 --- /dev/null +++ b/spec/unit/indirector/yaml/facts.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/node/facts' +require 'puppet/indirector/yaml/facts' + +describe Puppet::Indirector::Yaml::Facts do + it "should be a subclass of the Yaml terminus" do + Puppet::Indirector::Yaml::Facts.superclass.should equal(Puppet::Indirector::Yaml) + end + + + it "should have documentation" do + Puppet::Indirector::Yaml::Facts.doc.should_not be_nil + end + + it "should be registered with the facts indirection" do + indirection = Puppet::Indirector::Indirection.instance(:facts) + Puppet::Indirector::Yaml::Facts.indirection.should equal(indirection) + end + + it "should have its name set to :facts" do + Puppet::Indirector::Yaml::Facts.name.should == :facts + end +end -- cgit From ebe7290bf0c9119e268c9037c33da515e527aa5b Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 00:16:39 -0500 Subject: All indirections are working, and they have all been migrated over to the new organization. Where we would have previously had an 'ldap' node terminus at puppet/indirector/node/ldap.rb, we would not have it at puppet/indirector/ldap/node.rb, and it would be a subclass of puppet/indirector/ldap.rb. These are called terminus classes, and there are now three categories of them: The base class itself, abstract classes that provide most of the functionality (e.g., the ldap and yaml classes), and the classes themselves that implement the functionality for a given model like Node or Facts. The base terminus class handles auto-loading any of these classes from disk. --- lib/puppet/defaults.rb | 2 +- lib/puppet/indirector.rb | 2 +- lib/puppet/indirector/exec.rb | 57 ++++++++ lib/puppet/indirector/exec/node.rb | 50 +++++++ lib/puppet/indirector/indirection.rb | 5 +- lib/puppet/indirector/ldap.rb | 90 ++++++++++++ lib/puppet/indirector/ldap/node.rb | 75 ++++++++++ lib/puppet/indirector/node/external.rb | 89 ------------ lib/puppet/indirector/node/ldap.rb | 140 ------------------- lib/puppet/indirector/node/none.rb | 14 -- lib/puppet/indirector/null.rb | 9 ++ lib/puppet/indirector/null/node.rb | 14 ++ lib/puppet/indirector/terminus.rb | 8 ++ spec/unit/indirector/exec.rb | 49 +++++++ spec/unit/indirector/exec/node.rb | 62 +++++++++ spec/unit/indirector/indirection.rb | 20 ++- spec/unit/indirector/ldap.rb | 147 ++++++++++++++++++++ spec/unit/indirector/ldap/node.rb | 84 ++++++++++++ spec/unit/indirector/node/external.rb | 119 ---------------- spec/unit/indirector/node/ldap.rb | 243 --------------------------------- spec/unit/indirector/node/none.rb | 32 ----- spec/unit/indirector/null.rb | 27 ++++ spec/unit/indirector/null/node.rb | 18 +++ spec/unit/indirector/terminus.rb | 14 ++ 24 files changed, 722 insertions(+), 648 deletions(-) create mode 100644 lib/puppet/indirector/exec.rb create mode 100644 lib/puppet/indirector/exec/node.rb create mode 100644 lib/puppet/indirector/ldap.rb create mode 100644 lib/puppet/indirector/ldap/node.rb delete mode 100644 lib/puppet/indirector/node/external.rb delete mode 100644 lib/puppet/indirector/node/ldap.rb delete mode 100644 lib/puppet/indirector/node/none.rb create mode 100644 lib/puppet/indirector/null.rb create mode 100644 lib/puppet/indirector/null/node.rb create mode 100755 spec/unit/indirector/exec.rb create mode 100755 spec/unit/indirector/exec/node.rb create mode 100755 spec/unit/indirector/ldap.rb create mode 100755 spec/unit/indirector/ldap/node.rb delete mode 100755 spec/unit/indirector/node/external.rb delete mode 100755 spec/unit/indirector/node/ldap.rb delete mode 100755 spec/unit/indirector/node/none.rb create mode 100755 spec/unit/indirector/null.rb create mode 100755 spec/unit/indirector/null/node.rb diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index fa119bb5a..fa06c7fdb 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -554,7 +554,7 @@ module Puppet setdefaults(:parser, :typecheck => [true, "Whether to validate types during parsing."], :paramcheck => [true, "Whether to validate parameters during parsing."], - :node_source => ["none", "Where to look for node configuration information. + :node_terminus => ["null", "Where to look for node configuration information. The default node source, ``none``, just returns a node with its facts filled in, which is required for normal functionality. See the `NodeSourceReference`:trac: for more information."] diff --git a/lib/puppet/indirector.rb b/lib/puppet/indirector.rb index b89017c9a..bd2487e33 100644 --- a/lib/puppet/indirector.rb +++ b/lib/puppet/indirector.rb @@ -23,7 +23,7 @@ module Puppet::Indirector # instantiate the actual Terminus for that type and this name (:ldap, w/ args :node) # & hook the instantiated Terminus into this class (Node: @indirection = terminus) - @indirection = Puppet::Indirector::Indirection.new(indirection) + @indirection = Puppet::Indirector::Indirection.new(self, indirection) end module ClassMethods diff --git a/lib/puppet/indirector/exec.rb b/lib/puppet/indirector/exec.rb new file mode 100644 index 000000000..7e4ac8d18 --- /dev/null +++ b/lib/puppet/indirector/exec.rb @@ -0,0 +1,57 @@ +require 'puppet/indirector/terminus' +require 'puppet/util' + +class Puppet::Indirector::Exec < Puppet::Indirector::Terminus + # Look for external node definitions. + def find(name) + # Run the command. + unless output = query(name) + return nil + end + + # Translate the output to ruby. + return output + end + + private + + # Proxy the execution, so it's easier to test. + def execute(command) + Puppet::Util.execute(command) + end + + # Call the external command and see if it returns our output. + def query(name) + external_command = command + + # Make sure it's an arry + unless external_command.is_a?(Array) + raise Puppet::DevError, "Exec commands must be an array" + end + + # Make sure it's fully qualified. + unless external_command[0][0] == File::SEPARATOR[0] + raise ArgumentError, "You must set the exec parameter to a fully qualified command" + end + + # Add our name to it. + external_command << name + begin + output = execute(external_command) + rescue Puppet::ExecutionFailure => detail + if $?.exitstatus == 1 + return nil + else + Puppet.err "Could not retrieve external node information for %s: %s" % [name, detail] + end + return nil + end + + if output =~ /\A\s*\Z/ # all whitespace + Puppet.debug "Empty response for %s from exec %s terminus" % [name, self.name] + return nil + else + return output + end + end +end diff --git a/lib/puppet/indirector/exec/node.rb b/lib/puppet/indirector/exec/node.rb new file mode 100644 index 000000000..033afe3f0 --- /dev/null +++ b/lib/puppet/indirector/exec/node.rb @@ -0,0 +1,50 @@ +require 'puppet/indirector/exec' + +class Puppet::Indirector::Exec::Node < Puppet::Indirector::Exec + desc "Call an external program to get node information." + include Puppet::Util + + def command + command = Puppet[:external_nodes] + unless command != "none" + raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node terminus" + end + command.split + end + + # Look for external node definitions. + def find(name) + output = super or return nil + + # Translate the output to ruby. + result = translate(name, output) + + return create_node(name, result) + end + + private + + # Turn our outputted objects into a Puppet::Node instance. + def create_node(name, result) + node = Puppet::Node.new(name) + set = false + [:parameters, :classes].each do |param| + if value = result[param] + node.send(param.to_s + "=", value) + set = true + end + end + + node.fact_merge + return node + end + + # Translate the yaml string into Ruby objects. + def translate(name, output) + begin + YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } + rescue => detail + raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] + end + end +end diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index 31b9d68a5..12b8cf328 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -13,7 +13,7 @@ class Puppet::Indirector::Indirection @@indirections.find { |i| i.name == name } end - attr_accessor :name, :termini + attr_accessor :name, :model # Clear our cached list of termini. # This is only used for testing. @@ -26,7 +26,8 @@ class Puppet::Indirector::Indirection @@indirections.delete(self) if @@indirections.include?(self) end - def initialize(name, options = {}) + def initialize(model, name, options = {}) + @model = model @name = name options.each do |name, value| begin diff --git a/lib/puppet/indirector/ldap.rb b/lib/puppet/indirector/ldap.rb new file mode 100644 index 000000000..63848d10a --- /dev/null +++ b/lib/puppet/indirector/ldap.rb @@ -0,0 +1,90 @@ +require 'puppet/indirector/terminus' + +class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus + # Perform our ldap search and process the result. + def find(name) + # We have to use 'yield' here because the LDAP::Entry objects + # get destroyed outside the scope of the search, strangely. + ldapsearch(name) { |entry| return process(entry) } + + # Return nil if we haven't found something. + return nil + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(entry) + raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name + end + + # Default to all attributes. + def search_attributes + nil + end + + def search_base + Puppet[:ldapbase] + end + + # The ldap search filter to use. + def search_filter(name) + raise Puppet::DevError, "No search string set for LDAP terminus for %s" % self.name + end + + # Find the ldap node, return the class list and parent node specially, + # and everything else in a parameter hash. + def ldapsearch(node) + raise ArgumentError.new("You must pass a block to ldapsearch") unless block_given? + + found = false + count = 0 + + begin + connection.search(search_base, 2, search_filter(node), search_attributes) do |entry| + found = true + yield entry + end + rescue => detail + if count == 0 + # Try reconnecting to ldap if we get an exception and we haven't yet retried. + count += 1 + @connection = nil + Puppet.warning "Retrying LDAP connection" + retry + else + raise Puppet::Error, "LDAP Search failed: %s" % detail + end + end + + return found + end + + private + + # Create an ldap connection. + def connection + unless defined? @connection and @connection + unless Puppet.features.ldap? + raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" + end + begin + if Puppet[:ldapssl] + @connection = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) + elsif Puppet[:ldaptls] + @connection = LDAP::SSLConn.new( + Puppet[:ldapserver], Puppet[:ldapport], true + ) + else + @connection = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) + end + @connection.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) + @connection.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) + @connection.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) + rescue => detail + raise Puppet::Error, "Could not connect to LDAP: %s" % detail + end + end + + return @connection + end +end diff --git a/lib/puppet/indirector/ldap/node.rb b/lib/puppet/indirector/ldap/node.rb new file mode 100644 index 000000000..187c74b94 --- /dev/null +++ b/lib/puppet/indirector/ldap/node.rb @@ -0,0 +1,75 @@ +require 'puppet/indirector/ldap' + +class Puppet::Indirector::Ldap::Node < Puppet::Indirector::Ldap + desc "Search in LDAP for node configuration information." + + # The attributes that Puppet class information is stored in. + def class_attributes + Puppet[:ldapclassattrs].split(/\s*,\s*/) + end + + # Look for our node in ldap. + def find(name) + unless ary = ldapsearch(name) + return nil + end + parent, classes, parameters = ary + + while parent + parent, tmpclasses, tmpparams = ldapsearch(parent) + classes += tmpclasses if tmpclasses + tmpparams.each do |param, value| + # Specifically test for whether it's set, so false values are handled + # correctly. + parameters[param] = value unless parameters.include?(param) + end + end + + node = Puppet::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) + node.fact_merge + return node + end + + # The parent attribute, if we have one. + def parent_attribute + if pattr = Puppet[:ldapparentattr] and ! pattr.empty? + pattr + else + nil + end + end + + # Process the found entry. We assume that we don't just want the + # ldap object. + def process(entry) + raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name + end + + # Default to all attributes. + def search_attributes + ldapattrs = Puppet[:ldapattrs] + + # results in everything getting returned + return nil if ldapattrs == "all" + + search_attrs = class_attributes + ldapattrs.split(/\s*,\s*/) + + if pattr = parent_attribute + search_attrs << pattr + end + + search_attrs + end + + # The ldap search filter to use. + def search_filter(name) + filter = Puppet[:ldapstring] + + if filter.include? "%s" + # Don't replace the string in-line, since that would hard-code our node + # info. + filter = filter.gsub('%s', name) + end + filter + end +end diff --git a/lib/puppet/indirector/node/external.rb b/lib/puppet/indirector/node/external.rb deleted file mode 100644 index 66aca6048..000000000 --- a/lib/puppet/indirector/node/external.rb +++ /dev/null @@ -1,89 +0,0 @@ -require 'puppet/node/facts' - -Puppet::Indirector.register_terminus :node, :external do - desc "Call an external program to get node information." - - include Puppet::Util - - # Proxy the execution, so it's easier to test. - def execute(command) - Puppet::Util.execute(command) - end - - # Look for external node definitions. - def find(name) - unless Puppet[:external_nodes] != "none" - raise ArgumentError, "You must set the 'external_nodes' parameter to use the external node source" - end - - unless Puppet[:external_nodes][0] == File::SEPARATOR[0] - raise ArgumentError, "You must set the 'external_nodes' parameter to a fully qualified command" - end - - # Run the command. - unless output = query(name) - return nil - end - - # Translate the output to ruby. - result = translate(name, output) - - return create_node(name, result) - end - - private - - # Turn our outputted objects into a Puppet::Node instance. - def create_node(name, result) - node = Puppet::Node.new(name) - set = false - [:parameters, :classes].each do |param| - if value = result[param] - node.send(param.to_s + "=", value) - set = true - end - end - - if set - node.fact_merge - return node - else - return nil - end - end - - # Call the external command and see if it returns our output. - def query(name) - # This is a very cheap way to do this, since it will break on - # commands that have spaces in the arguments. But it's good - # enough for most cases. - external_node_command = Puppet[:external_nodes].split - external_node_command << name - begin - output = execute(external_node_command) - rescue Puppet::ExecutionFailure => detail - if $?.exitstatus == 1 - return nil - else - Puppet.err "Could not retrieve external node information for %s: %s" % [name, detail] - end - return nil - end - - if output =~ /\A\s*\Z/ # all whitespace - Puppet.debug "Empty response for %s from external node source" % name - return nil - else - return output - end - end - - # Translate the yaml string into Ruby objects. - def translate(name, output) - begin - YAML.load(output).inject({}) { |hash, data| hash[symbolize(data[0])] = data[1]; hash } - rescue => detail - raise Puppet::Error, "Could not load external node results for %s: %s" % [name, detail] - end - end -end diff --git a/lib/puppet/indirector/node/ldap.rb b/lib/puppet/indirector/node/ldap.rb deleted file mode 100644 index 230ca2467..000000000 --- a/lib/puppet/indirector/node/ldap.rb +++ /dev/null @@ -1,140 +0,0 @@ -Puppet::Indirector.register_terminus :node, :ldap do - desc "Search in LDAP for node configuration information." - - # Look for our node in ldap. - def find(name) - unless ary = ldapsearch(name) - return nil - end - parent, classes, parameters = ary - - while parent - parent, tmpclasses, tmpparams = ldapsearch(parent) - classes += tmpclasses if tmpclasses - tmpparams.each do |param, value| - # Specifically test for whether it's set, so false values are handled - # correctly. - parameters[param] = value unless parameters.include?(param) - end - end - - node = Puppet::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) - node.fact_merge - return node - end - - # Find the ldap node, return the class list and parent node specially, - # and everything else in a parameter hash. - def ldapsearch(node) - filter = Puppet[:ldapstring] - classattrs = Puppet[:ldapclassattrs].split("\s*,\s*") - if Puppet[:ldapattrs] == "all" - # A nil value here causes all attributes to be returned. - search_attrs = nil - else - search_attrs = classattrs + Puppet[:ldapattrs].split("\s*,\s*") - end - pattr = nil - if pattr = Puppet[:ldapparentattr] - if pattr == "" - pattr = nil - else - search_attrs << pattr unless search_attrs.nil? - end - end - - if filter =~ /%s/ - filter = filter.gsub(/%s/, node) - end - - parent = nil - classes = [] - parameters = nil - - found = false - count = 0 - - begin - # We're always doing a sub here; oh well. - ldap.search(Puppet[:ldapbase], 2, filter, search_attrs) do |entry| - found = true - if pattr - if values = entry.vals(pattr) - if values.length > 1 - raise Puppet::Error, - "Node %s has more than one parent: %s" % - [node, values.inspect] - end - unless values.empty? - parent = values.shift - end - end - end - - classattrs.each { |attr| - if values = entry.vals(attr) - values.each do |v| classes << v end - end - } - - parameters = entry.to_hash.inject({}) do |hash, ary| - if ary[1].length == 1 - hash[ary[0]] = ary[1].shift - else - hash[ary[0]] = ary[1] - end - hash - end - end - rescue => detail - if count == 0 - # Try reconnecting to ldap - @ldap = nil - retry - else - raise Puppet::Error, "LDAP Search failed: %s" % detail - end - end - - classes.flatten! - - if classes.empty? - classes = nil - end - - if parent or classes or parameters - return parent, classes, parameters - else - return nil - end - end - - private - - # Create an ldap connection. - def ldap - unless defined? @ldap and @ldap - unless Puppet.features.ldap? - raise Puppet::Error, "Could not set up LDAP Connection: Missing ruby/ldap libraries" - end - begin - if Puppet[:ldapssl] - @ldap = LDAP::SSLConn.new(Puppet[:ldapserver], Puppet[:ldapport]) - elsif Puppet[:ldaptls] - @ldap = LDAP::SSLConn.new( - Puppet[:ldapserver], Puppet[:ldapport], true - ) - else - @ldap = LDAP::Conn.new(Puppet[:ldapserver], Puppet[:ldapport]) - end - @ldap.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3) - @ldap.set_option(LDAP::LDAP_OPT_REFERRALS, LDAP::LDAP_OPT_ON) - @ldap.simple_bind(Puppet[:ldapuser], Puppet[:ldappassword]) - rescue => detail - raise Puppet::Error, "Could not connect to LDAP: %s" % detail - end - end - - return @ldap - end -end diff --git a/lib/puppet/indirector/node/none.rb b/lib/puppet/indirector/node/none.rb deleted file mode 100644 index 034c0b349..000000000 --- a/lib/puppet/indirector/node/none.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'puppet/node/facts' - -Puppet::Indirector.register_terminus :node, :none do - desc "Always return an empty node object. This is the node source you should - use when you don't have some other, functional source you want to use, - as the compiler will not work without this node information." - - # Just return an empty node. - def find(name) - node = Puppet::Node.new(name) - node.fact_merge - node - end -end diff --git a/lib/puppet/indirector/null.rb b/lib/puppet/indirector/null.rb new file mode 100644 index 000000000..db2b1db1c --- /dev/null +++ b/lib/puppet/indirector/null.rb @@ -0,0 +1,9 @@ +require 'puppet/indirector/terminus' + +# An empty terminus type, meant to just return empty objects. +class Puppet::Indirector::Null < Puppet::Indirector::Terminus + # Just return nothing. + def find(name) + indirection.model.new(name) + end +end diff --git a/lib/puppet/indirector/null/node.rb b/lib/puppet/indirector/null/node.rb new file mode 100644 index 000000000..eb08f5697 --- /dev/null +++ b/lib/puppet/indirector/null/node.rb @@ -0,0 +1,14 @@ +require 'puppet/indirector/null' + +class Puppet::Indirector::Null::Node < Puppet::Indirector::Null + desc "Always return an empty node object. This is the node source you should + use when you don't have some other, functional source you want to use, + as the compiler will not work without this node information." + + # Just return an empty node. + def find(name) + node = super + node.fact_merge + node + end +end diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb index f3797f471..bcff08d79 100644 --- a/lib/puppet/indirector/terminus.rb +++ b/lib/puppet/indirector/terminus.rb @@ -68,6 +68,10 @@ class Puppet::Indirector::Terminus @abstract_terminus = true end + def model + indirection.model + end + # Register a class, probably autoloaded. def register_terminus_class(klass) setup_instance_loading klass.terminus_type @@ -102,6 +106,10 @@ class Puppet::Indirector::Terminus def name self.class.name end + + def model + self.class.model + end def indirection self.class.indirection diff --git a/spec/unit/indirector/exec.rb b/spec/unit/indirector/exec.rb new file mode 100755 index 000000000..42fbe0955 --- /dev/null +++ b/spec/unit/indirector/exec.rb @@ -0,0 +1,49 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/exec' + +describe Puppet::Indirector::Exec do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.expects(:instance).with(:testing).returns(@indirection) + @exec_class = Class.new(Puppet::Indirector::Exec) do + def self.to_s + "Testing" + end + + attr_accessor :command + end + + @searcher = @exec_class.new + @searcher.command = ["/echo"] + end + + it "should throw an exception if the command is not an array" do + @searcher.command = "/usr/bin/echo" + proc { @searcher.find("foo") }.should raise_error(Puppet::DevError) + end + + it "should throw an exception if the command is not fully qualified" do + @searcher.command = ["mycommand"] + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end + + it "should execute the command with the object name as the only argument" do + @searcher.expects(:execute).with(%w{/echo yay}) + @searcher.find("yay") + end + + it "should return the output of the script" do + @searcher.expects(:execute).with(%w{/echo yay}).returns("whatever") + @searcher.find("yay").should == "whatever" + end + + it "should return nil when the command produces no output" do + @searcher.expects(:execute).with(%w{/echo yay}).returns(nil) + @searcher.find("yay").should be_nil + end + + it "should be able to execute commands with multiple arguments" +end diff --git a/spec/unit/indirector/exec/node.rb b/spec/unit/indirector/exec/node.rb new file mode 100755 index 000000000..2eaf3c12e --- /dev/null +++ b/spec/unit/indirector/exec/node.rb @@ -0,0 +1,62 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/exec/node' + +describe Puppet::Indirector::Exec::Node, " when constructing the command to run" do + before do + @indirection = mock 'indirection' + Puppet.config.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + end + + it "should use the external_node script as the command" do + Puppet.expects(:[]).with(:external_nodes).returns("/bin/echo") + @searcher.command.should == %w{/bin/echo} + end + + it "should throw an exception if no external node command is set" do + Puppet.expects(:[]).with(:external_nodes).returns("none") + proc { @searcher.find("foo") }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Exec::Node, " when handling the results of the command" do + before do + @indirection = mock 'indirection' + Puppet.config.stubs(:value).with(:external_nodes).returns("/echo") + @searcher = Puppet::Indirector::Exec::Node.new + @node = stub 'node', :fact_merge => nil + @name = "yay" + Puppet::Node.expects(:new).with(@name).returns(@node) + @result = {} + # Use a local variable so the reference is usable in the execute() definition. + result = @result + @searcher.meta_def(:execute) do |command| + return YAML.dump(result) + end + end + + it "should translate the YAML into a Node instance" do + # Use an empty hash + @searcher.find(@name).should equal(@node) + end + + it "should set the resulting parameters as the node parameters" do + @result[:parameters] = {"a" => "b", "c" => "d"} + @node.expects(:parameters=).with "a" => "b", "c" => "d" + @searcher.find(@name) + end + + it "should set the resulting classes as the node classes" do + @result[:classes] = %w{one two} + @node.expects(:classes=).with %w{one two} + @searcher.find(@name) + end + + it "should merge the node's facts with its parameters" do + @node.expects(:fact_merge) + @searcher.find(@name) + end +end diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index 5866f2d5f..e57f12072 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -5,13 +5,19 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/indirector' describe Puppet::Indirector::Indirection, " when initializing" do + it "should keep a reference to the indirecting model" do + model = mock 'model' + @indirection = Puppet::Indirector::Indirection.new(model, :myind) + @indirection.model.should equal(model) + end + it "should set the name" do - @indirection = Puppet::Indirector::Indirection.new(:myind) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :myind) @indirection.name.should == :myind end it "should require indirections to have unique names" do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) proc { Puppet::Indirector::Indirection.new(:test) }.should raise_error(ArgumentError) end @@ -22,7 +28,7 @@ end describe Puppet::Indirector::Indirection, " when managing indirection instances" do it "should allow an indirection to be retrieved by name" do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) Puppet::Indirector::Indirection.instance(:test).should equal(@indirection) end @@ -37,7 +43,7 @@ end describe Puppet::Indirector::Indirection, " when choosing terminus types" do before do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = stub 'terminus class', :new => @terminus end @@ -82,7 +88,7 @@ end describe Puppet::Indirector::Indirection, " when managing terminus instances" do before do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @terminus_class = mock 'terminus class' Puppet::Indirector::Terminus.stubs(:terminus_class).with(:test, :foo).returns(@terminus_class) @@ -111,7 +117,7 @@ describe Puppet::Indirector::Indirection, " when managing terminus instances" do it "should not create a terminus instance until one is actually needed" do Puppet::Indirector.expects(:terminus).never - indirection = Puppet::Indirector::Indirection.new(:lazytest) + indirection = Puppet::Indirector::Indirection.new(mock('model'), :lazytest) end after do @@ -122,7 +128,7 @@ end describe Puppet::Indirector::Indirection do before do - @indirection = Puppet::Indirector::Indirection.new(:test) + @indirection = Puppet::Indirector::Indirection.new(mock('model'), :test) @terminus = mock 'terminus' @indirection.stubs(:terminus).returns(@terminus) end diff --git a/spec/unit/indirector/ldap.rb b/spec/unit/indirector/ldap.rb new file mode 100755 index 000000000..a936936bc --- /dev/null +++ b/spec/unit/indirector/ldap.rb @@ -0,0 +1,147 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/indirector/ldap' + +describe Puppet::Indirector::Ldap, " when searching ldap" do + before do + @indirection = mock 'indirection' + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + @ldap_class = Class.new(Puppet::Indirector::Ldap) do + def self.to_s + "Testing" + end + end + + @connection = mock 'ldap' + + @searcher = @ldap_class.new + + # Stub everything, and we can selectively replace with an expect as + # we need to for testing. + @searcher.stubs(:connection).returns(@connection) + @searcher.stubs(:search_filter).returns(:filter) + @searcher.stubs(:search_base).returns(:base) + @searcher.stubs(:process) + end + + it "should call the ldapsearch method with the name being searched for" do + @searcher.expects(:ldapsearch).with("yay") + @searcher.find "yay" + end + + it "should fail if no block is passed to the ldapsearch method" do + proc { @searcher.ldapsearch("blah") }.should raise_error(ArgumentError) + end + + it "should use the results of the ldapbase method as the ldap search base" do + @searcher.stubs(:search_base).returns("mybase") + @connection.expects(:search).with do |*args| + args[0].should == "mybase" + true + end + @searcher.find "yay" + end + + it "should default to the value of the :search_base setting as the result of the ldapbase method" do + Puppet.expects(:[]).with(:ldapbase).returns("myldapbase") + searcher = @ldap_class.new + searcher.search_base.should == "myldapbase" + end + + it "should use the results of the :search_attributes method as the list of attributes to return" do + @searcher.stubs(:search_attributes).returns(:myattrs) + @connection.expects(:search).with do |*args| + args[3].should == :myattrs + true + end + @searcher.find "yay" + end + + it "should use the results of the :search_filter method as the search filter" do + @searcher.stubs(:search_filter).with("yay").returns("yay's filter") + @connection.expects(:search).with do |*args| + args[2].should == "yay's filter" + true + end + @searcher.find "yay" + end + + it "should use depth 2 when searching" do + @connection.expects(:search).with do |*args| + args[1].should == 2 + true + end + @searcher.find "yay" + end + + it "should call process() on the first found entry" do + @connection.expects(:search).yields("myresult") + @searcher.expects(:process).with("myresult") + @searcher.find "yay" + end + + it "should reconnect and retry the search if there is a failure" do + run = false + @connection.stubs(:search).with do |*args| + if run + true + else + run = true + raise "failed" + end + end.yields("myresult") + @searcher.expects(:process).with("myresult") + + @searcher.find "yay" + end + + it "should not reconnect on failure more than once" do + count = 0 + @connection.stubs(:search).with do |*args| + count += 1 + raise ArgumentError, "yay" + end + proc { @searcher.find("whatever") }.should raise_error(Puppet::Error) + count.should == 2 + end + + it "should return true if an entry is found" do + @connection.expects(:search).yields("result") + @searcher.ldapsearch("whatever") { |r| }.should be_true + end +end + +describe Puppet::Indirector::Ldap, " when connecting to ldap" do + confine "LDAP is not available" => Puppet.features.ldap? + confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" + + it "should only create the ldap connection when asked for it the first time" + + it "should throw an exception if it cannot connect to LDAP" + + it "should use SSL when the :ldapssl setting is true" + + it "should connect to the server specified in the :ldapserver setting" + + it "should use the port specified in the :ldapport setting" + + it "should use protocol version 3" + + it "should follow referrals" + + it "should use the user specified in the :ldapuser setting" + + it "should use the password specified in the :ldappassord setting" + + it "should have an ldap method that returns an LDAP connection object" + + it "should fail when LDAP support is missing" +end + +describe Puppet::Indirector::Ldap, " when reconnecting to ldap" do + confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") + + it "should reconnect to ldap when connections are lost" +end diff --git a/spec/unit/indirector/ldap/node.rb b/spec/unit/indirector/ldap/node.rb new file mode 100755 index 000000000..edaf77e92 --- /dev/null +++ b/spec/unit/indirector/ldap/node.rb @@ -0,0 +1,84 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/ldap/node' + +describe Puppet::Indirector::Ldap::Node, " when searching for nodes" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should return the value of the :ldapclassattrs split on commas as the class attributes" do + Puppet.stubs(:[]).with(:ldapclassattrs).returns("one,two") + @searcher.class_attributes.should == %w{one two} + end + + it "should return nil as the parent attribute if the :ldapparentattr is set to an empty string" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("") + @searcher.parent_attribute.should be_nil + end + + it "should return the value of the :ldapparentattr as the parent attribute" do + Puppet.stubs(:[]).with(:ldapparentattr).returns("pere") + @searcher.parent_attribute.should == "pere" + end + + it "should use the value of the :ldapstring as the search filter" do + Puppet.stubs(:[]).with(:ldapstring).returns("mystring") + @searcher.search_filter("testing").should == "mystring" + end + + it "should replace '%s' with the node name in the search filter if it is present" do + Puppet.stubs(:[]).with(:ldapstring).returns("my%sstring") + @searcher.search_filter("testing").should == "mytestingstring" + end + + it "should not modify the global :ldapstring when replacing '%s' in the search filter" do + filter = mock 'filter' + filter.expects(:include?).with("%s").returns(true) + filter.expects(:gsub).with("%s", "testing").returns("mynewstring") + Puppet.stubs(:[]).with(:ldapstring).returns(filter) + @searcher.search_filter("testing").should == "mynewstring" + end +end + +describe Puppet::Indirector::Ldap::Node, " when deciding attributes to search for" do + before do + @searcher = Puppet::Indirector::Ldap::Node.new + end + + it "should use 'nil' if the :ldapattrs setting is 'all'" do + Puppet.stubs(:[]).with(:ldapattrs).returns("all") + @searcher.search_attributes.should be_nil + end + + it "should split the value of :ldapattrs on commas and use the result as the attribute list" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end + + it "should add the class attributes to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns(%w{three four}) + @searcher.stubs(:parent_attribute).returns(nil) + # Sort them so i don't have to care about return order + @searcher.search_attributes.sort.should == %w{one two three four}.sort + end + + it "should add the parent attribute to the search attributes if not returning all attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns("parent") + @searcher.search_attributes.sort.should == %w{one two parent}.sort + end + + it "should not add nil parent attributes to the search attributes" do + Puppet.stubs(:[]).with(:ldapattrs).returns("one,two") + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.search_attributes.should == %w{one two} + end +end diff --git a/spec/unit/indirector/node/external.rb b/spec/unit/indirector/node/external.rb deleted file mode 100755 index c64a6f6e2..000000000 --- a/spec/unit/indirector/node/external.rb +++ /dev/null @@ -1,119 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'yaml' -require 'puppet/indirector' - -describe Puppet::Indirector.terminus(:node, :external), " when searching for nodes" do - require 'puppet/node' - - before do - Puppet.config[:external_nodes] = "/yay/ness" - @searcher = Puppet::Indirector.terminus(:node, :external).new - - # Set the searcher up so that we do not need to actually call the - # external script. - @searcher.meta_def(:execute) do |command| - name = command.last.chomp - result = {} - - if name =~ /a/ - result[:parameters] = {'one' => command.last + '1', 'two' => command.last + '2'} - end - - if name =~ /p/ - result['classes'] = [1,2,3].collect { |n| command.last + n.to_s } - end - - return YAML.dump(result) - end - end - - it "should throw an exception if the node_source is external but no external node command is set" do - Puppet[:external_nodes] = "none" - proc { @searcher.find("foo") }.should raise_error(ArgumentError) - end - - it "should throw an exception if the external node source is not fully qualified" do - Puppet[:external_nodes] = "mycommand" - proc { @searcher.find("foo") }.should raise_error(ArgumentError) - end - - it "should execute the command with the node name as the only argument" do - command = [Puppet[:external_nodes], "yay"] - @searcher.expects(:execute).with(command).returns("") - @searcher.find("yay") - end - - it "should return a node object" do - @searcher.find("apple").should be_instance_of(Puppet::Node) - end - - it "should set the node's name" do - @searcher.find("apple").name.should == "apple" - end - - # If we use a name that has a 'p' but no 'a', then our test generator - # will return classes but no parameters. - it "should be able to configure a node's classes" do - node = @searcher.find("plum") - node.classes.should == %w{plum1 plum2 plum3} - node.parameters.should == {} - end - - # If we use a name that has an 'a' but no 'p', then our test generator - # will return parameters but no classes. - it "should be able to configure a node's parameters" do - node = @searcher.find("guava") - node.classes.should == [] - node.parameters.should == {"one" => "guava1", "two" => "guava2"} - end - - it "should be able to configure a node's classes and parameters" do - node = @searcher.find("apple") - node.classes.should == %w{apple1 apple2 apple3} - node.parameters.should == {"one" => "apple1", "two" => "apple2"} - end - - it "should merge node facts with returned parameters" do - facts = Puppet::Node::Facts.new("apple", "three" => "four") - Puppet::Node::Facts.expects(:find).with("apple").returns(facts) - node = @searcher.find("apple") - node.parameters["three"].should == "four" - end - - it "should return nil when it cannot find the node" do - @searcher.find("honeydew").should be_nil - end - - # Make sure a nodesearch with arguments works - def test_nodesearch_external_arguments - mapper = mk_node_mapper - Puppet[:external_nodes] = "#{mapper} -s something -p somethingelse" - searcher = mk_searcher(:external) - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create node") - end - - # A wrapper test, to make sure we're correctly calling the external search method. - def test_nodesearch_external_functional - mapper = mk_node_mapper - searcher = mk_searcher(:external) - - Puppet[:external_nodes] = mapper - - node = nil - assert_nothing_raised do - node = searcher.nodesearch("apple") - end - assert_instance_of(SimpleNode, node, "did not create node") - end - - after do - Puppet.config.clear - end -end diff --git a/spec/unit/indirector/node/ldap.rb b/spec/unit/indirector/node/ldap.rb deleted file mode 100755 index e4b0cd7d4..000000000 --- a/spec/unit/indirector/node/ldap.rb +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' - -require 'yaml' -require 'puppet/indirector' - -describe Puppet::Indirector.terminus(:node, :ldap), " when searching for nodes" do - require 'puppet/node' - - def setup - Puppet.config[:external_nodes] = "/yay/ness" - @searcher = Puppet::Indirector.terminus(:node, :ldap).new - nodetable = {} - @nodetable = nodetable - # Override the ldapsearch definition, so we don't have to actually set it up. - @searcher.meta_def(:ldapsearch) do |name| - nodetable[name] - end - end - - it "should return nil for hosts that cannot be found" do - @searcher.find("foo").should be_nil - end - - it "should return Puppet::Node instances" do - @nodetable["foo"] = [nil, %w{}, {}] - @searcher.find("foo").should be_instance_of(Puppet::Node) - end - - it "should set the node name" do - @nodetable["foo"] = [nil, %w{}, {}] - @searcher.find("foo").name.should == "foo" - end - - it "should set the classes" do - @nodetable["foo"] = [nil, %w{one two}, {}] - @searcher.find("foo").classes.should == %w{one two} - end - - it "should set the parameters" do - @nodetable["foo"] = [nil, %w{}, {"one" => "two"}] - @searcher.find("foo").parameters.should == {"one" => "two"} - end - - it "should set classes and parameters from the parent node" do - @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] - @nodetable["middle"] = [nil, %w{three four}, {"three" => "four"}] - node = @searcher.find("foo") - node.classes.sort.should == %w{one two three four}.sort - node.parameters.should == {"one" => "two", "three" => "four"} - end - - it "should prefer child parameters to parent parameters" do - @nodetable["foo"] = ["middle", %w{}, {"one" => "two"}] - @nodetable["middle"] = [nil, %w{}, {"one" => "four"}] - @searcher.find("foo").parameters["one"].should == "two" - end - - it "should recurse indefinitely through parent relationships" do - @nodetable["foo"] = ["middle", %w{one two}, {"one" => "two"}] - @nodetable["middle"] = ["top", %w{three four}, {"three" => "four"}] - @nodetable["top"] = [nil, %w{five six}, {"five" => "six"}] - node = @searcher.find("foo") - node.parameters.should == {"one" => "two", "three" => "four", "five" => "six"} - node.classes.sort.should == %w{one two three four five six}.sort - end - - # This can stay in the main test suite because it doesn't actually use ldapsearch, - # it just overrides the method so it behaves as though it were hitting ldap. - def test_ldap_nodesearch - - # Make sure we get nothing for nonexistent hosts - node = nil - assert_nothing_raised do - node = searcher.nodesearch("nosuchhost") - end - - assert_nil(node, "Got a node for a non-existent host") - - # Now add a base node with some classes and parameters - nodetable["base"] = [nil, %w{one two}, {"base" => "true"}] - - assert_nothing_raised do - node = searcher.nodesearch("base") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("base", node.name, "node name was not set") - - assert_equal(%w{one two}, node.classes, "node classes were not set") - assert_equal({"base" => "true"}, node.parameters, "node parameters were not set") - - # Now use a different with this as the base - nodetable["middle"] = ["base", %w{three}, {"center" => "boo"}] - assert_nothing_raised do - node = searcher.nodesearch("middle") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("middle", node.name, "node name was not set") - - assert_equal(%w{one two three}.sort, node.classes.sort, "node classes were not set correctly with a parent node") - assert_equal({"base" => "true", "center" => "boo"}, node.parameters, "node parameters were not set correctly with a parent node") - - # And one further, to make sure we fully recurse - nodetable["top"] = ["middle", %w{four five}, {"master" => "far"}] - assert_nothing_raised do - node = searcher.nodesearch("top") - end - - assert_instance_of(SimpleNode, node, "Did not get node from ldap nodesearch") - assert_equal("top", node.name, "node name was not set") - - assert_equal(%w{one two three four five}.sort, node.classes.sort, "node classes were not set correctly with the top node") - assert_equal({"base" => "true", "center" => "boo", "master" => "far"}, node.parameters, "node parameters were not set correctly with the top node") - end -end - -describe Puppet::Indirector.terminus(:node, :ldap), " when interacting with ldap" do - confine "LDAP is not available" => Puppet.features.ldap? - confine "No LDAP test data for networks other than Luke's" => Facter.value(:domain) == "madstop.com" - - def ldapconnect - - @ldap = LDAP::Conn.new("ldap", 389) - @ldap.set_option( LDAP::LDAP_OPT_PROTOCOL_VERSION, 3 ) - @ldap.simple_bind("", "") - - return @ldap - end - - def ldaphost(name) - node = Puppet::Node.new(name) - parent = nil - found = false - @ldap.search( "ou=hosts, dc=madstop, dc=com", 2, - "(&(objectclass=puppetclient)(cn=%s))" % name - ) do |entry| - node.classes = entry.vals("puppetclass") || [] - node.parameters = entry.to_hash.inject({}) do |hash, ary| - if ary[1].length == 1 - hash[ary[0]] = ary[1].shift - else - hash[ary[0]] = ary[1] - end - hash - end - parent = node.parameters["parentnode"] - found = true - end - raise "Could not find node %s" % name unless found - - return node, parent - end - - it "should have tests" do - raise ArgumentError - end - - def test_ldapsearch - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - - ldapconnect() - - # Make sure we get nil and nil back when we search for something missing - parent, classes, parameters = nil - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("nosuchhost") - end - - assert_nil(parent, "Got a parent for a non-existent host") - assert_nil(classes, "Got classes for a non-existent host") - - # Make sure we can find 'culain' in ldap - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("culain") - end - - node, realparent = ldaphost("culain") - assert_equal(realparent, parent, "did not get correct parent node from ldap") - assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") - assert_equal(node.parameters, parameters, "did not get correct ldap parameters from ldap") - - # Now compare when we specify the attributes to get. - Puppet[:ldapattrs] = "cn" - assert_nothing_raised do - parent, classes, parameters = searcher.ldapsearch("culain") - end - assert_equal(realparent, parent, "did not get correct parent node from ldap") - assert_equal(node.classes, classes, "did not get correct ldap classes from ldap") - - list = %w{cn puppetclass parentnode dn} - should = node.parameters.inject({}) { |h, a| h[a[0]] = a[1] if list.include?(a[0]); h } - assert_equal(should, parameters, "did not get correct ldap parameters from ldap") - end -end - -describe Puppet::Indirector.terminus(:node, :ldap), " when connecting to ldap" do - confine "Not running on culain as root" => (Puppet::Util::SUIDManager.uid == 0 and Facter.value("hostname") == "culain") - - it "should have tests" do - raise ArgumentError - end - - def test_ldapreconnect - Puppet[:ldapbase] = "ou=hosts, dc=madstop, dc=com" - Puppet[:ldapnodes] = true - - searcher = Object.new - searcher.extend(Node.node_source(:ldap)) - hostname = "culain.madstop.com" - - # look for our host - assert_nothing_raised { - parent, classes = searcher.nodesearch(hostname) - } - - # Now restart ldap - system("/etc/init.d/slapd restart 2>/dev/null >/dev/null") - sleep(1) - - # and look again - assert_nothing_raised { - parent, classes = searcher.nodesearch(hostname) - } - - # Now stop ldap - system("/etc/init.d/slapd stop 2>/dev/null >/dev/null") - cleanup do - system("/etc/init.d/slapd start 2>/dev/null >/dev/null") - end - - # And make sure we actually fail here - assert_raise(Puppet::Error) { - parent, classes = searcher.nodesearch(hostname) - } - end -end diff --git a/spec/unit/indirector/node/none.rb b/spec/unit/indirector/node/none.rb deleted file mode 100755 index 2329cdfbb..000000000 --- a/spec/unit/indirector/node/none.rb +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../../spec_helper' -require 'puppet/indirector' -require 'puppet/node/facts' - -describe Puppet::Indirector.terminus(:node, :none), " when searching for nodes" do - before do - Puppet.config[:node_source] = "none" - @searcher = Puppet::Indirector.terminus(:node, :none).new - end - - it "should create a node instance" do - @searcher.find("yay").should be_instance_of(Puppet::Node) - end - - it "should create a new node with the correct name" do - @searcher.find("yay").name.should == "yay" - end - - it "should merge the node's facts" do - facts = Puppet::Node::Facts.new("yay", "one" => "two", "three" => "four") - Puppet::Node::Facts.expects(:find).with("yay").returns(facts) - node = @searcher.find("yay") - node.parameters["one"].should == "two" - node.parameters["three"].should == "four" - end - - after do - Puppet.config.clear - end -end diff --git a/spec/unit/indirector/null.rb b/spec/unit/indirector/null.rb new file mode 100755 index 000000000..9e1dcb07c --- /dev/null +++ b/spec/unit/indirector/null.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/null' + +describe Puppet::Indirector::Null do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @null_class = Class.new(Puppet::Indirector::Null) do + def self.to_s + "Testing" + end + end + + @searcher = @null_class.new + end + + it "should return return an instance of the indirected model" do + object = mock 'object' + @model.expects(:new).with("yay").returns object + @searcher.find("yay").should equal(object) + end +end diff --git a/spec/unit/indirector/null/node.rb b/spec/unit/indirector/null/node.rb new file mode 100755 index 000000000..c589e5820 --- /dev/null +++ b/spec/unit/indirector/null/node.rb @@ -0,0 +1,18 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/null/node' + +describe Puppet::Indirector::Null::Node do + before do + @searcher = Puppet::Indirector::Null::Node.new + end + + it "should call node_merge() on the returned node" do + node = mock 'node' + Puppet::Node.expects(:new).with("mynode").returns(node) + node.expects(:fact_merge) + @searcher.find("mynode") + end +end diff --git a/spec/unit/indirector/terminus.rb b/spec/unit/indirector/terminus.rb index 08e7e6ccb..dc86cf315 100755 --- a/spec/unit/indirector/terminus.rb +++ b/spec/unit/indirector/terminus.rb @@ -36,6 +36,10 @@ describe Puppet::Indirector::Terminus do @terminus.should respond_to(:terminus_type) end + it "should support a class-level model attribute" do + @terminus.should respond_to(:model) + end + it "should accept indirection instances as its indirection" do indirection = stub 'indirection', :is_a? => true, :register_terminus_type => nil proc { @terminus.indirection = indirection }.should_not raise_error @@ -177,6 +181,11 @@ describe Puppet::Indirector::Terminus, " when creating terminus classes" do it "should set the subclass's name to the indirection name" do @terminus.name.should == :myindirection end + + it "should set the subclass's model to the indirection model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end end describe Puppet::Indirector::Terminus, " when a terminus instance" do @@ -209,4 +218,9 @@ describe Puppet::Indirector::Terminus, " when a terminus instance" do it "should set the instances's type to the abstract terminus type's name" do @terminus.terminus_type.should == :abstract end + + it "should set the instances's model to the indirection's model" do + @indirection.expects(:model).returns :yay + @terminus.model.should == :yay + end end -- cgit From a66699596452f88d2bc467af4cff3f9a1aec3d1e Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 01:24:04 -0500 Subject: Adding the last tests for the ldap node terminus. I managed to forget the tests around the main find() method. --- lib/puppet/indirector/ldap.rb | 4 +- lib/puppet/indirector/ldap/node.rb | 62 ++++++++++++--- spec/unit/indirector/ldap.rb | 4 +- spec/unit/indirector/ldap/node.rb | 156 +++++++++++++++++++++++++++++++++++++ 4 files changed, 211 insertions(+), 15 deletions(-) diff --git a/lib/puppet/indirector/ldap.rb b/lib/puppet/indirector/ldap.rb index 63848d10a..fb883def6 100644 --- a/lib/puppet/indirector/ldap.rb +++ b/lib/puppet/indirector/ldap.rb @@ -5,7 +5,7 @@ class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus def find(name) # We have to use 'yield' here because the LDAP::Entry objects # get destroyed outside the scope of the search, strangely. - ldapsearch(name) { |entry| return process(entry) } + ldapsearch(name) { |entry| return process(name, entry) } # Return nil if we haven't found something. return nil @@ -13,7 +13,7 @@ class Puppet::Indirector::Ldap < Puppet::Indirector::Terminus # Process the found entry. We assume that we don't just want the # ldap object. - def process(entry) + def process(name, entry) raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name end diff --git a/lib/puppet/indirector/ldap/node.rb b/lib/puppet/indirector/ldap/node.rb index 187c74b94..6f35b575c 100644 --- a/lib/puppet/indirector/ldap/node.rb +++ b/lib/puppet/indirector/ldap/node.rb @@ -10,23 +10,34 @@ class Puppet::Indirector::Ldap::Node < Puppet::Indirector::Ldap # Look for our node in ldap. def find(name) - unless ary = ldapsearch(name) - return nil - end - parent, classes, parameters = ary + return nil unless information = super + node = Puppet::Node.new(name) + parent_info = nil + parent = information[:parent] + parents = [name] while parent - parent, tmpclasses, tmpparams = ldapsearch(parent) - classes += tmpclasses if tmpclasses - tmpparams.each do |param, value| + if parents.include?(parent) + raise ArgumentError, "Found loop in LDAP node parents; %s appears twice" % parent + end + parents << parent + ldapsearch(parent) do |entry| + parent_info = process(parent, entry) + end + information[:classes] += parent_info[:classes] + parent_info[:parameters].each do |param, value| # Specifically test for whether it's set, so false values are handled # correctly. - parameters[param] = value unless parameters.include?(param) + information[:parameters][param] = value unless information[:parameters].include?(param) end + + parent = parent_info[:parent] end - node = Puppet::Node.new(name, :classes => classes, :source => "ldap", :parameters => parameters) + node.classes = information[:classes].uniq unless information[:classes].empty? + node.parameters = information[:parameters] unless information[:parameters].empty? node.fact_merge + return node end @@ -41,8 +52,37 @@ class Puppet::Indirector::Ldap::Node < Puppet::Indirector::Ldap # Process the found entry. We assume that we don't just want the # ldap object. - def process(entry) - raise Puppet::DevError, "The 'process' method has not been overridden for the LDAP terminus for %s" % self.name + def process(name, entry) + result = {} + if pattr = parent_attribute + if values = entry.vals(pattr) + if values.length > 1 + raise Puppet::Error, + "Node %s has more than one parent: %s" % [name, values.inspect] + end + unless values.empty? + result[:parent] = values.shift + end + end + end + + result[:classes] = [] + class_attributes.each { |attr| + if values = entry.vals(attr) + values.each do |v| result[:classes] << v end + end + } + + result[:parameters] = entry.to_hash.inject({}) do |hash, ary| + if ary[1].length == 1 + hash[ary[0]] = ary[1].shift + else + hash[ary[0]] = ary[1] + end + hash + end + + return result end # Default to all attributes. diff --git a/spec/unit/indirector/ldap.rb b/spec/unit/indirector/ldap.rb index a936936bc..fe9408986 100755 --- a/spec/unit/indirector/ldap.rb +++ b/spec/unit/indirector/ldap.rb @@ -78,7 +78,7 @@ describe Puppet::Indirector::Ldap, " when searching ldap" do it "should call process() on the first found entry" do @connection.expects(:search).yields("myresult") - @searcher.expects(:process).with("myresult") + @searcher.expects(:process).with("yay", "myresult") @searcher.find "yay" end @@ -92,7 +92,7 @@ describe Puppet::Indirector::Ldap, " when searching ldap" do raise "failed" end end.yields("myresult") - @searcher.expects(:process).with("myresult") + @searcher.expects(:process).with("yay", "myresult") @searcher.find "yay" end diff --git a/spec/unit/indirector/ldap/node.rb b/spec/unit/indirector/ldap/node.rb index edaf77e92..37953db19 100755 --- a/spec/unit/indirector/ldap/node.rb +++ b/spec/unit/indirector/ldap/node.rb @@ -4,7 +4,163 @@ require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/indirector/ldap/node' +module LdapNodeSearching + def setup + @searcher = Puppet::Indirector::Ldap::Node.new + @entries = {} + entries = @entries + + @connection = mock 'connection' + @entry = mock 'entry' + @connection.stubs(:search).yields(@entry) + @searcher.stubs(:connection).returns(@connection) + @searcher.stubs(:class_attributes).returns([]) + @searcher.stubs(:parent_attribute).returns(nil) + @searcher.stubs(:search_base).returns(:yay) + @searcher.stubs(:search_filter).returns(:filter) + + @node = mock 'node' + @node.stubs(:fact_merge) + @name = "mynode" + Puppet::Node.stubs(:new).with(@name).returns(@node) + end +end + describe Puppet::Indirector::Ldap::Node, " when searching for nodes" do + include LdapNodeSearching + + it "should return nil if no results are found in ldap" do + @connection.stubs(:search) + @searcher.find("mynode").should be_nil + end + + it "should return a node object if results are found in ldap" do + @entry.stubs(:to_hash).returns({}) + @searcher.find("mynode").should equal(@node) + end + + it "should deduplicate class values" do + @entry.stubs(:to_hash).returns({}) + @searcher.stubs(:class_attributes).returns(%w{one two}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + @entry.stubs(:vals).with("two").returns(%w{b c}) + @node.expects(:classes=).with(%w{a b c}) + @searcher.find("mynode") + end + + it "should add any values stored in the class_attributes attributes to the node classes" do + @entry.stubs(:to_hash).returns({}) + @searcher.stubs(:class_attributes).returns(%w{one two}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + @entry.stubs(:vals).with("two").returns(%w{c d}) + @node.expects(:classes=).with(%w{a b c d}) + @searcher.find("mynode") + end + + it "should add all entry attributes as node parameters" do + @entry.stubs(:to_hash).returns("one" => ["two"], "three" => ["four"]) + @node.expects(:parameters=).with("one" => "two", "three" => "four") + @searcher.find("mynode") + end + + it "should retain false parameter values" do + @entry.stubs(:to_hash).returns("one" => [false]) + @node.expects(:parameters=).with("one" => false) + @searcher.find("mynode") + end + + it "should turn single-value parameter value arrays into single non-arrays" do + @entry.stubs(:to_hash).returns("one" => ["a"]) + @node.expects(:parameters=).with("one" => "a") + @searcher.find("mynode") + end + + it "should keep multi-valued parametes as arrays" do + @entry.stubs(:to_hash).returns("one" => ["a", "b"]) + @node.expects(:parameters=).with("one" => ["a", "b"]) + @searcher.find("mynode") + end +end + +describe Puppet::Indirector::Ldap::Node, " when a parent node exists" do + include LdapNodeSearching + + before do + @parent = mock 'parent' + @parent_parent = mock 'parent_parent' + + @searcher.meta_def(:search_filter) do |name| + return name + end + @connection.stubs(:search).with { |*args| args[2] == @name }.yields(@entry) + @connection.stubs(:search).with { |*args| args[2] == 'parent' }.yields(@parent) + @connection.stubs(:search).with { |*args| args[2] == 'parent_parent' }.yields(@parent_parent) + + @searcher.stubs(:parent_attribute).returns(:parent) + end + + it "should add any parent classes to the node's classes" do + @entry.stubs(:to_hash).returns({}) + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + @entry.stubs(:vals).with("one").returns(%w{a b}) + + @parent.stubs(:to_hash).returns({}) + @parent.stubs(:vals).with("one").returns(%w{c d}) + @parent.stubs(:vals).with(:parent).returns(nil) + + @searcher.stubs(:class_attributes).returns(%w{one}) + @node.expects(:classes=).with(%w{a b c d}) + @searcher.find("mynode") + end + + it "should add any parent parameters to the node's parameters" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two", "three" => "four") + @searcher.find("mynode") + end + + it "should prefer node parameters over parent parameters" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("one" => "three") + @parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two") + @searcher.find("mynode") + end + + it "should recursively look up parent information" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns(['parent_parent']) + + @parent_parent.stubs(:to_hash).returns("five" => "six") + @parent_parent.stubs(:vals).with(:parent).returns(nil) + @parent_parent.stubs(:vals).with(:parent).returns(nil) + + @node.expects(:parameters=).with("one" => "two", "three" => "four", "five" => "six") + @searcher.find("mynode") + end + + it "should not allow loops in parent declarations" do + @entry.stubs(:to_hash).returns("one" => "two") + @entry.stubs(:vals).with(:parent).returns(%w{parent}) + + @parent.stubs(:to_hash).returns("three" => "four") + @parent.stubs(:vals).with(:parent).returns([@name]) + proc { @searcher.find("mynode") }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Ldap::Node, " when developing the search query" do before do @searcher = Puppet::Indirector::Ldap::Node.new end -- cgit From 60cd6a73b2b0cb7b26b091d4214c66eb5ed3b0ad Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 12:56:32 -0500 Subject: The structure for handling resource generation is now in place, which means I'm over the hump in developing this branch. I have to fix some design flaws I made in the configurations, particularly that the 'runner' handler needs to be able to specify tags and whether to ignore schedules, but otherwise, I think it's straightforward test- and bug-fixing from here out. --- lib/puppet/metatype/instances.rb | 5 ++--- lib/puppet/node/configuration.rb | 12 +++++++++++- lib/puppet/transaction.rb | 3 ++- lib/puppet/type.rb | 12 +++++++++--- lib/puppet/type/pfile.rb | 3 +++ test/ral/types/file.rb | 12 +++++++----- test/ral/types/filesources.rb | 2 +- 7 files changed, 35 insertions(+), 14 deletions(-) diff --git a/lib/puppet/metatype/instances.rb b/lib/puppet/metatype/instances.rb index 4fd72e85c..4af230b28 100644 --- a/lib/puppet/metatype/instances.rb +++ b/lib/puppet/metatype/instances.rb @@ -137,9 +137,8 @@ class Puppet::Type # now pass through and create the new object elsif implicit - Puppet.notice "Ignoring implicit %s" % title - - return retobj + Puppet.debug "Ignoring implicit %s[%s]" % [self.name, title] + return nil else # If only one of the objects is being managed, then merge them if retobj.managed? diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index de6e776c5..747ab9ef6 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -118,11 +118,21 @@ class Puppet::Node::Configuration < Puppet::PGraph # Create an implicit resource, meaning that it will lose out # to any explicitly defined resources. This method often returns # nil. + # The quirk of this method is that it's not possible to create + # an implicit resource before an explicit resource of the same name, + # because all explicit resources are created before any generate() + # methods are called on the individual resources. Thus, this + # method can safely just check if an explicit resource already exists + # and toss this implicit resource if so. def create_implicit_resource(type, options) unless options.include?(:implicit) options[:implicit] = true end - # LAK:FIXME catch exceptions here and return nil when problems + + # This will return nil if an equivalent explicit resource already exists. + # When resource classes no longer retain references to resource instances, + # this will need to be modified to catch that conflict and discard + # implicit resources. if resource = create_resource(type, options) resource.implicit = true diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index 43889927c..bb8918845 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -26,7 +26,8 @@ class Transaction end end - # Check to see if we should actually allow deleition. + # Check to see if we should actually allow processing, but this really only + # matters when a resource is getting deleted. def allow_processing?(resource, changes) # If a resource is going to be deleted but it still has # dependencies, then don't delete it unless it's implicit or the diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index e55873c2b..19f676ff4 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -316,9 +316,15 @@ class Type def parent return nil unless configuration - # We should never have more than one parent, so let's just ignore - # it if we happen to. - if parents = configuration.relationship_graph.adjacent(self, :direction => :in) + # This is kinda weird. + if implicit? + parents = configuration.relationship_graph.adjacent(self, :direction => :in) + else + parents = configuration.adjacent(self, :direction => :in) + end + if parents + # We should never have more than one parent, so let's just ignore + # it if we happen to. return parents.shift else return nil diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index f2fa6a14d..8b5d6e399 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -650,6 +650,9 @@ module Puppet return nil end end + + # LAK:FIXME This shouldn't be necessary, but as long as we're + # modeling the relationship graph specifically, it is. configuration.relationship_graph.add_edge! self, child unless child.parent raise "Did not set parent of %s" % child.ref diff --git a/test/ral/types/file.rb b/test/ral/types/file.rb index b58814a7f..0c16d6dff 100755 --- a/test/ral/types/file.rb +++ b/test/ral/types/file.rb @@ -676,7 +676,7 @@ class TestFile < Test::Unit::TestCase :check => %w{owner mode group} ) } - mk_configuration dir + config = mk_configuration dir children = nil @@ -687,19 +687,22 @@ class TestFile < Test::Unit::TestCase assert_equal([subdir], children.collect {|c| c.title }, "Incorrect generated children") - dir.class[subdir].remove + # Remove our subdir resource, + subdir_resource = config.resource(:file, subdir) + config.remove_resource(subdir_resource) + # Create the test file File.open(tmpfile, "w") { |f| f.puts "yayness" } assert_nothing_raised { children = dir.eval_generate } + # And make sure we get both resources back. assert_equal([subdir, tmpfile].sort, children.collect {|c| c.title }.sort, - "Incorrect generated children") + "Incorrect generated children when recurse == %s" % value.inspect) File.unlink(tmpfile) - #system("rm -rf %s" % basedir) Puppet.type(:file).clear end end @@ -1297,7 +1300,6 @@ class TestFile < Test::Unit::TestCase assert(FileTest.exists?(purgee), "File got prematurely purged") assert_nothing_raised { destobj[:purge] = true } - Puppet.err :yay config.apply assert(FileTest.exists?(localfile), "Local file got purged") diff --git a/test/ral/types/filesources.rb b/test/ral/types/filesources.rb index 046d46c1e..01d9766db 100755 --- a/test/ral/types/filesources.rb +++ b/test/ral/types/filesources.rb @@ -290,7 +290,7 @@ class TestFileSources < Test::Unit::TestCase assert_equal([dfileobj], result) # Clean this up so it can be recreated - dfileobj.remove + config.remove_resource(dfileobj) # Make sure we correctly iterate over the sources nosource = tempfile() -- cgit From 86dde63473d29c45d8698ce4edd53c820a621362 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 14:25:17 -0500 Subject: All tests now pass in this configuration branch, which means it's time to merge it back into the indirection branch. Considering that this work was what drove me to create the indirection branch in the first place, i should now be able to merge both back in the master branch. --- lib/puppet/network/client/master.rb | 6 ++- lib/puppet/network/handler/runner.rb | 4 +- lib/puppet/node/configuration.rb | 40 +++++++++++------- lib/puppet/pgraph.rb | 2 +- lib/puppet/transaction.rb | 9 ++-- lib/puppet/type/component.rb | 44 +------------------ lib/puppet/type/pfile.rb | 12 ++++-- spec/unit/node/configuration.rb | 10 +++++ spec/unit/other/transaction.rb | 26 ++++++++++++ test/data/snippets/failmissingexecpath.pp | 14 ------- test/language/snippets.rb | 11 +---- test/other/relationships.rb | 13 +++--- test/other/transactions.rb | 25 ++++------- test/ral/manager/type.rb | 70 ------------------------------- 14 files changed, 100 insertions(+), 186 deletions(-) create mode 100755 spec/unit/other/transaction.rb delete mode 100644 test/data/snippets/failmissingexecpath.pp diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index b5824e6bd..cc66d9076 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -250,7 +250,9 @@ class Puppet::Network::Client::Master < Puppet::Network::Client end # The code that actually runs the configuration. - def run + # This just passes any options on to the configuration, + # which accepts :tags and :ignoreschedules. + def run(options = {}) got_lock = false splay Puppet::Util.sync(:puppetrun).synchronize(Sync::EX) do @@ -273,7 +275,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client Puppet.notice "Starting configuration run" end benchmark(:notice, "Finished configuration run") do - @configuration.apply + @configuration.apply(options) end end end diff --git a/lib/puppet/network/handler/runner.rb b/lib/puppet/network/handler/runner.rb index c41e83608..4b9ccab75 100755 --- a/lib/puppet/network/handler/runner.rb +++ b/lib/puppet/network/handler/runner.rb @@ -50,10 +50,10 @@ class Puppet::Network::Handler # And then we need to tell it to run, with this extra info. if fg - master.run(tags, ignoreschedules) + master.run(:tags => tags, :ignoreschedules => ignoreschedules) else Puppet.newthread do - master.run(tags, ignoreschedules) + master.run(:tags => tags, :ignoreschedules => ignoreschedules) end end diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 747ab9ef6..360c25fc8 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -53,13 +53,21 @@ class Puppet::Node::Configuration < Puppet::PGraph end end - # Apply our configuration to the local host. - def apply + # Apply our configuration to the local host. Valid options + # are: + # :tags - set the tags that restrict what resources run + # during the transaction + # :ignoreschedules - tell the transaction to ignore schedules + # when determining the resources to run + def apply(options = {}) @applying = true Puppet::Util::Storage.load if host_config? transaction = Puppet::Transaction.new(self) + transaction.tags = options[:tags] if options[:tags] + transaction.ignoreschedules = true if options[:ignoreschedules] + transaction.addtimes :config_retrieval => @retrieval_duration begin @@ -257,24 +265,29 @@ class Puppet::Node::Configuration < Puppet::PGraph # Create a graph of all of the relationships in our configuration. def relationship_graph unless defined? @relationship_graph and @relationship_graph - relationships = Puppet::Node::Configuration.new - relationships.host_config = host_config? + # It's important that we assign the graph immediately, because + # the debug messages below use the relationships in the + # relationship graph to determine the path to the resources + # spitting out the messages. If this is not set, + # then we get into an infinite loop. + @relationship_graph = Puppet::Node::Configuration.new + @relationship_graph.host_config = host_config? # First create the dependency graph self.vertices.each do |vertex| - relationships.add_vertex! vertex + @relationship_graph.add_vertex! vertex vertex.builddepends.each do |edge| - relationships.add_edge!(edge) + @relationship_graph.add_edge!(edge) end end # Lastly, add in any autorequires - relationships.vertices.each do |vertex| + @relationship_graph.vertices.each do |vertex| vertex.autorequire.each do |edge| - unless relationships.edge?(edge) - unless relationships.edge?(edge.target, edge.source) + unless @relationship_graph.edge?(edge) + unless @relationship_graph.edge?(edge.target, edge.source) vertex.debug "Autorequiring %s" % [edge.source] - relationships.add_edge!(edge) + @relationship_graph.add_edge!(edge) else vertex.debug "Skipping automatic relationship with %s" % (edge.source == vertex ? edge.target : edge.source) end @@ -282,13 +295,12 @@ class Puppet::Node::Configuration < Puppet::PGraph end end - relationships.write_graph(:relationships) + @relationship_graph.write_graph(:relationships) # Then splice in the container information - relationships.splice!(self, Puppet::Type::Component) + @relationship_graph.splice!(self, Puppet::Type::Component) - relationships.write_graph(:expanded_relationships) - @relationship_graph = relationships + @relationship_graph.write_graph(:expanded_relationships) end @relationship_graph end diff --git a/lib/puppet/pgraph.rb b/lib/puppet/pgraph.rb index 70b466225..2399d1651 100644 --- a/lib/puppet/pgraph.rb +++ b/lib/puppet/pgraph.rb @@ -84,7 +84,7 @@ class Puppet::PGraph < GRATR::Digraph def matching_edges(events, base = nil) events.collect do |event| source = base || event.source - + unless vertex?(source) Puppet.warning "Got an event from invalid vertex %s" % source.ref next diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index bb8918845..4e2ab702f 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -269,9 +269,12 @@ class Transaction # Collect the targets of any subscriptions to those events. We pass # the parent resource in so it will override the source in the events, # since eval_generated children can't have direct relationships. - relationship_graph.matching_edges(events, resource).each do |edge| - edge = edge.dup - label = edge.label + relationship_graph.matching_edges(events, resource).each do |orig_edge| + # We have to dup the label here, else we modify the original edge label, + # which affects whether a given event will match on the next run, which is, + # of course, bad. + edge = orig_edge.class.new(orig_edge.source, orig_edge.target) + label = orig_edge.label.dup label[:event] = events.collect { |e| e.event } edge.label = label set_trigger(edge) diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 1eadeb060..c5842e0e7 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -128,27 +128,6 @@ Puppet::Type.newtype(:component) do return false end end - - def push(*childs) - unless defined? @children - @children = [] - end - childs.each { |child| - # Make sure we don't have any loops here. - if parentof?(child) - devfail "Already the parent of %s[%s]" % [child.class.name, child.title] - end - unless child.is_a?(Puppet::Type) - self.debug "Got object of type %s" % child.class - self.devfail( - "Containers can only contain Puppet resources, not %s" % - child.class - ) - end - @children.push(child) - child.parent = self - } - end # Component paths are special because they function as containers. def pathbuilder @@ -174,25 +153,6 @@ Puppet::Type.newtype(:component) do title end - # Remove an object. The argument determines whether the object's - # subscriptions get eliminated, too. - def remove(rmdeps = true) - # Our children remove themselves from our @children array (else the object - # we called this on at the top would not be removed), so we duplicate the - # array and iterate over that. If we don't do this, only half of the - # objects get removed. - @children.dup.each { |child| - child.remove(rmdeps) - } - - @children.clear - - # Get rid of params and provider, too. - super - - @parent = nil - end - # We have a different way of setting the title def title unless defined? @title @@ -208,12 +168,12 @@ Puppet::Type.newtype(:component) do end def refresh - @children.collect { |child| + configuration.adjacent(self).each do |child| if child.respond_to?(:refresh) child.refresh child.log "triggering %s" % :refresh end - } + end end def to_s diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb index 8b5d6e399..5bf9fef78 100644 --- a/lib/puppet/type/pfile.rb +++ b/lib/puppet/type/pfile.rb @@ -643,7 +643,9 @@ module Puppet #notice "Creating new file with args %s" % args.inspect args[:parent] = self begin - return nil unless child = configuration.create_implicit_resource(:file, args) + # This method is used by subclasses of :file, so use the class name rather than hard-coding + # :file. + return nil unless child = configuration.create_implicit_resource(self.class.name, args) rescue => detail puts detail.backtrace self.notice "Cannot manage: %s" % [detail] @@ -664,12 +666,14 @@ module Puppet # path names, rather than including the full parent's title each # time. def pathbuilder - if defined? @parent + # We specifically need to call the method here, so it looks + # up our parent in the configuration graph. + if parent = parent() # We only need to behave specially when our parent is also # a file - if @parent.is_a?(self.class) + if parent.is_a?(self.class) # Remove the parent file name - list = @parent.pathbuilder + list = parent.pathbuilder list.pop # remove the parent's path info return list << self.ref else diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index eb4eaa418..b37cc754d 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -260,6 +260,16 @@ describe Puppet::Node::Configuration, " when applying" do it "should default to not being a host configuration" do @config.host_config.should be_nil end + + it "should pass supplied tags on to the transaction" do + @transaction.expects(:tags=).with(%w{one two}) + @config.apply(:tags => %w{one two}) + end + + it "should set ignoreschedules on the transaction if specified in apply()" do + @transaction.expects(:ignoreschedules=).with(true) + @config.apply(:ignoreschedules => true) + end end describe Puppet::Node::Configuration, " when applying host configurations" do diff --git a/spec/unit/other/transaction.rb b/spec/unit/other/transaction.rb new file mode 100755 index 000000000..7990d2eef --- /dev/null +++ b/spec/unit/other/transaction.rb @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Transaction, " when determining tags" do + before do + @config = Puppet::Node::Configuration.new + @transaction = Puppet::Transaction.new(@config) + end + + it "should default to the tags specified in the :tags setting" do + Puppet.expects(:[]).with(:tags).returns("one") + @transaction.tags.should == %w{one} + end + + it "should split tags based on ','" do + Puppet.expects(:[]).with(:tags).returns("one,two") + @transaction.tags.should == %w{one two} + end + + it "should use any tags set after creation" do + Puppet.expects(:[]).with(:tags).never + @transaction.tags = %w{one two} + @transaction.tags.should == %w{one two} + end +end diff --git a/test/data/snippets/failmissingexecpath.pp b/test/data/snippets/failmissingexecpath.pp deleted file mode 100644 index b03875547..000000000 --- a/test/data/snippets/failmissingexecpath.pp +++ /dev/null @@ -1,14 +0,0 @@ -define distloc($path) { - file { "/tmp/exectesting1": - ensure => file - } - exec { "exectest": - command => "touch $path", - subscribe => File["/tmp/exectesting1"], - refreshonly => true - } -} - -distloc { yay: - path => "/tmp/execdisttesting", -} diff --git a/test/language/snippets.rb b/test/language/snippets.rb index ff8a09881..2a9af37db 100755 --- a/test/language/snippets.rb +++ b/test/language/snippets.rb @@ -197,7 +197,7 @@ class TestSnippets < Test::Unit::TestCase def snippet_classpathtest path = "/tmp/classtest" - file = @file[path] + file = @configuration.resource(:file, path) assert(file, "did not create file %s" % path) assert_nothing_raised { @@ -271,14 +271,6 @@ class TestSnippets < Test::Unit::TestCase assert_mode_equal(0755, file) end - def snippet_failmissingexecpath - file = "/tmp/exectesting1" - execfile = "/tmp/execdisttesting" - assert_file(file) - - assert_nil(Puppet::Type.type(:exec)["exectest"], "invalid exec was created") - end - def snippet_selectorvalues nums = %w{1 2 3 4 5} files = nums.collect { |n| @@ -508,6 +500,7 @@ class TestSnippets < Test::Unit::TestCase assert(obj.name) } } + @configuration = client.configuration assert_nothing_raised { self.send(mname) } diff --git a/test/other/relationships.rb b/test/other/relationships.rb index f463b4e5a..771b119ee 100755 --- a/test/other/relationships.rb +++ b/test/other/relationships.rb @@ -172,15 +172,12 @@ class TestRelationships < Test::Unit::TestCase end assert_equal([Puppet::Relationship[file, exec]], reqs) - # Now make sure that these relationships are added to the transaction's - # relgraph - trans = Puppet::Transaction.new(mk_configuration(file, exec)) - assert_nothing_raised do - trans.evaluate + # Now make sure that these relationships are added to the + # relationship graph + config = mk_configuration(file, exec) + config.apply do |trans| + assert(config.relationship_graph.edge?(file, exec), "autorequire edge was not created") end - - graph = trans.relgraph - assert(graph.edge?(file, exec), "autorequire edge was not created") end def test_requires? diff --git a/test/other/transactions.rb b/test/other/transactions.rb index ecf0361fd..7d17a92e7 100755 --- a/test/other/transactions.rb +++ b/test/other/transactions.rb @@ -295,12 +295,9 @@ class TestTransactions < Test::Unit::TestCase config = mk_configuration(file) config.apply - config.clear(false) @@tmpfiles << execfile - config.add_resource exec - # 'subscribe' expects an array of arrays exec[:subscribe] = [[file.class.name,file.name]] exec[:refreshonly] = true @@ -317,8 +314,11 @@ class TestTransactions < Test::Unit::TestCase file[:mode] = "755" } + # Make a new configuration so the resource relationships get + # set up. + config = mk_configuration(file, exec) + trans = assert_events([:file_changed, :triggered], config) - config.clear(false) assert(FileTest.exists?(execfile), "Execfile does not exist") File.unlink(execfile) @@ -433,7 +433,8 @@ class TestTransactions < Test::Unit::TestCase Puppet[:ignoreschedules] = false file = Puppet.type(:file).create( :name => tempfile(), - :ensure => "file" + :ensure => "file", + :backup => false ) fname = tempfile() @@ -444,7 +445,7 @@ class TestTransactions < Test::Unit::TestCase :subscribe => ["file", file.name] ) - config = mk_configuration(file,exec) + config = mk_configuration(file, exec) # Run it once assert_apply(config) @@ -458,6 +459,7 @@ class TestTransactions < Test::Unit::TestCase file[:content] = "some content" assert_events([:file_changed, :triggered], config) + assert(FileTest.exists?(fname), "File did not get recreated") # Now remove it, so it can get created again @@ -711,17 +713,6 @@ class TestTransactions < Test::Unit::TestCase assert_equal(%w{yay ya y rah ra r}, $evaluated, "Not all resources were evaluated or not in the right order") end - - # Make sure tags on the transaction get copied to the configuration. - def test_tags - config = Puppet::Node::Configuration.new - transaction = Puppet::Transaction.new(config) - assert_equal([], transaction.tags, "Tags defaulted to non-empty") - - Puppet[:tags] = "one,two" - transaction = Puppet::Transaction.new(config) - assert_equal(%w{one two}, transaction.tags, "Tags were not copied from the central configuration") - end def test_ignore_tags? config = Puppet::Node::Configuration.new diff --git a/test/ral/manager/type.rb b/test/ral/manager/type.rb index 4f2db4805..b2c111e60 100755 --- a/test/ral/manager/type.rb +++ b/test/ral/manager/type.rb @@ -246,76 +246,6 @@ class TestType < Test::Unit::TestCase # and make sure managed objects start with them assert(user.property(:ensure), "User did not get an ensure property") end - - # Make sure removal works - def test_remove - objects = {} - top = Puppet.type(:component).create(:name => "top") - objects[top.class] = top - - base = tempfile() - - # now make a two-tier, 5 piece tree - %w{a b}.each do |letter| - name = "comp%s" % letter - comp = Puppet.type(:component).create(:name => name) - top.push comp - objects[comp.class] = comp - - 5.times do |i| - file = base + letter + i.to_s - - obj = Puppet.type(:file).create(:name => file, :ensure => "file") - - comp.push obj - objects[obj.class] = obj - end - end - - assert_nothing_raised do - top.remove - end - - objects.each do |klass, obj| - assert_nil(klass[obj.name], "object %s was not removed" % obj.name) - end - end - - # Verify that objects can't be their own children. - def test_object_recursion - comp = Puppet.type(:component).create(:name => "top") - - file = Puppet.type(:component).create(:name => "middle") - - assert_raise(Puppet::DevError) do - comp.push(comp) - end - - assert_raise(Puppet::DevError) do - file.push(file) - end - - assert_raise(Puppet::DevError) do - comp.parent = comp - end - - assert_raise(Puppet::DevError) do - file.parent = file - end - - assert_nothing_raised { - comp.push(file) - } - - assert_raise(Puppet::DevError) do - file.push(comp) - end - - assert_raise(Puppet::DevError) do - comp.parent = file - end - end - def test_newtype_methods assert_nothing_raised { Puppet::Type.newtype(:mytype) do -- cgit From e552c83b2875dab60a5508bfae352e7aa9235746 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 17:22:07 -0500 Subject: Adding the base file terminus. This will, at the least, be used as the back end for filebuckets and the certificate authority. --- lib/puppet/indirector/file.rb | 40 +++++++++++++ spec/unit/indirector/file.rb | 130 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 lib/puppet/indirector/file.rb create mode 100755 spec/unit/indirector/file.rb diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb new file mode 100644 index 000000000..75fcf1ddf --- /dev/null +++ b/lib/puppet/indirector/file.rb @@ -0,0 +1,40 @@ +require 'puppet/indirector/terminus' + +# An empty terminus type, meant to just return empty objects. +class Puppet::Indirector::File < Puppet::Indirector::Terminus + def destroy(file) + raise Puppet::Error.new("File %s does not exist; cannot destroy" % [file.name]) unless File.exist?(file.path) + + begin + File.unlink(file.path) + rescue => detail + raise Puppet::Error, "Could not remove %s: %s" % [file.path, detail] + end + end + + def find(path) + return nil unless File.exist?(path) + + begin + content = File.read(path) + rescue => detail + raise Puppet::Error, "Could not retrieve path %s: %s" % [path, detail] + end + + file = model.new(path) + file.content = content + return file + end + + def save(file) + dir = File.dirname(file.path) + + raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [file.name, dir]) unless File.directory?(dir) + + begin + File.open(file.path, "w") { |f| f.print file.content } + rescue => detail + raise Puppet::Error, "Could not write %s: %s" % [file.path, detail] + end + end +end diff --git a/spec/unit/indirector/file.rb b/spec/unit/indirector/file.rb new file mode 100755 index 000000000..d08ff6455 --- /dev/null +++ b/spec/unit/indirector/file.rb @@ -0,0 +1,130 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/file' + +module FileTerminusTesting + def setup + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @file_class = Class.new(Puppet::Indirector::File) do + def self.to_s + "Testing" + end + end + + @searcher = @file_class.new + + @path = "/my/file" + @dir = "/my" + end +end + +describe Puppet::Indirector::File, " when finding files" do + include FileTerminusTesting + + it "should provide a method to return file contents at a specified path" do + end + + it "should return file contents as an instance of the model" do + content = "my content" + + file = mock 'file' + @model.expects(:new).with(@path).returns(file) + file.expects(:content=).with(content) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + @searcher.find(@path) + end + + it "should set the file contents as the 'content' attribute of the returned instance" do + content = "my content" + + file = mock 'file' + @model.expects(:new).with(@path).returns(file) + file.expects(:content=).with(content) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + @searcher.find(@path).should equal(file) + end + + it "should return nil if no file is found" do + File.expects(:exist?).with(@path).returns(false) + @searcher.find(@path).should be_nil + end + + it "should fail intelligently if a found file cannot be read" do + content = "my content" + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).raises(RuntimeError) + proc { @searcher.find(@path) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::File, " when saving files" do + include FileTerminusTesting + + it "should provide a method to save file contents at a specified path" do + filehandle = mock 'file' + content = "my content" + File.expects(:directory?).with(@dir).returns(true) + File.expects(:open).with(@path, "w").yields(filehandle) + filehandle.expects(:print).with(content) + + file = stub 'file', :content => content, :path => @path, :name => @path + + @searcher.save(file) + end + + it "should fail intelligently if the file's parent directory does not exist" do + File.expects(:directory?).with(@dir).returns(false) + + file = stub 'file', :path => @path, :name => @path + + proc { @searcher.save(file) }.should raise_error(Puppet::Error) + end + + it "should fail intelligently if a file cannot be written" do + filehandle = mock 'file' + content = "my content" + File.expects(:directory?).with(@dir).returns(true) + File.expects(:open).with(@path, "w").yields(filehandle) + filehandle.expects(:print).with(content).raises(ArgumentError) + + file = stub 'file', :content => content, :path => @path, :name => @path + + proc { @searcher.save(file) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::File, " when removing files" do + include FileTerminusTesting + + it "should provide a method to remove files at a specified path" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path) + + @searcher.destroy(file) + end + + it "should throw an exception if the file is not found" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(false) + + proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) + end + + it "should fail intelligently if the file cannot be removed" do + file = stub 'file', :path => @path, :name => @path + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path).raises(ArgumentError) + + proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) + end +end -- cgit From 3a18348fdbea39f56857b03c8f531bd5a2a8105d Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 17:54:46 -0500 Subject: Renaming the 'Puppet::Util::Config' class to 'Puppet::Util::Settings'. This is to clear up confusion caused by the fact that we now have a 'Configuration' class to model host configurations, or any set of resources as a "configuration". --- bin/filebucket | 4 +- bin/puppet | 6 +- bin/puppetca | 4 +- bin/puppetd | 12 +- bin/puppetmasterd | 4 +- bin/puppetrun | 6 +- bin/ralsh | 4 +- ext/module_puppet | 6 +- lib/puppet.rb | 30 +- lib/puppet/config_stores/rest.rb | 2 +- lib/puppet/defaults.rb | 2 +- lib/puppet/fact_stores/yaml.rb | 2 +- lib/puppet/indirector/indirection.rb | 4 +- lib/puppet/indirector/yaml.rb | 2 +- lib/puppet/module.rb | 4 +- lib/puppet/network/client/ca.rb | 8 +- lib/puppet/network/client/master.rb | 4 +- lib/puppet/network/handler/ca.rb | 2 +- lib/puppet/network/handler/filebucket.rb | 2 +- lib/puppet/network/handler/report.rb | 4 +- lib/puppet/network/server/webrick.rb | 2 +- lib/puppet/node/configuration.rb | 2 +- lib/puppet/parser/interpreter.rb | 2 +- lib/puppet/rails.rb | 4 +- lib/puppet/reference/configuration.rb | 2 +- lib/puppet/reports/rrdgraph.rb | 2 +- lib/puppet/reports/store.rb | 4 +- lib/puppet/reports/tagmail.rb | 2 +- lib/puppet/sslcertificates/ca.rb | 18 +- lib/puppet/sslcertificates/inventory.rb | 2 +- lib/puppet/sslcertificates/support.rb | 8 +- lib/puppet/util/config.rb | 1223 ------------------------------ lib/puppet/util/metric.rb | 2 +- lib/puppet/util/settings.rb | 1223 ++++++++++++++++++++++++++++++ lib/puppet/util/storage.rb | 2 +- spec/unit/indirector/exec/node.rb | 4 +- spec/unit/indirector/indirection.rb | 8 +- spec/unit/indirector/yaml.rb | 4 +- spec/unit/node/configuration.rb | 6 +- spec/unit/node/node.rb | 4 +- spec/unit/node/searching.rb | 2 +- spec/unit/other/modules.rb | 20 +- spec/unit/parser/interpreter.rb | 4 +- spec/unit/util/config.rb | 535 ------------- spec/unit/util/settings.rb | 535 +++++++++++++ test/certmgr/certmgr.rb | 8 +- test/certmgr/inventory.rb | 4 +- test/lib/puppettest.rb | 4 +- test/network/client/ca.rb | 2 +- test/network/client/client.rb | 6 +- test/network/client/master.rb | 2 +- test/network/handler/master.rb | 2 +- test/other/report.rb | 4 +- test/puppet/conffiles.rb | 5 +- test/util/config.rb | 1107 --------------------------- test/util/settings.rb | 1107 +++++++++++++++++++++++++++ 56 files changed, 2991 insertions(+), 2992 deletions(-) delete mode 100644 lib/puppet/util/config.rb create mode 100644 lib/puppet/util/settings.rb delete mode 100755 spec/unit/util/config.rb create mode 100755 spec/unit/util/settings.rb delete mode 100755 test/util/config.rb create mode 100755 test/util/settings.rb diff --git a/bin/filebucket b/bin/filebucket index 2001eaf63..22580cbe3 100755 --- a/bin/filebucket +++ b/bin/filebucket @@ -109,7 +109,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -139,7 +139,7 @@ begin when "--remote" options[:remote] = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppet b/bin/puppet index 9520492a2..deeb65c42 100755 --- a/bin/puppet +++ b/bin/puppet @@ -76,7 +76,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -124,7 +124,7 @@ begin $stderr.puts detail.to_s end else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail @@ -156,7 +156,7 @@ end # Now parse the config if Puppet[:config] and File.exists? Puppet[:config] - Puppet.config.parse(Puppet[:config]) + Puppet.settings.parse(Puppet[:config]) end Puppet.genconfig diff --git a/bin/puppetca b/bin/puppetca index 3dbd87b89..72b2640a3 100755 --- a/bin/puppetca +++ b/bin/puppetca @@ -104,7 +104,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -143,7 +143,7 @@ begin when "--verbose" Puppet::Util::Log.level = :info else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppetd b/bin/puppetd index a03ed8f10..8d112ca3a 100755 --- a/bin/puppetd +++ b/bin/puppetd @@ -178,7 +178,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -216,10 +216,10 @@ begin options[:enable] = true when "--test" # Enable all of the most common test options. - Puppet.config.handlearg("--ignorecache") - Puppet.config.handlearg("--no-usecacheonfailure") - Puppet.config.handlearg("--no-splay") - Puppet.config.handlearg("--show_diff") + Puppet.settings.handlearg("--ignorecache") + Puppet.settings.handlearg("--no-usecacheonfailure") + Puppet.settings.handlearg("--no-splay") + Puppet.settings.handlearg("--show_diff") options[:onetime] = true options[:waitforcert] = 0 unless Puppet::Util::Log.level == :debug @@ -264,7 +264,7 @@ begin options[:waitforcert] = arg.to_i explicit_waitforcert = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppetmasterd b/bin/puppetmasterd index 4552fd0b8..51c714b15 100755 --- a/bin/puppetmasterd +++ b/bin/puppetmasterd @@ -95,7 +95,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -152,7 +152,7 @@ begin when "--verbose" options[:verbose] = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail diff --git a/bin/puppetrun b/bin/puppetrun index cd6b493b9..1aa0b295b 100755 --- a/bin/puppetrun +++ b/bin/puppetrun @@ -199,7 +199,7 @@ flags = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(flags) +Puppet.settings.addargs(flags) result = GetoptLong.new(*flags) @@ -259,7 +259,7 @@ begin when "--debug" options[:debug] = true else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail @@ -278,7 +278,7 @@ config = File.join(Puppet[:confdir], "puppetmasterd.conf") Puppet.parse_config(config) if File.exists? config - Puppet.config.parse(config) + Puppet.settings.parse(config) end if Puppet[:ldapnodes] diff --git a/bin/ralsh b/bin/ralsh index 7985f3995..7bf0bd3d7 100755 --- a/bin/ralsh +++ b/bin/ralsh @@ -99,7 +99,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -139,7 +139,7 @@ result.each { |opt,arg| debug = true else # Anything else is handled by the config stuff - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } diff --git a/ext/module_puppet b/ext/module_puppet index 194f3fa09..52f65b094 100755 --- a/ext/module_puppet +++ b/ext/module_puppet @@ -61,7 +61,7 @@ options = [ ] # Add all of the config parameters as valid options. -Puppet.config.addargs(options) +Puppet.settings.addargs(options) result = GetoptLong.new(*options) @@ -108,7 +108,7 @@ begin $stderr.puts detail.to_s end else - Puppet.config.handlearg(opt, arg) + Puppet.settings.handlearg(opt, arg) end } rescue GetoptLong::InvalidOption => detail @@ -121,7 +121,7 @@ end # Now parse the config if Puppet[:config] and File.exists? Puppet[:config] - Puppet.config.parse(Puppet[:config]) + Puppet.settings.parse(Puppet[:config]) end client = nil diff --git a/lib/puppet.rb b/lib/puppet.rb index b9a09bb49..949922459 100644 --- a/lib/puppet.rb +++ b/lib/puppet.rb @@ -12,7 +12,7 @@ require 'puppet/external/event-loop' require 'puppet/util' require 'puppet/util/log' require 'puppet/util/autoload' -require 'puppet/util/config' +require 'puppet/util/settings' require 'puppet/util/feature' require 'puppet/util/suidmanager' @@ -45,7 +45,7 @@ module Puppet end # the hash that determines how our system behaves - @@config = Puppet::Util::Config.new + @@settings = Puppet::Util::Settings.new # The services running in this process. @services ||= [] @@ -76,7 +76,7 @@ module Puppet # Store a new default value. def self.setdefaults(section, hash) - @@config.setdefaults(section, hash) + @@settings.setdefaults(section, hash) end # configuration parameter access and stuff @@ -89,17 +89,17 @@ module Puppet return false end else - return @@config[param] + return @@settings[param] end end # configuration parameter access and stuff def self.[]=(param,value) - @@config[param] = value + @@settings[param] = value end def self.clear - @@config.clear + @@settings.clear end def self.debug=(value) @@ -110,8 +110,8 @@ module Puppet end end - def self.config - @@config + def self.settings + @@settings end # Load all of the configuration parameters. @@ -122,7 +122,7 @@ module Puppet val = Puppet[:configprint] if val == "all" hash = {} - Puppet.config.each do |name, obj| + Puppet.settings.each do |name, obj| val = obj.value case val when true, false, "": val = val.inspect @@ -134,7 +134,7 @@ module Puppet end elsif val =~ /,/ val.split(/\s*,\s*/).sort.each do |v| - if Puppet.config.include?(v) + if Puppet.settings.include?(v) puts "%s = %s" % [v, Puppet[v]] else puts "invalid parameter: %s" % v @@ -143,7 +143,7 @@ module Puppet end else val.split(/\s*,\s*/).sort.each do |v| - if Puppet.config.include?(v) + if Puppet.settings.include?(v) puts Puppet[val] else puts "invalid parameter: %s" % v @@ -154,14 +154,14 @@ module Puppet exit(0) end if Puppet[:genconfig] - puts Puppet.config.to_config + puts Puppet.settings.to_config exit(0) end end def self.genmanifest if Puppet[:genmanifest] - puts Puppet.config.to_manifest + puts Puppet.settings.to_manifest exit(0) end end @@ -208,14 +208,14 @@ module Puppet oldconfig ||= File.join(Puppet[:confdir], Puppet[:name].to_s + ".conf") if FileTest.exists?(oldconfig) and Puppet[:name] != "puppet" Puppet.warning "Individual config files are deprecated; remove %s and use puppet.conf" % oldconfig - Puppet.config.old_parse(oldconfig) + Puppet.settings.old_parse(oldconfig) return end # Now check for the normal config. if Puppet[:config] and File.exists? Puppet[:config] Puppet.debug "Parsing %s" % Puppet[:config] - Puppet.config.parse(Puppet[:config]) + Puppet.settings.parse(Puppet[:config]) end end diff --git a/lib/puppet/config_stores/rest.rb b/lib/puppet/config_stores/rest.rb index 980968bd8..bb3d937ac 100644 --- a/lib/puppet/config_stores/rest.rb +++ b/lib/puppet/config_stores/rest.rb @@ -1,4 +1,4 @@ -Puppet::Util::ConfigStore.newstore(:rest) do +Puppet::Util::SettingsStore.newstore(:rest) do desc "Store client configurations via a REST web service." require 'net/http' diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 64f370bec..dbbe5e29d 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -267,7 +267,7 @@ module Puppet ) # Define the config default. - self.setdefaults(self.config[:name], + self.setdefaults(self.settings[:name], :config => ["$confdir/puppet.conf", "The configuration file for #{Puppet[:name]}."], :pidfile => ["", "The pid file"], diff --git a/lib/puppet/fact_stores/yaml.rb b/lib/puppet/fact_stores/yaml.rb index a4b12a2e5..b33e162ba 100644 --- a/lib/puppet/fact_stores/yaml.rb +++ b/lib/puppet/fact_stores/yaml.rb @@ -16,7 +16,7 @@ Puppet::Util::FactStore.newstore(:yaml) do end def initialize - Puppet.config.use(:yamlfacts) + Puppet.settings.use(:yamlfacts) end # Store the facts to disk. diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index aacad6c45..8afe0012d 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -47,8 +47,8 @@ class Puppet::Indirector::Indirection # Get the name of the terminus. unless terminus_name param_name = "%s_terminus" % self.name - if Puppet.config.valid?(param_name) - terminus_name = Puppet.config[param_name] + if Puppet.settings.valid?(param_name) + terminus_name = Puppet.settings[param_name] else terminus_name = Puppet[:default_terminus] end diff --git a/lib/puppet/indirector/yaml.rb b/lib/puppet/indirector/yaml.rb index 42f1bb703..b9ea54f39 100644 --- a/lib/puppet/indirector/yaml.rb +++ b/lib/puppet/indirector/yaml.rb @@ -4,7 +4,7 @@ require 'puppet/indirector/terminus' class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus def initialize # Make sure our base directory exists. - Puppet.config.use(:yaml) + Puppet.settings.use(:yaml) end # Read a given name's file in and convert it from YAML. diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 924958bbe..dc30d8167 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -9,7 +9,7 @@ class Puppet::Module # parameter. Only consider paths that are absolute and existing # directories def self.modulepath(environment = nil) - dirs = Puppet.config.value(:modulepath, environment).split(":") + dirs = Puppet.settings.value(:modulepath, environment).split(":") if ENV["PUPPETLIB"] dirs = ENV["PUPPETLIB"].split(":") + dirs else @@ -61,7 +61,7 @@ class Puppet::Module if mod return mod.template(file) else - return File.join(Puppet.config.value(:templatedir, environment), template) + return File.join(Puppet.settings.value(:templatedir, environment), template) end end diff --git a/lib/puppet/network/client/ca.rb b/lib/puppet/network/client/ca.rb index 412c9c59f..46fb9f51f 100644 --- a/lib/puppet/network/client/ca.rb +++ b/lib/puppet/network/client/ca.rb @@ -14,9 +14,9 @@ class Puppet::Network::Client::CA < Puppet::Network::Client end # This client is really only able to request certificates for the - # current host. It uses the Puppet.config settings to figure everything out. + # current host. It uses the Puppet.settings settings to figure everything out. def request_cert - Puppet.config.use(:main, :ssl) + Puppet.settings.use(:main, :ssl) if cert = read_cert return cert @@ -49,8 +49,8 @@ class Puppet::Network::Client::CA < Puppet::Network::Client end # Only write the cert out if it passes validating. - Puppet.config.write(:hostcert) do |f| f.print cert end - Puppet.config.write(:localcacert) do |f| f.print cacert end + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end return @cert end diff --git a/lib/puppet/network/client/master.rb b/lib/puppet/network/client/master.rb index cc66d9076..f950a6059 100644 --- a/lib/puppet/network/client/master.rb +++ b/lib/puppet/network/client/master.rb @@ -46,7 +46,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Return the list of dynamic facts as an array of symbols def self.dynamic_facts - Puppet.config[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase } + Puppet.settings[:dynamicfacts].split(/\s*,\s*/).collect { |fact| fact.downcase } end # Cache the config @@ -205,7 +205,7 @@ class Puppet::Network::Client::Master < Puppet::Network::Client # Just so we can specify that we are "the" instance. def initialize(*args) - Puppet.config.use(:main, :ssl, :puppetd) + Puppet.settings.use(:main, :ssl, :puppetd) super self.class.instance = self diff --git a/lib/puppet/network/handler/ca.rb b/lib/puppet/network/handler/ca.rb index 422b21ae1..052eb5c19 100644 --- a/lib/puppet/network/handler/ca.rb +++ b/lib/puppet/network/handler/ca.rb @@ -60,7 +60,7 @@ class Puppet::Network::Handler end def initialize(hash = {}) - Puppet.config.use(:main, :ssl, :ca) + Puppet.settings.use(:main, :ssl, :ca) if hash.include? :autosign @autosign = hash[:autosign] end diff --git a/lib/puppet/network/handler/filebucket.rb b/lib/puppet/network/handler/filebucket.rb index bb6a0e6d3..1bf8da854 100755 --- a/lib/puppet/network/handler/filebucket.rb +++ b/lib/puppet/network/handler/filebucket.rb @@ -64,7 +64,7 @@ class Puppet::Network::Handler # :nodoc: end end - Puppet.config.use(:filebucket) + Puppet.settings.use(:filebucket) @name = "Filebucket[#{@path}]" end diff --git a/lib/puppet/network/handler/report.rb b/lib/puppet/network/handler/report.rb index 81aee6a3c..62e9cfdec 100755 --- a/lib/puppet/network/handler/report.rb +++ b/lib/puppet/network/handler/report.rb @@ -71,8 +71,8 @@ class Puppet::Network::Handler def initialize(*args) super - Puppet.config.use(:reporting) - Puppet.config.use(:metrics) + Puppet.settings.use(:reporting) + Puppet.settings.use(:metrics) end # Accept a report from a client. diff --git a/lib/puppet/network/server/webrick.rb b/lib/puppet/network/server/webrick.rb index 3af0cfd3f..f24411ab3 100644 --- a/lib/puppet/network/server/webrick.rb +++ b/lib/puppet/network/server/webrick.rb @@ -48,7 +48,7 @@ module Puppet # yuck; separate http logs file = nil - Puppet.config.use(:main, :ssl, Puppet[:name]) + Puppet.settings.use(:main, :ssl, Puppet[:name]) if Puppet[:name] == "puppetmasterd" file = Puppet[:masterhttplog] else diff --git a/lib/puppet/node/configuration.rb b/lib/puppet/node/configuration.rb index 360c25fc8..0ae03a651 100644 --- a/lib/puppet/node/configuration.rb +++ b/lib/puppet/node/configuration.rb @@ -357,7 +357,7 @@ class Puppet::Node::Configuration < Puppet::PGraph return unless Puppet[:graph] - Puppet.config.use(:graphing) + Puppet.settings.use(:graphing) file = File.join(Puppet[:graphdir], "%s.dot" % name.to_s) File.open(file, "w") { |f| diff --git a/lib/puppet/parser/interpreter.rb b/lib/puppet/parser/interpreter.rb index a4ea26572..81867c84b 100644 --- a/lib/puppet/parser/interpreter.rb +++ b/lib/puppet/parser/interpreter.rb @@ -68,7 +68,7 @@ class Puppet::Parser::Interpreter elsif self.file parser.file = self.file else - file = Puppet.config.value(:manifest, environment) + file = Puppet.settings.value(:manifest, environment) parser.file = file end parser.parse diff --git a/lib/puppet/rails.rb b/lib/puppet/rails.rb index 670c622eb..53dafce79 100644 --- a/lib/puppet/rails.rb +++ b/lib/puppet/rails.rb @@ -9,7 +9,7 @@ module Puppet::Rails # This global init does not work for testing, because we remove # the state dir on every test. unless ActiveRecord::Base.connected? - Puppet.config.use(:main, :rails, :puppetmasterd) + Puppet.settings.use(:main, :rails, :puppetmasterd) ActiveRecord::Base.logger = Logger.new(Puppet[:railslog]) begin @@ -103,7 +103,7 @@ module Puppet::Rails raise Puppet::DevError, "No activerecord, cannot init Puppet::Rails" end - Puppet.config.use(:puppetmasterd, :rails) + Puppet.settings.use(:puppetmasterd, :rails) begin ActiveRecord::Base.establish_connection(database_arguments()) diff --git a/lib/puppet/reference/configuration.rb b/lib/puppet/reference/configuration.rb index 65fdbeb7d..4b09002b8 100644 --- a/lib/puppet/reference/configuration.rb +++ b/lib/puppet/reference/configuration.rb @@ -1,6 +1,6 @@ config = Puppet::Util::Reference.newreference(:configuration, :depth => 1, :doc => "A reference for all configuration parameters") do docs = {} - Puppet.config.each do |name, object| + Puppet.settings.each do |name, object| docs[name] = object end diff --git a/lib/puppet/reports/rrdgraph.rb b/lib/puppet/reports/rrdgraph.rb index 7209a8401..1516d331a 100644 --- a/lib/puppet/reports/rrdgraph.rb +++ b/lib/puppet/reports/rrdgraph.rb @@ -102,7 +102,7 @@ Puppet::Network::Handler.report.newreport(:rrdgraph) do unless File.directory?(hostdir) and FileTest.writable?(hostdir) # Some hackishness to create the dir with all of the right modes and ownership - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new config.setdefaults(:reports, :hostdir => {:default => hostdir, :owner => Puppet[:user], :mode => 0755, :group => Puppet[:group], :desc => "eh"}) # This creates the dir. diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb index d51f50372..389de4e6e 100644 --- a/lib/puppet/reports/store.rb +++ b/lib/puppet/reports/store.rb @@ -1,7 +1,7 @@ require 'puppet' Puppet::Network::Handler.report.newreport(:store, :useyaml => true) do - Puppet.config.use(:reporting) + Puppet.settings.use(:reporting) desc "Store the yaml report on disk. Each host sends its report as a YAML dump and this just stores the file on disk, in the ``reportdir`` directory. @@ -11,7 +11,7 @@ Puppet::Network::Handler.report.newreport(:store, :useyaml => true) do default report)." def mkclientdir(client, dir) - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new config.setdefaults("reportclient-#{client}", "client-#{client}-dir" => { :default => dir, :mode => 0750, diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index b62a6d2d3..8c65524ee 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -31,7 +31,7 @@ Puppet::Network::Handler.report.newreport(:tagmail) do " - Puppet.config.use(:tagmail) + Puppet.settings.use(:tagmail) # Find all matching messages. def match(taglists) diff --git a/lib/puppet/sslcertificates/ca.rb b/lib/puppet/sslcertificates/ca.rb index 5e355f7fe..fd416dd5e 100644 --- a/lib/puppet/sslcertificates/ca.rb +++ b/lib/puppet/sslcertificates/ca.rb @@ -53,7 +53,7 @@ class Puppet::SSLCertificates::CA end def initialize(hash = {}) - Puppet.config.use(:main, :ca, :ssl) + Puppet.settings.use(:main, :ca, :ssl) self.setconfig(hash) if Puppet[:capass] @@ -73,7 +73,7 @@ class Puppet::SSLCertificates::CA self.getcert init_crl unless FileTest.exists?(@config[:serial]) - Puppet.config.write(:serial) do |f| + Puppet.settings.write(:serial) do |f| f << "%04X" % 1 end end @@ -85,7 +85,7 @@ class Puppet::SSLCertificates::CA 20.times { pass += (rand(74) + 48).chr } begin - Puppet.config.write(:capass) { |f| f.print pass } + Puppet.settings.write(:capass) { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, detail.to_s end @@ -163,10 +163,10 @@ class Puppet::SSLCertificates::CA Puppet::Util::SUIDManager.asuser(Puppet[:user], Puppet[:group]) do @cert = cert.mkselfsigned end - Puppet.config.write(:cacert) do |f| + Puppet.settings.write(:cacert) do |f| f.puts @cert.to_pem end - Puppet.config.write(:capub) do |f| + Puppet.settings.write(:capub) do |f| f.puts @cert.public_key end return cert @@ -201,7 +201,7 @@ class Puppet::SSLCertificates::CA # Take the Puppet config and store it locally. def setconfig(hash) @config = {} - Puppet.config.params("ca").each { |param| + Puppet.settings.params("ca").each { |param| param = param.intern if param.is_a? String if hash.include?(param) @config[param] = hash[param] @@ -303,7 +303,7 @@ class Puppet::SSLCertificates::CA raise Puppet::Error, "Certificate request for %s already exists" % host end - Puppet.config.writesub(:csrdir, csrfile) do |f| + Puppet.settings.writesub(:csrdir, csrfile) do |f| f.print csr.to_pem end end @@ -319,7 +319,7 @@ class Puppet::SSLCertificates::CA end Puppet::SSLCertificates::Inventory::add(cert) - Puppet.config.writesub(:signeddir, certfile) do |f| + Puppet.settings.writesub(:signeddir, certfile) do |f| f.print cert.to_pem end end @@ -389,7 +389,7 @@ class Puppet::SSLCertificates::CA @crl.next_update = now + 5 * 365*24*60*60 sign_with_key(@crl) - Puppet.config.write(:cacrl) do |f| + Puppet.settings.write(:cacrl) do |f| f.puts @crl.to_pem end end diff --git a/lib/puppet/sslcertificates/inventory.rb b/lib/puppet/sslcertificates/inventory.rb index d475c1c86..14d2155a8 100644 --- a/lib/puppet/sslcertificates/inventory.rb +++ b/lib/puppet/sslcertificates/inventory.rb @@ -11,7 +11,7 @@ module Puppet::SSLCertificates inited = false end - Puppet.config.write(:cert_inventory, "a") do |f| + Puppet.settings.write(:cert_inventory, "a") do |f| f.puts((inited ? nil : self.init).to_s + format(cert)) end end diff --git a/lib/puppet/sslcertificates/support.rb b/lib/puppet/sslcertificates/support.rb index e37a52038..c8d2e846b 100644 --- a/lib/puppet/sslcertificates/support.rb +++ b/lib/puppet/sslcertificates/support.rb @@ -44,7 +44,7 @@ module Puppet::SSLCertificates::Support unless instance_variable_get(var) unless cert = send(reader) cert = send(maker) - Puppet.config.write(param) { |f| f.puts cert.to_pem } + Puppet.settings.write(param) { |f| f.puts cert.to_pem } end instance_variable_set(var, cert) end @@ -59,7 +59,7 @@ module Puppet::SSLCertificates::Support # Our key meta programming can only handle one file, so we have # to separately write out the public key. - Puppet.config.write(:hostpubkey) do |f| + Puppet.settings.write(:hostpubkey) do |f| f.print key.public_key.to_pem end return key @@ -104,8 +104,8 @@ module Puppet::SSLCertificates::Support if cert.nil? or cert == "" return nil end - Puppet.config.write(:hostcert) do |f| f.print cert end - Puppet.config.write(:localcacert) do |f| f.print cacert end + Puppet.settings.write(:hostcert) do |f| f.print cert end + Puppet.settings.write(:localcacert) do |f| f.print cacert end #File.open(@certfile, "w", 0644) { |f| f.print cert } #File.open(@cacertfile, "w", 0644) { |f| f.print cacert } begin diff --git a/lib/puppet/util/config.rb b/lib/puppet/util/config.rb deleted file mode 100644 index 4b6ae9e5b..000000000 --- a/lib/puppet/util/config.rb +++ /dev/null @@ -1,1223 +0,0 @@ -require 'puppet' -require 'sync' -require 'puppet/transportable' -require 'getoptlong' - - -# The class for handling configuration files. -class Puppet::Util::Config - include Enumerable - include Puppet::Util - - @@sync = Sync.new - - attr_accessor :file - attr_reader :timer - - # Retrieve a config value - def [](param) - value(param) - end - - # Set a config value. This doesn't set the defaults, it sets the value itself. - def []=(param, value) - @@sync.synchronize do # yay, thread-safe - param = symbolize(param) - unless element = @config[param] - raise ArgumentError, - "Attempt to assign a value to unknown configuration parameter %s" % param.inspect - end - if element.respond_to?(:munge) - value = element.munge(value) - end - if element.respond_to?(:handle) - element.handle(value) - end - # Reset the name, so it's looked up again. - if param == :name - @name = nil - end - @values[:memory][param] = value - @cache.clear - end - - return value - end - - # A simplified equality operator. - 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. - def addargs(options) - # Hackish, but acceptable. Copy the current ARGV for restarting. - Puppet.args = ARGV.dup - - # Add all of the config parameters as valid options. - self.each { |name, element| - element.getopt_args.each { |args| options << args } - } - - return options - end - - # Turn the config into a Puppet configuration and apply it - def apply - trans = self.to_transportable - begin - config = trans.to_configuration - config.store_state = false - config.apply - config.clear - rescue => detail - if Puppet[:trace] - puts detail.backtrace - end - Puppet.err "Could not configure myself: %s" % detail - end - end - - # Is our parameter a boolean parameter? - def boolean?(param) - param = symbolize(param) - if @config.include?(param) and @config[param].kind_of? CBoolean - return true - else - return false - end - end - - # Remove all set values, potentially skipping cli values. - def clear(exceptcli = false) - @config.each { |name, obj| - unless exceptcli and obj.setbycli - obj.clear - end - } - @values.each do |name, values| - next if name == :cli and exceptcli - @values.delete(name) - end - - # Don't clear the 'used' in this case, since it's a config file reparse, - # and we want to retain this info. - unless exceptcli - @used = [] - end - - @cache.clear - - @name = nil - end - - # This is mostly just used for testing. - def clearused - @cache.clear - @used = [] - end - - # Do variable interpolation on the value. - def convert(value) - return value unless value - return value unless value.is_a? String - newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value| - varname = $2 || $1 - if pval = self.value(varname) - pval - else - raise Puppet::DevError, "Could not find value for %s" % parent - end - end - - return newval - end - - # Return a value's description. - def description(name) - if obj = @config[symbolize(name)] - obj.desc - else - nil - end - end - - def each - @config.each { |name, object| - yield name, object - } - end - - # Iterate over each section name. - def eachsection - yielded = [] - @config.each do |name, object| - section = object.section - unless yielded.include? section - yield section - yielded << section - end - end - end - - # Return an object by name. - def element(param) - param = symbolize(param) - @config[param] - end - - # Handle a command-line argument. - def handlearg(opt, value = nil) - @cache.clear - value = munge_value(value) if value - str = opt.sub(/^--/,'') - bool = true - newstr = str.sub(/^no-/, '') - if newstr != str - str = newstr - bool = false - end - str = str.intern - if self.valid?(str) - if self.boolean?(str) - @values[:cli][str] = bool - else - @values[:cli][str] = value - end - else - raise ArgumentError, "Invalid argument %s" % opt - end - end - - def include?(name) - name = name.intern if name.is_a? String - @config.include?(name) - end - - # check to see if a short name is already defined - def shortinclude?(short) - short = short.intern if name.is_a? String - @shortnames.include?(short) - end - - # Create a new config object - def initialize - @config = {} - @shortnames = {} - - @created = [] - @searchpath = nil - - # Keep track of set values. - @values = Hash.new { |hash, key| hash[key] = {} } - - # And keep a per-environment cache - @cache = Hash.new { |hash, key| hash[key] = {} } - - # A central concept of a name. - @name = nil - end - - # Return a given object's file metadata. - def metadata(param) - if obj = @config[symbolize(param)] and obj.is_a?(CFile) - return [:owner, :group, :mode].inject({}) do |meta, p| - if v = obj.send(p) - meta[p] = v - end - meta - end - else - nil - end - end - - # Make a directory with the appropriate user, group, and mode - def mkdir(default) - obj = nil - unless obj = @config[default] - raise ArgumentError, "Unknown default %s" % default - end - - unless obj.is_a? CFile - raise ArgumentError, "Default %s is not a file" % default - end - - Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do - mode = obj.mode || 0750 - Dir.mkdir(obj.value, mode) - end - end - - # Figure out our name. - def name - unless @name - unless @config[:name] - return nil - end - searchpath.each do |source| - next if source == :name - break if @name = @values[source][:name] - end - unless @name - @name = convert(@config[:name].default).intern - end - end - @name - end - - # Return all of the parameters associated with a given section. - def params(section = nil) - if section - section = section.intern if section.is_a? String - @config.find_all { |name, obj| - obj.section == section - }.collect { |name, obj| - name - } - else - @config.keys - end - end - - # Parse the configuration file. - def parse(file) - clear(true) - - parse_file(file).each do |area, values| - @values[area] = values - end - - # We have to do it in the reverse of the search path, - # because multiple sections could set the same value. - searchpath.reverse.each do |source| - if meta = @values[source][:_meta] - set_metadata(meta) - end - end - end - - # Parse the configuration file. As of May 2007, this is a backward-compatibility method and - # will be deprecated soon. - def old_parse(file) - text = nil - - if file.is_a? Puppet::Util::LoadedFile - @file = file - else - @file = Puppet::Util::LoadedFile.new(file) - end - - # Don't create a timer for the old style parsing. - # settimer() - - begin - text = File.read(@file.file) - rescue Errno::ENOENT - raise Puppet::Error, "No such file %s" % file - rescue Errno::EACCES - raise Puppet::Error, "Permission denied to file %s" % file - end - - @values = Hash.new { |names, name| - names[name] = {} - } - - # Get rid of the values set by the file, keeping cli values. - self.clear(true) - - section = "puppet" - metas = %w{owner group mode} - values = Hash.new { |hash, key| hash[key] = {} } - text.split(/\n/).each { |line| - case line - when /^\[(\w+)\]$/: section = $1 # Section names - when /^\s*#/: next # Skip comments - when /^\s*$/: next # Skip blanks - when /^\s*(\w+)\s*=\s*(.+)$/: # settings - var = $1.intern - if var == :mode - value = $2 - else - value = munge_value($2) - end - - # Only warn if we don't know what this config var is. This - # prevents exceptions later on. - unless @config.include?(var) or metas.include?(var.to_s) - Puppet.warning "Discarded unknown configuration parameter %s" % var.inspect - next # Skip this line. - end - - # Mmm, "special" attributes - if metas.include?(var.to_s) - unless values.include?(section) - values[section] = {} - end - values[section][var.to_s] = value - - # If the parameter is valid, then set it. - if section == Puppet[:name] and @config.include?(var) - #@config[var].value = value - @values[:main][var] = value - end - next - end - - # Don't override set parameters, since the file is parsed - # after cli arguments are handled. - unless @config.include?(var) and @config[var].setbycli - Puppet.debug "%s: Setting %s to '%s'" % [section, var, value] - @values[:main][var] = value - end - @config[var].section = symbolize(section) - - metas.each { |meta| - if values[section][meta] - if @config[var].respond_to?(meta + "=") - @config[var].send(meta + "=", values[section][meta]) - end - end - } - else - raise Puppet::Error, "Could not match line %s" % line - end - } - end - - # Create a new element. The value is passed in because it's used to determine - # what kind of element we're creating, but the value itself might be either - # a default or a value, so we can't actually assign it. - def newelement(hash) - value = hash[:value] || hash[:default] - klass = nil - if hash[:section] - hash[:section] = symbolize(hash[:section]) - end - case value - when true, false, "true", "false": - klass = CBoolean - when /^\$\w+\//, /^\//: - klass = CFile - when String, Integer, Float: # nothing - klass = CElement - else - raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]] - end - hash[:parent] = self - element = klass.new(hash) - - return element - end - - # This has to be private, because it doesn't add the elements to @config - private :newelement - - # Iterate across all of the objects in a given section. - def persection(section) - section = symbolize(section) - self.each { |name, obj| - if obj.section == section - yield obj - end - } - end - - # Reparse our config file, if necessary. - def reparse - if defined? @file and @file.changed? - Puppet.notice "Reparsing %s" % @file.file - @@sync.synchronize do - parse(@file) - end - reuse() - end - end - - def reuse - return unless defined? @used - @@sync.synchronize do # yay, thread-safe - @used.each do |section| - @used.delete(section) - self.use(section) - end - end - end - - # The order in which to search for values. - def searchpath(environment = nil) - if environment - [:cli, :memory, environment, :name, :main] - else - [:cli, :memory, :name, :main] - end - end - - # Get a list of objects per section - def sectionlist - sectionlist = [] - self.each { |name, obj| - section = obj.section || "puppet" - sections[section] ||= [] - unless sectionlist.include?(section) - sectionlist << section - end - sections[section] << obj - } - - return sectionlist, sections - end - - # Convert a single section into transportable objects. - def section_to_transportable(section, done = nil) - done ||= Hash.new { |hash, key| hash[key] = {} } - objects = [] - persection(section) do |obj| - if @config[:mkusers] and value(:mkusers) - objects += add_user_resources(section, obj, done) - end - - # Only files are convertable to transportable resources. - if obj.respond_to? :to_transportable - next if value(obj.name) =~ /^\/dev/ - transobjects = obj.to_transportable - transobjects = [transobjects] unless transobjects.is_a? Array - transobjects.each do |trans| - # transportable could return nil - next unless trans - unless done[:file].include? trans.name - @created << trans.name - objects << trans - done[:file][trans.name] = trans - end - end - end - end - - bucket = Puppet::TransBucket.new - bucket.type = "Configuration" - bucket.name = section - bucket.push(*objects) - bucket.keyword = "class" - - return bucket - end - - # Set a bunch of defaults in a given section. The sections are actually pretty - # pointless, but they help break things up a bit, anyway. - def setdefaults(section, defs) - section = symbolize(section) - defs.each { |name, hash| - if hash.is_a? Array - unless hash.length == 2 - raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription" - end - tmp = hash - hash = {} - [:default, :desc].zip(tmp).each { |p,v| hash[p] = v } - end - name = symbolize(name) - hash[:name] = name - hash[:section] = section - name = hash[:name] - if @config.include?(name) - raise ArgumentError, "Parameter %s is already defined" % name - end - tryconfig = newelement(hash) - if short = tryconfig.short - if other = @shortnames[short] - raise ArgumentError, "Parameter %s is already using short name '%s'" % [other.name, short] - end - @shortnames[short] = tryconfig - end - @config[name] = tryconfig - } - end - - # Create a timer to check whether the file should be reparsed. - def settimer - if Puppet[:filetimeout] > 0 - @timer = Puppet.newtimer( - :interval => Puppet[:filetimeout], - :tolerance => 1, - :start? => true - ) do - self.reparse() - end - end - end - - # Convert our list of objects into a component that can be applied. - def to_configuration - transport = self.to_transportable - return transport.to_configuration - end - - # Convert our list of objects into a configuration file. - def to_config - str = %{The configuration file for #{Puppet[:name]}. Note that this file -is likely to have unused configuration parameters in it; any parameter that's -valid anywhere in Puppet can be in any config file, even if it's not used. - -Every section can specify three special parameters: owner, group, and mode. -These parameters affect the required permissions of any files specified after -their specification. Puppet will sometimes use these parameters to check its -own configured state, so they can be used to make Puppet a bit more self-managing. - -Note also that the section names are entirely for human-level organizational -purposes; they don't provide separate namespaces. All parameters are in a -single namespace. - -Generated on #{Time.now}. - -}.gsub(/^/, "# ") - - # Add a section heading that matches our name. - if @config.include?(:name) - str += "[%s]\n" % self[:name] - end - eachsection do |section| - persection(section) do |obj| - str += obj.to_config + "\n" - end - end - - return str - end - - # Convert our configuration into a list of transportable objects. - def to_transportable(*sections) - done = Hash.new { |hash, key| - hash[key] = {} - } - - topbucket = Puppet::TransBucket.new - if defined? @file.file and @file.file - topbucket.name = @file.file - else - topbucket.name = "top" - end - topbucket.type = "Configuration" - topbucket.top = true - - # Now iterate over each section - if sections.empty? - eachsection do |section| - sections << section - end - end - sections.each do |section| - obj = section_to_transportable(section, done) - topbucket.push obj - end - - topbucket - end - - # Convert to a parseable manifest - def to_manifest - transport = self.to_transportable - - manifest = transport.to_manifest + "\n" - eachsection { |section| - manifest += "include #{section}\n" - } - - return manifest - end - - # Create the necessary objects to use a section. This is idempotent; - # you can 'use' a section as many times as you want. - def use(*sections) - @@sync.synchronize do # yay, thread-safe - unless defined? @used - @used = [] - end - - runners = sections.collect { |s| - symbolize(s) - }.find_all { |s| - ! @used.include? s - } - return if runners.empty? - - bucket = to_transportable(*sections) - - config = bucket.to_configuration - config.host_config = false - config.apply - config.clear - -# tags = nil -# if Puppet[:tags] -# tags = Puppet[:tags] -# Puppet[:tags] = "" -# end -# trans = objects.evaluate -# trans.ignoretags = true -# trans.configurator = true -# trans.evaluate -# if tags -# Puppet[:tags] = tags -# end -# -# # Remove is a recursive process, so it's sufficient to just call -# # it on the component. -# objects.remove(true) -# -# objects = nil - - runners.each { |s| @used << s } - end - end - - def valid?(param) - param = symbolize(param) - @config.has_key?(param) - end - - # Find the correct value using our search path. Optionally accept an environment - # in which to search before the other configuration sections. - def value(param, environment = nil) - param = symbolize(param) - environment = symbolize(environment) if environment - - # Short circuit to nil for undefined parameters. - return nil unless @config.include?(param) - - # Yay, recursion. - self.reparse() unless param == :filetimeout - - # Check the cache first. It needs to be a per-environment - # cache so that we don't spread values from one env - # to another. - if @cache[environment||"none"].include?(param) - return @cache[environment||"none"][param] - end - - # See if we can find it within our searchable list of values - val = nil - searchpath(environment).each do |source| - # Modify the source as necessary. - source = case source - when :name: - self.name - else - source - end - - # Look for the value. We have to test the hash for whether - # it exists, because the value might be false. - if @values[source].include?(param) - val = @values[source][param] - break - end - end - - # If we didn't get a value, use the default - if val.nil? - val = @config[param].default - end - - # Convert it if necessary - val = convert(val) - - # And cache it - @cache[environment||"none"][param] = val - return val - end - - # Open a file with the appropriate user, group, and mode - def write(default, *args) - obj = nil - unless obj = @config[default] - raise ArgumentError, "Unknown default %s" % default - end - - unless obj.is_a? CFile - raise ArgumentError, "Default %s is not a file" % default - end - - chown = nil - if Puppet::Util::SUIDManager.uid == 0 - chown = [obj.owner, obj.group] - else - chown = [nil, nil] - end - Puppet::Util::SUIDManager.asuser(*chown) do - mode = obj.mode || 0640 - - if args.empty? - args << "w" - end - - args << mode - - File.open(value(obj.name), *args) do |file| - yield file - end - end - end - - # Open a non-default file under a default dir with the appropriate user, - # group, and mode - def writesub(default, file, *args) - obj = nil - unless obj = @config[default] - raise ArgumentError, "Unknown default %s" % default - end - - unless obj.is_a? CFile - raise ArgumentError, "Default %s is not a file" % default - end - - chown = nil - if Puppet::Util::SUIDManager.uid == 0 - chown = [obj.owner, obj.group] - else - chown = [nil, nil] - end - - Puppet::Util::SUIDManager.asuser(*chown) do - mode = obj.mode || 0640 - if args.empty? - args << "w" - end - - args << mode - - # Update the umask to make non-executable files - Puppet::Util.withumask(File.umask ^ 0111) do - File.open(file, *args) do |file| - yield file - end - end - end - end - - private - - # Create the transportable objects for users and groups. - def add_user_resources(section, obj, done) - resources = [] - [:owner, :group].each do |attr| - type = nil - if attr == :owner - type = :user - else - type = attr - end - # If a user and/or group is set, then make sure we're - # managing that object - if obj.respond_to? attr and name = obj.send(attr) - # Skip root or wheel - next if %w{root wheel}.include?(name.to_s) - - # Skip owners and groups we've already done, but tag - # them with our section if necessary - if done[type].include?(name) - tags = done[type][name].tags - unless tags.include?(section) - done[type][name].tags = tags << section - end - else - newobj = Puppet::TransObject.new(name, type.to_s) - newobj.tags = ["puppet", "configuration", section] - newobj[:ensure] = :present - if type == :user - newobj[:comment] ||= "%s user" % name - end - # Set the group appropriately for the user - if type == :user - newobj[:gid] = Puppet[:group] - end - done[type][name] = newobj - resources << newobj - end - end - end - resources - end - - # Extract extra setting information for files. - def extract_fileinfo(string) - result = {} - value = string.sub(/\{\s*([^}]+)\s*\}/) do - params = $1 - params.split(/\s*,\s*/).each do |str| - if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/ - param, value = $1.intern, $2 - result[param] = value - unless [:owner, :mode, :group].include?(param) - raise ArgumentError, "Invalid file option '%s'" % param - end - - if param == :mode and value !~ /^\d+$/ - raise ArgumentError, "File modes must be numbers" - end - else - raise ArgumentError, "Could not parse '%s'" % string - end - end - '' - end - result[:value] = value.sub(/\s*$/, '') - return result - - return nil - end - - # Convert arguments into booleans, integers, or whatever. - def munge_value(value) - # Handle different data types correctly - return case value - when /^false$/i: false - when /^true$/i: true - when /^\d+$/i: Integer(value) - else - value.gsub(/^["']|["']$/,'').sub(/\s+$/, '') - end - end - - # This is an abstract method that just turns a file in to a hash of hashes. - # We mostly need this for backward compatibility -- as of May 2007 we need to - # support parsing old files with any section, or new files with just two - # valid sections. - def parse_file(file) - text = read_file(file) - - # Create a timer so that this file will get checked automatically - # and reparsed if necessary. - settimer() - - result = Hash.new { |names, name| - names[name] = {} - } - - count = 0 - - # Default to 'main' for the section. - section = :main - result[section][:_meta] = {} - text.split(/\n/).each { |line| - count += 1 - case line - when /^\s*\[(\w+)\]$/: - section = $1.intern # Section names - # Add a meta section - result[section][:_meta] ||= {} - when /^\s*#/: next # Skip comments - when /^\s*$/: next # Skip blanks - when /^\s*(\w+)\s*=\s*(.+)$/: # settings - var = $1.intern - - # We don't want to munge modes, because they're specified in octal, so we'll - # just leave them as a String, since Puppet handles that case correctly. - if var == :mode - value = $2 - else - value = munge_value($2) - end - - # Check to see if this is a file argument and it has extra options - begin - if value.is_a?(String) and options = extract_fileinfo(value) - value = options[:value] - options.delete(:value) - result[section][:_meta][var] = options - end - result[section][var] = value - rescue Puppet::Error => detail - detail.file = file - detail.line = line - raise - end - else - error = Puppet::Error.new("Could not match line %s" % line) - error.file = file - error.line = line - raise error - end - } - - return result - end - - # Read the file in. - def read_file(file) - if file.is_a? Puppet::Util::LoadedFile - @file = file - else - @file = Puppet::Util::LoadedFile.new(file) - end - - begin - return File.read(@file.file) - rescue Errno::ENOENT - raise ArgumentError, "No such file %s" % file - rescue Errno::EACCES - raise ArgumentError, "Permission denied to file %s" % file - end - end - - # Set file metadata. - def set_metadata(meta) - meta.each do |var, values| - values.each do |param, value| - @config[var].send(param.to_s + "=", value) - end - end - end - - # The base element type. - class CElement - attr_accessor :name, :section, :default, :parent, :setbycli - attr_reader :desc, :short - - # Unset any set value. - def clear - @value = nil - end - - def desc=(value) - @desc = value.gsub(/^\s*/, '') - end - - # get the arguments in getopt format - def getopt_args - if short - [["--#{name}", "-#{short}", GetoptLong::REQUIRED_ARGUMENT]] - else - [["--#{name}", GetoptLong::REQUIRED_ARGUMENT]] - end - end - - def hook=(block) - meta_def :handle, &block - end - - # Create the new element. Pretty much just sets the name. - def initialize(args = {}) - if args.include?(:parent) - self.parent = args[:parent] - args.delete(:parent) - end - args.each do |param, value| - method = param.to_s + "=" - unless self.respond_to? method - raise ArgumentError, "%s does not accept %s" % [self.class, param] - end - - self.send(method, value) - end - - unless self.desc - raise ArgumentError, "You must provide a description for the %s config option" % self.name - end - end - - def iscreated - @iscreated = true - end - - def iscreated? - if defined? @iscreated - return @iscreated - else - return false - end - end - - def set? - if defined? @value and ! @value.nil? - return true - else - return false - end - end - - # short name for the celement - def short=(value) - if value.to_s.length != 1 - raise ArgumentError, "Short names can only be one character." - end - @short = value.to_s - end - - # Convert the object to a config statement. - def to_config - str = @desc.gsub(/^/, "# ") + "\n" - - # Add in a statement about the default. - if defined? @default and @default - str += "# The default value is '%s'.\n" % @default - end - - # If the value has not been overridden, then print it out commented - # and unconverted, so it's clear that that's the default and how it - # works. - value = @parent.value(self.name) - - if value != @default - line = "%s = %s" % [@name, value] - else - line = "# %s = %s" % [@name, @default] - end - - str += line + "\n" - - str.gsub(/^/, " ") - end - - # Retrieves the value, or if it's not set, retrieves the default. - def value - @parent.value(self.name) - end - end - - # A file. - class CFile < CElement - attr_writer :owner, :group - attr_accessor :mode, :create - - def group - if defined? @group - return @parent.convert(@group) - else - return nil - end - end - - def owner - if defined? @owner - return @parent.convert(@owner) - else - return nil - end - end - - # Set the type appropriately. Yep, a hack. This supports either naming - # the variable 'dir', or adding a slash at the end. - def munge(value) - # If it's not a fully qualified path... - if value.is_a?(String) and value !~ /^\$/ and value !~ /^\// - # Make it one - value = File.join(Dir.getwd, value) - end - if value.to_s =~ /\/$/ - @type = :directory - return value.sub(/\/$/, '') - end - return value - end - - # Return the appropriate type. - def type - value = @parent.value(self.name) - if @name.to_s =~ /dir/ - return :directory - elsif value.to_s =~ /\/$/ - return :directory - elsif value.is_a? String - return :file - else - return nil - end - end - - # Convert the object to a TransObject instance. - def to_transportable - type = self.type - return nil unless type - path = @parent.value(self.name).split(File::SEPARATOR) - path.shift # remove the leading nil - - objects = [] - path = self.value - - # Skip plain files that don't exist, since we won't be managing them anyway. - return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) or self.create - obj = Puppet::TransObject.new(path, "file") - - # Only create directories, or files that are specifically marked to - # create. - if type == :directory or self.create - obj[:ensure] = type - end - [:mode].each { |var| - if value = self.send(var) - # Don't bother converting the mode, since the file type - # can handle it any old way. - obj[var] = value - end - } - - # Only chown or chgrp when root - if Puppet.features.root? - [:group, :owner].each { |var| - if value = self.send(var) - obj[var] = value - end - } - end - - # And set the loglevel to debug for everything - obj[:loglevel] = "debug" - - # We're not actually modifying any files here, and if we allow a - # filebucket to get used here we get into an infinite recursion - # trying to set the filebucket up. - obj[:backup] = false - - if self.section - obj.tags += ["puppet", "configuration", self.section, self.name] - end - objects << obj - objects - end - - # Make sure any provided variables look up to something. - def validate(value) - return true unless value.is_a? String - value.scan(/\$(\w+)/) { |name| - name = $1 - unless @parent.include?(name) - raise ArgumentError, - "Configuration parameter '%s' is undefined" % - name - end - } - end - end - - # A simple boolean. - class CBoolean < CElement - # get the arguments in getopt format - def getopt_args - if short - [["--#{name}", "-#{short}", GetoptLong::NO_ARGUMENT], - ["--no-#{name}", GetoptLong::NO_ARGUMENT]] - else - [["--#{name}", GetoptLong::NO_ARGUMENT], - ["--no-#{name}", GetoptLong::NO_ARGUMENT]] - end - end - - def munge(value) - case value - when true, "true": return true - when false, "false": return false - else - raise ArgumentError, "Invalid value '%s' for %s" % - [value.inspect, @name] - end - end - end -end diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb index 13bbc2af1..133aa9c2a 100644 --- a/lib/puppet/util/metric.rb +++ b/lib/puppet/util/metric.rb @@ -21,7 +21,7 @@ class Puppet::Util::Metric end def create(start = nil) - Puppet.config.use(:metrics) + Puppet.settings.use(:metrics) start ||= Time.now.to_i - 5 diff --git a/lib/puppet/util/settings.rb b/lib/puppet/util/settings.rb new file mode 100644 index 000000000..f2af13dc2 --- /dev/null +++ b/lib/puppet/util/settings.rb @@ -0,0 +1,1223 @@ +require 'puppet' +require 'sync' +require 'puppet/transportable' +require 'getoptlong' + + +# The class for handling configuration files. +class Puppet::Util::Settings + include Enumerable + include Puppet::Util + + @@sync = Sync.new + + attr_accessor :file + attr_reader :timer + + # Retrieve a config value + def [](param) + value(param) + end + + # Set a config value. This doesn't set the defaults, it sets the value itself. + def []=(param, value) + @@sync.synchronize do # yay, thread-safe + param = symbolize(param) + unless element = @config[param] + raise ArgumentError, + "Attempt to assign a value to unknown configuration parameter %s" % param.inspect + end + if element.respond_to?(:munge) + value = element.munge(value) + end + if element.respond_to?(:handle) + element.handle(value) + end + # Reset the name, so it's looked up again. + if param == :name + @name = nil + end + @values[:memory][param] = value + @cache.clear + end + + return value + end + + # A simplified equality operator. + 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. + def addargs(options) + # Hackish, but acceptable. Copy the current ARGV for restarting. + Puppet.args = ARGV.dup + + # Add all of the config parameters as valid options. + self.each { |name, element| + element.getopt_args.each { |args| options << args } + } + + return options + end + + # Turn the config into a Puppet configuration and apply it + def apply + trans = self.to_transportable + begin + config = trans.to_configuration + config.store_state = false + config.apply + config.clear + rescue => detail + if Puppet[:trace] + puts detail.backtrace + end + Puppet.err "Could not configure myself: %s" % detail + end + end + + # Is our parameter a boolean parameter? + def boolean?(param) + param = symbolize(param) + if @config.include?(param) and @config[param].kind_of? CBoolean + return true + else + return false + end + end + + # Remove all set values, potentially skipping cli values. + def clear(exceptcli = false) + @config.each { |name, obj| + unless exceptcli and obj.setbycli + obj.clear + end + } + @values.each do |name, values| + next if name == :cli and exceptcli + @values.delete(name) + end + + # Don't clear the 'used' in this case, since it's a config file reparse, + # and we want to retain this info. + unless exceptcli + @used = [] + end + + @cache.clear + + @name = nil + end + + # This is mostly just used for testing. + def clearused + @cache.clear + @used = [] + end + + # Do variable interpolation on the value. + def convert(value) + return value unless value + return value unless value.is_a? String + newval = value.gsub(/\$(\w+)|\$\{(\w+)\}/) do |value| + varname = $2 || $1 + if pval = self.value(varname) + pval + else + raise Puppet::DevError, "Could not find value for %s" % parent + end + end + + return newval + end + + # Return a value's description. + def description(name) + if obj = @config[symbolize(name)] + obj.desc + else + nil + end + end + + def each + @config.each { |name, object| + yield name, object + } + end + + # Iterate over each section name. + def eachsection + yielded = [] + @config.each do |name, object| + section = object.section + unless yielded.include? section + yield section + yielded << section + end + end + end + + # Return an object by name. + def element(param) + param = symbolize(param) + @config[param] + end + + # Handle a command-line argument. + def handlearg(opt, value = nil) + @cache.clear + value = munge_value(value) if value + str = opt.sub(/^--/,'') + bool = true + newstr = str.sub(/^no-/, '') + if newstr != str + str = newstr + bool = false + end + str = str.intern + if self.valid?(str) + if self.boolean?(str) + @values[:cli][str] = bool + else + @values[:cli][str] = value + end + else + raise ArgumentError, "Invalid argument %s" % opt + end + end + + def include?(name) + name = name.intern if name.is_a? String + @config.include?(name) + end + + # check to see if a short name is already defined + def shortinclude?(short) + short = short.intern if name.is_a? String + @shortnames.include?(short) + end + + # Create a new config object + def initialize + @config = {} + @shortnames = {} + + @created = [] + @searchpath = nil + + # Keep track of set values. + @values = Hash.new { |hash, key| hash[key] = {} } + + # And keep a per-environment cache + @cache = Hash.new { |hash, key| hash[key] = {} } + + # A central concept of a name. + @name = nil + end + + # Return a given object's file metadata. + def metadata(param) + if obj = @config[symbolize(param)] and obj.is_a?(CFile) + return [:owner, :group, :mode].inject({}) do |meta, p| + if v = obj.send(p) + meta[p] = v + end + meta + end + else + nil + end + end + + # Make a directory with the appropriate user, group, and mode + def mkdir(default) + obj = nil + unless obj = @config[default] + raise ArgumentError, "Unknown default %s" % default + end + + unless obj.is_a? CFile + raise ArgumentError, "Default %s is not a file" % default + end + + Puppet::Util::SUIDManager.asuser(obj.owner, obj.group) do + mode = obj.mode || 0750 + Dir.mkdir(obj.value, mode) + end + end + + # Figure out our name. + def name + unless @name + unless @config[:name] + return nil + end + searchpath.each do |source| + next if source == :name + break if @name = @values[source][:name] + end + unless @name + @name = convert(@config[:name].default).intern + end + end + @name + end + + # Return all of the parameters associated with a given section. + def params(section = nil) + if section + section = section.intern if section.is_a? String + @config.find_all { |name, obj| + obj.section == section + }.collect { |name, obj| + name + } + else + @config.keys + end + end + + # Parse the configuration file. + def parse(file) + clear(true) + + parse_file(file).each do |area, values| + @values[area] = values + end + + # We have to do it in the reverse of the search path, + # because multiple sections could set the same value. + searchpath.reverse.each do |source| + if meta = @values[source][:_meta] + set_metadata(meta) + end + end + end + + # Parse the configuration file. As of May 2007, this is a backward-compatibility method and + # will be deprecated soon. + def old_parse(file) + text = nil + + if file.is_a? Puppet::Util::LoadedFile + @file = file + else + @file = Puppet::Util::LoadedFile.new(file) + end + + # Don't create a timer for the old style parsing. + # settimer() + + begin + text = File.read(@file.file) + rescue Errno::ENOENT + raise Puppet::Error, "No such file %s" % file + rescue Errno::EACCES + raise Puppet::Error, "Permission denied to file %s" % file + end + + @values = Hash.new { |names, name| + names[name] = {} + } + + # Get rid of the values set by the file, keeping cli values. + self.clear(true) + + section = "puppet" + metas = %w{owner group mode} + values = Hash.new { |hash, key| hash[key] = {} } + text.split(/\n/).each { |line| + case line + when /^\[(\w+)\]$/: section = $1 # Section names + when /^\s*#/: next # Skip comments + when /^\s*$/: next # Skip blanks + when /^\s*(\w+)\s*=\s*(.+)$/: # settings + var = $1.intern + if var == :mode + value = $2 + else + value = munge_value($2) + end + + # Only warn if we don't know what this config var is. This + # prevents exceptions later on. + unless @config.include?(var) or metas.include?(var.to_s) + Puppet.warning "Discarded unknown configuration parameter %s" % var.inspect + next # Skip this line. + end + + # Mmm, "special" attributes + if metas.include?(var.to_s) + unless values.include?(section) + values[section] = {} + end + values[section][var.to_s] = value + + # If the parameter is valid, then set it. + if section == Puppet[:name] and @config.include?(var) + #@config[var].value = value + @values[:main][var] = value + end + next + end + + # Don't override set parameters, since the file is parsed + # after cli arguments are handled. + unless @config.include?(var) and @config[var].setbycli + Puppet.debug "%s: Setting %s to '%s'" % [section, var, value] + @values[:main][var] = value + end + @config[var].section = symbolize(section) + + metas.each { |meta| + if values[section][meta] + if @config[var].respond_to?(meta + "=") + @config[var].send(meta + "=", values[section][meta]) + end + end + } + else + raise Puppet::Error, "Could not match line %s" % line + end + } + end + + # Create a new element. The value is passed in because it's used to determine + # what kind of element we're creating, but the value itself might be either + # a default or a value, so we can't actually assign it. + def newelement(hash) + value = hash[:value] || hash[:default] + klass = nil + if hash[:section] + hash[:section] = symbolize(hash[:section]) + end + case value + when true, false, "true", "false": + klass = CBoolean + when /^\$\w+\//, /^\//: + klass = CFile + when String, Integer, Float: # nothing + klass = CElement + else + raise Puppet::Error, "Invalid value '%s' for %s" % [value.inspect, hash[:name]] + end + hash[:parent] = self + element = klass.new(hash) + + return element + end + + # This has to be private, because it doesn't add the elements to @config + private :newelement + + # Iterate across all of the objects in a given section. + def persection(section) + section = symbolize(section) + self.each { |name, obj| + if obj.section == section + yield obj + end + } + end + + # Reparse our config file, if necessary. + def reparse + if defined? @file and @file.changed? + Puppet.notice "Reparsing %s" % @file.file + @@sync.synchronize do + parse(@file) + end + reuse() + end + end + + def reuse + return unless defined? @used + @@sync.synchronize do # yay, thread-safe + @used.each do |section| + @used.delete(section) + self.use(section) + end + end + end + + # The order in which to search for values. + def searchpath(environment = nil) + if environment + [:cli, :memory, environment, :name, :main] + else + [:cli, :memory, :name, :main] + end + end + + # Get a list of objects per section + def sectionlist + sectionlist = [] + self.each { |name, obj| + section = obj.section || "puppet" + sections[section] ||= [] + unless sectionlist.include?(section) + sectionlist << section + end + sections[section] << obj + } + + return sectionlist, sections + end + + # Convert a single section into transportable objects. + def section_to_transportable(section, done = nil) + done ||= Hash.new { |hash, key| hash[key] = {} } + objects = [] + persection(section) do |obj| + if @config[:mkusers] and value(:mkusers) + objects += add_user_resources(section, obj, done) + end + + # Only files are convertable to transportable resources. + if obj.respond_to? :to_transportable + next if value(obj.name) =~ /^\/dev/ + transobjects = obj.to_transportable + transobjects = [transobjects] unless transobjects.is_a? Array + transobjects.each do |trans| + # transportable could return nil + next unless trans + unless done[:file].include? trans.name + @created << trans.name + objects << trans + done[:file][trans.name] = trans + end + end + end + end + + bucket = Puppet::TransBucket.new + bucket.type = "Settings" + bucket.name = section + bucket.push(*objects) + bucket.keyword = "class" + + return bucket + end + + # Set a bunch of defaults in a given section. The sections are actually pretty + # pointless, but they help break things up a bit, anyway. + def setdefaults(section, defs) + section = symbolize(section) + defs.each { |name, hash| + if hash.is_a? Array + unless hash.length == 2 + raise ArgumentError, "Defaults specified as an array must contain only the default value and the decription" + end + tmp = hash + hash = {} + [:default, :desc].zip(tmp).each { |p,v| hash[p] = v } + end + name = symbolize(name) + hash[:name] = name + hash[:section] = section + name = hash[:name] + if @config.include?(name) + raise ArgumentError, "Parameter %s is already defined" % name + end + tryconfig = newelement(hash) + if short = tryconfig.short + if other = @shortnames[short] + raise ArgumentError, "Parameter %s is already using short name '%s'" % [other.name, short] + end + @shortnames[short] = tryconfig + end + @config[name] = tryconfig + } + end + + # Create a timer to check whether the file should be reparsed. + def settimer + if Puppet[:filetimeout] > 0 + @timer = Puppet.newtimer( + :interval => Puppet[:filetimeout], + :tolerance => 1, + :start? => true + ) do + self.reparse() + end + end + end + + # Convert our list of objects into a component that can be applied. + def to_configuration + transport = self.to_transportable + return transport.to_configuration + end + + # Convert our list of objects into a configuration file. + def to_config + str = %{The configuration file for #{Puppet[:name]}. Note that this file +is likely to have unused configuration parameters in it; any parameter that's +valid anywhere in Puppet can be in any config file, even if it's not used. + +Every section can specify three special parameters: owner, group, and mode. +These parameters affect the required permissions of any files specified after +their specification. Puppet will sometimes use these parameters to check its +own configured state, so they can be used to make Puppet a bit more self-managing. + +Note also that the section names are entirely for human-level organizational +purposes; they don't provide separate namespaces. All parameters are in a +single namespace. + +Generated on #{Time.now}. + +}.gsub(/^/, "# ") + + # Add a section heading that matches our name. + if @config.include?(:name) + str += "[%s]\n" % self[:name] + end + eachsection do |section| + persection(section) do |obj| + str += obj.to_config + "\n" + end + end + + return str + end + + # Convert our configuration into a list of transportable objects. + def to_transportable(*sections) + done = Hash.new { |hash, key| + hash[key] = {} + } + + topbucket = Puppet::TransBucket.new + if defined? @file.file and @file.file + topbucket.name = @file.file + else + topbucket.name = "top" + end + topbucket.type = "Settings" + topbucket.top = true + + # Now iterate over each section + if sections.empty? + eachsection do |section| + sections << section + end + end + sections.each do |section| + obj = section_to_transportable(section, done) + topbucket.push obj + end + + topbucket + end + + # Convert to a parseable manifest + def to_manifest + transport = self.to_transportable + + manifest = transport.to_manifest + "\n" + eachsection { |section| + manifest += "include #{section}\n" + } + + return manifest + end + + # Create the necessary objects to use a section. This is idempotent; + # you can 'use' a section as many times as you want. + def use(*sections) + @@sync.synchronize do # yay, thread-safe + unless defined? @used + @used = [] + end + + runners = sections.collect { |s| + symbolize(s) + }.find_all { |s| + ! @used.include? s + } + return if runners.empty? + + bucket = to_transportable(*sections) + + config = bucket.to_configuration + config.host_config = false + config.apply + config.clear + +# tags = nil +# if Puppet[:tags] +# tags = Puppet[:tags] +# Puppet[:tags] = "" +# end +# trans = objects.evaluate +# trans.ignoretags = true +# trans.configurator = true +# trans.evaluate +# if tags +# Puppet[:tags] = tags +# end +# +# # Remove is a recursive process, so it's sufficient to just call +# # it on the component. +# objects.remove(true) +# +# objects = nil + + runners.each { |s| @used << s } + end + end + + def valid?(param) + param = symbolize(param) + @config.has_key?(param) + end + + # Find the correct value using our search path. Optionally accept an environment + # in which to search before the other configuration sections. + def value(param, environment = nil) + param = symbolize(param) + environment = symbolize(environment) if environment + + # Short circuit to nil for undefined parameters. + return nil unless @config.include?(param) + + # Yay, recursion. + self.reparse() unless param == :filetimeout + + # Check the cache first. It needs to be a per-environment + # cache so that we don't spread values from one env + # to another. + if @cache[environment||"none"].include?(param) + return @cache[environment||"none"][param] + end + + # See if we can find it within our searchable list of values + val = nil + searchpath(environment).each do |source| + # Modify the source as necessary. + source = case source + when :name: + self.name + else + source + end + + # Look for the value. We have to test the hash for whether + # it exists, because the value might be false. + if @values[source].include?(param) + val = @values[source][param] + break + end + end + + # If we didn't get a value, use the default + if val.nil? + val = @config[param].default + end + + # Convert it if necessary + val = convert(val) + + # And cache it + @cache[environment||"none"][param] = val + return val + end + + # Open a file with the appropriate user, group, and mode + def write(default, *args) + obj = nil + unless obj = @config[default] + raise ArgumentError, "Unknown default %s" % default + end + + unless obj.is_a? CFile + raise ArgumentError, "Default %s is not a file" % default + end + + chown = nil + if Puppet::Util::SUIDManager.uid == 0 + chown = [obj.owner, obj.group] + else + chown = [nil, nil] + end + Puppet::Util::SUIDManager.asuser(*chown) do + mode = obj.mode || 0640 + + if args.empty? + args << "w" + end + + args << mode + + File.open(value(obj.name), *args) do |file| + yield file + end + end + end + + # Open a non-default file under a default dir with the appropriate user, + # group, and mode + def writesub(default, file, *args) + obj = nil + unless obj = @config[default] + raise ArgumentError, "Unknown default %s" % default + end + + unless obj.is_a? CFile + raise ArgumentError, "Default %s is not a file" % default + end + + chown = nil + if Puppet::Util::SUIDManager.uid == 0 + chown = [obj.owner, obj.group] + else + chown = [nil, nil] + end + + Puppet::Util::SUIDManager.asuser(*chown) do + mode = obj.mode || 0640 + if args.empty? + args << "w" + end + + args << mode + + # Update the umask to make non-executable files + Puppet::Util.withumask(File.umask ^ 0111) do + File.open(file, *args) do |file| + yield file + end + end + end + end + + private + + # Create the transportable objects for users and groups. + def add_user_resources(section, obj, done) + resources = [] + [:owner, :group].each do |attr| + type = nil + if attr == :owner + type = :user + else + type = attr + end + # If a user and/or group is set, then make sure we're + # managing that object + if obj.respond_to? attr and name = obj.send(attr) + # Skip root or wheel + next if %w{root wheel}.include?(name.to_s) + + # Skip owners and groups we've already done, but tag + # them with our section if necessary + if done[type].include?(name) + tags = done[type][name].tags + unless tags.include?(section) + done[type][name].tags = tags << section + end + else + newobj = Puppet::TransObject.new(name, type.to_s) + newobj.tags = ["puppet", "configuration", section] + newobj[:ensure] = :present + if type == :user + newobj[:comment] ||= "%s user" % name + end + # Set the group appropriately for the user + if type == :user + newobj[:gid] = Puppet[:group] + end + done[type][name] = newobj + resources << newobj + end + end + end + resources + end + + # Extract extra setting information for files. + def extract_fileinfo(string) + result = {} + value = string.sub(/\{\s*([^}]+)\s*\}/) do + params = $1 + params.split(/\s*,\s*/).each do |str| + if str =~ /^\s*(\w+)\s*=\s*([\w\d]+)\s*$/ + param, value = $1.intern, $2 + result[param] = value + unless [:owner, :mode, :group].include?(param) + raise ArgumentError, "Invalid file option '%s'" % param + end + + if param == :mode and value !~ /^\d+$/ + raise ArgumentError, "File modes must be numbers" + end + else + raise ArgumentError, "Could not parse '%s'" % string + end + end + '' + end + result[:value] = value.sub(/\s*$/, '') + return result + + return nil + end + + # Convert arguments into booleans, integers, or whatever. + def munge_value(value) + # Handle different data types correctly + return case value + when /^false$/i: false + when /^true$/i: true + when /^\d+$/i: Integer(value) + else + value.gsub(/^["']|["']$/,'').sub(/\s+$/, '') + end + end + + # This is an abstract method that just turns a file in to a hash of hashes. + # We mostly need this for backward compatibility -- as of May 2007 we need to + # support parsing old files with any section, or new files with just two + # valid sections. + def parse_file(file) + text = read_file(file) + + # Create a timer so that this file will get checked automatically + # and reparsed if necessary. + settimer() + + result = Hash.new { |names, name| + names[name] = {} + } + + count = 0 + + # Default to 'main' for the section. + section = :main + result[section][:_meta] = {} + text.split(/\n/).each { |line| + count += 1 + case line + when /^\s*\[(\w+)\]$/: + section = $1.intern # Section names + # Add a meta section + result[section][:_meta] ||= {} + when /^\s*#/: next # Skip comments + when /^\s*$/: next # Skip blanks + when /^\s*(\w+)\s*=\s*(.+)$/: # settings + var = $1.intern + + # We don't want to munge modes, because they're specified in octal, so we'll + # just leave them as a String, since Puppet handles that case correctly. + if var == :mode + value = $2 + else + value = munge_value($2) + end + + # Check to see if this is a file argument and it has extra options + begin + if value.is_a?(String) and options = extract_fileinfo(value) + value = options[:value] + options.delete(:value) + result[section][:_meta][var] = options + end + result[section][var] = value + rescue Puppet::Error => detail + detail.file = file + detail.line = line + raise + end + else + error = Puppet::Error.new("Could not match line %s" % line) + error.file = file + error.line = line + raise error + end + } + + return result + end + + # Read the file in. + def read_file(file) + if file.is_a? Puppet::Util::LoadedFile + @file = file + else + @file = Puppet::Util::LoadedFile.new(file) + end + + begin + return File.read(@file.file) + rescue Errno::ENOENT + raise ArgumentError, "No such file %s" % file + rescue Errno::EACCES + raise ArgumentError, "Permission denied to file %s" % file + end + end + + # Set file metadata. + def set_metadata(meta) + meta.each do |var, values| + values.each do |param, value| + @config[var].send(param.to_s + "=", value) + end + end + end + + # The base element type. + class CElement + attr_accessor :name, :section, :default, :parent, :setbycli + attr_reader :desc, :short + + # Unset any set value. + def clear + @value = nil + end + + def desc=(value) + @desc = value.gsub(/^\s*/, '') + end + + # get the arguments in getopt format + def getopt_args + if short + [["--#{name}", "-#{short}", GetoptLong::REQUIRED_ARGUMENT]] + else + [["--#{name}", GetoptLong::REQUIRED_ARGUMENT]] + end + end + + def hook=(block) + meta_def :handle, &block + end + + # Create the new element. Pretty much just sets the name. + def initialize(args = {}) + if args.include?(:parent) + self.parent = args[:parent] + args.delete(:parent) + end + args.each do |param, value| + method = param.to_s + "=" + unless self.respond_to? method + raise ArgumentError, "%s does not accept %s" % [self.class, param] + end + + self.send(method, value) + end + + unless self.desc + raise ArgumentError, "You must provide a description for the %s config option" % self.name + end + end + + def iscreated + @iscreated = true + end + + def iscreated? + if defined? @iscreated + return @iscreated + else + return false + end + end + + def set? + if defined? @value and ! @value.nil? + return true + else + return false + end + end + + # short name for the celement + def short=(value) + if value.to_s.length != 1 + raise ArgumentError, "Short names can only be one character." + end + @short = value.to_s + end + + # Convert the object to a config statement. + def to_config + str = @desc.gsub(/^/, "# ") + "\n" + + # Add in a statement about the default. + if defined? @default and @default + str += "# The default value is '%s'.\n" % @default + end + + # If the value has not been overridden, then print it out commented + # and unconverted, so it's clear that that's the default and how it + # works. + value = @parent.value(self.name) + + if value != @default + line = "%s = %s" % [@name, value] + else + line = "# %s = %s" % [@name, @default] + end + + str += line + "\n" + + str.gsub(/^/, " ") + end + + # Retrieves the value, or if it's not set, retrieves the default. + def value + @parent.value(self.name) + end + end + + # A file. + class CFile < CElement + attr_writer :owner, :group + attr_accessor :mode, :create + + def group + if defined? @group + return @parent.convert(@group) + else + return nil + end + end + + def owner + if defined? @owner + return @parent.convert(@owner) + else + return nil + end + end + + # Set the type appropriately. Yep, a hack. This supports either naming + # the variable 'dir', or adding a slash at the end. + def munge(value) + # If it's not a fully qualified path... + if value.is_a?(String) and value !~ /^\$/ and value !~ /^\// + # Make it one + value = File.join(Dir.getwd, value) + end + if value.to_s =~ /\/$/ + @type = :directory + return value.sub(/\/$/, '') + end + return value + end + + # Return the appropriate type. + def type + value = @parent.value(self.name) + if @name.to_s =~ /dir/ + return :directory + elsif value.to_s =~ /\/$/ + return :directory + elsif value.is_a? String + return :file + else + return nil + end + end + + # Convert the object to a TransObject instance. + def to_transportable + type = self.type + return nil unless type + path = @parent.value(self.name).split(File::SEPARATOR) + path.shift # remove the leading nil + + objects = [] + path = self.value + + # Skip plain files that don't exist, since we won't be managing them anyway. + return nil unless self.name.to_s =~ /dir$/ or File.exist?(path) or self.create + obj = Puppet::TransObject.new(path, "file") + + # Only create directories, or files that are specifically marked to + # create. + if type == :directory or self.create + obj[:ensure] = type + end + [:mode].each { |var| + if value = self.send(var) + # Don't bother converting the mode, since the file type + # can handle it any old way. + obj[var] = value + end + } + + # Only chown or chgrp when root + if Puppet.features.root? + [:group, :owner].each { |var| + if value = self.send(var) + obj[var] = value + end + } + end + + # And set the loglevel to debug for everything + obj[:loglevel] = "debug" + + # We're not actually modifying any files here, and if we allow a + # filebucket to get used here we get into an infinite recursion + # trying to set the filebucket up. + obj[:backup] = false + + if self.section + obj.tags += ["puppet", "configuration", self.section, self.name] + end + objects << obj + objects + end + + # Make sure any provided variables look up to something. + def validate(value) + return true unless value.is_a? String + value.scan(/\$(\w+)/) { |name| + name = $1 + unless @parent.include?(name) + raise ArgumentError, + "Settings parameter '%s' is undefined" % + name + end + } + end + end + + # A simple boolean. + class CBoolean < CElement + # get the arguments in getopt format + def getopt_args + if short + [["--#{name}", "-#{short}", GetoptLong::NO_ARGUMENT], + ["--no-#{name}", GetoptLong::NO_ARGUMENT]] + else + [["--#{name}", GetoptLong::NO_ARGUMENT], + ["--no-#{name}", GetoptLong::NO_ARGUMENT]] + end + end + + def munge(value) + case value + when true, "true": return true + when false, "false": return false + else + raise ArgumentError, "Invalid value '%s' for %s" % + [value.inspect, @name] + end + end + end +end diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index cd41aa572..9e99057d9 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -46,7 +46,7 @@ class Puppet::Util::Storage self.init def self.load - Puppet.config.use(:main) + Puppet.settings.use(:main) unless File.exists?(Puppet[:statefile]) unless defined? @@state and ! @@state.nil? diff --git a/spec/unit/indirector/exec/node.rb b/spec/unit/indirector/exec/node.rb index 2eaf3c12e..47f4ce7a5 100755 --- a/spec/unit/indirector/exec/node.rb +++ b/spec/unit/indirector/exec/node.rb @@ -7,7 +7,7 @@ require 'puppet/indirector/exec/node' describe Puppet::Indirector::Exec::Node, " when constructing the command to run" do before do @indirection = mock 'indirection' - Puppet.config.stubs(:value).with(:external_nodes).returns("/echo") + Puppet.settings.stubs(:value).with(:external_nodes).returns("/echo") @searcher = Puppet::Indirector::Exec::Node.new end @@ -25,7 +25,7 @@ end describe Puppet::Indirector::Exec::Node, " when handling the results of the command" do before do @indirection = mock 'indirection' - Puppet.config.stubs(:value).with(:external_nodes).returns("/echo") + Puppet.settings.stubs(:value).with(:external_nodes).returns("/echo") @searcher = Puppet::Indirector::Exec::Node.new @node = stub 'node', :fact_merge => nil @name = "yay" diff --git a/spec/unit/indirector/indirection.rb b/spec/unit/indirector/indirection.rb index b2320e0da..326b6b470 100755 --- a/spec/unit/indirector/indirection.rb +++ b/spec/unit/indirector/indirection.rb @@ -49,16 +49,16 @@ describe Puppet::Indirector::Indirection, " when choosing terminus types" do end it "should follow a convention on using per-model configuration parameters to determine the terminus class" do - Puppet.config.expects(:valid?).with('test_terminus').returns(true) - Puppet.config.expects(:value).with('test_terminus').returns(:foo) + Puppet.settings.expects(:valid?).with('test_terminus').returns(true) + Puppet.settings.expects(:value).with('test_terminus').returns(:foo) Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) @indirection.terminus.should equal(@terminus) end it "should use a default system-wide configuration parameter parameter to determine the terminus class when no per-model configuration parameter is available" do - Puppet.config.expects(:valid?).with('test_terminus').returns(false) - Puppet.config.expects(:value).with(:default_terminus).returns(:foo) + Puppet.settings.expects(:valid?).with('test_terminus').returns(false) + Puppet.settings.expects(:value).with(:default_terminus).returns(:foo) Puppet::Indirector::Terminus.expects(:terminus_class).with(:foo, :test).returns(@terminus_class) @indirection.terminus.should equal(@terminus) end diff --git a/spec/unit/indirector/yaml.rb b/spec/unit/indirector/yaml.rb index 339a2acfb..9e1d65e49 100755 --- a/spec/unit/indirector/yaml.rb +++ b/spec/unit/indirector/yaml.rb @@ -20,8 +20,8 @@ module YamlTesting @subject.name = :me @dir = "/what/ever" - Puppet.config.stubs(:use) - Puppet.config.stubs(:value).with(:yamldir).returns(@dir) + Puppet.settings.stubs(:use) + Puppet.settings.stubs(:value).with(:yamldir).returns(@dir) end end diff --git a/spec/unit/node/configuration.rb b/spec/unit/node/configuration.rb index b37cc754d..8ba55f50c 100755 --- a/spec/unit/node/configuration.rb +++ b/spec/unit/node/configuration.rb @@ -305,7 +305,7 @@ describe Puppet::Node::Configuration, " when applying host configurations" do @config.apply end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Node::Configuration, " when applying non-host configurations" do @@ -328,7 +328,7 @@ describe Puppet::Node::Configuration, " when applying non-host configurations" d @config.apply end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Node::Configuration, " when creating a relationship graph" do @@ -467,6 +467,6 @@ describe Puppet::Node::Configuration, " when writing dot files" do end after do - Puppet.config.clear + Puppet.settings.clear end end diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb index 53f362da6..3146f6e7e 100755 --- a/spec/unit/node/node.rb +++ b/spec/unit/node/node.rb @@ -66,7 +66,7 @@ describe Puppet::Node, " when returning the environment" do end it "should return the central environment if there is no environment fact nor explicit environment" do - Puppet.config.expects(:[]).with(:environment).returns(:centralenv) + Puppet.settings.expects(:[]).with(:environment).returns(:centralenv) @node.environment.should == :centralenv end @@ -81,7 +81,7 @@ describe Puppet::Node, " when returning the environment" do end it "should not use an explicit environment that is an empty string" do - Puppet.config.expects(:[]).with(:environment).returns(nil) + Puppet.settings.expects(:[]).with(:environment).returns(nil) @node.environment.should be_nil end end diff --git a/spec/unit/node/searching.rb b/spec/unit/node/searching.rb index b7105050a..e747996e4 100755 --- a/spec/unit/node/searching.rb +++ b/spec/unit/node/searching.rb @@ -74,6 +74,6 @@ describe Puppet::Node::Searching, " when searching for nodes" do end after do - Puppet.config.clear + Puppet.settings.clear end end diff --git a/spec/unit/other/modules.rb b/spec/unit/other/modules.rb index dbd091d9a..f53c43e89 100755 --- a/spec/unit/other/modules.rb +++ b/spec/unit/other/modules.rb @@ -26,7 +26,7 @@ describe Puppet::Module, " when building its search path" do end it "should use the environment-specific search path when a node environment is provided" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/mone:/mtwo") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/mone:/mtwo") File.stubs(:directory?).returns(true) Puppet::Module.modulepath("myenv").should == %w{/mone /mtwo} end @@ -82,30 +82,30 @@ describe Puppet::Module, " when searching for templates" do end it "should use the main templatedir if no module is found" do - Puppet.config.expects(:value).with(:templatedir, nil).returns("/my/templates") + Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet::Module.expects(:find).with("mymod", nil).returns(nil) Puppet::Module.find_template("mymod/mytemplate").should == "/my/templates/mymod/mytemplate" end it "should return unqualified templates directly in the template dir" do - Puppet.config.expects(:value).with(:templatedir, nil).returns("/my/templates") + Puppet.settings.expects(:value).with(:templatedir, nil).returns("/my/templates") Puppet::Module.expects(:find).never Puppet::Module.find_template("mytemplate").should == "/my/templates/mytemplate" end it "should use the environment templatedir if no module is found and an environment is specified" do - Puppet.config.expects(:value).with(:templatedir, "myenv").returns("/myenv/templates") + Puppet.settings.expects(:value).with(:templatedir, "myenv").returns("/myenv/templates") Puppet::Module.expects(:find).with("mymod", "myenv").returns(nil) Puppet::Module.find_template("mymod/mytemplate", "myenv").should == "/myenv/templates/mymod/mytemplate" end it "should use the node environment if specified" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/my/templates") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/my/templates") File.stubs(:directory?).returns(true) Puppet::Module.find_template("mymod/envtemplate", "myenv").should == "/my/templates/mymod/templates/envtemplate" end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Module, " when searching for manifests" do @@ -125,27 +125,27 @@ describe Puppet::Module, " when searching for manifests" do end it "should use the node environment if specified" do - Puppet.config.expects(:value).with(:modulepath, "myenv").returns("/env/modules") + Puppet.settings.expects(:value).with(:modulepath, "myenv").returns("/env/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/env/modules/mymod/manifests/envmanifest.pp").returns(%w{/env/modules/mymod/manifests/envmanifest.pp}) Puppet::Module.find_manifests("mymod/envmanifest.pp", :environment => "myenv").should == ["/env/modules/mymod/manifests/envmanifest.pp"] end it "should return all manifests matching the glob pattern" do - Puppet.config.expects(:value).with(:modulepath, nil).returns("/my/modules") + Puppet.settings.expects(:value).with(:modulepath, nil).returns("/my/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/my/modules/mymod/manifests/yay/*.pp").returns(%w{/one /two}) Puppet::Module.find_manifests("mymod/yay/*.pp").should == %w{/one /two} end it "should default to the 'init.pp' file in the manifests directory" do - Puppet.config.expects(:value).with(:modulepath, nil).returns("/my/modules") + Puppet.settings.expects(:value).with(:modulepath, nil).returns("/my/modules") File.stubs(:directory?).returns(true) Dir.expects(:glob).with("/my/modules/mymod/manifests/init.pp").returns(%w{my manifest}) Puppet::Module.find_manifests("mymod").should == %w{my manifest} end - after { Puppet.config.clear } + after { Puppet.settings.clear } end describe Puppet::Module, " when returning files" do diff --git a/spec/unit/parser/interpreter.rb b/spec/unit/parser/interpreter.rb index c0f9d54b3..a79267b52 100755 --- a/spec/unit/parser/interpreter.rb +++ b/spec/unit/parser/interpreter.rb @@ -77,8 +77,8 @@ describe Puppet::Parser::Interpreter, " when creating parser instances" do file = mock 'file' file.stubs(:changed?).returns(true) file.stubs(:file).returns("/whatever") - Puppet.config.stubs(:read_file).with(file).returns(text) - Puppet.config.parse(file) + Puppet.settings.stubs(:read_file).with(file).returns(text) + Puppet.settings.parse(file) parser1 = mock 'parser1' Puppet::Parser::Parser.expects(:new).with(:environment => :env1).returns(parser1) diff --git a/spec/unit/util/config.rb b/spec/unit/util/config.rb deleted file mode 100755 index 8114acb8e..000000000 --- a/spec/unit/util/config.rb +++ /dev/null @@ -1,535 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../../spec_helper' - -describe Puppet::Util::Config, " when specifying defaults" do - before do - @config = Puppet::Util::Config.new - end - - it "should start with no defined parameters" do - @config.params.length.should == 0 - end - - it "should allow specification of default values associated with a section as an array" do - @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) - end - - it "should not allow duplicate parameter specifications" do - @config.setdefaults(:section, :myvalue => ["a", "b"]) - lambda { @config.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) - end - - it "should allow specification of default values associated with a section as a hash" do - @config.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) - end - - it "should consider defined parameters to be valid" do - @config.setdefaults(:section, :myvalue => ["defaultval", "my description"]) - @config.valid?(:myvalue).should be_true - end - - it "should require a description when defaults are specified with an array" do - lambda { @config.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) - end - - it "should require a description when defaults are specified with a hash" do - lambda { @config.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) - end - - it "should support specifying owner, group, and mode when specifying files" do - @config.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"}) - end - - it "should support specifying a short name" do - @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) - end - - it "should fail when short names conflict" do - @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) - lambda { @config.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) - end -end - -describe Puppet::Util::Config, " when setting values" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :main, :myval => ["val", "desc"] - @config.setdefaults :main, :bool => [true, "desc"] - end - - it "should provide a method for setting values from other objects" do - @config[:myval] = "something else" - @config[:myval].should == "something else" - end - - it "should support a getopt-specific mechanism for setting values" do - @config.handlearg("--myval", "newval") - @config[:myval].should == "newval" - end - - it "should support a getopt-specific mechanism for turning booleans off" do - @config.handlearg("--no-bool") - @config[:bool].should == false - end - - it "should support a getopt-specific mechanism for turning booleans on" do - # Turn it off first - @config[:bool] = false - @config.handlearg("--bool") - @config[:bool].should == true - end - - it "should clear the cache when setting getopt-specific values" do - @config.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] - @config[:two].should == "whah yay" - @config.handlearg("--one", "else") - @config[:two].should == "else yay" - end - - it "should not clear other values when setting getopt-specific values" do - @config[:myval] = "yay" - @config.handlearg("--no-bool") - @config[:myval].should == "yay" - end - - it "should call passed blocks when values are set" do - values = [] - @config.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) - values.should == [] - - @config[:hooker] = "something" - values.should == %w{something} - end - - it "should munge values using the element-specific methods" do - @config[:bool] = "false" - @config[:bool].should == false - end - - it "should prefer cli values to values set in Ruby code" do - @config.handlearg("--myval", "cliarg") - @config[:myval] = "memarg" - @config[:myval].should == "cliarg" - end -end - -describe Puppet::Util::Config, " when returning values" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] - end - - it "should provide a mechanism for returning set values" do - @config[:one] = "other" - @config[:one].should == "other" - end - - it "should interpolate default values for other parameters into returned parameter values" do - @config[:one].should == "ONE" - @config[:two].should == "ONE TWO" - @config[:three].should == "ONE ONE TWO THREE" - end - - it "should interpolate default values that themselves need to be interpolated" do - @config[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" - end - - it "should interpolate set values for other parameters into returned parameter values" do - @config[:one] = "on3" - @config[:two] = "$one tw0" - @config[:three] = "$one $two thr33" - @config[:four] = "$one $two $three f0ur" - @config[:one].should == "on3" - @config[:two].should == "on3 tw0" - @config[:three].should == "on3 on3 tw0 thr33" - @config[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" - end - - it "should not cache interpolated values such that stale information is returned" do - @config[:two].should == "ONE TWO" - @config[:one] = "one" - @config[:two].should == "one TWO" - end - - it "should not cache values such that information from one environment is returned for another environment" do - text = "[env1]\none = oneval\n[env2]\none = twoval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config.value(:one, "env1").should == "oneval" - @config.value(:one, "env2").should == "twoval" - end - - it "should have a name determined by the 'name' parameter" do - @config.setdefaults(:whatever, :name => ["something", "yayness"]) - @config.name.should == :something - @config[:name] = :other - @config.name.should == :other - end -end - -describe Puppet::Util::Config, " when choosing which value to return" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, - :one => ["ONE", "a"], - :name => ["myname", "w"] - end - - it "should return default values if no values have been set" do - @config[:one].should == "ONE" - end - - it "should return values set on the cli before values set in the configuration file" do - text = "[main]\none = fileval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:parse_file).returns(text) - @config.handlearg("--one", "clival") - @config.parse(file) - - @config[:one].should == "clival" - end - - it "should return values set on the cli before values set in Ruby" do - @config[:one] = "rubyval" - @config.handlearg("--one", "clival") - @config[:one].should == "clival" - end - - it "should return values set in the executable-specific section before values set in the main section" do - text = "[main]\none = mainval\n[myname]\none = nameval\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config[:one].should == "nameval" - end - - it "should not return values outside of its search path" do - text = "[other]\none = oval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "ONE" - end - - it "should return values in a specified environment" do - text = "[env]\none = envval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config.value(:one, "env").should == "envval" - end - - it "should return values in a specified environment before values in the main or name sections" do - text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" - file = "/some/file" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/whatever") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - @config.value(:one, "env").should == "envval" - end -end - -describe Puppet::Util::Config, " when parsing its configuration" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - end - - it "should return values set in the configuration file" do - text = "[main] - one = fileval - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "fileval" - end - - #484 - this should probably be in the regression area - it "should not throw an exception on unknown parameters" do - text = "[main]\nnosuchparam = mval\n" - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - lambda { @config.parse(file) }.should_not raise_error - end - - it "should support an old parse method when per-executable configuration files still exist" do - # I'm not going to bother testing this method. - @config.should respond_to(:old_parse) - end - - it "should convert booleans in the configuration file into Ruby booleans" do - text = "[main] - one = true - two = false - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == true - @config[:two].should == false - end - - it "should convert integers in the configuration file into Ruby Integers" do - text = "[main] - one = 65 - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == 65 - end - - it "should support specifying file all metadata (owner, group, mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/myfile", "a"] - - text = "[main] - myfile = /other/file {owner = luke, group = luke, mode = 644} - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:myfile].should == "/other/file" - @config.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"} - end - - it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do - @config.setdefaults :section, :myfile => ["/myfile", "a"] - - text = "[main] - myfile = /other/file {owner = luke} - " - file = "/some/file" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:myfile].should == "/other/file" - @config.metadata(:myfile).should == {:owner => "luke"} - end -end - -describe Puppet::Util::Config, " when reparsing its configuration" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] - end - - it "should replace in-memory values with on-file values" do - # Init the value - text = "[main]\none = disk-init\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/test/file") - @config[:one] = "init" - @config.file = file - - # Now replace the value - text = "[main]\none = disk-replace\n" - - # This is kinda ridiculous - the reason it parses twice is that - # it goes to parse again when we ask for the value, because the - # mock always says it should get reparsed. - @config.expects(:read_file).with(file).returns(text).times(2) - @config.reparse - @config[:one].should == "disk-replace" - end - - it "should retain parameters set by cli when configuration files are reparsed" do - @config.handlearg("--one", "clival") - - text = "[main]\none = on-disk\n" - file = mock 'file' - file.stubs(:file).returns("/test/file") - @config.stubs(:read_file).with(file).returns(text) - @config.parse(file) - - @config[:one].should == "clival" - end - - it "should remove in-memory values that are no longer set in the file" do - # Init the value - text = "[main]\none = disk-init\n" - file = mock 'file' - file.stubs(:changed?).returns(true) - file.stubs(:file).returns("/test/file") - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - @config[:one].should == "disk-init" - - # Now replace the value - text = "[main]\ntwo = disk-replace\n" - @config.expects(:read_file).with(file).returns(text) - @config.parse(file) - #@config.reparse - - # The originally-overridden value should be replaced with the default - @config[:one].should == "ONE" - - # and we should now have the new value in memory - @config[:two].should == "disk-replace" - end -end - -describe Puppet::Util::Config, " when being used to manage the host machine" do - before do - @config = Puppet::Util::Config.new - @config.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] - @config.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "luke", :group => "johnny", :mode => 0755} - @config.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755} - end - - it "should provide a method that writes files with the correct modes" do - pending "Not converted from test/unit yet" - end - - it "should provide a method that creates directories with the correct modes" do - Puppet::Util::SUIDManager.expects(:asuser).with("luke", "johnny").yields - Dir.expects(:mkdir).with("/otherdir", 0755) - @config.mkdir(:otherdir) - end - - it "should be able to create needed directories in a single section" do - Dir.expects(:mkdir).with("/maindir") - Dir.expects(:mkdir).with("/seconddir") - @config.use(:main) - end - - it "should be able to create needed directories in multiple sections" do - Dir.expects(:mkdir).with("/maindir") - Dir.expects(:mkdir).with("/otherdir", 0755) - Dir.expects(:mkdir).with("/seconddir") - @config.use(:main, :other) - end - - it "should provide a method to trigger enforcing of file modes on existing files and directories" do - pending "Not converted from test/unit yet" - end - - it "should provide a method to convert the file mode enforcement into a Puppet manifest" do - pending "Not converted from test/unit yet" - end - - it "should create files when configured to do so with the :create parameter" - - it "should provide a method to convert the file mode enforcement into transportable resources" do - # Make it think we're root so it tries to manage user and group. - Puppet.features.stubs(:root?).returns(true) - File.stubs(:exist?).with("/myfile").returns(true) - trans = nil - trans = @config.to_transportable - resources = [] - trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject } - %w{/maindir /seconddir /otherdir /myfile}.each do |path| - obj = resources.find { |r| r.type == "file" and r.name == path } - if path.include?("dir") - obj[:ensure].should == :directory - else - # Do not create the file, just manage mode - obj[:ensure].should be_nil - end - obj.should be_instance_of(Puppet::TransObject) - case path - when "/otherdir": - obj[:owner].should == "luke" - obj[:group].should == "johnny" - obj[:mode].should == 0755 - when "/myfile": - obj[:mode].should == 0755 - end - end - end - - it "should not try to manage user or group when not running as root" do - Puppet.features.stubs(:root?).returns(false) - trans = nil - trans = @config.to_transportable(:other) - trans.delve do |obj| - next unless obj.is_a?(Puppet::TransObject) - obj[:owner].should be_nil - obj[:group].should be_nil - end - end - - it "should add needed users and groups to the manifest when asked" do - # This is how we enable user/group management - @config.setdefaults :main, :mkusers => [true, "w"] - Puppet.features.stubs(:root?).returns(false) - trans = nil - trans = @config.to_transportable(:other) - resources = [] - trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject and obj.type != "file" } - - user = resources.find { |r| r.type == "user" } - user.should be_instance_of(Puppet::TransObject) - user.name.should == "luke" - user[:ensure].should == :present - - # This should maybe be a separate test, but... - group = resources.find { |r| r.type == "group" } - group.should be_instance_of(Puppet::TransObject) - group.name.should == "johnny" - group[:ensure].should == :present - end - - it "should ignore tags and schedules when creating files and directories" - - it "should apply all resources in debug mode to reduce logging" - - it "should not try to manage absent files" do - # Make it think we're root so it tries to manage user and group. - Puppet.features.stubs(:root?).returns(true) - trans = nil - trans = @config.to_transportable - file = nil - trans.delve { |obj| file = obj if obj.name == "/myfile" } - file.should be_nil - end - - it "should be able to turn the current configuration into a parseable manifest" - - it "should convert octal numbers correctly when producing a manifest" - - it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do - pending "Not converted from test/unit yet" - end - - it "should not attempt to manage files within /dev" do - pending "Not converted from test/unit yet" - end - - it "should not modify the stored state database when managing resources" do - Puppet::Util::Storage.expects(:store).never - Puppet::Util::Storage.expects(:load).never - Dir.expects(:mkdir).with("/maindir") - @config.use(:main) - end - - it "should convert all relative paths to fully-qualified paths (#795)" do - @config[:myfile] = "unqualified" - dir = Dir.getwd - @config[:myfile].should == File.join(dir, "unqualified") - end - - it "should support a method for re-using all currently used sections" do - Dir.expects(:mkdir).with(@config[:otherdir], 0755).times(2) - @config.use(:other) - @config.reuse - end -end diff --git a/spec/unit/util/settings.rb b/spec/unit/util/settings.rb new file mode 100755 index 000000000..8d11737b3 --- /dev/null +++ b/spec/unit/util/settings.rb @@ -0,0 +1,535 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' + +describe Puppet::Util::Settings, " when specifying defaults" do + before do + @settings = Puppet::Util::Settings.new + end + + it "should start with no defined parameters" do + @settings.params.length.should == 0 + end + + it "should allow specification of default values associated with a section as an array" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + end + + it "should not allow duplicate parameter specifications" do + @settings.setdefaults(:section, :myvalue => ["a", "b"]) + lambda { @settings.setdefaults(:section, :myvalue => ["c", "d"]) }.should raise_error(ArgumentError) + end + + it "should allow specification of default values associated with a section as a hash" do + @settings.setdefaults(:section, :myvalue => {:default => "defaultval", :desc => "my description"}) + end + + it "should consider defined parameters to be valid" do + @settings.setdefaults(:section, :myvalue => ["defaultval", "my description"]) + @settings.valid?(:myvalue).should be_true + end + + it "should require a description when defaults are specified with an array" do + lambda { @settings.setdefaults(:section, :myvalue => ["a value"]) }.should raise_error(ArgumentError) + end + + it "should require a description when defaults are specified with a hash" do + lambda { @settings.setdefaults(:section, :myvalue => {:default => "a value"}) }.should raise_error(ArgumentError) + end + + it "should support specifying owner, group, and mode when specifying files" do + @settings.setdefaults(:section, :myvalue => {:default => "/some/file", :owner => "blah", :mode => "boo", :group => "yay", :desc => "whatever"}) + end + + it "should support specifying a short name" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + end + + it "should fail when short names conflict" do + @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) + lambda { @settings.setdefaults(:section, :myvalue => {:default => "w", :desc => "b", :short => "m"}) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Util::Settings, " when setting values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :myval => ["val", "desc"] + @settings.setdefaults :main, :bool => [true, "desc"] + end + + it "should provide a method for setting values from other objects" do + @settings[:myval] = "something else" + @settings[:myval].should == "something else" + end + + it "should support a getopt-specific mechanism for setting values" do + @settings.handlearg("--myval", "newval") + @settings[:myval].should == "newval" + end + + it "should support a getopt-specific mechanism for turning booleans off" do + @settings.handlearg("--no-bool") + @settings[:bool].should == false + end + + it "should support a getopt-specific mechanism for turning booleans on" do + # Turn it off first + @settings[:bool] = false + @settings.handlearg("--bool") + @settings[:bool].should == true + end + + it "should clear the cache when setting getopt-specific values" do + @settings.setdefaults :mysection, :one => ["whah", "yay"], :two => ["$one yay", "bah"] + @settings[:two].should == "whah yay" + @settings.handlearg("--one", "else") + @settings[:two].should == "else yay" + end + + it "should not clear other values when setting getopt-specific values" do + @settings[:myval] = "yay" + @settings.handlearg("--no-bool") + @settings[:myval].should == "yay" + end + + it "should call passed blocks when values are set" do + values = [] + @settings.setdefaults(:section, :hooker => {:default => "yay", :desc => "boo", :hook => lambda { |v| values << v }}) + values.should == [] + + @settings[:hooker] = "something" + values.should == %w{something} + end + + it "should munge values using the element-specific methods" do + @settings[:bool] = "false" + @settings[:bool].should == false + end + + it "should prefer cli values to values set in Ruby code" do + @settings.handlearg("--myval", "cliarg") + @settings[:myval] = "memarg" + @settings[:myval].should == "cliarg" + end +end + +describe Puppet::Util::Settings, " when returning values" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"], :four => ["$two $three FOUR", "d"] + end + + it "should provide a mechanism for returning set values" do + @settings[:one] = "other" + @settings[:one].should == "other" + end + + it "should interpolate default values for other parameters into returned parameter values" do + @settings[:one].should == "ONE" + @settings[:two].should == "ONE TWO" + @settings[:three].should == "ONE ONE TWO THREE" + end + + it "should interpolate default values that themselves need to be interpolated" do + @settings[:four].should == "ONE TWO ONE ONE TWO THREE FOUR" + end + + it "should interpolate set values for other parameters into returned parameter values" do + @settings[:one] = "on3" + @settings[:two] = "$one tw0" + @settings[:three] = "$one $two thr33" + @settings[:four] = "$one $two $three f0ur" + @settings[:one].should == "on3" + @settings[:two].should == "on3 tw0" + @settings[:three].should == "on3 on3 tw0 thr33" + @settings[:four].should == "on3 on3 tw0 on3 on3 tw0 thr33 f0ur" + end + + it "should not cache interpolated values such that stale information is returned" do + @settings[:two].should == "ONE TWO" + @settings[:one] = "one" + @settings[:two].should == "one TWO" + end + + it "should not cache values such that information from one environment is returned for another environment" do + text = "[env1]\none = oneval\n[env2]\none = twoval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings.value(:one, "env1").should == "oneval" + @settings.value(:one, "env2").should == "twoval" + end + + it "should have a name determined by the 'name' parameter" do + @settings.setdefaults(:whatever, :name => ["something", "yayness"]) + @settings.name.should == :something + @settings[:name] = :other + @settings.name.should == :other + end +end + +describe Puppet::Util::Settings, " when choosing which value to return" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, + :one => ["ONE", "a"], + :name => ["myname", "w"] + end + + it "should return default values if no values have been set" do + @settings[:one].should == "ONE" + end + + it "should return values set on the cli before values set in the configuration file" do + text = "[main]\none = fileval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:parse_file).returns(text) + @settings.handlearg("--one", "clival") + @settings.parse(file) + + @settings[:one].should == "clival" + end + + it "should return values set on the cli before values set in Ruby" do + @settings[:one] = "rubyval" + @settings.handlearg("--one", "clival") + @settings[:one].should == "clival" + end + + it "should return values set in the executable-specific section before values set in the main section" do + text = "[main]\none = mainval\n[myname]\none = nameval\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings[:one].should == "nameval" + end + + it "should not return values outside of its search path" do + text = "[other]\none = oval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "ONE" + end + + it "should return values in a specified environment" do + text = "[env]\none = envval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings.value(:one, "env").should == "envval" + end + + it "should return values in a specified environment before values in the main or name sections" do + text = "[env]\none = envval\n[main]\none = mainval\n[myname]\none = nameval\n" + file = "/some/file" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/whatever") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + @settings.value(:one, "env").should == "envval" + end +end + +describe Puppet::Util::Settings, " when parsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + end + + it "should return values set in the configuration file" do + text = "[main] + one = fileval + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "fileval" + end + + #484 - this should probably be in the regression area + it "should not throw an exception on unknown parameters" do + text = "[main]\nnosuchparam = mval\n" + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + lambda { @settings.parse(file) }.should_not raise_error + end + + it "should support an old parse method when per-executable configuration files still exist" do + # I'm not going to bother testing this method. + @settings.should respond_to(:old_parse) + end + + it "should convert booleans in the configuration file into Ruby booleans" do + text = "[main] + one = true + two = false + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == true + @settings[:two].should == false + end + + it "should convert integers in the configuration file into Ruby Integers" do + text = "[main] + one = 65 + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == 65 + end + + it "should support specifying file all metadata (owner, group, mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = luke, group = luke, mode = 644} + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "luke", :group => "luke", :mode => "644"} + end + + it "should support specifying file a single piece of metadata (owner, group, or mode) in the configuration file" do + @settings.setdefaults :section, :myfile => ["/myfile", "a"] + + text = "[main] + myfile = /other/file {owner = luke} + " + file = "/some/file" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:myfile].should == "/other/file" + @settings.metadata(:myfile).should == {:owner => "luke"} + end +end + +describe Puppet::Util::Settings, " when reparsing its configuration" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :section, :one => ["ONE", "a"], :two => ["$one TWO", "b"], :three => ["$one $two THREE", "c"] + end + + it "should replace in-memory values with on-file values" do + # Init the value + text = "[main]\none = disk-init\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/test/file") + @settings[:one] = "init" + @settings.file = file + + # Now replace the value + text = "[main]\none = disk-replace\n" + + # This is kinda ridiculous - the reason it parses twice is that + # it goes to parse again when we ask for the value, because the + # mock always says it should get reparsed. + @settings.expects(:read_file).with(file).returns(text).times(2) + @settings.reparse + @settings[:one].should == "disk-replace" + end + + it "should retain parameters set by cli when configuration files are reparsed" do + @settings.handlearg("--one", "clival") + + text = "[main]\none = on-disk\n" + file = mock 'file' + file.stubs(:file).returns("/test/file") + @settings.stubs(:read_file).with(file).returns(text) + @settings.parse(file) + + @settings[:one].should == "clival" + end + + it "should remove in-memory values that are no longer set in the file" do + # Init the value + text = "[main]\none = disk-init\n" + file = mock 'file' + file.stubs(:changed?).returns(true) + file.stubs(:file).returns("/test/file") + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + @settings[:one].should == "disk-init" + + # Now replace the value + text = "[main]\ntwo = disk-replace\n" + @settings.expects(:read_file).with(file).returns(text) + @settings.parse(file) + #@settings.reparse + + # The originally-overridden value should be replaced with the default + @settings[:one].should == "ONE" + + # and we should now have the new value in memory + @settings[:two].should == "disk-replace" + end +end + +describe Puppet::Util::Settings, " when being used to manage the host machine" do + before do + @settings = Puppet::Util::Settings.new + @settings.setdefaults :main, :maindir => ["/maindir", "a"], :seconddir => ["/seconddir", "a"] + @settings.setdefaults :other, :otherdir => {:default => "/otherdir", :desc => "a", :owner => "luke", :group => "johnny", :mode => 0755} + @settings.setdefaults :files, :myfile => {:default => "/myfile", :desc => "a", :mode => 0755} + end + + it "should provide a method that writes files with the correct modes" do + pending "Not converted from test/unit yet" + end + + it "should provide a method that creates directories with the correct modes" do + Puppet::Util::SUIDManager.expects(:asuser).with("luke", "johnny").yields + Dir.expects(:mkdir).with("/otherdir", 0755) + @settings.mkdir(:otherdir) + end + + it "should be able to create needed directories in a single section" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/seconddir") + @settings.use(:main) + end + + it "should be able to create needed directories in multiple sections" do + Dir.expects(:mkdir).with("/maindir") + Dir.expects(:mkdir).with("/otherdir", 0755) + Dir.expects(:mkdir).with("/seconddir") + @settings.use(:main, :other) + end + + it "should provide a method to trigger enforcing of file modes on existing files and directories" do + pending "Not converted from test/unit yet" + end + + it "should provide a method to convert the file mode enforcement into a Puppet manifest" do + pending "Not converted from test/unit yet" + end + + it "should create files when configured to do so with the :create parameter" + + it "should provide a method to convert the file mode enforcement into transportable resources" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + File.stubs(:exist?).with("/myfile").returns(true) + trans = nil + trans = @settings.to_transportable + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject } + %w{/maindir /seconddir /otherdir /myfile}.each do |path| + obj = resources.find { |r| r.type == "file" and r.name == path } + if path.include?("dir") + obj[:ensure].should == :directory + else + # Do not create the file, just manage mode + obj[:ensure].should be_nil + end + obj.should be_instance_of(Puppet::TransObject) + case path + when "/otherdir": + obj[:owner].should == "luke" + obj[:group].should == "johnny" + obj[:mode].should == 0755 + when "/myfile": + obj[:mode].should == 0755 + end + end + end + + it "should not try to manage user or group when not running as root" do + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @settings.to_transportable(:other) + trans.delve do |obj| + next unless obj.is_a?(Puppet::TransObject) + obj[:owner].should be_nil + obj[:group].should be_nil + end + end + + it "should add needed users and groups to the manifest when asked" do + # This is how we enable user/group management + @settings.setdefaults :main, :mkusers => [true, "w"] + Puppet.features.stubs(:root?).returns(false) + trans = nil + trans = @settings.to_transportable(:other) + resources = [] + trans.delve { |obj| resources << obj if obj.is_a? Puppet::TransObject and obj.type != "file" } + + user = resources.find { |r| r.type == "user" } + user.should be_instance_of(Puppet::TransObject) + user.name.should == "luke" + user[:ensure].should == :present + + # This should maybe be a separate test, but... + group = resources.find { |r| r.type == "group" } + group.should be_instance_of(Puppet::TransObject) + group.name.should == "johnny" + group[:ensure].should == :present + end + + it "should ignore tags and schedules when creating files and directories" + + it "should apply all resources in debug mode to reduce logging" + + it "should not try to manage absent files" do + # Make it think we're root so it tries to manage user and group. + Puppet.features.stubs(:root?).returns(true) + trans = nil + trans = @settings.to_transportable + file = nil + trans.delve { |obj| file = obj if obj.name == "/myfile" } + file.should be_nil + end + + it "should be able to turn the current configuration into a parseable manifest" + + it "should convert octal numbers correctly when producing a manifest" + + it "should be able to provide all of its parameters in a format compatible with GetOpt::Long" do + pending "Not converted from test/unit yet" + end + + it "should not attempt to manage files within /dev" do + pending "Not converted from test/unit yet" + end + + it "should not modify the stored state database when managing resources" do + Puppet::Util::Storage.expects(:store).never + Puppet::Util::Storage.expects(:load).never + Dir.expects(:mkdir).with("/maindir") + @settings.use(:main) + end + + it "should convert all relative paths to fully-qualified paths (#795)" do + @settings[:myfile] = "unqualified" + dir = Dir.getwd + @settings[:myfile].should == File.join(dir, "unqualified") + end + + it "should support a method for re-using all currently used sections" do + Dir.expects(:mkdir).with(@settings[:otherdir], 0755).times(2) + @settings.use(:other) + @settings.reuse + end +end diff --git a/test/certmgr/certmgr.rb b/test/certmgr/certmgr.rb index fb1611d7f..3d863dc27 100755 --- a/test/certmgr/certmgr.rb +++ b/test/certmgr/certmgr.rb @@ -239,13 +239,13 @@ class TestCertMgr < Test::Unit::TestCase ca.revoke(h1.serial) - oldcert = File.read(Puppet.config[:cacert]) - oldserial = File.read(Puppet.config[:serial]) + oldcert = File.read(Puppet.settings[:cacert]) + oldserial = File.read(Puppet.settings[:serial]) # Recreate the CA from disk ca = mkCA() - newcert = File.read(Puppet.config[:cacert]) - newserial = File.read(Puppet.config[:serial]) + newcert = File.read(Puppet.settings[:cacert]) + newserial = File.read(Puppet.settings[:serial]) assert_equal(oldcert, newcert, "The certs are not equal after making a new CA.") assert_equal(oldserial, newserial, "The serials are not equal after making a new CA.") store = mkStore(ca) diff --git a/test/certmgr/inventory.rb b/test/certmgr/inventory.rb index 15d3e5217..9efcb0c09 100755 --- a/test/certmgr/inventory.rb +++ b/test/certmgr/inventory.rb @@ -56,8 +56,8 @@ class TestCertInventory < Test::Unit::TestCase file.expects(:puts).with do |written| written.include? cert.subject.to_s end - Puppet::Util::Config.any_instance.stubs(:write) - Puppet::Util::Config.any_instance.expects(:write). + Puppet::Util::Settings.any_instance.stubs(:write) + Puppet::Util::Settings.any_instance.expects(:write). with(:cert_inventory, 'a').yields(file) Puppet::SSLCertificates::Inventory.add(cert) diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb index 33e3b2daf..021d6d1de 100755 --- a/test/lib/puppettest.rb +++ b/test/lib/puppettest.rb @@ -154,7 +154,7 @@ module PuppetTest $group = nonrootgroup().gid.to_s end - Puppet.config.clear + Puppet.settings.clear Puppet[:user] = $user Puppet[:group] = $group @@ -264,7 +264,7 @@ module PuppetTest Puppet::Type.allclear Puppet::Util::Storage.clear Puppet.clear - Puppet.config.clear + Puppet.settings.clear Puppet::Indirector::Indirection.clear_cache @memoryatend = Puppet::Util.memory diff --git a/test/network/client/ca.rb b/test/network/client/ca.rb index 00ed7413a..511b6fcaa 100755 --- a/test/network/client/ca.rb +++ b/test/network/client/ca.rb @@ -23,7 +23,7 @@ class TestClientCA < Test::Unit::TestCase end [:hostprivkey, :hostcert, :localcacert].each do |name| - assert(FileTest.exists?(Puppet.config[name]), + assert(FileTest.exists?(Puppet.settings[name]), "Did not create cert %s" % name) end end diff --git a/test/network/client/client.rb b/test/network/client/client.rb index 93c63d637..382cd55cf 100755 --- a/test/network/client/client.rb +++ b/test/network/client/client.rb @@ -113,9 +113,9 @@ class TestClient < Test::Unit::TestCase # Create a new ssl root. confdir = tempfile() Puppet[:ssldir] = confdir - Puppet.config.mkdir(:ssldir) - Puppet.config.clearused - Puppet.config.use(:ssl, :ca) + Puppet.settings.mkdir(:ssldir) + Puppet.settings.clearused + Puppet.settings.use(:ssl, :ca) mkserver diff --git a/test/network/client/master.rb b/test/network/client/master.rb index 2e9ed2752..169a1de5f 100755 --- a/test/network/client/master.rb +++ b/test/network/client/master.rb @@ -680,7 +680,7 @@ end client.apply # Make sure the config is not cached. - config = Puppet.config[:localconfig] + ".yaml" + config = Puppet.settings[:localconfig] + ".yaml" assert(! File.exists?(config), "Cached an invalid configuration") end end diff --git a/test/network/handler/master.rb b/test/network/handler/master.rb index df946fa33..4f8e7fab2 100755 --- a/test/network/handler/master.rb +++ b/test/network/handler/master.rb @@ -75,7 +75,7 @@ class TestMaster < Test::Unit::TestCase assert(! client.fresh?(facts), "Client is incorrectly up to date") - Puppet.config.use(:main) + Puppet.settings.use(:main) config = nil assert_nothing_raised { config = client.getconfig diff --git a/test/other/report.rb b/test/other/report.rb index 7499c51e2..3de2dfbee 100755 --- a/test/other/report.rb +++ b/test/other/report.rb @@ -86,7 +86,7 @@ class TestReports < Test::Unit::TestCase # We have to reuse reporting here because of something going on in the # server/report.rb file - Puppet.config.use(:reporting) + Puppet.settings.use(:reporting) 3.times { |i| log = Puppet.warning("Report test message %s" % i) @@ -113,7 +113,7 @@ class TestReports < Test::Unit::TestCase if Puppet.features.rrd? def test_rrdgraph_report - Puppet.config.use(:metrics) + Puppet.settings.use(:metrics) report = mkreport assert(! report.metrics.empty?, "Did not receive any metrics") diff --git a/test/puppet/conffiles.rb b/test/puppet/conffiles.rb index 3dfa53a13..1800c80f6 100755 --- a/test/puppet/conffiles.rb +++ b/test/puppet/conffiles.rb @@ -3,7 +3,6 @@ $:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ require 'puppettest' -require 'puppet/util/config' class TestConfFiles < Test::Unit::TestCase include PuppetTest @@ -72,7 +71,7 @@ class TestConfFiles < Test::Unit::TestCase path = tempfile() sampledata { |data| - config = Puppet::Util::Config.new + config = Puppet::Util::Settings.new data.each { |section, hash| hash.each { |param, value| config.setdefaults(section, param => [value, value]) @@ -100,7 +99,7 @@ class TestConfFiles < Test::Unit::TestCase # that the default config is free of simple typos etc. def test_genconfig assert_nothing_raised { - Puppet::config::to_config + Puppet::settings::to_config } end diff --git a/test/util/config.rb b/test/util/config.rb deleted file mode 100755 index cbd02b4f9..000000000 --- a/test/util/config.rb +++ /dev/null @@ -1,1107 +0,0 @@ -#!/usr/bin/env ruby - -$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ - -require 'mocha' -require 'puppettest' -require 'puppet/util/config' -require 'puppettest/parsertesting' - -class TestConfig < Test::Unit::TestCase - include PuppetTest - include PuppetTest::ParserTesting - CElement = Puppet::Util::Config::CElement - CBoolean = Puppet::Util::Config::CBoolean - - def setup - super - @config = mkconfig - end - - def set_configs(config = nil) - config ||= @config - config.setdefaults("main", - :one => ["a", "one"], - :two => ["a", "two"], - :yay => ["/default/path", "boo"], - :mkusers => [true, "uh, yeah"], - :name => ["testing", "a"] - ) - - config.setdefaults("section1", - :attr => ["a", "one"], - :attrdir => ["/another/dir", "two"], - :attr3 => ["$attrdir/maybe", "boo"] - ) - end - - def check_for_users - count = Puppet::Type.type(:user).inject(0) { |c,o| - c + 1 - } - assert(count > 0, "Found no users") - end - - def test_to_transportable - set_configs - trans = nil - assert_nothing_raised("Could not convert to a transportable") { - trans = @config.to_transportable - } - - comp = nil - assert_nothing_raised("Could not convert transportable to component") { - comp = trans.to_type - } - - assert_nothing_raised("Could not retrieve transported config") { - comp.retrieve - } - end - - def test_to_config - set_configs - - newc = mkconfig - set_configs(newc) - - # Reset all of the values, so we know they're changing. - newc.each do |name, obj| - next if name == :name - newc[name] = true - end - - newfile = tempfile() - File.open(newfile, "w") { |f| - @config.to_config.split("\n").each do |line| - # Uncomment the settings, so they actually take. - if line =~ / = / - f.puts line.sub(/^\s*#/, '') - else - f.puts line - end - end - } - - assert_nothing_raised("Could not parse generated configuration") { - newc.parse(newfile) - } - - @config.each do |name, object| - assert_equal(@config[name], newc[name], "Parameter %s is not the same" % name) - end - end - - def mkconfig - c = nil - assert_nothing_raised { - c = Puppet::Util::Config.new - } - return c - end - - def test_addbools - assert_nothing_raised { - @config.setdefaults(:testing, :booltest => [true, "testing"]) - } - - assert(@config[:booltest]) - @config = mkconfig - - assert_nothing_raised { - @config.setdefaults(:testing, :booltest => ["true", "testing"]) - } - - assert(@config[:booltest]) - - assert_nothing_raised { - @config[:booltest] = false - } - - assert(! @config[:booltest], "Booltest is not false") - - assert_nothing_raised { - @config[:booltest] = "false" - } - - assert(! @config[:booltest], "Booltest is not false") - - assert_raise(ArgumentError) { - @config[:booltest] = "yayness" - } - - assert_raise(ArgumentError) { - @config[:booltest] = "/some/file" - } - end - - def test_strings - val = "this is a string" - assert_nothing_raised { - @config.setdefaults(:testing, :strtest => [val, "testing"]) - } - - assert_equal(val, @config[:strtest]) - - # Verify that variables are interpolated - assert_nothing_raised { - @config.setdefaults(:testing, :another => ["another $strtest", "testing"]) - } - - assert_equal("another #{val}", @config[:another]) - end - - def test_files - c = mkconfig - - parent = "/puppet" - assert_nothing_raised { - @config.setdefaults(:testing, :parentdir => [parent, "booh"]) - } - - assert_nothing_raised { - @config.setdefaults(:testing, :child => ["$parent/child", "rah"]) - } - - assert_equal(parent, @config[:parentdir]) - assert_equal("/puppet/child", File.join(@config[:parentdir], "child")) - end - - def test_getset - initial = "an initial value" - assert_raise(ArgumentError) { - @config[:yayness] = initial - } - - default = "this is a default" - assert_nothing_raised { - @config.setdefaults(:testing, :yayness => [default, "rah"]) - } - - assert_equal(default, @config[:yayness]) - - assert_nothing_raised { - @config[:yayness] = initial - } - - assert_equal(initial, @config[:yayness]) - - assert_nothing_raised { - @config.clear - } - - assert_equal(default, @config[:yayness], "'clear' did not remove old values") - - assert_nothing_raised { - @config[:yayness] = "not default" - } - assert_equal("not default", @config[:yayness]) - end - - def test_parse_file - text = %{ -one = this is a test -two = another test -owner = root -group = root -yay = /a/path - -[main] - four = five - six = seven - -[section1] - attr = value - owner = puppet - group = puppet - attrdir = /some/dir - attr3 = $attrdir/other - } - - file = tempfile() - File.open(file, "w") { |f| f.puts text } - - @config.expects(:settimer) - - result = nil - assert_nothing_raised { - result = @config.send(:parse_file, file) - } - - main = result[:main] - assert(main, "Did not get section for main") - { - :one => "this is a test", - :two => "another test", - :owner => "root", - :group => "root", - :yay => "/a/path", - :four => "five", - :six => "seven" - }.each do |param, value| - assert_equal(value, main[param], "Param %s was not set correctly in main" % param) - end - - section1 = result[:section1] - assert(section1, "Did not get section1") - - { - :attr => "value", - :owner => "puppet", - :group => "puppet", - :attrdir => "/some/dir", - :attr3 => "$attrdir/other" - }.each do |param, value| - assert_equal(value, section1[param], "Param %s was not set correctly in section1" % param) - end - end - - def test_old_parse - text = %{ -one = this is a test -two = another test -owner = root -group = root -yay = /a/path - -[section1] - attr = value - owner = puppet - group = puppet - attrdir = /some/dir - attr3 = $attrdir/other - } - - file = tempfile() - File.open(file, "w") { |f| f.puts text } - - assert_nothing_raised { - @config.setdefaults("puppet", - :one => ["a", "one"], - :two => ["a", "two"], - :yay => ["/default/path", "boo"], - :mkusers => [true, "uh, yeah"] - ) - } - - assert_nothing_raised { - @config.setdefaults("section1", - :attr => ["a", "one"], - :attrdir => ["/another/dir", "two"], - :attr3 => ["$attrdir/maybe", "boo"] - ) - } - - assert_nothing_raised { - @config.old_parse(file) - } - - assert_equal("value", @config[:attr]) - assert_equal("/some/dir", @config[:attrdir]) - assert_equal(:directory, @config.element(:attrdir).type) - assert_equal("/some/dir/other", @config[:attr3]) - - elem = nil - assert_nothing_raised { - elem = @config.element(:attr3) - } - - assert(elem) - assert_equal("puppet", elem.owner) - - config = nil - assert_nothing_raised { - config = @config.to_config - } - - assert_nothing_raised("Could not create transportable config") { - @config.to_transportable - } - end - - def test_parse - result = { - :main => {:main => "main", :bad => "invalid", :cliparam => "reset"}, - :puppet => {:other => "puppet", :cliparam => "reset"}, - :puppetd => {:other => "puppetd", :cliparam => "reset"} - } - # Set our defaults, so they're valid. Don't define 'bad', since we want to test for failures. - @config.setdefaults(:main, - :main => ["whatever", "a"], - :cliparam => ["default", "y"], - :other => ["a", "b"], - :name => ["puppet", "b"] # our default name - ) - @config.setdefaults(:other, - :one => ["whatever", "a"], - :two => ["default", "y"], - :apple => ["a", "b"], - :shoe => ["puppet", "b"] # our default name - ) - @config.handlearg("--cliparam", "changed") - @config.stubs(:parse_file).returns(result) - - # First do it with our name being 'puppet' - assert_nothing_raised("Could not handle parse results") do - @config.parse(tempfile) - end - - assert_equal(:puppet, @config.name, "Did not get correct name") - assert_equal("main", @config[:main], "Did not get main value") - assert_equal("puppet", @config[:other], "Did not get name value") - assert_equal("changed", @config[:cliparam], "CLI values were overridden by config") - - # Now switch names and make sure the parsing switches, too. - @config.clear(true) - assert_nothing_raised("Could not handle parse results") do - @config.parse(tempfile) - end - @config[:name] = :puppetd - - assert_equal(:puppetd, @config.name, "Did not get correct name") - assert_equal("main", @config[:main], "Did not get main value") - assert_equal("puppetd", @config[:other], "Did not get name value") - assert_equal("changed", @config[:cliparam], "CLI values were overridden by config") - end - - # Make sure we can extract file options correctly. - def test_parsing_file_options - @config.setdefaults(:whev, - :file => { - :desc => "whev", - :default => "/default", - :owner => "me", - :group => "me", - :mode => "755" - } - ) - - file = tempfile - count = 0 - - { - :pass => { - " {owner = you}" => {:owner => "you"}, - " {owner = you, group = you}" => {:owner => "you", :group => "you"}, - " {owner = you, group = you, mode = 755}" => {:owner => "you", :group => "you", :mode => "755"}, - " { owner = you, group = you } " => {:owner => "you", :group => "you"}, - "{owner=you,group=you} " => {:owner => "you", :group => "you"}, - "{owner=you,} " => {:owner => "you"} - }, - :fail => [ - %{{owner = you group = you}}, - %{{owner => you, group => you}}, - %{{user => you}}, - %{{random => you}}, - %{{mode => you}}, # make sure modes are numbers - %{{owner => you}} - ] - }.each do |type, list| - count += 1 - list.each do |value| - if type == :pass - value, should = value[0], value[1] - end - path = "/other%s" % count - # Write our file out - File.open(file, "w") do |f| - f.puts %{[main]\nfile = #{path}#{value}} - end - - if type == :fail - assert_raise(ArgumentError, "Did not fail on %s" % value.inspect) do - @config.send(:parse_file, file) - end - else - result = nil - assert_nothing_raised("Failed to parse %s" % value.inspect) do - result = @config.send(:parse_file, file) - end - assert_equal(should, result[:main][:_meta][:file], "Got incorrect return for %s" % value.inspect) - assert_equal(path, result[:main][:file], "Got incorrect value for %s" % value.inspect) - end - end - end - end - - # Make sure file options returned from parse_file are handled correctly. - def test_parsed_file_options - @config.setdefaults(:whev, - :file => { - :desc => "whev", - :default => "/default", - :owner => "me", - :group => "me", - :mode => "755" - } - ) - - result = { - :main => { - :file => "/other", - :_meta => { - :file => { - :owner => "you", - :group => "you", - :mode => "644" - } - } - } - } - - @config.expects(:parse_file).returns(result) - - assert_nothing_raised("Could not handle file options") do - @config.parse("/whatever") - end - - # Get the actual object, so we can verify metadata - file = @config.element(:file) - - assert_equal("/other", @config[:file], "Did not get correct value") - assert_equal("you", file.owner, "Did not pass on user") - assert_equal("you", file.group, "Did not pass on group") - assert_equal("644", file.mode, "Did not pass on mode") - end - - def test_arghandling - c = mkconfig - - assert_nothing_raised { - @config.setdefaults("testing", - :onboolean => [true, "An on bool"], - :offboolean => [false, "An off bool"], - :string => ["a string", "A string arg"], - :file => ["/path/to/file", "A file arg"] - ) - } - - data = { - :onboolean => [true, false], - :offboolean => [true, false], - :string => ["one string", "another string"], - :file => %w{/a/file /another/file} - } - data.each { |param, values| - values.each { |val| - opt = nil - arg = nil - if @config.boolean?(param) - if val - opt = "--%s" % param - else - opt = "--no-%s" % param - end - else - opt = "--%s" % param - arg = val - end - - assert_nothing_raised("Could not handle arg %s with value %s" % - [opt, val]) { - - @config.handlearg(opt, arg) - } - } - } - end - - def test_addargs - @config.setdefaults("testing", - :onboolean => [true, "An on bool"], - :offboolean => [false, "An off bool"], - :string => ["a string", "A string arg"], - :file => ["/path/to/file", "A file arg"] - ) - - should = [] - @config.each { |name, element| - element.expects(:getopt_args).returns([name]) - should << name - } - result = [] - assert_nothing_raised("Add args failed") do - @config.addargs(result) - end - assert_equal(should, result, "Did not call addargs correctly.") - - end - - def test_addargs_functional - @config.setdefaults("testing", - :onboolean => [true, "An on bool"], - :string => ["a string", "A string arg"] - ) - result = [] - should = [] - assert_nothing_raised("Add args failed") do - @config.addargs(result) - end - @config.each do |name, element| - if name == :onboolean - should << ["--onboolean", GetoptLong::NO_ARGUMENT] - should << ["--no-onboolean", GetoptLong::NO_ARGUMENT] - elsif name == :string - should << ["--string", GetoptLong::REQUIRED_ARGUMENT] - end - end - assert_equal(should, result, "Add args functional test failed") - end - - def test_usesection - # We want to make sure that config processes do not result in graphing. - Puppet[:graphdir] = tempfile() - Puppet[:graph] = true - Dir.mkdir(Puppet[:graphdir]) - c = mkconfig - - dir = tempfile() - file = "$mydir/myfile" - realfile = File.join(dir, "myfile") - otherfile = File.join(dir, "otherfile") - section = "testing" - assert_nothing_raised { - @config.setdefaults(section, - :mydir => [dir, "A dir arg"], - :otherfile => { - :default => "$mydir/otherfile", - :create => true, - :desc => "A file arg" - }, - :myfile => [file, "A file arg"] - ) - } - - assert_nothing_raised("Could not use a section") { - @config.use(section) - } - - assert_nothing_raised("Could not reuse a section") { - @config.use(section) - } - - # Make sure it didn't graph anything, which is the only real way - # to test that the transaction was marked as a configurator. - assert(Dir.entries(Puppet[:graphdir]).reject { |f| f =~ /^\.\.?$/ }.empty?, "Graphed config process") - - assert(FileTest.directory?(dir), "Did not create directory") - assert(FileTest.exists?(otherfile), "Did not create file") - assert(!FileTest.exists?(realfile), "Created file") - end - - def test_setdefaultsarray - c = mkconfig - - assert_nothing_raised { - @config.setdefaults("yay", - :a => [false, "some value"], - :b => ["/my/file", "a file"] - ) - } - - assert_equal(false, @config[:a], "Values are not equal") - assert_equal("/my/file", @config[:b], "Values are not equal") - end - - def test_setdefaultshash - c = mkconfig - - assert_nothing_raised { - @config.setdefaults("yay", - :a => {:default => false, :desc => "some value"}, - :b => {:default => "/my/file", :desc => "a file"} - ) - } - - assert_equal(false, @config[:a], "Values are not equal") - assert_equal("/my/file", @config[:b], "Values are not equal") - end - - def test_notmanagingdev - c = mkconfig - path = "/dev/testing" - @config.setdefaults(:test, - :file => { - :default => path, - :mode => 0640, - :desc => 'yay' - } - ) - - config = @config.to_configuration - - assert(! config.resource(:file, "/dev/testing"), "Created dev file") - end - - def test_groupsetting - cfile = tempfile() - - group = "yayness" - - File.open(cfile, "w") do |f| - f.puts "[main] - group = #{group} - " - end - - config = mkconfig - config.setdefaults(Puppet[:name], :group => ["puppet", "a group"]) - - assert_nothing_raised { - config.parse(cfile) - } - - assert_equal(group, config[:group], "Group did not take") - end - - # provide a method to modify and create files w/out specifying the info - # already stored in a config - def test_writingfiles - File.umask(0022) - - path = tempfile() - mode = 0644 - - config = mkconfig - - args = { :default => path, :mode => mode, :desc => "yay" } - - user = nonrootuser() - group = nonrootgroup() - - if Puppet::Util::SUIDManager.uid == 0 - args[:owner] = user.name - args[:group] = group.name - end - - config.setdefaults(:testing, :myfile => args) - - assert_nothing_raised { - config.write(:myfile) do |file| - file.puts "yay" - end - } - - assert_equal(mode, filemode(path), "Modes are not equal") - - # OS X is broken in how it chgrps files - if Puppet::Util::SUIDManager.uid == 0 - assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") - - case Facter["operatingsystem"].value - when /BSD/, "Darwin": # nothing - else - assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") - end - end - end - - def test_mkdir - File.umask(0022) - - path = tempfile() - mode = 0755 - - config = mkconfig - - args = { :default => path, :mode => mode, :desc => "a file" } - - user = nonrootuser() - group = nonrootgroup() - - if Puppet::Util::SUIDManager.uid == 0 - args[:owner] = user.name - args[:group] = group.name - end - - config.setdefaults(:testing, :mydir => args) - - assert_nothing_raised { - config.mkdir(:mydir) - } - - assert_equal(mode, filemode(path), "Modes are not equal") - - - # OS X and *BSD is broken in how it chgrps files - if Puppet::Util::SUIDManager.uid == 0 - assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") - - case Facter["operatingsystem"].value - when /BSD/, "Darwin": # nothing - else - assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") - end - end - end - - # Make sure that tags are ignored when configuring - def test_configs_ignore_tags - config = mkconfig - file = tempfile() - - config.setdefaults(:mysection, - :mydir => [file, "a file"] - ) - - Puppet[:tags] = "yayness" - - assert_nothing_raised { - config.use(:mysection) - } - - assert(FileTest.directory?(file), "Directory did not get created") - - assert_equal("yayness", Puppet[:tags], - "Tags got changed during config") - end - - def test_configs_replace_in_url - config = mkconfig - - config.setdefaults(:mysection, :name => ["yayness", "yay"]) - config.setdefaults(:mysection, :url => ["http://$name/rahness", "yay"]) - - val = nil - assert_nothing_raised { - val = config[:url] - } - - assert_equal("http://yayness/rahness", val, - "Config got messed up") - end - - def test_correct_type_assumptions - config = mkconfig - - file = Puppet::Util::Config::CFile - element = Puppet::Util::Config::CElement - bool = Puppet::Util::Config::CBoolean - - # We have to keep these ordered, unfortunately. - [ - ["/this/is/a/file", file], - ["true", bool], - [true, bool], - ["false", bool], - ["server", element], - ["http://$server/yay", element], - ["$server/yayness", file], - ["$server/yayness.conf", file] - ].each do |ary| - value, type = ary - assert_nothing_raised { - config.setdefaults(:yayness, value => { :default => value, :desc => name.to_s}) - } - elem = config.element(value) - - assert_instance_of(type, elem, - "%s got created as wrong type" % value.inspect) - end - end - - # Make sure we correctly reparse our config files but don't lose CLI values. - def test_reparse - Puppet[:filetimeout] = 0 - - config = mkconfig() - config.setdefaults(:mysection, :default => ["default", "yay"]) - config.setdefaults(:mysection, :clichange => ["clichange", "yay"]) - config.setdefaults(:mysection, :filechange => ["filechange", "yay"]) - - config.stubs(:read_file).returns(%{[main]\nfilechange = filevalue\n}) - file = mock 'file' - file.stubs(:changed?).returns(true) - - assert_nothing_raised { - config.parse(file) - } - - # Set another "from the cli" - assert_nothing_raised { - config.handlearg("clichange", "clivalue") - } - - # And leave the other unset - assert_equal("default", config[:default]) - assert_equal("filevalue", config[:filechange], "Did not get value from file") - assert_equal("clivalue", config[:clichange]) - - # Now reparse - config.stubs(:read_file).returns(%{[main]\nfilechange = newvalue\n}) - file = mock 'file' - file.stubs(:changed?).returns(true) - config.parse(file) - - # And check all of the values - assert_equal("default", config[:default]) - assert_equal("clivalue", config[:clichange]) - assert_equal("newvalue", config[:filechange]) - end - - def test_parse_removes_quotes - config = mkconfig() - config.setdefaults(:mysection, :singleq => ["single", "yay"]) - config.setdefaults(:mysection, :doubleq => ["double", "yay"]) - config.setdefaults(:mysection, :none => ["noquote", "yay"]) - config.setdefaults(:mysection, :middle => ["midquote", "yay"]) - - file = tempfile() - # Set one parameter in the file - File.open(file, "w") { |f| - f.puts %{[main]\n - singleq = 'one' - doubleq = "one" - none = one - middle = mid"quote -} - } - - assert_nothing_raised { - config.parse(file) - } - - %w{singleq doubleq none}.each do |p| - assert_equal("one", config[p], "%s did not match" % p) - end - assert_equal('mid"quote', config["middle"], "middle did not match") - end - - def test_timer - Puppet[:filetimeout] = 0.1 - origpath = tempfile() - config = mkconfig() - config.setdefaults(:mysection, :paramdir => [tempfile(), "yay"]) - - file = tempfile() - # Set one parameter in the file - File.open(file, "w") { |f| - f.puts %{[main]\n - paramdir = #{origpath} -} - } - - assert_nothing_raised { - config.parse(file) - config.use(:mysection) - } - - assert(FileTest.directory?(origpath), "dir did not get created") - - # Now start the timer - assert_nothing_raised { - EventLoop.current.monitor_timer config.timer - } - - newpath = tempfile() - - File.open(file, "w") { |f| - f.puts %{[main]\n - paramdir = #{newpath} -} - } - config.file.send("tstamp=".intern, Time.now - 50) - sleep 1 - - assert_equal(newpath, config["paramdir"], - "File did not get reparsed from timer") - assert(FileTest.directory?(newpath), "new dir did not get created") - - - end - - # Test that config parameters correctly call passed-in blocks when the value - # is set. - def test_paramblocks - config = mkconfig() - - testing = nil - assert_nothing_raised do - config.setdefaults :test, :blocktest => {:default => "yay", :desc => "boo", :hook => proc { |value| testing = value }} - end - elem = config.element(:blocktest) - - assert_nothing_raised do - assert_equal("yay", elem.value) - end - - assert_nothing_raised do - config[:blocktest] = "yaytest" - end - - assert_nothing_raised do - assert_equal("yaytest", elem.value) - end - assert_equal("yaytest", testing) - - assert_nothing_raised do - config[:blocktest] = "another" - end - - assert_nothing_raised do - assert_equal("another", elem.value) - end - assert_equal("another", testing) - - # Now verify it works from setdefault - assert_nothing_raised do - config.setdefaults :test, - :blocktest2 => { - :default => "yay", - :desc => "yay", - :hook => proc { |v| testing = v } - } - end - - assert_equal("yay", config[:blocktest2]) - - assert_nothing_raised do - config[:blocktest2] = "footest" - end - assert_equal("footest", config[:blocktest2]) - assert_equal("footest", testing) - end - - def test_no_modify_root - config = mkconfig - config.setdefaults(:yay, - :mydir => {:default => tempfile(), - :mode => 0644, - :owner => "root", - :group => "root", - :desc => "yay" - }, - :mkusers => [false, "yay"] - ) - - assert_nothing_raised do - config.use(:yay) - end - - # Now enable it so they'll be added - config[:mkusers] = true - - comp = config.to_configuration - - comp.vertices.find_all { |r| r.class.name == :user }.each do |u| - assert(u.name != "root", "Tried to manage root user") - end - comp.vertices.find_all { |r| r.class.name == :group }.each do |u| - assert(u.name != "root", "Tried to manage root group") - assert(u.name != "wheel", "Tried to manage wheel group") - end - -# assert(yay, "Did not find yay component") -# yay.each do |c| -# puts @config.ref -# end -# assert(! yay.find { |o| o.class.name == :user and o.name == "root" }, -# "Found root user") -# assert(! yay.find { |o| o.class.name == :group and o.name == "root" }, -# "Found root group") - end - - # #415 - def test_remove_trailing_spaces - config = mkconfig() - config.setdefaults(:yay, :rah => ["testing", "a desc"]) - - file = tempfile() - File.open(file, "w") { |f| f.puts "rah = something " } - - assert_nothing_raised { config.parse(file) } - assert_equal("something", config[:rah], "did not remove trailing whitespace in parsing") - end - - # #484 - def test_parsing_unknown_variables - logstore() - config = mkconfig() - config.setdefaults(:mysection, :one => ["yay", "yay"]) - file = tempfile() - File.open(file, "w") { |f| - f.puts %{[main]\n - one = one - two = yay - } - } - - assert_nothing_raised("Unknown parameter threw an exception") do - config.parse(file) - end - end - - def test_multiple_interpolations - @config.setdefaults(:section, - :one => ["oneval", "yay"], - :two => ["twoval", "yay"], - :three => ["$one/$two", "yay"] - ) - - assert_equal("oneval/twoval", @config[:three], - "Did not interpolate multiple variables") - end - - # Make sure we can replace ${style} var names - def test_curly_replacements - @config.setdefaults(:section, - :one => ["oneval", "yay"], - :two => ["twoval", "yay"], - :three => ["$one/${two}/${one}/$two", "yay"] - ) - - assert_equal("oneval/twoval/oneval/twoval", @config[:three], - "Did not interpolate curlied variables") - end - - # Test to make sure that we can set and get a short name - def test_celement_short_name - element = nil - assert_nothing_raised("Could not create celement") do - element = CElement.new :short => "n", :desc => "anything" - end - assert_equal("n", element.short, "Short value is not retained") - - assert_raise(ArgumentError,"Allowed multicharactered short names.") do - element = CElement.new :short => "no", :desc => "anything" - end - end - - # Test to make sure that no two celements have the same short name - def test_celement_short_name_not_duplicated - config = mkconfig - assert_nothing_raised("Could not create celement with short name.") do - config.setdefaults(:main, - :one => { :default => "blah", :desc => "anything", :short => "o" }) - end - assert_nothing_raised("Could not create second celement with short name.") do - config.setdefaults(:main, - :two => { :default => "blah", :desc => "anything", :short => "i" }) - end - assert_raise(ArgumentError, "Could create second celement with duplicate short name.") do - config.setdefaults(:main, - :three => { :default => "blah", :desc => "anything", :short => "i" }) - end - # make sure that when the above raises an expection that the config is not included - assert(!config.include?(:three), "Invalid configuration item was retained") - end - - # Tell getopt which arguments are valid - def test_get_getopt_args - element = CElement.new :name => "foo", :desc => "anything" - assert_equal([["--foo", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") - - element.short = "n" - assert_equal([["--foo", "-n", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") - - element = CBoolean.new :name => "foo", :desc => "anything" - assert_equal([["--foo", GetoptLong::NO_ARGUMENT], ["--no-foo", GetoptLong::NO_ARGUMENT]], - element.getopt_args, "Did not produce appropriate getopt args") - - element.short = "n" - assert_equal([["--foo", "-n", GetoptLong::NO_ARGUMENT],["--no-foo", GetoptLong::NO_ARGUMENT]], - element.getopt_args, "Did not produce appropriate getopt args") - end -end - -# $Id$ diff --git a/test/util/settings.rb b/test/util/settings.rb new file mode 100755 index 000000000..62f34fda6 --- /dev/null +++ b/test/util/settings.rb @@ -0,0 +1,1107 @@ +#!/usr/bin/env ruby + +$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/ + +require 'mocha' +require 'puppettest' +require 'puppet/util/settings' +require 'puppettest/parsertesting' + +class TestSettings < Test::Unit::TestCase + include PuppetTest + include PuppetTest::ParserTesting + CElement = Puppet::Util::Settings::CElement + CBoolean = Puppet::Util::Settings::CBoolean + + def setup + super + @config = mkconfig + end + + def set_configs(config = nil) + config ||= @config + config.setdefaults("main", + :one => ["a", "one"], + :two => ["a", "two"], + :yay => ["/default/path", "boo"], + :mkusers => [true, "uh, yeah"], + :name => ["testing", "a"] + ) + + config.setdefaults("section1", + :attr => ["a", "one"], + :attrdir => ["/another/dir", "two"], + :attr3 => ["$attrdir/maybe", "boo"] + ) + end + + def check_for_users + count = Puppet::Type.type(:user).inject(0) { |c,o| + c + 1 + } + assert(count > 0, "Found no users") + end + + def test_to_transportable + set_configs + trans = nil + assert_nothing_raised("Could not convert to a transportable") { + trans = @config.to_transportable + } + + comp = nil + assert_nothing_raised("Could not convert transportable to component") { + comp = trans.to_type + } + + assert_nothing_raised("Could not retrieve transported config") { + comp.retrieve + } + end + + def test_to_config + set_configs + + newc = mkconfig + set_configs(newc) + + # Reset all of the values, so we know they're changing. + newc.each do |name, obj| + next if name == :name + newc[name] = true + end + + newfile = tempfile() + File.open(newfile, "w") { |f| + @config.to_config.split("\n").each do |line| + # Uncomment the settings, so they actually take. + if line =~ / = / + f.puts line.sub(/^\s*#/, '') + else + f.puts line + end + end + } + + assert_nothing_raised("Could not parse generated configuration") { + newc.parse(newfile) + } + + @config.each do |name, object| + assert_equal(@config[name], newc[name], "Parameter %s is not the same" % name) + end + end + + def mkconfig + c = nil + assert_nothing_raised { + c = Puppet::Util::Settings.new + } + return c + end + + def test_addbools + assert_nothing_raised { + @config.setdefaults(:testing, :booltest => [true, "testing"]) + } + + assert(@config[:booltest]) + @config = mkconfig + + assert_nothing_raised { + @config.setdefaults(:testing, :booltest => ["true", "testing"]) + } + + assert(@config[:booltest]) + + assert_nothing_raised { + @config[:booltest] = false + } + + assert(! @config[:booltest], "Booltest is not false") + + assert_nothing_raised { + @config[:booltest] = "false" + } + + assert(! @config[:booltest], "Booltest is not false") + + assert_raise(ArgumentError) { + @config[:booltest] = "yayness" + } + + assert_raise(ArgumentError) { + @config[:booltest] = "/some/file" + } + end + + def test_strings + val = "this is a string" + assert_nothing_raised { + @config.setdefaults(:testing, :strtest => [val, "testing"]) + } + + assert_equal(val, @config[:strtest]) + + # Verify that variables are interpolated + assert_nothing_raised { + @config.setdefaults(:testing, :another => ["another $strtest", "testing"]) + } + + assert_equal("another #{val}", @config[:another]) + end + + def test_files + c = mkconfig + + parent = "/puppet" + assert_nothing_raised { + @config.setdefaults(:testing, :parentdir => [parent, "booh"]) + } + + assert_nothing_raised { + @config.setdefaults(:testing, :child => ["$parent/child", "rah"]) + } + + assert_equal(parent, @config[:parentdir]) + assert_equal("/puppet/child", File.join(@config[:parentdir], "child")) + end + + def test_getset + initial = "an initial value" + assert_raise(ArgumentError) { + @config[:yayness] = initial + } + + default = "this is a default" + assert_nothing_raised { + @config.setdefaults(:testing, :yayness => [default, "rah"]) + } + + assert_equal(default, @config[:yayness]) + + assert_nothing_raised { + @config[:yayness] = initial + } + + assert_equal(initial, @config[:yayness]) + + assert_nothing_raised { + @config.clear + } + + assert_equal(default, @config[:yayness], "'clear' did not remove old values") + + assert_nothing_raised { + @config[:yayness] = "not default" + } + assert_equal("not default", @config[:yayness]) + end + + def test_parse_file + text = %{ +one = this is a test +two = another test +owner = root +group = root +yay = /a/path + +[main] + four = five + six = seven + +[section1] + attr = value + owner = puppet + group = puppet + attrdir = /some/dir + attr3 = $attrdir/other + } + + file = tempfile() + File.open(file, "w") { |f| f.puts text } + + @config.expects(:settimer) + + result = nil + assert_nothing_raised { + result = @config.send(:parse_file, file) + } + + main = result[:main] + assert(main, "Did not get section for main") + { + :one => "this is a test", + :two => "another test", + :owner => "root", + :group => "root", + :yay => "/a/path", + :four => "five", + :six => "seven" + }.each do |param, value| + assert_equal(value, main[param], "Param %s was not set correctly in main" % param) + end + + section1 = result[:section1] + assert(section1, "Did not get section1") + + { + :attr => "value", + :owner => "puppet", + :group => "puppet", + :attrdir => "/some/dir", + :attr3 => "$attrdir/other" + }.each do |param, value| + assert_equal(value, section1[param], "Param %s was not set correctly in section1" % param) + end + end + + def test_old_parse + text = %{ +one = this is a test +two = another test +owner = root +group = root +yay = /a/path + +[section1] + attr = value + owner = puppet + group = puppet + attrdir = /some/dir + attr3 = $attrdir/other + } + + file = tempfile() + File.open(file, "w") { |f| f.puts text } + + assert_nothing_raised { + @config.setdefaults("puppet", + :one => ["a", "one"], + :two => ["a", "two"], + :yay => ["/default/path", "boo"], + :mkusers => [true, "uh, yeah"] + ) + } + + assert_nothing_raised { + @config.setdefaults("section1", + :attr => ["a", "one"], + :attrdir => ["/another/dir", "two"], + :attr3 => ["$attrdir/maybe", "boo"] + ) + } + + assert_nothing_raised { + @config.old_parse(file) + } + + assert_equal("value", @config[:attr]) + assert_equal("/some/dir", @config[:attrdir]) + assert_equal(:directory, @config.element(:attrdir).type) + assert_equal("/some/dir/other", @config[:attr3]) + + elem = nil + assert_nothing_raised { + elem = @config.element(:attr3) + } + + assert(elem) + assert_equal("puppet", elem.owner) + + config = nil + assert_nothing_raised { + config = @config.to_config + } + + assert_nothing_raised("Could not create transportable config") { + @config.to_transportable + } + end + + def test_parse + result = { + :main => {:main => "main", :bad => "invalid", :cliparam => "reset"}, + :puppet => {:other => "puppet", :cliparam => "reset"}, + :puppetd => {:other => "puppetd", :cliparam => "reset"} + } + # Set our defaults, so they're valid. Don't define 'bad', since we want to test for failures. + @config.setdefaults(:main, + :main => ["whatever", "a"], + :cliparam => ["default", "y"], + :other => ["a", "b"], + :name => ["puppet", "b"] # our default name + ) + @config.setdefaults(:other, + :one => ["whatever", "a"], + :two => ["default", "y"], + :apple => ["a", "b"], + :shoe => ["puppet", "b"] # our default name + ) + @config.handlearg("--cliparam", "changed") + @config.stubs(:parse_file).returns(result) + + # First do it with our name being 'puppet' + assert_nothing_raised("Could not handle parse results") do + @config.parse(tempfile) + end + + assert_equal(:puppet, @config.name, "Did not get correct name") + assert_equal("main", @config[:main], "Did not get main value") + assert_equal("puppet", @config[:other], "Did not get name value") + assert_equal("changed", @config[:cliparam], "CLI values were overridden by config") + + # Now switch names and make sure the parsing switches, too. + @config.clear(true) + assert_nothing_raised("Could not handle parse results") do + @config.parse(tempfile) + end + @config[:name] = :puppetd + + assert_equal(:puppetd, @config.name, "Did not get correct name") + assert_equal("main", @config[:main], "Did not get main value") + assert_equal("puppetd", @config[:other], "Did not get name value") + assert_equal("changed", @config[:cliparam], "CLI values were overridden by config") + end + + # Make sure we can extract file options correctly. + def test_parsing_file_options + @config.setdefaults(:whev, + :file => { + :desc => "whev", + :default => "/default", + :owner => "me", + :group => "me", + :mode => "755" + } + ) + + file = tempfile + count = 0 + + { + :pass => { + " {owner = you}" => {:owner => "you"}, + " {owner = you, group = you}" => {:owner => "you", :group => "you"}, + " {owner = you, group = you, mode = 755}" => {:owner => "you", :group => "you", :mode => "755"}, + " { owner = you, group = you } " => {:owner => "you", :group => "you"}, + "{owner=you,group=you} " => {:owner => "you", :group => "you"}, + "{owner=you,} " => {:owner => "you"} + }, + :fail => [ + %{{owner = you group = you}}, + %{{owner => you, group => you}}, + %{{user => you}}, + %{{random => you}}, + %{{mode => you}}, # make sure modes are numbers + %{{owner => you}} + ] + }.each do |type, list| + count += 1 + list.each do |value| + if type == :pass + value, should = value[0], value[1] + end + path = "/other%s" % count + # Write our file out + File.open(file, "w") do |f| + f.puts %{[main]\nfile = #{path}#{value}} + end + + if type == :fail + assert_raise(ArgumentError, "Did not fail on %s" % value.inspect) do + @config.send(:parse_file, file) + end + else + result = nil + assert_nothing_raised("Failed to parse %s" % value.inspect) do + result = @config.send(:parse_file, file) + end + assert_equal(should, result[:main][:_meta][:file], "Got incorrect return for %s" % value.inspect) + assert_equal(path, result[:main][:file], "Got incorrect value for %s" % value.inspect) + end + end + end + end + + # Make sure file options returned from parse_file are handled correctly. + def test_parsed_file_options + @config.setdefaults(:whev, + :file => { + :desc => "whev", + :default => "/default", + :owner => "me", + :group => "me", + :mode => "755" + } + ) + + result = { + :main => { + :file => "/other", + :_meta => { + :file => { + :owner => "you", + :group => "you", + :mode => "644" + } + } + } + } + + @config.expects(:parse_file).returns(result) + + assert_nothing_raised("Could not handle file options") do + @config.parse("/whatever") + end + + # Get the actual object, so we can verify metadata + file = @config.element(:file) + + assert_equal("/other", @config[:file], "Did not get correct value") + assert_equal("you", file.owner, "Did not pass on user") + assert_equal("you", file.group, "Did not pass on group") + assert_equal("644", file.mode, "Did not pass on mode") + end + + def test_arghandling + c = mkconfig + + assert_nothing_raised { + @config.setdefaults("testing", + :onboolean => [true, "An on bool"], + :offboolean => [false, "An off bool"], + :string => ["a string", "A string arg"], + :file => ["/path/to/file", "A file arg"] + ) + } + + data = { + :onboolean => [true, false], + :offboolean => [true, false], + :string => ["one string", "another string"], + :file => %w{/a/file /another/file} + } + data.each { |param, values| + values.each { |val| + opt = nil + arg = nil + if @config.boolean?(param) + if val + opt = "--%s" % param + else + opt = "--no-%s" % param + end + else + opt = "--%s" % param + arg = val + end + + assert_nothing_raised("Could not handle arg %s with value %s" % + [opt, val]) { + + @config.handlearg(opt, arg) + } + } + } + end + + def test_addargs + @config.setdefaults("testing", + :onboolean => [true, "An on bool"], + :offboolean => [false, "An off bool"], + :string => ["a string", "A string arg"], + :file => ["/path/to/file", "A file arg"] + ) + + should = [] + @config.each { |name, element| + element.expects(:getopt_args).returns([name]) + should << name + } + result = [] + assert_nothing_raised("Add args failed") do + @config.addargs(result) + end + assert_equal(should, result, "Did not call addargs correctly.") + + end + + def test_addargs_functional + @config.setdefaults("testing", + :onboolean => [true, "An on bool"], + :string => ["a string", "A string arg"] + ) + result = [] + should = [] + assert_nothing_raised("Add args failed") do + @config.addargs(result) + end + @config.each do |name, element| + if name == :onboolean + should << ["--onboolean", GetoptLong::NO_ARGUMENT] + should << ["--no-onboolean", GetoptLong::NO_ARGUMENT] + elsif name == :string + should << ["--string", GetoptLong::REQUIRED_ARGUMENT] + end + end + assert_equal(should, result, "Add args functional test failed") + end + + def test_usesection + # We want to make sure that config processes do not result in graphing. + Puppet[:graphdir] = tempfile() + Puppet[:graph] = true + Dir.mkdir(Puppet[:graphdir]) + c = mkconfig + + dir = tempfile() + file = "$mydir/myfile" + realfile = File.join(dir, "myfile") + otherfile = File.join(dir, "otherfile") + section = "testing" + assert_nothing_raised { + @config.setdefaults(section, + :mydir => [dir, "A dir arg"], + :otherfile => { + :default => "$mydir/otherfile", + :create => true, + :desc => "A file arg" + }, + :myfile => [file, "A file arg"] + ) + } + + assert_nothing_raised("Could not use a section") { + @config.use(section) + } + + assert_nothing_raised("Could not reuse a section") { + @config.use(section) + } + + # Make sure it didn't graph anything, which is the only real way + # to test that the transaction was marked as a configurator. + assert(Dir.entries(Puppet[:graphdir]).reject { |f| f =~ /^\.\.?$/ }.empty?, "Graphed config process") + + assert(FileTest.directory?(dir), "Did not create directory") + assert(FileTest.exists?(otherfile), "Did not create file") + assert(!FileTest.exists?(realfile), "Created file") + end + + def test_setdefaultsarray + c = mkconfig + + assert_nothing_raised { + @config.setdefaults("yay", + :a => [false, "some value"], + :b => ["/my/file", "a file"] + ) + } + + assert_equal(false, @config[:a], "Values are not equal") + assert_equal("/my/file", @config[:b], "Values are not equal") + end + + def test_setdefaultshash + c = mkconfig + + assert_nothing_raised { + @config.setdefaults("yay", + :a => {:default => false, :desc => "some value"}, + :b => {:default => "/my/file", :desc => "a file"} + ) + } + + assert_equal(false, @config[:a], "Values are not equal") + assert_equal("/my/file", @config[:b], "Values are not equal") + end + + def test_notmanagingdev + c = mkconfig + path = "/dev/testing" + @config.setdefaults(:test, + :file => { + :default => path, + :mode => 0640, + :desc => 'yay' + } + ) + + config = @config.to_configuration + + assert(! config.resource(:file, "/dev/testing"), "Created dev file") + end + + def test_groupsetting + cfile = tempfile() + + group = "yayness" + + File.open(cfile, "w") do |f| + f.puts "[main] + group = #{group} + " + end + + config = mkconfig + config.setdefaults(Puppet[:name], :group => ["puppet", "a group"]) + + assert_nothing_raised { + config.parse(cfile) + } + + assert_equal(group, config[:group], "Group did not take") + end + + # provide a method to modify and create files w/out specifying the info + # already stored in a config + def test_writingfiles + File.umask(0022) + + path = tempfile() + mode = 0644 + + config = mkconfig + + args = { :default => path, :mode => mode, :desc => "yay" } + + user = nonrootuser() + group = nonrootgroup() + + if Puppet::Util::SUIDManager.uid == 0 + args[:owner] = user.name + args[:group] = group.name + end + + config.setdefaults(:testing, :myfile => args) + + assert_nothing_raised { + config.write(:myfile) do |file| + file.puts "yay" + end + } + + assert_equal(mode, filemode(path), "Modes are not equal") + + # OS X is broken in how it chgrps files + if Puppet::Util::SUIDManager.uid == 0 + assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") + + case Facter["operatingsystem"].value + when /BSD/, "Darwin": # nothing + else + assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") + end + end + end + + def test_mkdir + File.umask(0022) + + path = tempfile() + mode = 0755 + + config = mkconfig + + args = { :default => path, :mode => mode, :desc => "a file" } + + user = nonrootuser() + group = nonrootgroup() + + if Puppet::Util::SUIDManager.uid == 0 + args[:owner] = user.name + args[:group] = group.name + end + + config.setdefaults(:testing, :mydir => args) + + assert_nothing_raised { + config.mkdir(:mydir) + } + + assert_equal(mode, filemode(path), "Modes are not equal") + + + # OS X and *BSD is broken in how it chgrps files + if Puppet::Util::SUIDManager.uid == 0 + assert_equal(user.uid, File.stat(path).uid, "UIDS are not equal") + + case Facter["operatingsystem"].value + when /BSD/, "Darwin": # nothing + else + assert_equal(group.gid, File.stat(path).gid, "GIDS are not equal") + end + end + end + + # Make sure that tags are ignored when configuring + def test_configs_ignore_tags + config = mkconfig + file = tempfile() + + config.setdefaults(:mysection, + :mydir => [file, "a file"] + ) + + Puppet[:tags] = "yayness" + + assert_nothing_raised { + config.use(:mysection) + } + + assert(FileTest.directory?(file), "Directory did not get created") + + assert_equal("yayness", Puppet[:tags], + "Tags got changed during config") + end + + def test_configs_replace_in_url + config = mkconfig + + config.setdefaults(:mysection, :name => ["yayness", "yay"]) + config.setdefaults(:mysection, :url => ["http://$name/rahness", "yay"]) + + val = nil + assert_nothing_raised { + val = config[:url] + } + + assert_equal("http://yayness/rahness", val, + "Settings got messed up") + end + + def test_correct_type_assumptions + config = mkconfig + + file = Puppet::Util::Settings::CFile + element = Puppet::Util::Settings::CElement + bool = Puppet::Util::Settings::CBoolean + + # We have to keep these ordered, unfortunately. + [ + ["/this/is/a/file", file], + ["true", bool], + [true, bool], + ["false", bool], + ["server", element], + ["http://$server/yay", element], + ["$server/yayness", file], + ["$server/yayness.conf", file] + ].each do |ary| + value, type = ary + assert_nothing_raised { + config.setdefaults(:yayness, value => { :default => value, :desc => name.to_s}) + } + elem = config.element(value) + + assert_instance_of(type, elem, + "%s got created as wrong type" % value.inspect) + end + end + + # Make sure we correctly reparse our config files but don't lose CLI values. + def test_reparse + Puppet[:filetimeout] = 0 + + config = mkconfig() + config.setdefaults(:mysection, :default => ["default", "yay"]) + config.setdefaults(:mysection, :clichange => ["clichange", "yay"]) + config.setdefaults(:mysection, :filechange => ["filechange", "yay"]) + + config.stubs(:read_file).returns(%{[main]\nfilechange = filevalue\n}) + file = mock 'file' + file.stubs(:changed?).returns(true) + + assert_nothing_raised { + config.parse(file) + } + + # Set another "from the cli" + assert_nothing_raised { + config.handlearg("clichange", "clivalue") + } + + # And leave the other unset + assert_equal("default", config[:default]) + assert_equal("filevalue", config[:filechange], "Did not get value from file") + assert_equal("clivalue", config[:clichange]) + + # Now reparse + config.stubs(:read_file).returns(%{[main]\nfilechange = newvalue\n}) + file = mock 'file' + file.stubs(:changed?).returns(true) + config.parse(file) + + # And check all of the values + assert_equal("default", config[:default]) + assert_equal("clivalue", config[:clichange]) + assert_equal("newvalue", config[:filechange]) + end + + def test_parse_removes_quotes + config = mkconfig() + config.setdefaults(:mysection, :singleq => ["single", "yay"]) + config.setdefaults(:mysection, :doubleq => ["double", "yay"]) + config.setdefaults(:mysection, :none => ["noquote", "yay"]) + config.setdefaults(:mysection, :middle => ["midquote", "yay"]) + + file = tempfile() + # Set one parameter in the file + File.open(file, "w") { |f| + f.puts %{[main]\n + singleq = 'one' + doubleq = "one" + none = one + middle = mid"quote +} + } + + assert_nothing_raised { + config.parse(file) + } + + %w{singleq doubleq none}.each do |p| + assert_equal("one", config[p], "%s did not match" % p) + end + assert_equal('mid"quote', config["middle"], "middle did not match") + end + + def test_timer + Puppet[:filetimeout] = 0.1 + origpath = tempfile() + config = mkconfig() + config.setdefaults(:mysection, :paramdir => [tempfile(), "yay"]) + + file = tempfile() + # Set one parameter in the file + File.open(file, "w") { |f| + f.puts %{[main]\n + paramdir = #{origpath} +} + } + + assert_nothing_raised { + config.parse(file) + config.use(:mysection) + } + + assert(FileTest.directory?(origpath), "dir did not get created") + + # Now start the timer + assert_nothing_raised { + EventLoop.current.monitor_timer config.timer + } + + newpath = tempfile() + + File.open(file, "w") { |f| + f.puts %{[main]\n + paramdir = #{newpath} +} + } + config.file.send("tstamp=".intern, Time.now - 50) + sleep 1 + + assert_equal(newpath, config["paramdir"], + "File did not get reparsed from timer") + assert(FileTest.directory?(newpath), "new dir did not get created") + + + end + + # Test that config parameters correctly call passed-in blocks when the value + # is set. + def test_paramblocks + config = mkconfig() + + testing = nil + assert_nothing_raised do + config.setdefaults :test, :blocktest => {:default => "yay", :desc => "boo", :hook => proc { |value| testing = value }} + end + elem = config.element(:blocktest) + + assert_nothing_raised do + assert_equal("yay", elem.value) + end + + assert_nothing_raised do + config[:blocktest] = "yaytest" + end + + assert_nothing_raised do + assert_equal("yaytest", elem.value) + end + assert_equal("yaytest", testing) + + assert_nothing_raised do + config[:blocktest] = "another" + end + + assert_nothing_raised do + assert_equal("another", elem.value) + end + assert_equal("another", testing) + + # Now verify it works from setdefault + assert_nothing_raised do + config.setdefaults :test, + :blocktest2 => { + :default => "yay", + :desc => "yay", + :hook => proc { |v| testing = v } + } + end + + assert_equal("yay", config[:blocktest2]) + + assert_nothing_raised do + config[:blocktest2] = "footest" + end + assert_equal("footest", config[:blocktest2]) + assert_equal("footest", testing) + end + + def test_no_modify_root + config = mkconfig + config.setdefaults(:yay, + :mydir => {:default => tempfile(), + :mode => 0644, + :owner => "root", + :group => "root", + :desc => "yay" + }, + :mkusers => [false, "yay"] + ) + + assert_nothing_raised do + config.use(:yay) + end + + # Now enable it so they'll be added + config[:mkusers] = true + + comp = config.to_configuration + + comp.vertices.find_all { |r| r.class.name == :user }.each do |u| + assert(u.name != "root", "Tried to manage root user") + end + comp.vertices.find_all { |r| r.class.name == :group }.each do |u| + assert(u.name != "root", "Tried to manage root group") + assert(u.name != "wheel", "Tried to manage wheel group") + end + +# assert(yay, "Did not find yay component") +# yay.each do |c| +# puts @config.ref +# end +# assert(! yay.find { |o| o.class.name == :user and o.name == "root" }, +# "Found root user") +# assert(! yay.find { |o| o.class.name == :group and o.name == "root" }, +# "Found root group") + end + + # #415 + def test_remove_trailing_spaces + config = mkconfig() + config.setdefaults(:yay, :rah => ["testing", "a desc"]) + + file = tempfile() + File.open(file, "w") { |f| f.puts "rah = something " } + + assert_nothing_raised { config.parse(file) } + assert_equal("something", config[:rah], "did not remove trailing whitespace in parsing") + end + + # #484 + def test_parsing_unknown_variables + logstore() + config = mkconfig() + config.setdefaults(:mysection, :one => ["yay", "yay"]) + file = tempfile() + File.open(file, "w") { |f| + f.puts %{[main]\n + one = one + two = yay + } + } + + assert_nothing_raised("Unknown parameter threw an exception") do + config.parse(file) + end + end + + def test_multiple_interpolations + @config.setdefaults(:section, + :one => ["oneval", "yay"], + :two => ["twoval", "yay"], + :three => ["$one/$two", "yay"] + ) + + assert_equal("oneval/twoval", @config[:three], + "Did not interpolate multiple variables") + end + + # Make sure we can replace ${style} var names + def test_curly_replacements + @config.setdefaults(:section, + :one => ["oneval", "yay"], + :two => ["twoval", "yay"], + :three => ["$one/${two}/${one}/$two", "yay"] + ) + + assert_equal("oneval/twoval/oneval/twoval", @config[:three], + "Did not interpolate curlied variables") + end + + # Test to make sure that we can set and get a short name + def test_celement_short_name + element = nil + assert_nothing_raised("Could not create celement") do + element = CElement.new :short => "n", :desc => "anything" + end + assert_equal("n", element.short, "Short value is not retained") + + assert_raise(ArgumentError,"Allowed multicharactered short names.") do + element = CElement.new :short => "no", :desc => "anything" + end + end + + # Test to make sure that no two celements have the same short name + def test_celement_short_name_not_duplicated + config = mkconfig + assert_nothing_raised("Could not create celement with short name.") do + config.setdefaults(:main, + :one => { :default => "blah", :desc => "anything", :short => "o" }) + end + assert_nothing_raised("Could not create second celement with short name.") do + config.setdefaults(:main, + :two => { :default => "blah", :desc => "anything", :short => "i" }) + end + assert_raise(ArgumentError, "Could create second celement with duplicate short name.") do + config.setdefaults(:main, + :three => { :default => "blah", :desc => "anything", :short => "i" }) + end + # make sure that when the above raises an expection that the config is not included + assert(!config.include?(:three), "Invalid configuration item was retained") + end + + # Tell getopt which arguments are valid + def test_get_getopt_args + element = CElement.new :name => "foo", :desc => "anything" + assert_equal([["--foo", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") + + element.short = "n" + assert_equal([["--foo", "-n", GetoptLong::REQUIRED_ARGUMENT]], element.getopt_args, "Did not produce appropriate getopt args") + + element = CBoolean.new :name => "foo", :desc => "anything" + assert_equal([["--foo", GetoptLong::NO_ARGUMENT], ["--no-foo", GetoptLong::NO_ARGUMENT]], + element.getopt_args, "Did not produce appropriate getopt args") + + element.short = "n" + assert_equal([["--foo", "-n", GetoptLong::NO_ARGUMENT],["--no-foo", GetoptLong::NO_ARGUMENT]], + element.getopt_args, "Did not produce appropriate getopt args") + end +end + +# $Id$ -- cgit From 84146d00eed4765e6dbe05dd8de9f4c3625463b7 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sat, 22 Sep 2007 20:00:15 -0500 Subject: Adding the first version of checksum support, which will acquire the behaviour of FileBuckets. --- lib/puppet/checksum.rb | 37 ++++++++ lib/puppet/indirector/file.rb | 34 ++++++-- lib/puppet/indirector/file/checksum.rb | 33 +++++++ spec/unit/indirector/file.rb | 50 ++++++++++- spec/unit/indirector/file/checksum.rb | 154 +++++++++++++++++++++++++++++++++ spec/unit/other/checksum.rb | 60 +++++++++++++ 6 files changed, 358 insertions(+), 10 deletions(-) create mode 100644 lib/puppet/checksum.rb create mode 100644 lib/puppet/indirector/file/checksum.rb create mode 100755 spec/unit/indirector/file/checksum.rb create mode 100755 spec/unit/other/checksum.rb diff --git a/lib/puppet/checksum.rb b/lib/puppet/checksum.rb new file mode 100644 index 000000000..65fb7ef76 --- /dev/null +++ b/lib/puppet/checksum.rb @@ -0,0 +1,37 @@ +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require 'puppet' +require 'puppet/indirector' + +# A checksum class to model translating checksums to file paths. This +# is the new filebucket. +class Puppet::Checksum + extend Puppet::Indirector + + indirects :checksum + + attr_accessor :name, :content + attr_reader :algorithm + + def algorithm=(value) + value = value.intern if value.respond_to?(:intern) + @algorithm = value + end + + def initialize(name) + raise ArgumentError.new("You must specify the checksum") unless name + + if name =~ /^\{(\w+)\}(.+$)$/ + @algorithm, @name = $1.intern, $2 + else + @name = name + @algorithm = :md5 + end + end + + def to_s + "Checksum<{%s}%s>" % [algorithm, name] + end +end diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb index 75fcf1ddf..4f231e9ec 100644 --- a/lib/puppet/indirector/file.rb +++ b/lib/puppet/indirector/file.rb @@ -3,16 +3,27 @@ require 'puppet/indirector/terminus' # An empty terminus type, meant to just return empty objects. class Puppet::Indirector::File < Puppet::Indirector::Terminus def destroy(file) - raise Puppet::Error.new("File %s does not exist; cannot destroy" % [file.name]) unless File.exist?(file.path) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + raise Puppet::Error.new("File %s does not exist; cannot destroy" % [file]) unless File.exist?(path) begin - File.unlink(file.path) + File.unlink(path) rescue => detail - raise Puppet::Error, "Could not remove %s: %s" % [file.path, detail] + raise Puppet::Error, "Could not remove %s: %s" % [file, detail] end end - def find(path) + def find(name) + if respond_to?(:path) + path = path(name) + else + path = name + end + return nil unless File.exist?(path) begin @@ -21,20 +32,25 @@ class Puppet::Indirector::File < Puppet::Indirector::Terminus raise Puppet::Error, "Could not retrieve path %s: %s" % [path, detail] end - file = model.new(path) + file = model.new(name) file.content = content return file end def save(file) - dir = File.dirname(file.path) + if respond_to?(:path) + path = path(file.name) + else + path = file.path + end + dir = File.dirname(path) - raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [file.name, dir]) unless File.directory?(dir) + raise Puppet::Error.new("Cannot save %s; parent directory %s does not exist" % [file, dir]) unless File.directory?(dir) begin - File.open(file.path, "w") { |f| f.print file.content } + File.open(path, "w") { |f| f.print file.content } rescue => detail - raise Puppet::Error, "Could not write %s: %s" % [file.path, detail] + raise Puppet::Error, "Could not write %s: %s" % [file, detail] end end end diff --git a/lib/puppet/indirector/file/checksum.rb b/lib/puppet/indirector/file/checksum.rb new file mode 100644 index 000000000..2f0974ced --- /dev/null +++ b/lib/puppet/indirector/file/checksum.rb @@ -0,0 +1,33 @@ +require 'puppet/checksum' +require 'puppet/indirector/file' + +class Puppet::Indirector::File::Checksum < Puppet::Indirector::File + desc "Store files in a directory set based on their checksums." + + def initialize + Puppet.settings.use(:filebucket) + end + + def path(checksum) + path = [] + path << Puppet[:bucketdir] # Start with the base directory + path << checksum[0..7].split("").join(File::SEPARATOR) # Add sets of directories based on the checksum + path << checksum # And the full checksum name itself + path << "contents" # And the actual file name + + path.join(File::SEPARATOR) + end + + def save(file) + path = File.dirname(path(file.name)) + + # Make the directories if necessary. + unless FileTest.directory?(path) + Puppet::Util.withumask(0007) do + FileUtils.mkdir_p(path) + end + end + + super + end +end diff --git a/spec/unit/indirector/file.rb b/spec/unit/indirector/file.rb index d08ff6455..176832880 100755 --- a/spec/unit/indirector/file.rb +++ b/spec/unit/indirector/file.rb @@ -59,11 +59,36 @@ describe Puppet::Indirector::File, " when finding files" do end it "should fail intelligently if a found file cannot be read" do - content = "my content" File.expects(:exist?).with(@path).returns(true) File.expects(:read).with(@path).raises(RuntimeError) proc { @searcher.find(@path) }.should raise_error(Puppet::Error) end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + File.expects(:exist?).with(@path.upcase).returns(false) + @searcher.find(@path) + end + + it "should use the passed-in name for the model instance even if a path() method exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + content = "something" + file = mock 'file' + file.expects(:content=).with(content) + + # The passed-in path, rather than the upcased version + @model.expects(:new).with(@path).returns(file) + + File.expects(:exist?).with(@path.upcase).returns(true) + File.expects(:read).with(@path.upcase).returns(content) + @searcher.find(@path).should equal(file) + end end describe Puppet::Indirector::File, " when saving files" do @@ -100,6 +125,17 @@ describe Puppet::Indirector::File, " when saving files" do proc { @searcher.save(file) }.should raise_error(Puppet::Error) end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + + File.expects(:open).with("/YAY", "w") + @searcher.save(file) + end end describe Puppet::Indirector::File, " when removing files" do @@ -127,4 +163,16 @@ describe Puppet::Indirector::File, " when removing files" do proc { @searcher.destroy(file) }.should raise_error(Puppet::Error) end + + it "should use the path() method to calculate the path if it exists" do + @searcher.meta_def(:path) do |name| + name.upcase + end + + file = stub 'file', :name => "/yay" + File.expects(:exist?).with("/YAY").returns(true) + File.expects(:unlink).with("/YAY") + + @searcher.destroy(file) + end end diff --git a/spec/unit/indirector/file/checksum.rb b/spec/unit/indirector/file/checksum.rb new file mode 100755 index 000000000..950a377a7 --- /dev/null +++ b/spec/unit/indirector/file/checksum.rb @@ -0,0 +1,154 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/file/checksum' + +describe Puppet::Indirector::File::Checksum do + it "should be a subclass of the File terminus class" do + Puppet::Indirector::File::Checksum.superclass.should equal(Puppet::Indirector::File) + end + + it "should have documentation" do + Puppet::Indirector::File::Checksum.doc.should be_instance_of(String) + end +end + +describe Puppet::Indirector::File::Checksum, " when initializing" do + it "should use the filebucket settings section" do + Puppet.settings.expects(:use).with(:filebucket) + Puppet::Indirector::File::Checksum.new + end +end + +describe Puppet::Indirector::File::Checksum, " when determining file paths" do + before do + Puppet.settings.stubs(:use) + @store = Puppet::Indirector::File::Checksum.new + + @value = "70924d6fa4b2d745185fa4660703a5c0" + + @dir = "/what/ever" + + Puppet.stubs(:[]).with(:bucketdir).returns(@dir) + + @path = @store.path(@value) + end + + # I was previously passing the object in. + it "should use the value passed in to path() as the checksum" do + @value.expects(:name).never + @store.path(@value) + end + + it "should use the value of the :bucketdir setting as the root directory" do + @path.should =~ %r{^#{@dir}} + end + + it "should choose a path 8 directories deep with each directory name being the respective character in the checksum" do + dirs = @value[0..7].split("").join(File::SEPARATOR) + @path.should be_include(dirs) + end + + it "should use the full checksum as the final directory name" do + File.basename(File.dirname(@path)).should == @value + end + + it "should use 'contents' as the actual file name" do + File.basename(@path).should == "contents" + end + + it "should use the bucketdir, the 8 sum character directories, the full checksum, and 'contents' as the full file name" do + @path.should == [@dir, @value[0..7].split(""), @value, "contents"].flatten.join(File::SEPARATOR) + end +end + +module FileChecksumTesting + def setup + Puppet.settings.stubs(:use) + @store = Puppet::Indirector::File::Checksum.new + + @value = "70924d6fa4b2d745185fa4660703a5c0" + @sum = stub 'sum', :name => @value + + @dir = "/what/ever" + + Puppet.stubs(:[]).with(:bucketdir).returns(@dir) + + @path = @store.path(@value) + end +end + +describe Puppet::Indirector::File::Checksum, " when retrieving files" do + include FileChecksumTesting + + # The smallest test that will use the calculated path + it "should look for the calculated path" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value) + end + + it "should return an instance of Puppet::Checksum with the name and content set correctly if the file exists" do + content = "my content" + sum = stub 'file' + sum.expects(:content=).with(content) + Puppet::Checksum.expects(:new).with(@value).returns(sum) + + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).returns(content) + + @store.find(@value).should equal(sum) + end + + it "should return nil if no file is found" do + File.expects(:exist?).with(@path).returns(false) + @store.find(@value).should be_nil + end + + it "should fail intelligently if a found file cannot be read" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:read).with(@path).raises(RuntimeError) + proc { @store.find(@value) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::File::Checksum, " when saving files" do + include FileChecksumTesting + + # LAK:FIXME I don't know how to include in the spec the fact that we're + # using the superclass's save() method and thus are acquiring all of + # it's behaviours. + it "should save the content to the calculated path" do + File.stubs(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end + + it "should make any directories necessary for storage" do + FileUtils.expects(:mkdir_p).with do |arg| + File.umask == 0007 and arg == File.dirname(@path) + end + File.expects(:directory?).with(File.dirname(@path)).returns(true) + File.expects(:open).with(@path, "w") + + file = stub 'file', :name => @value + @store.save(file) + end +end + +describe Puppet::Indirector::File::Checksum, " when deleting files" do + include FileChecksumTesting + + it "should remove the file at the calculated path" do + File.expects(:exist?).with(@path).returns(true) + File.expects(:unlink).with(@path) + + file = stub 'file', :name => @value + @store.destroy(file) + end +end diff --git a/spec/unit/other/checksum.rb b/spec/unit/other/checksum.rb new file mode 100755 index 000000000..5610870a6 --- /dev/null +++ b/spec/unit/other/checksum.rb @@ -0,0 +1,60 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../spec_helper' + +require 'puppet/checksum' + +describe Puppet::Checksum do + it "should have 'Checksum' and the checksum algorithm when converted to a string" do + sum = Puppet::Checksum.new("whatever") + sum.algorithm = "yay" + sum.to_s.should == "Checksum<{yay}whatever>" + end + + it "should convert algorithm names to symbols when they are set after checksum creation" do + sum = Puppet::Checksum.new("whatever") + sum.algorithm = "yay" + sum.algorithm.should == :yay + end +end + +describe Puppet::Checksum, " when initializing" do + it "should require a name" do + proc { Puppet::Checksum.new(nil) }.should raise_error(ArgumentError) + end + + it "should set the name appropriately" do + Puppet::Checksum.new("whatever").name.should == "whatever" + end + + it "should parse checksum algorithms out of the name if they are there" do + sum = Puppet::Checksum.new("{other}whatever") + sum.algorithm.should == :other + sum.name.should == "whatever" + end + + it "should default to 'md5' as the checksum algorithm if the algorithm is not in the name" do + Puppet::Checksum.new("whatever").algorithm.should == :md5 + end +end + +describe Puppet::Checksum, " when using back-ends" do + it "should redirect using Puppet::Indirector" do + Puppet::Indirector::Indirection.instance(:checksum).model.should equal(Puppet::Checksum) + end + + it "should have a :save instance method" do + Puppet::Checksum.new("mysum").should respond_to(:save) + end + + it "should respond to :find" do + Puppet::Checksum.should respond_to(:find) + end + + it "should respond to :destroy" do + Puppet::Checksum.should respond_to(:destroy) + end +end -- cgit From 048464f2563d3ccf781c16b873be6b74441f1f85 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 23 Sep 2007 14:26:56 -0500 Subject: Adding my first integration test, verifying that checksum interaction behaves as I expect when interacting with the file terminus. I've also changed how files and checksums behave a bit. Files now create model instances with the content as the only argument during initialization, and checksums now calculate their checksums rather than having them passed in. --- lib/puppet/checksum.rb | 51 ++++++++++++++++++++++-------- lib/puppet/defaults.rb | 4 ++- lib/puppet/indirector/file.rb | 4 +-- spec/integration/checksum.rb | 48 +++++++++++++++++++++++++++++ spec/unit/indirector/file.rb | 26 +++------------- spec/unit/indirector/file/checksum.rb | 47 +++++++++++----------------- spec/unit/indirector/indirector.rb | 18 ++++++++--- spec/unit/other/checksum.rb | 58 +++++++++++++++++++++++++++-------- 8 files changed, 171 insertions(+), 85 deletions(-) create mode 100755 spec/integration/checksum.rb diff --git a/lib/puppet/checksum.rb b/lib/puppet/checksum.rb index 65fb7ef76..c607953c1 100644 --- a/lib/puppet/checksum.rb +++ b/lib/puppet/checksum.rb @@ -12,26 +12,53 @@ class Puppet::Checksum indirects :checksum - attr_accessor :name, :content - attr_reader :algorithm + attr_reader :algorithm, :content def algorithm=(value) - value = value.intern if value.respond_to?(:intern) + unless respond_to?(value) + raise ArgumentError, "Checksum algorithm %s is not supported" % value + end + value = value.intern if value.is_a?(String) @algorithm = value + # Reset the checksum so it's forced to be recalculated. + @checksum = nil end - def initialize(name) - raise ArgumentError.new("You must specify the checksum") unless name - - if name =~ /^\{(\w+)\}(.+$)$/ - @algorithm, @name = $1.intern, $2 - else - @name = name - @algorithm = :md5 + # Calculate (if necessary) and return the checksum + def checksum + unless @checksum + @checksum = send(algorithm) end + @checksum + end + + def initialize(content, algorithm = nil) + raise ArgumentError.new("You must specify the content") unless content + + @content = content + self.algorithm = algorithm || "md5" + + # Init to avoid warnings. + @checksum = nil + end + + # This can't be private, else respond_to? returns false. + def md5 + require 'digest/md5' + Digest::MD5.hexdigest(content) + end + + # This is here so the Indirector::File terminus works correctly. + def name + checksum + end + + def sha1 + require 'digest/sha1' + Digest::SHA1.hexdigest(content) end def to_s - "Checksum<{%s}%s>" % [algorithm, name] + "Checksum<{%s}%s>" % [algorithm, checksum] end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index dbbe5e29d..f76ae9b84 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -498,7 +498,9 @@ module Puppet # we get an infinite loop otherwise. self.setdefaults(:main, :facts_terminus => ["yaml", - "The backend store to use for client facts."] + "The backend store to use for client facts."], + :checksum_terminus => ["file", + "The backend store to use for storing files by checksum (i.e., filebuckets)."] ) self.setdefaults(:yaml, diff --git a/lib/puppet/indirector/file.rb b/lib/puppet/indirector/file.rb index 4f231e9ec..c2d36c46b 100644 --- a/lib/puppet/indirector/file.rb +++ b/lib/puppet/indirector/file.rb @@ -32,9 +32,7 @@ class Puppet::Indirector::File < Puppet::Indirector::Terminus raise Puppet::Error, "Could not retrieve path %s: %s" % [path, detail] end - file = model.new(name) - file.content = content - return file + return model.new(content) end def save(file) diff --git a/spec/integration/checksum.rb b/spec/integration/checksum.rb new file mode 100755 index 000000000..f112f7502 --- /dev/null +++ b/spec/integration/checksum.rb @@ -0,0 +1,48 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-22. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'puppet/checksum' + +describe Puppet::Checksum, " when using the file terminus" do + before do + Puppet[:checksum_terminus] = "file" + + @content = "this is some content" + @sum = Puppet::Checksum.new(@content) + + @file = Puppet::Checksum.indirection.terminus.path(@sum.checksum) + end + + it "should store content at a path determined by its checksum" do + File.stubs(:directory?).returns(true) + filehandle = mock 'filehandle' + filehandle.expects(:print).with(@content) + File.expects(:open).with(@file, "w").yields(filehandle) + + @sum.save + end + + it "should retrieve stored content when the checksum is provided as the key" do + File.stubs(:exist?).returns(true) + File.expects(:read).with(@file).returns(@content) + + newsum = Puppet::Checksum.find(@sum.checksum) + + newsum.content.should == @content + end + + it "should remove specified files when asked" do + File.stubs(:exist?).returns(true) + File.expects(:unlink).with(@file) + + Puppet::Checksum.destroy(@sum) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/unit/indirector/file.rb b/spec/unit/indirector/file.rb index 176832880..cc86f9fa9 100755 --- a/spec/unit/indirector/file.rb +++ b/spec/unit/indirector/file.rb @@ -27,26 +27,25 @@ describe Puppet::Indirector::File, " when finding files" do include FileTerminusTesting it "should provide a method to return file contents at a specified path" do + @searcher.should respond_to(:find) end it "should return file contents as an instance of the model" do content = "my content" file = mock 'file' - @model.expects(:new).with(@path).returns(file) - file.expects(:content=).with(content) + @model.expects(:new).with(content).returns(file) File.expects(:exist?).with(@path).returns(true) File.expects(:read).with(@path).returns(content) @searcher.find(@path) end - it "should set the file contents as the 'content' attribute of the returned instance" do + it "should create the model instance with the content as the only argument to initialization" do content = "my content" file = mock 'file' - @model.expects(:new).with(@path).returns(file) - file.expects(:content=).with(content) + @model.expects(:new).with(content).returns(file) File.expects(:exist?).with(@path).returns(true) File.expects(:read).with(@path).returns(content) @@ -72,23 +71,6 @@ describe Puppet::Indirector::File, " when finding files" do File.expects(:exist?).with(@path.upcase).returns(false) @searcher.find(@path) end - - it "should use the passed-in name for the model instance even if a path() method exists" do - @searcher.meta_def(:path) do |name| - name.upcase - end - - content = "something" - file = mock 'file' - file.expects(:content=).with(content) - - # The passed-in path, rather than the upcased version - @model.expects(:new).with(@path).returns(file) - - File.expects(:exist?).with(@path.upcase).returns(true) - File.expects(:read).with(@path.upcase).returns(content) - @searcher.find(@path).should equal(file) - end end describe Puppet::Indirector::File, " when saving files" do diff --git a/spec/unit/indirector/file/checksum.rb b/spec/unit/indirector/file/checksum.rb index 950a377a7..3a4cd3d61 100755 --- a/spec/unit/indirector/file/checksum.rb +++ b/spec/unit/indirector/file/checksum.rb @@ -7,6 +7,22 @@ require File.dirname(__FILE__) + '/../../../spec_helper' require 'puppet/indirector/file/checksum' +module FileChecksumTesting + def setup + Puppet.settings.stubs(:use) + @store = Puppet::Indirector::File::Checksum.new + + @value = "70924d6fa4b2d745185fa4660703a5c0" + @sum = stub 'sum', :name => @value + + @dir = "/what/ever" + + Puppet.stubs(:[]).with(:bucketdir).returns(@dir) + + @path = @store.path(@value) + end +end + describe Puppet::Indirector::File::Checksum do it "should be a subclass of the File terminus class" do Puppet::Indirector::File::Checksum.superclass.should equal(Puppet::Indirector::File) @@ -25,18 +41,7 @@ describe Puppet::Indirector::File::Checksum, " when initializing" do end describe Puppet::Indirector::File::Checksum, " when determining file paths" do - before do - Puppet.settings.stubs(:use) - @store = Puppet::Indirector::File::Checksum.new - - @value = "70924d6fa4b2d745185fa4660703a5c0" - - @dir = "/what/ever" - - Puppet.stubs(:[]).with(:bucketdir).returns(@dir) - - @path = @store.path(@value) - end + include FileChecksumTesting # I was previously passing the object in. it "should use the value passed in to path() as the checksum" do @@ -66,22 +71,6 @@ describe Puppet::Indirector::File::Checksum, " when determining file paths" do end end -module FileChecksumTesting - def setup - Puppet.settings.stubs(:use) - @store = Puppet::Indirector::File::Checksum.new - - @value = "70924d6fa4b2d745185fa4660703a5c0" - @sum = stub 'sum', :name => @value - - @dir = "/what/ever" - - Puppet.stubs(:[]).with(:bucketdir).returns(@dir) - - @path = @store.path(@value) - end -end - describe Puppet::Indirector::File::Checksum, " when retrieving files" do include FileChecksumTesting @@ -91,7 +80,7 @@ describe Puppet::Indirector::File::Checksum, " when retrieving files" do @store.find(@value) end - it "should return an instance of Puppet::Checksum with the name and content set correctly if the file exists" do + it "should return an instance of Puppet::Checksum with the checksum and content set correctly if the file exists" do content = "my content" sum = stub 'file' sum.expects(:content=).with(content) diff --git a/spec/unit/indirector/indirector.rb b/spec/unit/indirector/indirector.rb index 47e93cbaa..1702bf51f 100755 --- a/spec/unit/indirector/indirector.rb +++ b/spec/unit/indirector/indirector.rb @@ -1,4 +1,7 @@ +#!/usr/bin/env ruby + require File.dirname(__FILE__) + '/../../spec_helper' + require 'puppet/defaults' require 'puppet/indirector' @@ -36,12 +39,17 @@ describe Puppet::Indirector, "when registering an indirection" do Proc.new { @thingie.indirects :second }.should raise_error(ArgumentError) end + it "should make the indirection available via an accessor" do + @indirection = @thingie.indirects :first + @thingie.indirection.should equal(@indirection) + end + after do @indirection.delete if @indirection end end -describe Puppet::Indirector, " when redirecting model" do +describe Puppet::Indirector, " when redirecting a model" do before do @thingie = Class.new do extend Puppet::Indirector @@ -51,22 +59,22 @@ describe Puppet::Indirector, " when redirecting model" do @thingie.expects(:indirection).returns(@mock_terminus) end - it "should give model the ability to lookup a model instance by letting the indirection perform the lookup" do + it "should give the model the ability to lookup a model instance by letting the indirection perform the lookup" do @mock_terminus.expects(:find) @thingie.find end - it "should give model the ability to remove model instances from a terminus by letting the indirection remove the instance" do + it "should give the model the ability to remove model instances from a terminus by letting the indirection remove the instance" do @mock_terminus.expects(:destroy) @thingie.destroy end - it "should give model the ability to search for model instances by letting the indirection find the matching instances" do + it "should give the model the ability to search for model instances by letting the indirection find the matching instances" do @mock_terminus.expects(:search) @thingie.search end - it "should give model the ability to store a model instance by letting the indirection store the instance" do + it "should give the model the ability to store a model instance by letting the indirection store the instance" do thing = @thingie.new @mock_terminus.expects(:save).with(thing) thing.save diff --git a/spec/unit/other/checksum.rb b/spec/unit/other/checksum.rb index 5610870a6..6a63e833d 100755 --- a/spec/unit/other/checksum.rb +++ b/spec/unit/other/checksum.rb @@ -9,35 +9,67 @@ require 'puppet/checksum' describe Puppet::Checksum do it "should have 'Checksum' and the checksum algorithm when converted to a string" do - sum = Puppet::Checksum.new("whatever") - sum.algorithm = "yay" - sum.to_s.should == "Checksum<{yay}whatever>" + inst = Puppet::Checksum.new("whatever", "md5") + inst.to_s.should == "Checksum<{md5}#{inst.checksum}>" end it "should convert algorithm names to symbols when they are set after checksum creation" do sum = Puppet::Checksum.new("whatever") - sum.algorithm = "yay" - sum.algorithm.should == :yay + sum.algorithm = "md5" + sum.algorithm.should == :md5 + end + + it "should return the checksum as the name" do + sum = Puppet::Checksum.new("whatever") + sum.checksum.should == sum.name end end describe Puppet::Checksum, " when initializing" do - it "should require a name" do + before do + @content = "this is some content" + @sum = Puppet::Checksum.new(@content) + end + + it "should require content" do proc { Puppet::Checksum.new(nil) }.should raise_error(ArgumentError) end - it "should set the name appropriately" do - Puppet::Checksum.new("whatever").name.should == "whatever" + it "should set the content appropriately" do + @sum.content.should == @content + end + + it "should calculate the checksum" do + require 'digest/md5' + Digest::MD5.expects(:hexdigest).with(@content).returns(:mychecksum) + @sum.checksum.should == :mychecksum + end + + it "should not calculate the checksum until it is asked for" do + require 'digest/md5' + Digest::MD5.expects(:hexdigest).never + sum = Puppet::Checksum.new(@content, :md5) end - it "should parse checksum algorithms out of the name if they are there" do - sum = Puppet::Checksum.new("{other}whatever") - sum.algorithm.should == :other - sum.name.should == "whatever" + it "should remove the old checksum value if the algorithm is changed" do + Digest::MD5.expects(:hexdigest).with(@content).returns(:oldsum) + oldsum = @sum.checksum + @sum.algorithm = :sha1 + Digest::SHA1.expects(:hexdigest).with(@content).returns(:newsum) + @sum.checksum.should == :newsum end it "should default to 'md5' as the checksum algorithm if the algorithm is not in the name" do - Puppet::Checksum.new("whatever").algorithm.should == :md5 + @sum.algorithm.should == :md5 + end + + it "should support specifying the algorithm during initialization" do + sum = Puppet::Checksum.new(@content, :sha1) + sum.algorithm.should == :sha1 + end + + it "should fail when an unsupported algorithm is used" do + proc { Puppet::Checksum.new(@content, :nope) }.should raise_error(ArgumentError) end end -- cgit From 1e7c648220214eaab253212f1d9dd6bcca9963b7 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 23 Sep 2007 14:28:31 -0500 Subject: Fixing the spec for the checksum terminus to match the changed design in the previous commit. --- spec/unit/indirector/file/checksum.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/unit/indirector/file/checksum.rb b/spec/unit/indirector/file/checksum.rb index 3a4cd3d61..539bb973c 100755 --- a/spec/unit/indirector/file/checksum.rb +++ b/spec/unit/indirector/file/checksum.rb @@ -80,11 +80,10 @@ describe Puppet::Indirector::File::Checksum, " when retrieving files" do @store.find(@value) end - it "should return an instance of Puppet::Checksum with the checksum and content set correctly if the file exists" do + it "should return an instance of Puppet::Checksum created with the content if the file exists" do content = "my content" sum = stub 'file' - sum.expects(:content=).with(content) - Puppet::Checksum.expects(:new).with(@value).returns(sum) + Puppet::Checksum.expects(:new).with(content).returns(sum) File.expects(:exist?).with(@path).returns(true) File.expects(:read).with(@path).returns(content) -- cgit From c40da335123ee839294b37134d1e6361000bf216 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 23 Sep 2007 15:39:07 -0500 Subject: Adding a "memory" node terminus, which will be used by 'puppet' and the Cfengine 'module_puppet', since they need to set up the node specially with classes and other weird things. --- lib/puppet/indirector/memory.rb | 21 ++++++++++++++ lib/puppet/indirector/memory/node.rb | 8 ++++++ spec/integration/node.rb | 46 +++++++++++++++++++++++++++++++ spec/unit/indirector/memory.rb | 53 ++++++++++++++++++++++++++++++++++++ spec/unit/indirector/memory/node.rb | 19 +++++++++++++ 5 files changed, 147 insertions(+) create mode 100644 lib/puppet/indirector/memory.rb create mode 100644 lib/puppet/indirector/memory/node.rb create mode 100755 spec/integration/node.rb create mode 100755 spec/unit/indirector/memory.rb create mode 100755 spec/unit/indirector/memory/node.rb diff --git a/lib/puppet/indirector/memory.rb b/lib/puppet/indirector/memory.rb new file mode 100644 index 000000000..5bfcec95d --- /dev/null +++ b/lib/puppet/indirector/memory.rb @@ -0,0 +1,21 @@ +require 'puppet/indirector/terminus' + +# Manage a memory-cached list of instances. +class Puppet::Indirector::Memory < Puppet::Indirector::Terminus + def initialize + @instances = {} + end + + def destroy(instance) + raise ArgumentError.new("Could not find %s to destroy" % instance) unless @instances.include?(instance.name) + @instances.delete(instance.name) + end + + def find(name) + @instances[name] + end + + def save(instance) + @instances[instance.name] = instance + end +end diff --git a/lib/puppet/indirector/memory/node.rb b/lib/puppet/indirector/memory/node.rb new file mode 100644 index 000000000..c5000b879 --- /dev/null +++ b/lib/puppet/indirector/memory/node.rb @@ -0,0 +1,8 @@ +require 'puppet/indirector/memory' + +class Puppet::Indirector::Memory::Node < Puppet::Indirector::Memory + desc "Keep track of nodes in memory but nowhere else. This is used for + one-time compiles, such as what the stand-alone ``puppet`` does. + To use this terminus, you must load it with the data you want it + to contain." +end diff --git a/spec/integration/node.rb b/spec/integration/node.rb new file mode 100755 index 000000000..8bc641bae --- /dev/null +++ b/spec/integration/node.rb @@ -0,0 +1,46 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-23. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../spec_helper' + +require 'puppet/node' + +describe Puppet::Node, " when using the memory terminus" do + before do + @name = "me" + @node = Puppet::Node.new(@name) + Puppet[:node_terminus] = "memory" + end + + it "should find no nodes by default" do + Puppet::Node.find(@name).should be_nil + end + + it "should be able to find nodes that were previously saved" do + @node.save + Puppet::Node.find(@name).should equal(@node) + end + + it "should replace existing saved nodes when a new node with the same name is saved" do + @node.save + two = Puppet::Node.new(@name) + two.save + Puppet::Node.find(@name).should equal(two) + end + + it "should be able to remove previously saved nodes" do + @node.save + Puppet::Node.destroy(@node) + Puppet::Node.find(@name).should be_nil + end + + it "should fail when asked to destroy a node that does not exist" do + proc { Puppet::Node.destroy(@node) }.should raise_error(ArgumentError) + end + + after do + Puppet.settings.clear + end +end diff --git a/spec/unit/indirector/memory.rb b/spec/unit/indirector/memory.rb new file mode 100755 index 000000000..ac6f055ce --- /dev/null +++ b/spec/unit/indirector/memory.rb @@ -0,0 +1,53 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/memory' + +describe "A Memory Terminus", :shared => true do + it "should find no instances by default" do + @searcher.find(@name).should be_nil + end + + it "should be able to find instances that were previously saved" do + @searcher.save(@instance) + @searcher.find(@name).should equal(@instance) + end + + it "should replace existing saved instances when a new instance with the same name is saved" do + @searcher.save(@instance) + two = stub 'second', :name => @name + @searcher.save(two) + @searcher.find(@name).should equal(two) + end + + it "should be able to remove previously saved instances" do + @searcher.save(@instance) + @searcher.destroy(@instance) + @searcher.find(@name).should be_nil + end + + it "should fail when asked to destroy an instance that does not exist" do + proc { @searcher.destroy(@instance) }.should raise_error(ArgumentError) + end +end + +describe Puppet::Indirector::Memory do + it_should_behave_like "A Memory Terminus" + + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @memory_class = Class.new(Puppet::Indirector::Memory) do + def self.to_s + "Testing" + end + end + + @searcher = @memory_class.new + @name = "me" + @instance = stub 'instance', :name => @name + end +end diff --git a/spec/unit/indirector/memory/node.rb b/spec/unit/indirector/memory/node.rb new file mode 100755 index 000000000..cba4af53a --- /dev/null +++ b/spec/unit/indirector/memory/node.rb @@ -0,0 +1,19 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/memory/node' + +# All of our behaviour is described here, so we always have to +# include it. +require 'unit/indirector/memory' + +describe Puppet::Indirector::Memory::Node do + before do + @name = "me" + @searcher = Puppet::Indirector::Memory::Node.new + @instance = stub 'instance', :name => @name + end + + it_should_behave_like "A Memory Terminus" +end -- cgit From cdc8ea6e81c1b5eba5ea784bb7079c4c1f3965a4 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Sun, 23 Sep 2007 19:04:31 -0500 Subject: 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. --- lib/puppet/indirector.rb | 2 +- lib/puppet/indirector/code.rb | 6 + lib/puppet/indirector/code/configuration.rb | 171 ++++++++++++++++++++++++++++ lib/puppet/node/configuration.rb | 4 + lib/puppet/util/settings.rb | 20 ++-- spec/unit/indirector/code.rb | 33 ++++++ spec/unit/indirector/code/configuration.rb | 158 +++++++++++++++++++++++++ spec/unit/node/node.rb | 7 ++ 8 files changed, 391 insertions(+), 10 deletions(-) create mode 100644 lib/puppet/indirector/code.rb create mode 100644 lib/puppet/indirector/code/configuration.rb create mode 100755 spec/unit/indirector/code.rb create mode 100755 spec/unit/indirector/code/configuration.rb 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. diff --git a/spec/unit/indirector/code.rb b/spec/unit/indirector/code.rb new file mode 100755 index 000000000..f34dcf402 --- /dev/null +++ b/spec/unit/indirector/code.rb @@ -0,0 +1,33 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../spec_helper' +require 'puppet/indirector/code' + +describe Puppet::Indirector::Code do + before do + Puppet::Indirector::Terminus.stubs(:register_terminus_class) + @model = mock 'model' + @indirection = stub 'indirection', :name => :mystuff, :register_terminus_type => nil, :model => @model + Puppet::Indirector::Indirection.stubs(:instance).returns(@indirection) + + @code_class = Class.new(Puppet::Indirector::Code) do + def self.to_s + "Testing" + end + end + + @searcher = @code_class.new + end + + it "should not have a find() method defined" do + @searcher.should_not respond_to(:find) + end + + it "should not have a save() method defined" do + @searcher.should_not respond_to(:save) + end + + it "should not have a destroy() method defined" do + @searcher.should_not respond_to(:destroy) + end +end diff --git a/spec/unit/indirector/code/configuration.rb b/spec/unit/indirector/code/configuration.rb new file mode 100755 index 000000000..8652f342d --- /dev/null +++ b/spec/unit/indirector/code/configuration.rb @@ -0,0 +1,158 @@ +#!/usr/bin/env ruby +# +# Created by Luke Kanies on 2007-9-23. +# Copyright (c) 2007. All rights reserved. + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/indirector/code/configuration' + +describe Puppet::Indirector::Code::Configuration do + # LAK:TODO I have no idea how to do this, or even if it should be in this class or test or what. + # This is used for determining if the client should recompile its configuration, so it's not sufficient + # to recompile and compare versions. + # It might be that the right solution is to require configuration caching, and then compare the cached + # configuration version to the current version, via some querying mechanism (i.e., the client asks for just + # the configuration's 'up-to-date' attribute, rather than the whole configuration). + it "should provide a mechanism for determining if the client's configuration is up to date" +end + +describe Puppet::Indirector::Code::Configuration 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::Indirector::Code::Configuration.new + end + + it "should cache the server metadata and reuse it" do + compiler = Puppet::Indirector::Code::Configuration.new + node1 = stub 'node1', :merge => nil + node2 = stub 'node2', :merge => nil + compiler.stubs(:compile) + Puppet::Node.stubs(:search).with('node1').returns(node1) + Puppet::Node.stubs(:search).with('node2').returns(node2) + + compiler.find('node1') + compiler.find('node2') + end +end + +describe Puppet::Indirector::Code::Configuration, " when creating the interpreter" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + end + + it "should not create the interpreter until it is asked for the first time" do + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with({}).returns(interp) + @compiler.interpreter.should equal(interp) + end + + it "should use the same interpreter for all compiles" do + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with({}).returns(interp) + @compiler.interpreter.should equal(interp) + @compiler.interpreter.should equal(interp) + end + + it "should provide a mechanism for setting the code to pass to the interpreter" do + @compiler.should respond_to(:code=) + end + + it "should pass any specified code on to the interpreter when it is being initialized" do + code = "some code" + @compiler.code = code + interp = mock 'interp' + Puppet::Parser::Interpreter.expects(:new).with(:Code => code).returns(interp) + @compiler.send(:interpreter).should equal(interp) + end + +end + +describe Puppet::Indirector::Code::Configuration, " when finding nodes" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = mock 'node' + @compiler.stubs(:compile) + end + + it "should look node information up via the Node class with the provided key" do + @node.stubs :merge + Puppet::Node.expects(:search).with(@name).returns(@node) + @compiler.find(@name) + end + + it "should fail if it cannot find the node" do + @node.stubs :merge + Puppet::Node.expects(:search).with(@name).returns(nil) + proc { @compiler.find(@name) }.should raise_error(Puppet::Error) + end +end + +describe Puppet::Indirector::Code::Configuration, " after finding nodes" do + before do + Puppet.expects(:version).returns(1) + Puppet.settings.stubs(:value).with(:node_name).returns("cert") + Facter.expects(:value).with('fqdn').returns("my.server.com") + Facter.expects(:value).with('ipaddress').returns("my.ip.address") + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = mock 'node' + @compiler.stubs(:compile) + Puppet::Node.stubs(:search).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(@name) + 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(@name) + 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(@name) + end + + # LAK:TODO This is going to be difficult, because this whole process is so + # far removed from the actual connection that the certificate information + # will be quite hard to come by, dum by, gum by. + it "should search for the name using the client certificate's DN if the :node_name setting is set to 'cert'" +end + +describe Puppet::Indirector::Code::Configuration, " when creating configurations" do + before do + @compiler = Puppet::Indirector::Code::Configuration.new + @name = "me" + @node = stub 'node', :merge => nil, :name => @name, :environment => "yay" + Puppet::Node.stubs(:search).with(@name).returns(@node) + end + + it "should pass the found node to the interpreter for compiling" do + config = mock 'config' + @compiler.interpreter.expects(:compile).with(@node) + @compiler.find(@name) + end + + it "should return the results of compiling as the configuration" do + config = mock 'config' + @compiler.interpreter.expects(:compile).with(@node).returns(:configuration) + @compiler.find(@name).should == :configuration + end + + it "should benchmark the compile process" do + @compiler.expects(:benchmark).with do |level, message| + level == :notice and message =~ /^Compiled configuration/ + end + @compiler.interpreter.stubs(:compile).with(@node) + @compiler.find(@name) + end +end diff --git a/spec/unit/node/node.rb b/spec/unit/node/node.rb index 3146f6e7e..fe5d2be8b 100755 --- a/spec/unit/node/node.rb +++ b/spec/unit/node/node.rb @@ -126,3 +126,10 @@ describe Puppet::Node, " when indirecting" do Puppet::Indirector::Indirection.clear_cache end end + +describe Puppet::Node do + # LAK:NOTE This is used to keep track of when a given node has connected, + # so we can report on nodes that do not appear to connecting to the + # central server. + it "should provide a method for noting that the node has connected" +end -- cgit From c3c3e519219ad80ac07d21c74849fbc4246c9d7a Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 25 Sep 2007 00:42:50 -0500 Subject: Fixing a small problem with the mailman type --- lib/puppet/provider/maillist/mailman.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/maillist/mailman.rb b/lib/puppet/provider/maillist/mailman.rb index b556eecd7..903689178 100755 --- a/lib/puppet/provider/maillist/mailman.rb +++ b/lib/puppet/provider/maillist/mailman.rb @@ -101,9 +101,9 @@ Puppet::Type.type(:maillist).provide(:mailman) do # Pull the current state of the list from the full list. We're # getting some double entendre here.... def query - provider.class.instances.each do |list| + self.class.instances.each do |list| if list.name == self.name or list.name.downcase == self.name - return list.property_hash + return list.properties end end nil -- cgit From ffaa8ce07979f4db860950fa9be08ca37964206f Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Tue, 25 Sep 2007 11:24:33 -0500 Subject: Demoting the "file does not exist" log to debug from notice --- lib/puppet/network/handler/fileserver.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/puppet/network/handler/fileserver.rb b/lib/puppet/network/handler/fileserver.rb index 11cb17a90..ae0e6553d 100755 --- a/lib/puppet/network/handler/fileserver.rb +++ b/lib/puppet/network/handler/fileserver.rb @@ -450,7 +450,7 @@ class Puppet::Network::Handler def getfileobject(dir, links) unless FileTest.exists?(dir) - self.notice "File source %s does not exist" % dir + self.debug "File source %s does not exist" % dir return nil end -- cgit