summaryrefslogtreecommitdiffstats
path: root/lib/puppet/indirector/file_bucket_file/file.rb
blob: 1667c1d7e3681cde596544acc5c66389ef7a1ee2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
require 'puppet/indirector/code'
require 'puppet/file_bucket/file'
require 'puppet/util/checksums'
require 'fileutils'

module Puppet::FileBucketFile
    class File < Puppet::Indirector::Code
        include Puppet::Util::Checksums

        desc "Store files in a directory set based on their checksums."

        def initialize
            Puppet.settings.use(:filebucket)
        end

        def find( request )
            checksum, path = request_to_checksum_and_path( request )
            return find_by_checksum( checksum, request.options )
        end

        def save( request )
            checksum, path = request_to_checksum_and_path( request )

            instance = request.instance
            instance.checksum = checksum if checksum
            instance.path = path if path

            save_to_disk(instance)
            instance.to_s
        end

        private

        def find_by_checksum( checksum, options )
            model.new( nil, :checksum => checksum ) do |bucket_file|
                bucket_file.bucket_path = options[:bucket_path]
                filename = contents_path_for( bucket_file )

                if ! ::File.exist? filename
                    return nil
                end

                begin
                    contents = ::File.read filename
                    Puppet.info "FileBucket read #{bucket_file.checksum}"
                rescue RuntimeError => e
                    raise Puppet::Error, "file could not be read: #{e.message}"
                end

                if ::File.exist?(paths_path_for( bucket_file) )
                    ::File.open(paths_path_for( bucket_file) ) do |f|
                        bucket_file.paths = f.readlines.map { |l| l.chomp }
                    end
                end

                bucket_file.contents = contents
            end
        end

        def save_to_disk( bucket_file )
            # If the file already exists, just return the md5 sum.
            if ::File.exist?(contents_path_for( bucket_file) )
                verify_identical_file!(bucket_file)
            else
                # Make the directories if necessary.
                unless ::File.directory?( path_for( bucket_file) )
                    Puppet::Util.withumask(0007) do
                        ::FileUtils.mkdir_p( path_for( bucket_file) )
                    end
                end

                Puppet.info "FileBucket adding #{bucket_file.path} as #{bucket_file.checksum}"

                # Write the file to disk.
                Puppet::Util.withumask(0007) do
                    ::File.open(contents_path_for(bucket_file), ::File::WRONLY|::File::CREAT, 0440) do |of|
                        of.print bucket_file.contents
                    end
                end
            end

            save_path_to_paths_file(bucket_file)
            return bucket_file.checksum_data
        end

        def request_to_checksum_and_path( request )
            return [request.key, nil] if checksum?(request.key)

            checksum_type, checksum, path = request.key.split(/\//, 3)
            return nil if checksum_type.to_s == ""
            return [ "{#{checksum_type}}#{checksum}", path ]
        end

        def path_for(bucket_file, subfile = nil)
            bucket_path = bucket_file.bucket_path || Puppet[:bucketdir]
            digest      = bucket_file.checksum_data

            dir     = ::File.join(digest[0..7].split(""))
            basedir = ::File.join(bucket_path, dir, digest)

            return basedir unless subfile
            return ::File.join(basedir, subfile)
        end

        def contents_path_for(bucket_file)
            path_for(bucket_file, "contents")
        end

        def paths_path_for(bucket_file)
            path_for(bucket_file, "paths")
        end

        def content_check?
            true
        end

        # If conflict_check is enabled, verify that the passed text is
        # the same as the text in our file.
        def verify_identical_file!(bucket_file)
            return unless content_check?
            disk_contents = ::File.read(contents_path_for(bucket_file))

            # If the contents don't match, then we've found a conflict.
            # Unlikely, but quite bad.
            if disk_contents != bucket_file.contents
                raise Puppet::FileBucket::BucketError, "Got passed new contents for sum #{bucket_file.checksum}", caller
            else
                Puppet.info "FileBucket got a duplicate file #{bucket_file.path} (#{bucket_file.checksum})"
            end
        end

        def save_path_to_paths_file(bucket_file)
            return unless bucket_file.path

            # check for dupes
            if ::File.exist?(paths_path_for( bucket_file) )
                ::File.open(paths_path_for( bucket_file) ) do |f|
                    return if f.readlines.collect { |l| l.chomp }.include?(bucket_file.path)
                end
            end

            # if it's a new file, or if our path isn't in the file yet, add it
            ::File.open(paths_path_for(bucket_file), ::File::WRONLY|::File::CREAT|::File::APPEND) do |of|
                of.puts bucket_file.path
            end
        end

    end
end