class Puppet::FileBucketFile::File

Public Instance Methods

find(request) click to toggle source
   # 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
head(request) click to toggle source
   # 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
list(request) click to toggle source
   # 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
save(request) click to toggle source
    # 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
validate_key(request) click to toggle source
    # 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

contents_file_matches_checksum?(contents_file, expected_checksum_data, checksum_type) click to toggle source

@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
copy_bucket_file_to_contents_file(contents_file, bucket_file) click to toggle source

@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
matches(paths_file, files_original_path) click to toggle source

@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
path_for(bucket_path, digest, subfile = nil) click to toggle source

@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
path_match(file_handle, files_original_path) click to toggle source
    # 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
request_to_checksum_and_path(request) click to toggle source
    # 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
save_to_disk(bucket_file, files_original_path, contents_file, paths_file) click to toggle source

@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
verify_identical_file(contents_file, bucket_file) click to toggle source

@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