class Puppet::FileBucketFile::File
Public Instance Methods
# File lib/puppet/indirector/file_bucket_file/file.rb 15 def find(request) 16 request.options[:bucket_path] ||= Puppet[:bucketdir] 17 # If filebucket mode is 'list' 18 if request.options[:list_all] 19 return nil unless ::File.exist?(request.options[:bucket_path]) 20 return list(request) 21 end 22 checksum, files_original_path = request_to_checksum_and_path(request) 23 contents_file = path_for(request.options[:bucket_path], checksum, 'contents') 24 paths_file = path_for(request.options[:bucket_path], checksum, 'paths') 25 26 if Puppet::FileSystem.exist?(contents_file) && matches(paths_file, files_original_path) 27 if request.options[:diff_with] 28 other_contents_file = path_for(request.options[:bucket_path], request.options[:diff_with], 'contents') 29 raise _("could not find diff_with %{diff}") % { diff: request.options[:diff_with] } unless Puppet::FileSystem.exist?(other_contents_file) 30 raise _("Unable to diff on this platform") unless Puppet[:diff] != "" 31 return diff(Puppet::FileSystem.path_string(contents_file), Puppet::FileSystem.path_string(other_contents_file)) 32 else 33 #TRANSLATORS "FileBucket" should not be translated 34 Puppet.info _("FileBucket read %{checksum}") % { checksum: checksum } 35 model.new(Puppet::FileSystem.binread(contents_file)) 36 end 37 else 38 nil 39 end 40 end
# File lib/puppet/indirector/file_bucket_file/file.rb 91 def head(request) 92 checksum, files_original_path = request_to_checksum_and_path(request) 93 contents_file = path_for(request.options[:bucket_path], checksum, 'contents') 94 paths_file = path_for(request.options[:bucket_path], checksum, 'paths') 95 96 Puppet::FileSystem.exist?(contents_file) && matches(paths_file, files_original_path) 97 end
# File lib/puppet/indirector/file_bucket_file/file.rb 42 def list(request) 43 if request.remote? 44 raise Puppet::Error, _("Listing remote file buckets is not allowed") 45 end 46 47 fromdate = request.options[:fromdate] || "0:0:0 1-1-1970" 48 todate = request.options[:todate] || Time.now.strftime("%F %T") 49 begin 50 to = Time.parse(todate) 51 rescue ArgumentError 52 raise Puppet::Error, _("Error while parsing 'todate'") 53 end 54 begin 55 from = Time.parse(fromdate) 56 rescue ArgumentError 57 raise Puppet::Error, _("Error while parsing 'fromdate'") 58 end 59 # Setting hash's default value to [], needed by the following loop 60 bucket = Hash.new {[]} 61 msg = String.new 62 # Get all files with mtime between 'from' and 'to' 63 Pathname.new(request.options[:bucket_path]).find { |item| 64 if item.file? and item.basename.to_s == "paths" 65 filenames = item.read.strip.split("\n") 66 filestat = Time.parse(item.stat.mtime.to_s) 67 if from <= filestat and filestat <= to 68 filenames.each do |filename| 69 bucket[filename] += [[ item.stat.mtime , item.parent.basename ]] 70 end 71 end 72 end 73 } 74 # Sort the results 75 bucket.each { |filename, contents| 76 contents.sort_by! do |item| 77 # NOTE: Ruby 2.4 may reshuffle item order even if the keys in sequence are sorted already 78 item[0] 79 end 80 } 81 # Build the output message. Sorted by names then by dates 82 bucket.sort.each { |filename,contents| 83 contents.each { |mtime, chksum| 84 date = mtime.strftime("%F %T") 85 msg += "#{chksum} #{date} #{filename}\n" 86 } 87 } 88 return model.new(msg) 89 end
# File lib/puppet/indirector/file_bucket_file/file.rb 99 def save(request) 100 instance = request.instance 101 _, files_original_path = request_to_checksum_and_path(request) 102 contents_file = path_for(instance.bucket_path, instance.checksum_data, 'contents') 103 paths_file = path_for(instance.bucket_path, instance.checksum_data, 'paths') 104 105 save_to_disk(instance, files_original_path, contents_file, paths_file) 106 107 # don't echo the request content back to the agent 108 model.new('') 109 end
# File lib/puppet/indirector/file_bucket_file/file.rb 111 def validate_key(request) 112 # There are no ACLs on filebucket files so validating key is not important 113 end
Private Instance Methods
@param contents_file [Pathname] Opaque file path to intended backup
location
@param expected_checksum_data [String] expected value of checksum of type
checksum_type
@param checksum_type [String] type of check sum of checksum_data, ie “md5” @return [Boolean] whether the checksum of the contents_file matches the
supplied checksum
@api private
# File lib/puppet/indirector/file_bucket_file/file.rb 242 def contents_file_matches_checksum?(contents_file, expected_checksum_data, checksum_type) 243 contents_file_checksum_data = Puppet::Util::Checksums.method(:"#{checksum_type}_file").call(contents_file.to_path) 244 contents_file_checksum_data == expected_checksum_data 245 end
@param contents_file [Pathname] Opaque file path to intended backup
location
@param bucket_file [Puppet::FileBucket::File] IO object representing
content to back up
@return [void] @api private
# File lib/puppet/indirector/file_bucket_file/file.rb 253 def copy_bucket_file_to_contents_file(contents_file, bucket_file) 254 Puppet::FileSystem.replace_file(contents_file, 0440) do |of| 255 # PUP-1044 writes all of the contents 256 bucket_file.stream() do |src| 257 FileUtils.copy_stream(src, of) 258 end 259 end 260 end
@param paths_file [Object] Opaque file path @param files_original_path [String]
# File lib/puppet/indirector/file_bucket_file/file.rb 120 def matches(paths_file, files_original_path) 121 # Puppet will have already written the paths_file in the systems encoding 122 # given its possible that request.options[:bucket_path] or Puppet[:bucketdir] 123 # contained characters in an encoding that are not represented the 124 # same way when the bytes are decoded as UTF-8, continue using system encoding 125 Puppet::FileSystem.open(paths_file, 0640, 'a+:external') do |f| 126 path_match(f, files_original_path) 127 end 128 end
@return [Object] Opaque path as constructed by the Puppet::FileSystem
# File lib/puppet/indirector/file_bucket_file/file.rb 213 def path_for(bucket_path, digest, subfile = nil) 214 bucket_path ||= Puppet[:bucketdir] 215 216 dir = ::File.join(digest[0..7].split("")) 217 basedir = ::File.join(bucket_path, dir, digest) 218 219 Puppet::FileSystem.pathname(subfile ? ::File.join(basedir, subfile) : basedir) 220 end
# File lib/puppet/indirector/file_bucket_file/file.rb 130 def path_match(file_handle, files_original_path) 131 return true unless files_original_path # if no path was provided, it's a match 132 file_handle.rewind 133 file_handle.each_line do |line| 134 return true if line.chomp == files_original_path 135 end 136 return false 137 end
# File lib/puppet/indirector/file_bucket_file/file.rb 200 def request_to_checksum_and_path(request) 201 checksum_type, checksum, path = request.key.split(/\//, 3) 202 if path == '' # Treat "md5/<checksum>/" like "md5/<checksum>" 203 path = nil 204 end 205 raise ArgumentError, _("Unsupported checksum type %{checksum_type}") % { checksum_type: checksum_type.inspect } if checksum_type != Puppet[:digest_algorithm] 206 expected = method(checksum_type + "_hex_length").call 207 raise _("Invalid checksum %{checksum}") % { checksum: checksum.inspect } if checksum !~ /^[0-9a-f]{#{expected}}$/ 208 [checksum, path] 209 end
@param bucket_file [Puppet::FileBucket::File] IO object representing
content to back up
@param files_original_path [String] Path to original source file on disk @param contents_file [Pathname] Opaque file path to intended backup
location
@param paths_file [Pathname] Opaque file path to file containing source
file paths on disk
@return [void] @raise [Puppet::FileBucket::BucketError] on possible sum collision between
existing and new backup
@api private
# File lib/puppet/indirector/file_bucket_file/file.rb 150 def save_to_disk(bucket_file, files_original_path, contents_file, paths_file) 151 Puppet::Util.withumask(0007) do 152 unless Puppet::FileSystem.dir_exist?(paths_file) 153 Puppet::FileSystem.dir_mkpath(paths_file) 154 end 155 156 # Puppet will have already written the paths_file in the systems encoding 157 # given its possible that request.options[:bucket_path] or Puppet[:bucketdir] 158 # contained characters in an encoding that are not represented the 159 # same way when the bytes are decoded as UTF-8, continue using system encoding 160 Puppet::FileSystem.exclusive_open(paths_file, 0640, 'a+:external') do |f| 161 if Puppet::FileSystem.exist?(contents_file) 162 if verify_identical_file(contents_file, bucket_file) 163 #TRANSLATORS "FileBucket" should not be translated 164 Puppet.info _("FileBucket got a duplicate file %{file_checksum}") % { file_checksum: bucket_file.checksum } 165 # Don't touch the contents file on Windows, since we can't update the 166 # mtime of read-only files there. 167 if !Puppet::Util::Platform.windows? 168 Puppet::FileSystem.touch(contents_file) 169 end 170 elsif contents_file_matches_checksum?(contents_file, bucket_file.checksum_data, bucket_file.checksum_type) 171 # If the contents or sizes don't match, but the checksum does, 172 # then we've found a conflict (potential hash collision). 173 # Unlikely, but quite bad. Don't remove the file in case it's 174 # needed, but ask the user to validate. 175 # Note: Don't print the full path to the bucket file in the 176 # exception to avoid disclosing file system layout on server. 177 #TRANSLATORS "FileBucket" should not be translated 178 Puppet.err(_("Unable to verify existing FileBucket backup at '%{path}'.") % { path: contents_file.to_path }) 179 raise Puppet::FileBucket::BucketError, _("Existing backup and new file have different content but same checksum, %{value}. Verify existing backup and remove if incorrect.") % 180 { value: bucket_file.checksum } 181 else 182 # PUP-1334 If the contents_file exists but does not match its 183 # checksum, our backup has been corrupted. Warn about overwriting 184 # it, and proceed with new backup. 185 Puppet.warning(_("Existing backup does not match its expected sum, %{sum}. Overwriting corrupted backup.") % { sum: bucket_file.checksum }) 186 copy_bucket_file_to_contents_file(contents_file, bucket_file) 187 end 188 else 189 copy_bucket_file_to_contents_file(contents_file, bucket_file) 190 end 191 192 unless path_match(f, files_original_path) 193 f.seek(0, IO::SEEK_END) 194 f.puts(files_original_path) 195 end 196 end 197 end 198 end
@param contents_file [Pathname] Opaque file path to intended backup
location
@param bucket_file [Puppet::FileBucket::File] IO object representing
content to back up
@return [Boolean] whether the data in contents_file is of the same size
and content as that in the bucket_file
@api private
# File lib/puppet/indirector/file_bucket_file/file.rb 229 def verify_identical_file(contents_file, bucket_file) 230 (bucket_file.to_binary.bytesize == Puppet::FileSystem.size(contents_file)) && 231 (bucket_file.stream() {|s| Puppet::FileSystem.compare_stream(contents_file, s) }) 232 end