~nvalcarcel/ubuntu/lucid/puppet/fix-546677

« back to all changes in this revision

Viewing changes to lib/puppet/type/tidy.rb

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2009-12-23 00:48:10 UTC
  • mfrom: (1.1.10 upstream) (3.1.7 squeeze)
  • Revision ID: james.westby@ubuntu.com-20091223004810-3i4oryds922g5n59
Tags: 0.25.1-3ubuntu1
* Merge from debian testing.  Remaining changes:
  - debian/rules:
    + Don't start puppet when first installing puppet.
  - debian/puppet.conf, lib/puppet/defaults.rb:
    + Move templates to /etc/puppet
  - lib/puppet/defaults.rb:
    + Fix /var/lib/puppet/state ownership.
  - man/man8/puppet.conf.8: 
    + Fix broken URL in manpage.
  - debian/control:
    + Update maintainer accordint to spec.
    + Puppetmaster Recommends -> Suggests
    + Created puppet-testsuite as a seperate. Allow the users to run puppet's 
      testsuite.
  - tests/Rakefile: Fix rakefile so that the testsuite can acutally be ran.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
module Puppet
2
 
    newtype(:tidy, :parent => Puppet.type(:file)) do
3
 
        @doc = "Remove unwanted files based on specific criteria.  Multiple
4
 
            criteria are OR'd together, so a file that is too large but is not
5
 
            old enough will still get tidied.
6
 
            
7
 
            You must specify either the size or age of the file (or both) for
8
 
            files to be tidied."
9
 
 
10
 
        newparam(:path) do
11
 
            desc "The path to the file or directory to manage.  Must be fully
12
 
                qualified."
13
 
            isnamevar
14
 
        end
15
 
 
16
 
        newparam(:matches) do
17
 
            desc "One or more file glob patterns, which restrict the list of
18
 
                files to be tidied to those whose basenames match at least one
19
 
                of the patterns specified.  Multiple patterns can be specified
20
 
                using an array."
21
 
        end
22
 
 
23
 
        copyparam(Puppet.type(:file), :backup)
24
 
        
25
 
        newproperty(:ensure) do
26
 
            desc "An internal attribute used to determine which files should be removed."
27
 
 
28
 
            @nodoc = true
29
 
            
30
 
            TATTRS = [:age, :size]
31
 
            
32
 
            defaultto :anything # just so we always get this property
33
 
 
34
 
            def change_to_s(currentvalue, newvalue)
35
 
                start = "Tidying"
36
 
                if @out.include?(:age)
37
 
                    start += ", older than %s seconds" % @resource.should(:age)
38
 
                end
39
 
                if @out.include?(:size)
40
 
                    start += ", larger than %s bytes" % @resource.should(:size)
41
 
                end
42
 
 
43
 
                start
44
 
            end
45
 
 
46
 
            def insync?(is)
47
 
                begin
48
 
                    stat = File.lstat(resource[:path])
49
 
                rescue Errno::ENOENT
50
 
                    info "Tidy target does not exist; ignoring"
51
 
                    return true
52
 
                end
53
 
 
54
 
                if stat.ftype == "directory" and ! @resource[:rmdirs]
55
 
                    self.debug "Not tidying directories"
56
 
                    return true
57
 
                end
58
 
 
59
 
                if is.is_a?(Symbol)
60
 
                    if [:absent, :notidy].include?(is)
61
 
                        return true
62
 
                    else
63
 
                        return false
64
 
                    end
65
 
                else
66
 
                    @out = []
67
 
                    if @resource[:matches]
68
 
                        basename = File.basename(@resource[:path])
69
 
                        flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
70
 
                        unless @resource[:matches].any? {|pattern| File.fnmatch(pattern, basename, flags) }
71
 
                            self.debug "No patterns specified match basename, skipping"
72
 
                            return true
73
 
                        end
74
 
                    end
75
 
                    TATTRS.each do |param|
76
 
                        if property = @resource.property(param)
77
 
                            self.debug "No is value for %s", [param] if is[property].nil?
78
 
                            unless property.insync?(is[property])
79
 
                                @out << param
80
 
                            end
81
 
                        end
82
 
                    end
83
 
                    
84
 
                    if @out.length > 0
85
 
                        return false
86
 
                    else
87
 
                        return true
88
 
                    end
89
 
                end
90
 
            end
91
 
            
92
 
            def retrieve
93
 
                stat = nil
94
 
                unless stat = @resource.stat
95
 
                    return { self => :absent}
96
 
                end
97
 
                
98
 
                if stat.ftype == "directory" and ! @resource[:rmdirs]
99
 
                    return {self => :notidy}
100
 
                end
101
 
 
102
 
                allprops = TATTRS.inject({}) { |prophash, param|
103
 
                    if property = @resource.property(param)
104
 
                        prophash[property] = property.assess(stat)
105
 
                    end
106
 
                    prophash
107
 
                }
108
 
                return { self => allprops } 
109
 
            end
110
 
 
111
 
            def sync
112
 
                file = @resource[:path]
113
 
                case File.lstat(file).ftype
114
 
                when "directory":
115
 
                    # If 'rmdirs' is disabled, then we would have never
116
 
                    # gotten to this method.
117
 
                    subs = Dir.entries(@resource[:path]).reject { |d|
118
 
                        d == "." or d == ".."
119
 
                    }.length
120
 
                    if subs > 0
121
 
                        self.info "%s has %s children; not tidying" %
122
 
                            [@resource[:path], subs]
123
 
                        self.info Dir.entries(@resource[:path]).inspect
124
 
                    else
125
 
                        Dir.rmdir(@resource[:path])
126
 
                    end
127
 
                when "file":
128
 
                    @resource.handlebackup(file)
129
 
                    File.unlink(file)
130
 
                when "link":
131
 
                    File.unlink(file)
132
 
                else
133
 
                    self.fail "Cannot tidy files of type %s" %
134
 
                        File.lstat(file).ftype
135
 
                end
136
 
 
137
 
                return :file_tidied
138
 
            end
139
 
        end
140
 
 
141
 
        newproperty(:age) do
142
 
            desc "Tidy files whose age is equal to or greater than
143
 
                the specified time.  You can choose seconds, minutes,
144
 
                hours, days, or weeks by specifying the first letter of any
145
 
                of those words (e.g., '1w')."
146
 
 
147
 
            @@ageconvertors = {
148
 
                :s => 1,
149
 
                :m => 60
150
 
            }
151
 
 
152
 
            @@ageconvertors[:h] = @@ageconvertors[:m] * 60
153
 
            @@ageconvertors[:d] = @@ageconvertors[:h] * 24
154
 
            @@ageconvertors[:w] = @@ageconvertors[:d] * 7
155
 
 
156
 
            def assess(stat)
157
 
                type = nil
158
 
                if stat.ftype == "directory"
159
 
                    type = :mtime
160
 
                else
161
 
                    type = @resource[:type] || :atime
162
 
                end
163
 
                
164
 
                return stat.send(type).to_i
165
 
            end
166
 
 
167
 
            def convert(unit, multi)
168
 
                if num = @@ageconvertors[unit]
169
 
                    return num * multi
170
 
                else
171
 
                    self.fail "Invalid age unit '%s'" % unit
172
 
                end
173
 
            end
174
 
 
175
 
            def insync?(is)
176
 
                if (Time.now.to_i - is) > self.should
177
 
                    return false
178
 
                end
179
 
 
180
 
                true
181
 
            end
182
 
 
183
 
            munge do |age|
184
 
                unit = multi = nil
185
 
                case age
186
 
                when /^([0-9]+)(\w)\w*$/:
187
 
                    multi = Integer($1)
188
 
                    unit = $2.downcase.intern
189
 
                when /^([0-9]+)$/:
190
 
                    multi = Integer($1)
191
 
                    unit = :d
192
 
                else
193
 
                    self.fail "Invalid tidy age %s" % age
194
 
                end
195
 
 
196
 
                convert(unit, multi)
197
 
            end
198
 
        end
199
 
 
200
 
        newproperty(:size) do
201
 
            desc "Tidy files whose size is equal to or greater than
202
 
                the specified size.  Unqualified values are in kilobytes, but
203
 
                *b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*,
204
 
                and *megabytes*, respectively.  Only the first character is
205
 
                significant, so the full word can also be used."
206
 
 
207
 
            @@sizeconvertors = {
208
 
                :b => 0,
209
 
                :k => 1,
210
 
                :m => 2,
211
 
                :g => 3
212
 
            }
213
 
 
214
 
            # Retrieve the size from a File::Stat object
215
 
            def assess(stat)
216
 
                return stat.size
217
 
            end
218
 
 
219
 
            def convert(unit, multi)
220
 
                if num = @@sizeconvertors[unit]
221
 
                    result = multi
222
 
                    num.times do result *= 1024 end
223
 
                    return result
224
 
                else
225
 
                    self.fail "Invalid size unit '%s'" % unit
226
 
                end
227
 
            end
228
 
            
229
 
            def insync?(is)
230
 
                if is > self.should
231
 
                    return false
232
 
                end
233
 
 
234
 
                true
235
 
            end
236
 
            
237
 
            munge do |size|
238
 
                case size
239
 
                when /^([0-9]+)(\w)\w*$/:
240
 
                    multi = Integer($1)
241
 
                    unit = $2.downcase.intern
242
 
                when /^([0-9]+)$/:
243
 
                    multi = Integer($1)
244
 
                    unit = :k
245
 
                else
246
 
                    self.fail "Invalid tidy size %s" % age
247
 
                end
248
 
 
249
 
                convert(unit, multi)
250
 
            end
251
 
        end
252
 
 
253
 
        newparam(:type) do
254
 
            desc "Set the mechanism for determining age."
255
 
            
256
 
            newvalues(:atime, :mtime, :ctime)
257
 
 
258
 
            defaultto :atime
259
 
        end
260
 
 
261
 
        newparam(:recurse) do
262
 
            desc "If target is a directory, recursively descend
263
 
                into the directory looking for files to tidy."
264
 
 
265
 
            newvalues(:true, :false, :inf, /^[0-9]+$/)
266
 
 
267
 
            # Replace the validation so that we allow numbers in
268
 
            # addition to string representations of them.
269
 
            validate { |arg| }
270
 
            munge do |value|
271
 
                newval = super(value)
272
 
                case newval
273
 
                when :true, :inf: true
274
 
                when :false: false
275
 
                when Integer, Fixnum, Bignum: value
276
 
                when /^\d+$/: Integer(value)
277
 
                else
278
 
                    raise ArgumentError, "Invalid recurse value %s" % value.inspect
279
 
                end
280
 
            end
281
 
        end
282
 
 
283
 
        newparam(:rmdirs) do
284
 
            desc "Tidy directories in addition to files; that is, remove
285
 
                directories whose age is older than the specified criteria.
286
 
                This will only remove empty directories, so all contained
287
 
                files must also be tidied before a directory gets removed."
288
 
        end
289
 
        
290
 
        # Erase PFile's validate method
291
 
        validate do
292
 
        end
293
 
 
294
 
        def self.instances
295
 
            []
296
 
        end
297
 
 
298
 
        @depthfirst = true
299
 
 
300
 
        def initialize(hash)
301
 
            super
302
 
 
303
 
            unless  @parameters.include?(:age) or
304
 
                    @parameters.include?(:size)
305
 
                unless FileTest.directory?(self[:path])
306
 
                    # don't do size comparisons for directories
307
 
                    self.fail "Tidy must specify size, age, or both"
308
 
                end
309
 
            end
310
 
 
311
 
            # only allow backing up into filebuckets
312
 
            unless self[:backup].is_a? Puppet::Network::Client.dipper
313
 
                self[:backup] = false
314
 
            end
315
 
        end
316
 
        
317
 
        def retrieve
318
 
            # Our ensure property knows how to retrieve everything for us.
319
 
            if obj = @parameters[:ensure] 
320
 
                return obj.retrieve
321
 
            else
322
 
                return {}
323
 
            end
324
 
        end
325
 
        
326
 
        # Hack things a bit so we only ever check the ensure property.
327
 
        def properties
328
 
            []
 
1
Puppet::Type.newtype(:tidy) do
 
2
    require 'puppet/file_serving/fileset'
 
3
 
 
4
    @doc = "Remove unwanted files based on specific criteria.  Multiple
 
5
        criteria are OR'd together, so a file that is too large but is not
 
6
        old enough will still get tidied.
 
7
 
 
8
        If you don't specify either 'age' or 'size', then all files will
 
9
        be removed.
 
10
 
 
11
        This resource type works by generating a file resource for every file
 
12
        that should be deleted and then letting that resource perform the
 
13
        actual deletion.
 
14
        "
 
15
 
 
16
    newparam(:path) do
 
17
        desc "The path to the file or directory to manage.  Must be fully
 
18
            qualified."
 
19
        isnamevar
 
20
    end
 
21
 
 
22
    newparam(:matches) do
 
23
        desc "One or more (shell type) file glob patterns, which restrict
 
24
            the list of files to be tidied to those whose basenames match
 
25
            at least one of the patterns specified. Multiple patterns can
 
26
            be specified using an array.
 
27
 
 
28
            Example::
 
29
 
 
30
                    tidy { \"/tmp\":
 
31
                        age => \"1w\",
 
32
                        recurse => false,
 
33
                        matches => [ \"[0-9]pub*.tmp\", \"*.temp\", \"tmpfile?\" ]
 
34
                    }
 
35
 
 
36
            This removes files from \/tmp if they are one week old or older,
 
37
            are not in a subdirectory and match one of the shell globs given.
 
38
 
 
39
            Note that the patterns are matched against the
 
40
            basename of each file -- that is, your glob patterns should not
 
41
            have any '/' characters in them, since you are only specifying
 
42
            against the last bit of the file."
 
43
 
 
44
        # Make sure we convert to an array.
 
45
        munge do |value|
 
46
            value = [value] unless value.is_a?(Array)
 
47
            value
 
48
        end
 
49
 
 
50
        # Does a given path match our glob patterns, if any?  Return true
 
51
        # if no patterns have been provided.
 
52
        def tidy?(path, stat)
 
53
            basename = File.basename(path)
 
54
            flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
 
55
            return true if value.find {|pattern| File.fnmatch(pattern, basename, flags) }
 
56
            return false
 
57
        end
 
58
    end
 
59
 
 
60
    newparam(:backup) do
 
61
        desc "Whether tidied files should be backed up.  Any values are passed
 
62
            directly to the file resources used for actual file deletion, so use
 
63
            its backup documentation to determine valid values."
 
64
    end
 
65
 
 
66
    newparam(:age) do
 
67
        desc "Tidy files whose age is equal to or greater than
 
68
            the specified time.  You can choose seconds, minutes,
 
69
            hours, days, or weeks by specifying the first letter of any
 
70
            of those words (e.g., '1w').
 
71
 
 
72
            Specifying 0 will remove all files."
 
73
 
 
74
        @@ageconvertors = {
 
75
            :s => 1,
 
76
            :m => 60
 
77
        }
 
78
 
 
79
        @@ageconvertors[:h] = @@ageconvertors[:m] * 60
 
80
        @@ageconvertors[:d] = @@ageconvertors[:h] * 24
 
81
        @@ageconvertors[:w] = @@ageconvertors[:d] * 7
 
82
 
 
83
        def convert(unit, multi)
 
84
            if num = @@ageconvertors[unit]
 
85
                return num * multi
 
86
            else
 
87
                self.fail "Invalid age unit '%s'" % unit
 
88
            end
 
89
        end
 
90
 
 
91
        def tidy?(path, stat)
 
92
            # If the file's older than we allow, we should get rid of it.
 
93
            if (Time.now.to_i - stat.send(resource[:type]).to_i) > value
 
94
                return true
 
95
            else
 
96
                return false
 
97
            end
 
98
        end
 
99
 
 
100
        munge do |age|
 
101
            unit = multi = nil
 
102
            case age
 
103
            when /^([0-9]+)(\w)\w*$/
 
104
                multi = Integer($1)
 
105
                unit = $2.downcase.intern
 
106
            when /^([0-9]+)$/
 
107
                multi = Integer($1)
 
108
                unit = :d
 
109
            else
 
110
                self.fail "Invalid tidy age %s" % age
 
111
            end
 
112
 
 
113
            convert(unit, multi)
 
114
        end
 
115
    end
 
116
 
 
117
    newparam(:size) do
 
118
        desc "Tidy files whose size is equal to or greater than
 
119
            the specified size.  Unqualified values are in kilobytes, but
 
120
            *b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*,
 
121
            and *megabytes*, respectively.  Only the first character is
 
122
            significant, so the full word can also be used."
 
123
 
 
124
        @@sizeconvertors = {
 
125
            :b => 0,
 
126
            :k => 1,
 
127
            :m => 2,
 
128
            :g => 3
 
129
        }
 
130
 
 
131
        def convert(unit, multi)
 
132
            if num = @@sizeconvertors[unit]
 
133
                result = multi
 
134
                num.times do result *= 1024 end
 
135
                return result
 
136
            else
 
137
                self.fail "Invalid size unit '%s'" % unit
 
138
            end
 
139
        end
 
140
 
 
141
        def tidy?(path, stat)
 
142
            if stat.size > value
 
143
                return true
 
144
            else
 
145
                return false
 
146
            end
 
147
        end
 
148
 
 
149
        munge do |size|
 
150
            case size
 
151
            when /^([0-9]+)(\w)\w*$/
 
152
                multi = Integer($1)
 
153
                unit = $2.downcase.intern
 
154
            when /^([0-9]+)$/
 
155
                multi = Integer($1)
 
156
                unit = :k
 
157
            else
 
158
                self.fail "Invalid tidy size %s" % age
 
159
            end
 
160
 
 
161
            convert(unit, multi)
 
162
        end
 
163
    end
 
164
 
 
165
    newparam(:type) do
 
166
        desc "Set the mechanism for determining age."
 
167
 
 
168
        newvalues(:atime, :mtime, :ctime)
 
169
 
 
170
        defaultto :atime
 
171
    end
 
172
 
 
173
    newparam(:recurse) do
 
174
        desc "If target is a directory, recursively descend
 
175
            into the directory looking for files to tidy."
 
176
 
 
177
        newvalues(:true, :false, :inf, /^[0-9]+$/)
 
178
 
 
179
        # Replace the validation so that we allow numbers in
 
180
        # addition to string representations of them.
 
181
        validate { |arg| }
 
182
        munge do |value|
 
183
            newval = super(value)
 
184
            case newval
 
185
            when :true, :inf; true
 
186
            when :false; false
 
187
            when Integer, Fixnum, Bignum; value
 
188
            when /^\d+$/; Integer(value)
 
189
            else
 
190
                raise ArgumentError, "Invalid recurse value %s" % value.inspect
 
191
            end
 
192
        end
 
193
    end
 
194
 
 
195
    newparam(:rmdirs, :boolean => true) do
 
196
        desc "Tidy directories in addition to files; that is, remove
 
197
            directories whose age is older than the specified criteria.
 
198
            This will only remove empty directories, so all contained
 
199
            files must also be tidied before a directory gets removed."
 
200
 
 
201
        newvalues :true, :false
 
202
    end
 
203
 
 
204
    # Erase PFile's validate method
 
205
    validate do
 
206
    end
 
207
 
 
208
    def self.instances
 
209
        []
 
210
    end
 
211
 
 
212
    @depthfirst = true
 
213
 
 
214
    def initialize(hash)
 
215
        super
 
216
 
 
217
        # only allow backing up into filebuckets
 
218
        unless self[:backup].is_a? Puppet::Network::Client.dipper
 
219
            self[:backup] = false
 
220
        end
 
221
    end
 
222
 
 
223
    # Make a file resource to remove a given file.
 
224
    def mkfile(path)
 
225
        # Force deletion, so directories actually get deleted.
 
226
        Puppet::Type.type(:file).new :path => path, :backup => self[:backup], :ensure => :absent, :force => true
 
227
    end
 
228
 
 
229
    def retrieve
 
230
        # Our ensure property knows how to retrieve everything for us.
 
231
        if obj = @parameters[:ensure]
 
232
            return obj.retrieve
 
233
        else
 
234
            return {}
 
235
        end
 
236
    end
 
237
 
 
238
    # Hack things a bit so we only ever check the ensure property.
 
239
    def properties
 
240
        []
 
241
    end
 
242
 
 
243
    def eval_generate
 
244
        []
 
245
    end
 
246
 
 
247
    def generate
 
248
        return [] unless stat(self[:path])
 
249
 
 
250
        if self[:recurse]
 
251
            files = Puppet::FileServing::Fileset.new(self[:path], :recurse => self[:recurse]).files.collect do |f|
 
252
                f == "." ? self[:path] : File.join(self[:path], f)
 
253
            end
 
254
        else
 
255
            files = [self[:path]]
 
256
        end
 
257
        result = files.find_all { |path| tidy?(path) }.collect { |path| mkfile(path) }.each { |file| notice "Tidying %s" % file.ref }.sort { |a,b| b[:path] <=> a[:path] }
 
258
 
 
259
        # No need to worry about relationships if we don't have rmdirs; there won't be
 
260
        # any directories.
 
261
        return result unless rmdirs?
 
262
 
 
263
        # Now make sure that all directories require the files they contain, if all are available,
 
264
        # so that a directory is emptied before we try to remove it.
 
265
        files_by_name = result.inject({}) { |hash, file| hash[file[:path]] = file; hash }
 
266
 
 
267
        files_by_name.keys.sort { |a,b| b <=> b }.each do |path|
 
268
            dir = File.dirname(path)
 
269
            next unless resource = files_by_name[dir]
 
270
            if resource[:require]
 
271
                resource[:require] << Puppet::Resource::Reference.new(:file, path)
 
272
            else
 
273
                resource[:require] = [Puppet::Resource::Reference.new(:file, path)]
 
274
            end
 
275
        end
 
276
 
 
277
        return result
 
278
    end
 
279
 
 
280
    # Does a given path match our glob patterns, if any?  Return true
 
281
    # if no patterns have been provided.
 
282
    def matches?(path)
 
283
        return true unless self[:matches]
 
284
 
 
285
        basename = File.basename(path)
 
286
        flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
 
287
        if self[:matches].find {|pattern| File.fnmatch(pattern, basename, flags) }
 
288
            return true
 
289
        else
 
290
            debug "No specified patterns match %s, not tidying" % path
 
291
            return false
 
292
        end
 
293
    end
 
294
 
 
295
    # Should we remove the specified file?
 
296
    def tidy?(path)
 
297
        return false unless stat = self.stat(path)
 
298
 
 
299
        return false if stat.ftype == "directory" and ! rmdirs?
 
300
 
 
301
        # The 'matches' parameter isn't OR'ed with the other tests --
 
302
        # it's just used to reduce the list of files we can match.
 
303
        return false if param = parameter(:matches) and ! param.tidy?(path, stat)
 
304
 
 
305
        tested = false
 
306
        [:age, :size].each do |name|
 
307
            next unless param = parameter(name)
 
308
            tested = true
 
309
            return true if param.tidy?(path, stat)
 
310
        end
 
311
 
 
312
        # If they don't specify either, then the file should always be removed.
 
313
        return true unless tested
 
314
        return false
 
315
    end
 
316
 
 
317
    def stat(path)
 
318
        begin
 
319
            File.lstat(path)
 
320
        rescue Errno::ENOENT => error
 
321
            info "File does not exist"
 
322
            return nil
 
323
        rescue Errno::EACCES => error
 
324
            warning "Could not stat; permission denied"
 
325
            return nil
329
326
        end
330
327
    end
331
328
end
332