diff options
| author | Luke Kanies <luke@madstop.com> | 2007-10-18 11:56:45 -0500 |
|---|---|---|
| committer | Luke Kanies <luke@madstop.com> | 2007-10-18 11:56:45 -0500 |
| commit | 2718b638d1df7fe37941952e396d84d1eff1efc9 (patch) | |
| tree | cd3f528e8dcaaa1e3bcb1d4215c40535e1ac9909 | |
| parent | e1dd5dd24a550ef2f887a6e4263fb433b22430cd (diff) | |
This is the first mostly functional commit of the
new file serving structure. The next step is to
hook it up to the indirection so we can start writing
integration tests to see if we can actually serve up files.
| -rw-r--r-- | lib/puppet/file_serving/configuration.rb | 230 | ||||
| -rw-r--r-- | lib/puppet/file_serving/configuration/parser.rb | 123 | ||||
| -rw-r--r-- | lib/puppet/file_serving/mount.rb | 80 | ||||
| -rwxr-xr-x | spec/unit/file_serving/configuration.rb | 246 | ||||
| -rwxr-xr-x | spec/unit/file_serving/configuration/parser.rb | 132 | ||||
| -rwxr-xr-x | spec/unit/file_serving/mount.rb | 37 | ||||
| -rwxr-xr-x | spec/unit/indirector/content/rest.rb | 9 | ||||
| -rwxr-xr-x | spec/unit/indirector/metadata/rest.rb | 9 |
8 files changed, 624 insertions, 242 deletions
diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb index 234f66962..b1512e14e 100644 --- a/lib/puppet/file_serving/configuration.rb +++ b/lib/puppet/file_serving/configuration.rb @@ -4,37 +4,56 @@ require 'puppet' require 'puppet/file_serving' +require 'puppet/file_serving/mount' class Puppet::FileServing::Configuration + require 'puppet/file_serving/configuration/parser' - def self.create(options = {}) - unless defined?(@configuration) - @configuration = new(options) + @config_fileuration = nil + + Mount = Puppet::FileServing::Mount + + # Remove our singleton instance. + def self.clear_cache + @config_fileuration = nil + end + + # Create our singleton configuration. + def self.create + unless @config_fileuration + @config_fileuration = new() end - @configuration + @config_fileuration end - def initialize(options = {}) - if options.include?(:Mount) - @passedconfig = true - unless options[:Mount].is_a?(Hash) - raise Puppet::DevError, "Invalid mount options %s" % - options[:Mount].inspect - end + private_class_method :new - options[:Mount].each { |dir, name| - if FileTest.exists?(dir) - mount(dir, name) - end - } - mount(nil, MODULES) - else - @passedconfig = false - readconfig(false) # don't check the file the first time. - end + # Return a content instance. + def content(path, options = {}) + mount, file_path = splitpath(path, options[:node]) + + return nil unless mount + + mount.file_instance :content, file_path, options + end + + def initialize + @mounts = {} + @config_file = nil + + # We don't check to see if the file is modified the first time, + # because we always want to parse at first. + readconfig(false) end - private :initialize + # Return a metadata instance. + def metadata(path, options = {}) + mount, file_path = splitpath(path, options[:node]) + + return nil unless mount + + mount.file_instance :metadata, file_path, options + end # Mount a new directory with a name. def mount(path, name) @@ -55,44 +74,17 @@ class Puppet::FileServing::Configuration return @mounts[name] end + # Is a given mount available? + def mounted?(name) + @mounts.include?(name) + end + def umount(name) @mounts.delete(name) if @mounts.include? name end private - def authcheck(file, mount, client, clientip) - # If we're local, don't bother passing in information. - if local? - client = nil - clientip = nil - end - unless mount.allowed?(client, clientip) - mount.warning "%s cannot access %s" % - [client, file] - raise Puppet::AuthorizationError, "Cannot access %s" % mount - end - end - - def convert(url, client, clientip) - readconfig - - url = URI.unescape(url) - - mount, stub = splitpath(url, client) - - authcheck(url, mount, client, clientip) - - path = nil - unless path = mount.subdir(stub, client) - mount.notice "Could not find subdirectory %s" % - "//%s/%s" % [mount, stub] - return "" - end - - return mount, path - end - # Deal with ignore parameters. def handleignore(children, path, ignore) ignore.each { |ignore| @@ -106,7 +98,6 @@ class Puppet::FileServing::Configuration # Return the mount for the Puppet modules; allows file copying from # the modules. def modules_mount(module_name, client) - # Find our environment, if we have one. unless hostname = (client || Facter.value("hostname")) raise ArgumentError, "Could not find hostname" end @@ -119,7 +110,7 @@ class Puppet::FileServing::Configuration # And use the environment to look up the module. mod = Puppet::Module::find(module_name, env) if mod - return @mounts[MODULES].copy(mod.name, mod.files) + return @mounts[Mount::MODULES].copy(mod.name, mod.files) else return nil end @@ -127,123 +118,38 @@ class Puppet::FileServing::Configuration # Read the configuration file. def readconfig(check = true) - return if @noreadconfig + config = Puppet[:fileserverconfig] + + return unless FileTest.exists?(config) - if check and ! @config.changed? + @parser ||= Puppet::FileServing::Configuration::Parser.new(config) + + if check and ! @parser.changed? return end - newmounts = {} begin - File.open(@config.file) { |f| - mount = nil - count = 1 - f.each { |line| - case line - when /^\s*#/: next # skip comments - when /^\s*$/: next # skip blank lines - when /\[([-\w]+)\]/: - name = $1 - if newmounts.include?(name) - raise FileServerError, "%s is already mounted at %s" % - [newmounts[name], name], count, @config.file - end - mount = Mount.new(name) - newmounts[name] = mount - when /^\s*(\w+)\s+(.+)$/: - var = $1 - value = $2 - case var - when "path": - if mount.name == MODULES - Puppet.warning "The '#{MODULES}' module can not have a path. Ignoring attempt to set it" - else - begin - mount.path = value - rescue FileServerError => detail - Puppet.err "Removing mount %s: %s" % - [mount.name, detail] - newmounts.delete(mount.name) - end - end - when "allow": - value.split(/\s*,\s*/).each { |val| - begin - mount.info "allowing %s access" % val - mount.allow(val) - rescue AuthStoreError => detail - raise FileServerError.new(detail.to_s, - count, @config.file) - end - } - when "deny": - value.split(/\s*,\s*/).each { |val| - begin - mount.info "denying %s access" % val - mount.deny(val) - rescue AuthStoreError => detail - raise FileServerError.new(detail.to_s, - count, @config.file) - end - } - else - raise FileServerError.new("Invalid argument '%s'" % var, - count, @config.file) - end - else - raise FileServerError.new("Invalid line '%s'" % line.chomp, - count, @config.file) - end - count += 1 - } - } - rescue Errno::EACCES => detail - Puppet.err "FileServer error: Cannot read %s; cannot serve" % @config - #raise Puppet::Error, "Cannot read %s" % @config - rescue Errno::ENOENT => detail - Puppet.err "FileServer error: '%s' does not exist; cannot serve" % - @config - #raise Puppet::Error, "%s does not exit" % @config - #rescue FileServerError => detail - # Puppet.err "FileServer error: %s" % detail - end - - unless newmounts[MODULES] - mount = Mount.new(MODULES) - mount.allow("*") - newmounts[MODULES] = mount + newmounts = @parser.parse + @mounts = newmounts + rescue => detail + Puppet.err "Error parsing fileserver configuration: %s; using old configuration" % detail end - - # Verify each of the mounts are valid. - # We let the check raise an error, so that it can raise an error - # pointing to the specific problem. - newmounts.each { |name, mount| - unless mount.valid? - raise FileServerError, "No path specified for mount %s" % - name - end - } - @mounts = newmounts end # Split the path into the separate mount point and path. - def splitpath(dir, client) + def splitpath(uri, node) + # Reparse the configuration if necessary. + readconfig + + raise(ArgumentError, "Cannot find file: Invalid path '%s'" % uri) unless uri =~ %r{/([-\w]+)/?} + # the dir is based on one of the mounts # so first retrieve the mount path - mount = nil - path = nil - if dir =~ %r{/([-\w]+)/?} - # Strip off the mount name. - mount_name, path = dir.sub(%r{^/}, '').split(File::Separator, 2) - - unless mount = modules_mount(mount_name, client) - unless mount = @mounts[mount_name] - raise FileServerError, "Fileserver module '%s' not mounted" % mount_name - end - end - else - raise FileServerError, "Fileserver error: Invalid path '%s'" % dir - end + mount = path = nil + # Strip off the mount name. + mount_name, path = uri.sub(%r{^/}, '').split(File::Separator, 2) + + return nil unless mount = modules_mount(mount_name, node) || @mounts[mount_name] if path == "" path = nil diff --git a/lib/puppet/file_serving/configuration/parser.rb b/lib/puppet/file_serving/configuration/parser.rb new file mode 100644 index 000000000..5b17d9801 --- /dev/null +++ b/lib/puppet/file_serving/configuration/parser.rb @@ -0,0 +1,123 @@ +require 'puppet/file_serving/configuration' +require 'puppet/util/loadedfile' + +class Puppet::FileServing::Configuration::Parser < Puppet::Util::LoadedFile + Mount = Puppet::FileServing::Mount + + # Parse our configuration file. + def parse + raise("File server configuration %s does not exist" % self.file) unless FileTest.exists?(self.file) + raise("Cannot read file server configuration %s" % self.file) unless FileTest.readable?(self.file) + + @mounts = {} + @count = 0 + + File.open(self.file) { |f| + mount = nil + f.each { |line| + # Have the count increment at the top, in case we throw exceptions. + @count += 1 + + case line + when /^\s*#/: next # skip comments + when /^\s*$/: next # skip blank lines + when /\[([-\w]+)\]/: + mount = newmount($1) + when /^\s*(\w+)\s+(.+)$/: + var = $1 + value = $2 + raise(ArgumentError, "Fileserver configuration file does not use '=' as a separator") if value =~ /^=/ + case var + when "path": + path(mount, value) + when "allow": + allow(mount, value) + when "deny": + deny(mount, value) + else + raise ArgumentError.new("Invalid argument '%s'" % var, + @count, file) + end + else + raise ArgumentError.new("Invalid line '%s'" % line.chomp, + @count, file) + end + } + } + + return @mounts + end + + private + + # Add the mount for getting files from modules. + def add_module_mount + unless @mounts[Mount::MODULES] + mount = Mount.new(Mount::MODULES) + mount.allow("*") + @mounts[Mount::MODULES] = mount + end + end + + # Allow a given pattern access to a mount. + def allow(mount, value) + value.split(/\s*,\s*/).each { |val| + begin + mount.info "allowing %s access" % val + mount.allow(val) + rescue AuthStoreError => detail + raise ArgumentError.new(detail.to_s, + @count, file) + end + } + end + + # Deny a given pattern access to a mount. + def deny(mount, value) + value.split(/\s*,\s*/).each { |val| + begin + mount.info "denying %s access" % val + mount.deny(val) + rescue AuthStoreError => detail + raise ArgumentError.new(detail.to_s, + @count, file) + end + } + end + + # Create a new mount. + def newmount(name) + if @mounts.include?(name) + raise ArgumentError, "%s is already mounted at %s" % + [@mounts[name], name], @count, file + end + mount = Mount.new(name) + @mounts[name] = mount + return mount + end + + # Set the path for a mount. + def path(mount, value) + if mount.name == Mount::MODULES + Puppet.warning "The '#{Mount::MODULES}' module can not have a path. Ignoring attempt to set it" + else + begin + mount.path = value + rescue ArgumentError => detail + Puppet.err "Removing mount %s: %s" % + [mount.name, detail] + @mounts.delete(mount.name) + end + end + end + + # Make sure all of our mounts are valid. We have to do this after the fact + # because details are added over time as the file is parsed. + def validate + @mounts.each { |name, mount| + unless mount.valid? + raise ArgumentError, "No path specified for mount %s" % name + end + } + end +end diff --git a/lib/puppet/file_serving/mount.rb b/lib/puppet/file_serving/mount.rb index 719c30b16..be67e6d2b 100644 --- a/lib/puppet/file_serving/mount.rb +++ b/lib/puppet/file_serving/mount.rb @@ -13,11 +13,12 @@ require 'puppet/file_serving/content' class Puppet::FileServing::Mount < Puppet::Network::AuthStore include Puppet::Util::Logging - attr_reader :name + # A constant that defines how we refer to our modules mount. + MODULES = "modules" - @@syncs = {} + InstanceTypes = {:metadata => Puppet::FileServing::Metadata, :content => Puppet::FileServing::Content} - @@files = {} + attr_reader :name # Return a new mount with the same properties as +self+, except # with a different name and path. @@ -28,17 +29,27 @@ class Puppet::FileServing::Mount < Puppet::Network::AuthStore return result end - # Return a content instance for a given file. - def content(short_file, client = nil) - file_instance(Puppet::FileServing::Content, short_file, client) + # Return an instance of the appropriate class. + def file_instance(return_type, short_file, options = {}) + raise(ArgumentError, "Invalid file type %s" % return_type) unless InstanceTypes.include?(return_type) + + file = file_path(short_file, options[:node]) + + return nil unless FileTest.exists?(file) + + return InstanceTypes[return_type].new(file) end # Return a fully qualified path, given a short path and # possibly a client name. - def file_path(short, client = nil) - p = path(client) - raise ArgumentError.new("Mounts without paths are not usable") unless p - File.join(p, short) + def file_path(relative_path, node = nil) + full_path = path(node) + raise ArgumentError.new("Mounts without paths are not usable") unless full_path + + # If there's no relative path name, then we're serving the mount itself. + return full_path unless relative_path + + return File.join(full_path, relative_path) end # Create out object. It must have a name. @@ -57,15 +68,10 @@ class Puppet::FileServing::Mount < Puppet::Network::AuthStore super() end - # Return a metadata instance with the appropriate information provided. - def metadata(short_file, client = nil) - file_instance(Puppet::FileServing::Metadata, short_file, client) - end - # Return the path as appropriate, expanding as necessary. - def path(client = nil) + def path(node = nil) if expandable? - return expand(@path, client) + return expand(@path, node) else return @path end @@ -114,22 +120,37 @@ class Puppet::FileServing::Mount < Puppet::Network::AuthStore private - # Create a map for a specific client. - def clientmap(client) + # LAK:FIXME Move this method to the REST terminus hook. + def authcheck(file, client, clientip) + raise "This method should be replaced by a REST/terminus hook" + # If we're local, don't bother passing in information. + if local? + client = nil + clientip = nil + end + unless mount.allowed?(client, clientip) + mount.warning "%s cannot access %s" % + [client, file] + raise Puppet::AuthorizationError, "Cannot access %s" % mount + end + end + + # Create a map for a specific node. + def clientmap(node) { - "h" => client.sub(/\..*$/, ""), - "H" => client, - "d" => client.sub(/[^.]+\./, "") # domain name + "h" => node.sub(/\..*$/, ""), + "H" => node, + "d" => node.sub(/[^.]+\./, "") # domain name } end # Replace % patterns as appropriate. - def expand(path, client = nil) + def expand(path, node = nil) # This map should probably be moved into a method. map = nil - if client - map = clientmap(client) + if node + map = clientmap(node) else Puppet.notice "No client; expanding '%s' with local host" % path @@ -155,15 +176,6 @@ class Puppet::FileServing::Mount < Puppet::Network::AuthStore end end - # Return an instance of the appropriate class. - def file_instance(klass, short_file, client = nil) - file = file_path(short_file, client) - - return nil unless FileTest.exists?(file) - - return klass.new(file) - end - # Cache this manufactured map, since if it's used it's likely # to get used a lot. def localmap diff --git a/spec/unit/file_serving/configuration.rb b/spec/unit/file_serving/configuration.rb index caff0fd14..eabad0838 100755 --- a/spec/unit/file_serving/configuration.rb +++ b/spec/unit/file_serving/configuration.rb @@ -4,62 +4,254 @@ require File.dirname(__FILE__) + '/../../spec_helper' require 'puppet/file_serving/configuration' -describe Puppet::FileServing::Configuration do - it "should work without a configuration file" +module FSConfigurationTesting + def setup + @path = "/path/to/configuration/file.conf" + Puppet.settings.stubs(:value).with(:fileserverconfig).returns(@path) + end + + def teardown + Puppet::FileServing::Configuration.clear_cache + end end -describe Puppet::FileServing::Configuration, " when initializing" do +describe Puppet::FileServing::Configuration do it "should make :new a private method" do - pending "Not implemented yet" - #proc { Puppet::FileServing::Configuration }.should raise_error + proc { Puppet::FileServing::Configuration.new }.should raise_error end it "should return the same configuration each time :create is called" do - pending "Not implemented yet" - #Puppet::FileServing::Configuration.create.should equal(Puppet::FileServing::Configuration.create) + Puppet::FileServing::Configuration.create.should equal(Puppet::FileServing::Configuration.create) + end + + it "should have a method for removing the current configuration instance" do + old = Puppet::FileServing::Configuration.create + Puppet::FileServing::Configuration.clear_cache + Puppet::FileServing::Configuration.create.should_not equal(old) + end +end + +describe Puppet::FileServing::Configuration, " when initializing" do + include FSConfigurationTesting + + it "should work without a configuration file" do + FileTest.stubs(:exists?).with(@path).returns(false) + proc { Puppet::FileServing::Configuration.create }.should_not raise_error + end + + it "should parse the configuration file if present" do + FileTest.stubs(:exists?).with(@path).returns(true) + @parser = mock 'parser' + @parser.expects(:parse).returns({}) + Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) + Puppet::FileServing::Configuration.create + end + + it "should determine the path to the configuration file from the Puppet settings" do + Puppet::FileServing::Configuration.create end end describe Puppet::FileServing::Configuration, " when parsing the configuration file" do - it "should not raise exceptions" + include FSConfigurationTesting - it "should not replace the mount list until the file is entirely parsed successfully" + before do + FileTest.stubs(:exists?).with(@path).returns(true) + @parser = mock 'parser' + Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) + end + + it "should set the mount list to the results of parsing" do + @parser.expects(:parse).returns("one" => mock("mount")) + config = Puppet::FileServing::Configuration.create + config.mounted?("one").should be_true + end + + it "should not raise exceptions" do + @parser.expects(:parse).raises(ArgumentError) + proc { Puppet::FileServing::Configuration.create }.should_not raise_error + end + + it "should replace the existing mount list with the results of reparsing" do + @parser.expects(:parse).returns("one" => mock("mount")) + config = Puppet::FileServing::Configuration.create + config.mounted?("one").should be_true + # Now parse again + @parser.expects(:parse).returns("two" => mock('other')) + config.send(:readconfig, false) + config.mounted?("one").should be_false + config.mounted?("two").should be_true + end - it "should skip comments" + it "should not replace the mount list until the file is entirely parsed successfully" do + @parser.expects(:parse).returns("one" => mock("mount")) + @parser.expects(:parse).raises(ArgumentError) + config = Puppet::FileServing::Configuration.create + # Now parse again, so the exception gets thrown + config.send(:readconfig, false) + config.mounted?("one").should be_true + end +end - it "should skip blank lines" +describe Puppet::FileServing::Configuration, " when using a module mount" do + include FSConfigurationTesting - it "should create a new mount for each section in the configuration" + before do + @parser = mock 'parser' + @parser.stubs(:changed?).returns true + FileTest.stubs(:exists?).with(@path).returns(true) + Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) - it "should only allow mount names that are alphanumeric plus dashes" + @mount1 = stub 'mount', :name => "one" + @mounts = {"one" => @mount1} - it "should set the mount path to the path attribute from that section" + Facter.stubs(:value).with("hostname").returns("whatever") - it "should refuse to allow a path for the modules mount" + @config = Puppet::FileServing::Configuration.create + end - it "should tell the mount to allow any allow values from the section" + it "should use a module mount if a module's name matches the mount name" - it "should tell the mount to deny any deny values from the section" + it "should use any provided node name during module creation" - it "should fail on any attributes other than path, allow, and deny" + it "should prefer module mounts to static mounts" +end + +# We're kind of testing the implementation here, because we know that both +# content and metadata use the same internal method. Oh well. +describe Puppet::FileServing::Configuration, " when using File URIs to pick the correct mount and file" do + include FSConfigurationTesting + + before do + @parser = mock 'parser' + @parser.stubs(:changed?).returns true + FileTest.stubs(:exists?).with(@path).returns(true) + Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) + + @mount1 = stub 'mount', :name => "one" + @mounts = {"one" => @mount1} + + Facter.stubs(:value).with("hostname").returns("whatever") + + @config = Puppet::FileServing::Configuration.create + @config.stubs(:modules_mount).returns(nil) + end + + it "should fail if the uri does not match a leading slash followed by a valid mount name" do + @parser.expects(:parse).returns(@mounts) + proc { @config.metadata("something") }.should raise_error(ArgumentError) + end + + it "should use the first term after the first slash for the mount name" do + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance) + @config.metadata("/one") + end + + it "should use the remainder of the URI after the mount name as the file name" do + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance).with(:metadata, "something/else", {}) + @config.metadata("/one/something/else") + end + + it "should treat a bare name as a mount and no relative file" do + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance).with(:metadata, nil, {}) + @config.metadata("/one") + end + + it "should treat a name with a trailing slash equivalently to a name with no trailing slash" do + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance).with(:metadata, nil, {}) + @config.metadata("/one/") + end + + it "should return nil if the mount cannot be found" do + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns({}) + @config.metadata("/one/something").should be_nil + end + + it "should reparse the configuration file when it has changed" do + @mount1.stubs(:file_instance).returns("whatever") + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns(@mounts) + @config.metadata("/one/something") + + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns({}) + @config.metadata("/one/something").should be_nil + end end describe Puppet::FileServing::Configuration, " when finding file metadata" do - it "should require authorization" + include FSConfigurationTesting - it "should return nil if the mount cannot be found" + before do + @parser = mock 'parser' + FileTest.stubs(:exists?).with(@path).returns(true) + Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) - it "should use the mount object to return a Metadata instance if the mount exists" + @mount1 = stub 'mount', :name => "one" + @mounts = {"one" => @mount1} + + @config = Puppet::FileServing::Configuration.create + @config.stubs(:modules_mount).returns(nil) + end + + it "should return nil if the mount cannot be found" do + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns({}) + @config.metadata("/one/something").should be_nil + end + + it "should use the mount object to return a Metadata instance if the mount exists" do + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance).with(:metadata, "something", {}).returns(:mydata) + @config.metadata("/one/something").should == :mydata + end + + it "should pass any options on to the mount" do + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance).with(:metadata, "something", :node => "me").returns(:mydata) + @config.metadata("/one/something", :node => "me").should == :mydata + end end describe Puppet::FileServing::Configuration, " when finding file content" do - it "should require authorization" + include FSConfigurationTesting - it "should return nil if the mount cannot be found" + before do + @parser = mock 'parser' + FileTest.stubs(:exists?).with(@path).returns(true) + Puppet::FileServing::Configuration::Parser.stubs(:new).returns(@parser) - it "should use the mount object to return a Content instance if the mount exists" -end + @mount1 = stub 'mount', :name => "one" + @mounts = {"one" => @mount1} + + @config = Puppet::FileServing::Configuration.create + @config.stubs(:modules_mount).returns(nil) + end + + it "should return nil if the mount cannot be found" do + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns({}) + @config.content("/one/something").should be_nil + end + + it "should use the mount object to return a Content instance if the mount exists" do + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance).with(:content, "something", {}).returns(:mydata) + @config.content("/one/something").should == :mydata + end -describe Puppet::FileServing::Configuration, " when authorizing" do - it "should reparse the configuration file when it has changed" + it "should pass any options on to the mount" do + @parser.expects(:changed?).returns(true) + @parser.expects(:parse).returns(@mounts) + @mount1.expects(:file_instance).with(:content, "something", :node => "me").returns(:mydata) + @config.content("/one/something", :node => "me").should == :mydata + end end diff --git a/spec/unit/file_serving/configuration/parser.rb b/spec/unit/file_serving/configuration/parser.rb new file mode 100755 index 000000000..aa296cf43 --- /dev/null +++ b/spec/unit/file_serving/configuration/parser.rb @@ -0,0 +1,132 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/file_serving/configuration/parser' + +describe Puppet::FileServing::Configuration::Parser do + it "should subclass the LoadedFile class" do + Puppet::FileServing::Configuration::Parser.superclass.should equal(Puppet::Util::LoadedFile) + end +end + +module FSConfigurationParserTesting + def setup + @path = "/my/config.conf" + FileTest.stubs(:exists?).with(@path).returns(true) + FileTest.stubs(:readable?).with(@path).returns(true) + @filehandle = mock 'filehandle' + File.expects(:open).with(@path).yields(@filehandle) + @parser = Puppet::FileServing::Configuration::Parser.new(@path) + end + + def mock_file_content(content) + # We want an array, but we actually want our carriage returns on all of it. + lines = content.split("\n").collect { |l| l + "\n" } + @filehandle.stubs(:each).multiple_yields(*lines) + end +end + +describe Puppet::FileServing::Configuration::Parser, " when parsing" do + include FSConfigurationParserTesting + + before do + @parser.stubs(:add_modules_mount) + end + + it "should allow comments" do + @filehandle.expects(:each).yields("# this is a comment\n") + proc { @parser.parse }.should_not raise_error + end + + it "should allow blank lines" do + @filehandle.expects(:each).yields("\n") + proc { @parser.parse }.should_not raise_error + end + + it "should create a new mount for each section in the configuration" do + mount1 = mock 'one' + mount2 = mock 'two' + Puppet::FileServing::Mount.expects(:new).with("one").returns(mount1) + Puppet::FileServing::Mount.expects(:new).with("two").returns(mount2) + mock_file_content "[one]\n[two]\n" + @parser.parse + end + + # This test is almost the exact same as the previous one. + it "should return a hash of the created mounts" do + mount1 = mock 'one' + mount2 = mock 'two' + Puppet::FileServing::Mount.expects(:new).with("one").returns(mount1) + Puppet::FileServing::Mount.expects(:new).with("two").returns(mount2) + mock_file_content "[one]\n[two]\n" + + @parser.parse.should == {"one" => mount1, "two" => mount2} + end + + it "should only allow mount names that are alphanumeric plus dashes" do + mock_file_content "[a*b]\n" + proc { @parser.parse }.should raise_error(ArgumentError) + end + + it "should fail if the value for path/allow/deny starts with an equals sign" do + mock_file_content "[one]\npath = /testing" + proc { @parser.parse }.should raise_error(ArgumentError) + end +end + +describe Puppet::FileServing::Configuration::Parser, " when parsing mount attributes" do + include FSConfigurationParserTesting + + before do + @mount = stub 'mount', :name => "one" + Puppet::FileServing::Mount.expects(:new).with("one").returns(@mount) + @parser.stubs(:add_modules_mount) + end + + it "should set the mount path to the path attribute from that section" do + mock_file_content "[one]\npath /some/path\n" + + @mount.expects(:path=).with("/some/path") + @parser.parse + end + + it "should tell the mount to allow any allow values from the section" do + mock_file_content "[one]\nallow something\n" + + @mount.expects(:info) + @mount.expects(:allow).with("something") + @parser.parse + end + + it "should tell the mount to deny any deny values from the section" do + mock_file_content "[one]\ndeny something\n" + + @mount.expects(:info) + @mount.expects(:deny).with("something") + @parser.parse + end + + it "should fail on any attributes other than path, allow, and deny" do + mock_file_content "[one]\ndo something\n" + + proc { @parser.parse }.should raise_error(ArgumentError) + end +end + +describe Puppet::FileServing::Configuration::Parser, " when parsing the modules mount" do + include FSConfigurationParserTesting + + before do + @mount = stub 'mount', :name => "modules" + Puppet::FileServing::Mount.expects(:new).with("modules").returns(@mount) + end + + it "should warn if a path is set" do + mock_file_content "[modules]\npath /some/path\n" + + @modules.expects(:path=).never + Puppet.expects(:warning) + @parser.parse + end +end diff --git a/spec/unit/file_serving/mount.rb b/spec/unit/file_serving/mount.rb index cf658fe61..b646e10ca 100755 --- a/spec/unit/file_serving/mount.rb +++ b/spec/unit/file_serving/mount.rb @@ -102,44 +102,43 @@ describe Puppet::FileServing::Mount, " when finding files" do it "should follow links when asked" end -describe Puppet::FileServing::Mount, " when providing metadata" do +describe Puppet::FileServing::Mount, " when providing file instances" do before do FileTest.stubs(:exists?).returns(true) FileTest.stubs(:directory?).returns(true) FileTest.stubs(:readable?).returns(true) - @mount = Puppet::FileServing::Mount.new("test", "/mount") + @mount = Puppet::FileServing::Mount.new("test", "/mount/%h") @host = "host.domain.com" end it "should return nil if the file is absent" do Puppet::FileServing::Metadata.expects(:new).never FileTest.stubs(:exists?).returns(false) - @mount.metadata("/my/path").should be_nil + @mount.file_instance(:metadata, "/my/path").should be_nil end - it "should return a Metadata instance if the file is present" do - Puppet::FileServing::Metadata.expects(:new).returns(:myobj) - @mount.metadata("/my/path").should == :myobj + it "should fail if any type other than metadata or content is requested" do + proc { @mount.file_instance(:else, "/my/path") }.should raise_error(ArgumentError) end -end -describe Puppet::FileServing::Mount, " when providing content" do - before do + it "should treat a nil file name as the path to the mount itself" do + Puppet::FileServing::Metadata.expects(:new).with("/mount/myhost").returns(:myobj) FileTest.stubs(:exists?).returns(true) - FileTest.stubs(:directory?).returns(true) - FileTest.stubs(:readable?).returns(true) - @mount = Puppet::FileServing::Mount.new("test", "/mount") - @host = "host.domain.com" + @mount.file_instance(:metadata, nil).should == :myobj end - it "should return nil if the file is absent" do - Puppet::FileServing::Content.expects(:new).never - FileTest.stubs(:exists?).returns(false) - @mount.content("/my/path").should be_nil + it "should return a Metadata instance if the file is present and metadata was asked for" do + Puppet::FileServing::Metadata.expects(:new).returns(:myobj) + @mount.file_instance(:metadata, "/my/path").should == :myobj end - it "should return a Content instance if the file is present" do + it "should return a Content instance if the file is present and content was asked for" do Puppet::FileServing::Content.expects(:new).returns(:myobj) - @mount.content("/my/path").should == :myobj + @mount.file_instance(:content, "/my/path").should == :myobj + end + + it "should use the client host name if provided in the options" do + Puppet::FileServing::Content.expects(:new).with("/mount/host/my/path").returns(:myobj) + @mount.file_instance(:content, "/my/path", :node => @host).should == :myobj end end diff --git a/spec/unit/indirector/content/rest.rb b/spec/unit/indirector/content/rest.rb new file mode 100755 index 000000000..8527e2548 --- /dev/null +++ b/spec/unit/indirector/content/rest.rb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/file_serving/content' + +describe "Puppet::Indirector::Content::Rest" do + it "should add the node's cert name to the arguments" +end diff --git a/spec/unit/indirector/metadata/rest.rb b/spec/unit/indirector/metadata/rest.rb new file mode 100755 index 000000000..745456e1b --- /dev/null +++ b/spec/unit/indirector/metadata/rest.rb @@ -0,0 +1,9 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/file_serving/metadata' + +describe "Puppet::Indirector::Metadata::Rest" do + it "should add the node's cert name to the arguments" +end |
