1
require 'puppet/indirector/code'
2
require 'puppet/file_bucket/file'
3
require 'puppet/util/checksums'
6
module Puppet::FileBucketFile
7
class File < Puppet::Indirector::Code
8
include Puppet::Util::Checksums
10
desc "Store files in a directory set based on their checksums."
13
Puppet.settings.use(:filebucket)
17
checksum, files_original_path = request_to_checksum_and_path( request )
18
dir_path = path_for(request.options[:bucket_path], checksum)
19
file_path = ::File.join(dir_path, 'contents')
21
return nil unless ::File.exists?(file_path)
22
return nil unless path_match(dir_path, files_original_path)
24
if request.options[:diff_with]
25
hash_protocol = sumtype(checksum)
26
file2_path = path_for(request.options[:bucket_path], request.options[:diff_with], 'contents')
27
raise "could not find diff_with #{request.options[:diff_with]}" unless ::File.exists?(file2_path)
28
return `diff #{file_path.inspect} #{file2_path.inspect}`
30
contents = IO.binread(file_path)
31
Puppet.info "FileBucket read #{checksum}"
37
checksum, files_original_path = request_to_checksum_and_path(request)
38
dir_path = path_for(request.options[:bucket_path], checksum)
40
::File.exists?(::File.join(dir_path, 'contents')) and path_match(dir_path, files_original_path)
44
instance = request.instance
45
checksum, files_original_path = request_to_checksum_and_path(request)
47
save_to_disk(instance, files_original_path)
53
def path_match(dir_path, files_original_path)
54
return true unless files_original_path # if no path was provided, it's a match
55
paths_path = ::File.join(dir_path, 'paths')
56
return false unless ::File.exists?(paths_path)
57
::File.open(paths_path) do |f|
59
return true if line.chomp == files_original_path
65
def save_to_disk( bucket_file, files_original_path )
66
filename = path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents')
67
dir_path = path_for(bucket_file.bucket_path, bucket_file.checksum_data)
68
paths_path = ::File.join(dir_path, 'paths')
70
# If the file already exists, do nothing.
71
if ::File.exist?(filename)
72
verify_identical_file!(bucket_file)
74
# Make the directories if necessary.
75
unless ::File.directory?(dir_path)
76
Puppet::Util.withumask(0007) do
77
::FileUtils.mkdir_p(dir_path)
81
Puppet.info "FileBucket adding #{bucket_file.checksum}"
83
# Write the file to disk.
84
Puppet::Util.withumask(0007) do
85
::File.open(filename, ::File::WRONLY|::File::CREAT, 0440) do |of|
87
of.print bucket_file.contents
89
::File.open(paths_path, ::File::WRONLY|::File::CREAT, 0640) do |of|
90
# path will be written below
95
unless path_match(dir_path, files_original_path)
96
::File.open(paths_path, 'a') do |f|
97
f.puts(files_original_path)
102
def request_to_checksum_and_path( request )
103
checksum_type, checksum, path = request.key.split(/\//, 3)
104
if path == '' # Treat "md5/<checksum>/" like "md5/<checksum>"
107
raise "Unsupported checksum type #{checksum_type.inspect}" if checksum_type != 'md5'
108
raise "Invalid checksum #{checksum.inspect}" if checksum !~ /^[0-9a-f]{32}$/
112
def path_for(bucket_path, digest, subfile = nil)
113
bucket_path ||= Puppet[:bucketdir]
115
dir = ::File.join(digest[0..7].split(""))
116
basedir = ::File.join(bucket_path, dir, digest)
118
return basedir unless subfile
119
::File.join(basedir, subfile)
122
# If conflict_check is enabled, verify that the passed text is
123
# the same as the text in our file.
124
def verify_identical_file!(bucket_file)
125
disk_contents = IO.binread(path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents'))
127
# If the contents don't match, then we've found a conflict.
128
# Unlikely, but quite bad.
129
if disk_contents != bucket_file.contents
130
raise Puppet::FileBucket::BucketError, "Got passed new contents for sum #{bucket_file.checksum}"
132
Puppet.info "FileBucket got a duplicate file #{bucket_file.checksum}"