summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorLuke Kanies <luke@madstop.com>2007-10-18 11:56:45 -0500
committerLuke Kanies <luke@madstop.com>2007-10-18 11:56:45 -0500
commit2718b638d1df7fe37941952e396d84d1eff1efc9 (patch)
treecd3f528e8dcaaa1e3bcb1d4215c40535e1ac9909 /lib
parente1dd5dd24a550ef2f887a6e4263fb433b22430cd (diff)
downloadpuppet-2718b638d1df7fe37941952e396d84d1eff1efc9.tar.gz
puppet-2718b638d1df7fe37941952e396d84d1eff1efc9.tar.xz
puppet-2718b638d1df7fe37941952e396d84d1eff1efc9.zip
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.
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet/file_serving/configuration.rb230
-rw-r--r--lib/puppet/file_serving/configuration/parser.rb123
-rw-r--r--lib/puppet/file_serving/mount.rb80
3 files changed, 237 insertions, 196 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