diff options
-rw-r--r-- | lib/puppet/defaults.rb | 6 | ||||
-rw-r--r-- | lib/puppet/indirector/indirection.rb | 6 | ||||
-rw-r--r-- | lib/puppet/indirector/terminus.rb | 21 | ||||
-rw-r--r-- | lib/puppet/indirector/yaml.rb | 47 | ||||
-rwxr-xr-x | spec/unit/indirector/indirection.rb | 15 | ||||
-rwxr-xr-x | spec/unit/indirector/terminus.rb | 48 | ||||
-rwxr-xr-x | spec/unit/indirector/yaml.rb | 113 |
7 files changed, 242 insertions, 14 deletions
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 |