diff options
Diffstat (limited to 'lib/puppet/file_serving')
-rw-r--r-- | lib/puppet/file_serving/configuration.rb | 9 | ||||
-rw-r--r-- | lib/puppet/file_serving/content.rb | 18 | ||||
-rw-r--r-- | lib/puppet/file_serving/file_base.rb | 46 | ||||
-rw-r--r-- | lib/puppet/file_serving/fileset.rb | 138 | ||||
-rw-r--r-- | lib/puppet/file_serving/metadata.rb | 35 | ||||
-rw-r--r-- | lib/puppet/file_serving/terminus_helper.rb | 15 | ||||
-rw-r--r-- | lib/puppet/file_serving/terminus_selector.rb | 18 |
7 files changed, 248 insertions, 31 deletions
diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb index 03be1b9dd..ccf0957d1 100644 --- a/lib/puppet/file_serving/configuration.rb +++ b/lib/puppet/file_serving/configuration.rb @@ -28,6 +28,14 @@ class Puppet::FileServing::Configuration private_class_method :new + # Verify that the client is allowed access to this file. + def authorized?(file, options = {}) + mount, file_path = split_path(file, options[:node]) + # If we're not serving this mount, then access is denied. + return false unless mount + return mount.allowed?(options[:node], options[:ipaddress]) + end + # Search for a file. def file_path(key, options = {}) mount, file_path = split_path(key, options[:node]) @@ -81,6 +89,7 @@ class Puppet::FileServing::Configuration return end + # Don't assign the mounts hash until we're sure the parsing succeeded. begin newmounts = @parser.parse @mounts = newmounts diff --git a/lib/puppet/file_serving/content.rb b/lib/puppet/file_serving/content.rb index 38ca80fb0..3cb428e63 100644 --- a/lib/puppet/file_serving/content.rb +++ b/lib/puppet/file_serving/content.rb @@ -4,30 +4,28 @@ require 'puppet/indirector' require 'puppet/file_serving' +require 'puppet/file_serving/file_base' require 'puppet/file_serving/terminus_selector' # A class that handles retrieving file contents. # It only reads the file when its content is specifically # asked for. -class Puppet::FileServing::Content +class Puppet::FileServing::Content < Puppet::FileServing::FileBase extend Puppet::Indirector indirects :file_content, :extend => Puppet::FileServing::TerminusSelector attr_reader :path - def content - ::File.read(@path) - end - - def initialize(path) - raise ArgumentError.new("Files must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/ - raise ArgumentError.new("Files must exist") unless FileTest.exists?(path) + # Read the content of our file in. + def content(base = nil) + # This stat can raise an exception, too. + raise(ArgumentError, "Cannot read the contents of links unless following links") if stat(base).ftype == "symlink" - @path = path + ::File.read(full_path(base)) end # Just return the file contents as the yaml. This allows us to - # avoid escaping or any such thing. LAK:FIXME Not really sure how + # avoid escaping or any such thing. LAK:NOTE Not really sure how # this will behave if the file contains yaml... I think the far # side needs to understand that it's a plain string. def to_yaml diff --git a/lib/puppet/file_serving/file_base.rb b/lib/puppet/file_serving/file_base.rb new file mode 100644 index 000000000..b2e9a0656 --- /dev/null +++ b/lib/puppet/file_serving/file_base.rb @@ -0,0 +1,46 @@ +# +# Created by Luke Kanies on 2007-10-22. +# Copyright (c) 2007. All rights reserved. + +require 'puppet/file_serving' + +# The base class for Content and Metadata; provides common +# functionality like the behaviour around links. +class Puppet::FileServing::FileBase + attr_accessor :path, :base_path + + def full_path(base = nil) + base ||= base_path || raise(ArgumentError, "You must set or provide a base path") + + full = File.join(base, self.path) + end + + def initialize(path, options = {}) + raise ArgumentError.new("Files must not be fully qualified") if path =~ /^#{::File::SEPARATOR}/ + + @path = path + @links = :manage + + options.each do |param, value| + begin + send param.to_s + "=", value + rescue NoMethodError + raise ArgumentError, "Invalid option %s for %s" % [param, self.class] + end + end + end + + attr_reader :links + def links=(value) + raise(ArgumentError, ":links can only be set to :manage or :follow") unless [:manage, :follow].include?(value) + @links = value + end + + # Stat our file, using the appropriate link-sensitive method. + def stat(base = nil) + unless defined?(@stat_method) + @stat_method = self.links == :manage ? :lstat : :stat + end + File.send(@stat_method, full_path(base)) + end +end diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb new file mode 100644 index 000000000..fe54350b1 --- /dev/null +++ b/lib/puppet/file_serving/fileset.rb @@ -0,0 +1,138 @@ +# +# Created by Luke Kanies on 2007-10-22. +# Copyright (c) 2007. All rights reserved. + +require 'find' +require 'puppet/file_serving' +require 'puppet/file_serving/metadata' + +# Operate recursively on a path, returning a set of file paths. +class Puppet::FileServing::Fileset + attr_reader :path, :ignore, :links + attr_accessor :recurse + + # Return a list of all files in our fileset. This is different from the + # normal definition of find in that we support specific levels + # of recursion, which means we need to know when we're going another + # level deep, which Find doesn't do. + def files + files = perform_recursion + + # Now strip off the leading path, so each file becomes relative, and remove + # any slashes that might end up at the beginning of the path. + result = files.collect { |file| file.sub(%r{^#{@path}/*}, '') } + + # And add the path itself. + result.unshift(".") + + result + end + + # Should we ignore this path? + def ignore?(path) + # 'detect' normally returns the found result, whereas we just want true/false. + ! @ignore.detect { |pattern| File.fnmatch?(pattern, path) }.nil? + end + + def ignore=(values) + values = [values] unless values.is_a?(Array) + @ignore = values + end + + def initialize(path, options = {}) + raise ArgumentError.new("Fileset paths must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/ + + @path = path + + # Set our defaults. + @ignore = [] + @links = :manage + @recurse = false + + options.each do |option, value| + method = option.to_s + "=" + begin + send(method, value) + rescue NoMethodError + raise ArgumentError, "Invalid option '%s'" % option + end + end + + raise ArgumentError.new("Fileset paths must exist") unless stat = stat(path) + end + + def links=(links) + links = links.intern if links.is_a?(String) + raise(ArgumentError, "Invalid :links value '%s'" % links) unless [:manage, :follow].include?(links) + @links = links + @stat_method = links == :manage ? :lstat : :stat + end + + # Should we recurse further? This is basically a single + # place for all of the logic around recursion. + def recurse?(depth) + # If recurse is true, just return true + return true if self.recurse == true + + # Return false if the value is false or zero. + return false if [false, 0].include?(self.recurse) + + # Return true if our current depth is less than the allowed recursion depth. + return true if self.recurse.is_a?(Fixnum) and depth <= self.recurse + + # Else, return false. + return false + end + + private + + # Pull the recursion logic into one place. It's moderately hairy, and this + # allows us to keep the hairiness apart from what we do with the files. + def perform_recursion + # Start out with just our base directory. + current_dirs = [@path] + + next_dirs = [] + + depth = 1 + + result = [] + return result unless recurse?(depth) + + while dir_path = current_dirs.shift or ((depth += 1) and recurse?(depth) and current_dirs = next_dirs and next_dirs = [] and dir_path = current_dirs.shift) + next unless stat = stat(dir_path) + next unless stat.directory? + + Dir.entries(dir_path).each do |file_path| + next if [".", ".."].include?(file_path) + + # Note that this also causes matching directories not + # to be recursed into. + next if ignore?(file_path) + + # Add it to our list of files to return + result << File.join(dir_path, file_path) + + # And to our list of files/directories to iterate over. + next_dirs << File.join(dir_path, file_path) + end + end + + return result + end + + # Stat a given file, using the links-appropriate method. + def stat(path) + unless defined?(@stat_method) + @stat_method = self.links == :manage ? :lstat : :stat + end + + begin + return File.send(@stat_method, path) + rescue + # If this happens, it is almost surely because we're + # trying to manage a link to a file that does not exist. + return nil + end + end +end diff --git a/lib/puppet/file_serving/metadata.rb b/lib/puppet/file_serving/metadata.rb index 7adb66981..62ebccca9 100644 --- a/lib/puppet/file_serving/metadata.rb +++ b/lib/puppet/file_serving/metadata.rb @@ -5,17 +5,18 @@ require 'puppet' require 'puppet/indirector' require 'puppet/file_serving' +require 'puppet/file_serving/file_base' require 'puppet/util/checksums' require 'puppet/file_serving/terminus_selector' # A class that handles retrieving file metadata. -class Puppet::FileServing::Metadata +class Puppet::FileServing::Metadata < Puppet::FileServing::FileBase include Puppet::Util::Checksums extend Puppet::Indirector indirects :file_metadata, :extend => Puppet::FileServing::TerminusSelector - attr_reader :path, :owner, :group, :mode, :checksum_type, :checksum + attr_reader :path, :owner, :group, :mode, :checksum_type, :checksum, :ftype, :destination def checksum_type=(type) raise(ArgumentError, "Unsupported checksum type %s" % type) unless respond_to?("%s_file" % type) @@ -23,32 +24,36 @@ class Puppet::FileServing::Metadata @checksum_type = type end - def get_attributes - stat = File.stat(path) + # Retrieve the attributes for this file, relative to a base directory. + # Note that File.stat raises Errno::ENOENT if the file is absent and this + # method does not catch that exception. + def collect_attributes(base = nil) + real_path = full_path(base) + stat = stat(base) @owner = stat.uid @group = stat.gid + @ftype = stat.ftype + # Set the octal mode, but as a string. @mode = "%o" % (stat.mode & 007777) - @checksum = get_checksum - end - - def initialize(path = nil) - if path - raise ArgumentError.new("Files must be fully qualified") unless path =~ /^#{::File::SEPARATOR}/ - raise ArgumentError.new("Files must exist") unless FileTest.exists?(path) - - @path = path + if stat.ftype == "symlink" + @destination = File.readlink(real_path) + else + @checksum = get_checksum(real_path) end + end + def initialize(*args) @checksum_type = "md5" + super end private # Retrieve our checksum. - def get_checksum - ("{%s}" % @checksum_type) + send("%s_file" % @checksum_type, @path) + def get_checksum(path) + ("{%s}" % @checksum_type) + send("%s_file" % @checksum_type, path) end end diff --git a/lib/puppet/file_serving/terminus_helper.rb b/lib/puppet/file_serving/terminus_helper.rb new file mode 100644 index 000000000..9542cbf84 --- /dev/null +++ b/lib/puppet/file_serving/terminus_helper.rb @@ -0,0 +1,15 @@ +# +# Created by Luke Kanies on 2007-10-22. +# Copyright (c) 2007. All rights reserved. + +require 'puppet/file_serving' +require 'puppet/file_serving/fileset' + +# Define some common methods for FileServing termini. +module Puppet::FileServing::TerminusHelper + # Create model instances for all files in a fileset. + def path2instances(path, options = {}) + args = [:links, :ignore, :recurse].inject({}) { |hash, param| hash[param] = options[param] if options[param]; hash } + Puppet::FileServing::Fileset.new(path, args).files.collect { |file| model.new(file) } + end +end diff --git a/lib/puppet/file_serving/terminus_selector.rb b/lib/puppet/file_serving/terminus_selector.rb index 08009cd2b..06b53ddb1 100644 --- a/lib/puppet/file_serving/terminus_selector.rb +++ b/lib/puppet/file_serving/terminus_selector.rb @@ -9,11 +9,11 @@ require 'puppet/file_serving' # in file-serving indirections. This is necessary because # the terminus varies based on the URI asked for. module Puppet::FileServing::TerminusSelector - PROTOCOL_MAP = {"puppet" => :rest, "file" => :local, "puppetmounts" => :mounts} + PROTOCOL_MAP = {"puppet" => :rest, "file" => :file, "puppetmounts" => :file_server} # Pick an appropriate terminus based on the protocol. - def select_terminus(full_uri) - # Short-circuit to :local if it's a fully-qualified path. + def select_terminus(full_uri, options = {}) + # Short-circuit to :file if it's a fully-qualified path. return PROTOCOL_MAP["file"] if full_uri =~ /^#{::File::SEPARATOR}/ begin uri = URI.parse(URI.escape(full_uri)) @@ -26,11 +26,17 @@ module Puppet::FileServing::TerminusSelector # This provides a convenient mechanism for people to write configurations work # well in both a networked and local setting. if uri.host.nil? and uri.scheme == "puppet" and Puppet.settings[:name] == "puppet" - terminus = :mounts + terminus = :file_server end - if uri.path =~ /^\/modules\b/ and terminus == :mounts - terminus = :modules + if terminus == :file_server and uri.path =~ %r{^/([^/]+)\b} + modname = $1 + if modname == "modules" + terminus = :modules + elsif terminus(:modules).find_module(modname, options[:node]) + Puppet.warning "DEPRECATION NOTICE: Found file '%s' in module without using the 'modules' mount; please prefix path with '/modules'" % uri.path + terminus = :modules + end end return terminus |