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.
7
You must specify either the size or age of the file (or both) for
11
desc "The path to the file or directory to manage. Must be fully
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
23
copyparam(Puppet.type(:file), :backup)
25
newproperty(:ensure) do
26
desc "An internal attribute used to determine which files should be removed."
30
TATTRS = [:age, :size]
32
defaultto :anything # just so we always get this property
34
def change_to_s(currentvalue, newvalue)
36
if @out.include?(:age)
37
start += ", older than %s seconds" % @resource.should(:age)
39
if @out.include?(:size)
40
start += ", larger than %s bytes" % @resource.should(:size)
48
stat = File.lstat(resource[:path])
50
info "Tidy target does not exist; ignoring"
54
if stat.ftype == "directory" and ! @resource[:rmdirs]
55
self.debug "Not tidying directories"
60
if [:absent, :notidy].include?(is)
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"
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])
94
unless stat = @resource.stat
95
return { self => :absent}
98
if stat.ftype == "directory" and ! @resource[:rmdirs]
99
return {self => :notidy}
102
allprops = TATTRS.inject({}) { |prophash, param|
103
if property = @resource.property(param)
104
prophash[property] = property.assess(stat)
108
return { self => allprops }
112
file = @resource[:path]
113
case File.lstat(file).ftype
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 == ".."
121
self.info "%s has %s children; not tidying" %
122
[@resource[:path], subs]
123
self.info Dir.entries(@resource[:path]).inspect
125
Dir.rmdir(@resource[:path])
128
@resource.handlebackup(file)
133
self.fail "Cannot tidy files of type %s" %
134
File.lstat(file).ftype
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')."
152
@@ageconvertors[:h] = @@ageconvertors[:m] * 60
153
@@ageconvertors[:d] = @@ageconvertors[:h] * 24
154
@@ageconvertors[:w] = @@ageconvertors[:d] * 7
158
if stat.ftype == "directory"
161
type = @resource[:type] || :atime
164
return stat.send(type).to_i
167
def convert(unit, multi)
168
if num = @@ageconvertors[unit]
171
self.fail "Invalid age unit '%s'" % unit
176
if (Time.now.to_i - is) > self.should
186
when /^([0-9]+)(\w)\w*$/:
188
unit = $2.downcase.intern
193
self.fail "Invalid tidy age %s" % age
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."
214
# Retrieve the size from a File::Stat object
219
def convert(unit, multi)
220
if num = @@sizeconvertors[unit]
222
num.times do result *= 1024 end
225
self.fail "Invalid size unit '%s'" % unit
239
when /^([0-9]+)(\w)\w*$/:
241
unit = $2.downcase.intern
246
self.fail "Invalid tidy size %s" % age
254
desc "Set the mechanism for determining age."
256
newvalues(:atime, :mtime, :ctime)
261
newparam(:recurse) do
262
desc "If target is a directory, recursively descend
263
into the directory looking for files to tidy."
265
newvalues(:true, :false, :inf, /^[0-9]+$/)
267
# Replace the validation so that we allow numbers in
268
# addition to string representations of them.
271
newval = super(value)
273
when :true, :inf: true
275
when Integer, Fixnum, Bignum: value
276
when /^\d+$/: Integer(value)
278
raise ArgumentError, "Invalid recurse value %s" % value.inspect
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."
290
# Erase PFile's validate method
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"
311
# only allow backing up into filebuckets
312
unless self[:backup].is_a? Puppet::Network::Client.dipper
313
self[:backup] = false
318
# Our ensure property knows how to retrieve everything for us.
319
if obj = @parameters[:ensure]
326
# Hack things a bit so we only ever check the ensure property.
1
Puppet::Type.newtype(:tidy) do
2
require 'puppet/file_serving/fileset'
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.
8
If you don't specify either 'age' or 'size', then all files will
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
17
desc "The path to the file or directory to manage. Must be fully
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.
33
matches => [ \"[0-9]pub*.tmp\", \"*.temp\", \"tmpfile?\" ]
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.
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."
44
# Make sure we convert to an array.
46
value = [value] unless value.is_a?(Array)
50
# Does a given path match our glob patterns, if any? Return true
51
# if no patterns have been provided.
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) }
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."
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').
72
Specifying 0 will remove all files."
79
@@ageconvertors[:h] = @@ageconvertors[:m] * 60
80
@@ageconvertors[:d] = @@ageconvertors[:h] * 24
81
@@ageconvertors[:w] = @@ageconvertors[:d] * 7
83
def convert(unit, multi)
84
if num = @@ageconvertors[unit]
87
self.fail "Invalid age unit '%s'" % unit
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
103
when /^([0-9]+)(\w)\w*$/
105
unit = $2.downcase.intern
110
self.fail "Invalid tidy age %s" % age
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."
131
def convert(unit, multi)
132
if num = @@sizeconvertors[unit]
134
num.times do result *= 1024 end
137
self.fail "Invalid size unit '%s'" % unit
141
def tidy?(path, stat)
151
when /^([0-9]+)(\w)\w*$/
153
unit = $2.downcase.intern
158
self.fail "Invalid tidy size %s" % age
166
desc "Set the mechanism for determining age."
168
newvalues(:atime, :mtime, :ctime)
173
newparam(:recurse) do
174
desc "If target is a directory, recursively descend
175
into the directory looking for files to tidy."
177
newvalues(:true, :false, :inf, /^[0-9]+$/)
179
# Replace the validation so that we allow numbers in
180
# addition to string representations of them.
183
newval = super(value)
185
when :true, :inf; true
187
when Integer, Fixnum, Bignum; value
188
when /^\d+$/; Integer(value)
190
raise ArgumentError, "Invalid recurse value %s" % value.inspect
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."
201
newvalues :true, :false
204
# Erase PFile's validate method
217
# only allow backing up into filebuckets
218
unless self[:backup].is_a? Puppet::Network::Client.dipper
219
self[:backup] = false
223
# Make a file resource to remove a given file.
225
# Force deletion, so directories actually get deleted.
226
Puppet::Type.type(:file).new :path => path, :backup => self[:backup], :ensure => :absent, :force => true
230
# Our ensure property knows how to retrieve everything for us.
231
if obj = @parameters[:ensure]
238
# Hack things a bit so we only ever check the ensure property.
248
return [] unless stat(self[:path])
251
files = Puppet::FileServing::Fileset.new(self[:path], :recurse => self[:recurse]).files.collect do |f|
252
f == "." ? self[:path] : File.join(self[:path], f)
255
files = [self[:path]]
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] }
259
# No need to worry about relationships if we don't have rmdirs; there won't be
261
return result unless rmdirs?
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 }
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)
273
resource[:require] = [Puppet::Resource::Reference.new(:file, path)]
280
# Does a given path match our glob patterns, if any? Return true
281
# if no patterns have been provided.
283
return true unless self[:matches]
285
basename = File.basename(path)
286
flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
287
if self[:matches].find {|pattern| File.fnmatch(pattern, basename, flags) }
290
debug "No specified patterns match %s, not tidying" % path
295
# Should we remove the specified file?
297
return false unless stat = self.stat(path)
299
return false if stat.ftype == "directory" and ! rmdirs?
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)
306
[:age, :size].each do |name|
307
next unless param = parameter(name)
309
return true if param.tidy?(path, stat)
312
# If they don't specify either, then the file should always be removed.
313
return true unless tested
320
rescue Errno::ENOENT => error
321
info "File does not exist"
323
rescue Errno::EACCES => error
324
warning "Could not stat; permission denied"