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, :recurselimit, :checksum_type # Produce a hash of files, with merged so that earlier files # with the same postfix win. E.g., /dir1/subfile beats /dir2/subfile. # It's a hash because we need to know the relative path of each file, # and the base directory. # This will probably only ever be used for searching for plugins. def self.merge(*filesets) result = {} filesets.each do |fileset| fileset.files.each do |file| result[file] ||= fileset.path end end result end def initialize(path, options = {}) if Puppet::Util::Platform.windows? # REMIND: UNC path path = path.chomp(File::SEPARATOR) unless path =~ /^[A-Za-z]:\/$/ else path = path.chomp(File::SEPARATOR) unless path == File::SEPARATOR end raise ArgumentError.new(_("Fileset paths must be fully qualified: %{path}") % { path: path }) unless Puppet::Util.absolute_path?(path) @path = path # Set our defaults. self.ignore = [] self.links = :manage @recurse = false @recurselimit = :infinite if options.is_a?(Puppet::Indirector::Request) initialize_from_request(options) else initialize_from_hash(options) end raise ArgumentError.new(_("Fileset paths must exist")) unless valid?(path) #TRANSLATORS "recurse" and "recurselimit" are parameter names and should not be translated raise ArgumentError.new(_("Fileset recurse parameter must not be a number anymore, please use recurselimit")) if @recurse.is_a?(Integer) end # 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{^#{Regexp.escape(@path)}/*}, '') } # And add the path itself. result.unshift(".") result end def ignore=(values) values = [values] unless values.is_a?(Array) @ignore = values.collect(&:to_s) end def links=(links) links = links.to_sym #TRANSLATORS ":links" is a parameter name and should not be translated raise(ArgumentError, _("Invalid :links value '%{links}'") % { links: links }) unless [:manage, :follow].include?(links) @links = links @stat_method = @links == :manage ? :lstat : :stat end private def initialize_from_hash(options) options.each do |option, value| method = option.to_s + "=" begin send(method, value) rescue NoMethodError raise ArgumentError, _("Invalid option '%{option}'") % { option: option }, $!.backtrace end end end def initialize_from_request(request) [:links, :ignore, :recurse, :recurselimit, :checksum_type].each do |param| if request.options.include?(param) # use 'include?' so the values can be false value = request.options[param] elsif request.options.include?(param.to_s) value = request.options[param.to_s] end next if value.nil? value = true if value == "true" value = false if value == "false" value = Integer(value) if value.is_a?(String) and value =~ /^\d+$/ send(param.to_s + "=", value) end end FileSetEntry = Struct.new(:depth, :path, :ignored, :stat_method) do def down_level(to) FileSetEntry.new(depth + 1, File.join(path, to), ignored, stat_method) end def basename File.basename(path) end def children return [] unless directory? Dir.entries(path, encoding: Encoding::UTF_8). reject { |child| ignore?(child) }. collect { |child| down_level(child) } end def ignore?(child) return true if child == "." || child == ".." return false if ignored == [nil] ignored.any? { |pattern| File.fnmatch?(pattern, child) } end def directory? Puppet::FileSystem.send(stat_method, path).directory? rescue Errno::ENOENT, Errno::EACCES false end end # 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 current_dirs = [FileSetEntry.new(0, @path, @ignore, @stat_method)] result = [] while entry = current_dirs.shift if continue_recursion_at?(entry.depth + 1) entry.children.each do |child| result << child.path current_dirs << child end end end result end def valid?(path) Puppet::FileSystem.send(@stat_method, path) true rescue Errno::ENOENT, Errno::EACCES false end def continue_recursion_at?(depth) # recurse if told to, and infinite recursion or current depth not at the limit self.recurse && (self.recurselimit == :infinite || depth <= self.recurselimit) end end