summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/defaults.rb6
-rw-r--r--lib/puppet/indirector/indirection.rb6
-rw-r--r--lib/puppet/indirector/terminus.rb21
-rw-r--r--lib/puppet/indirector/yaml.rb47
-rwxr-xr-xspec/unit/indirector/indirection.rb15
-rwxr-xr-xspec/unit/indirector/terminus.rb48
-rwxr-xr-xspec/unit/indirector/yaml.rb113
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