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 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
|
require_relative '../../puppet/parameter/boolean'
Puppet::Type.newtype(:tidy) do
require_relative '../../puppet/file_serving/fileset'
require_relative '../../puppet/file_bucket/dipper'
@doc = "Remove unwanted files based on specific criteria. Multiple
criteria are OR'd together, so a file that is too large but is not
old enough will still get tidied. Ignores managed resources.
If you don't specify either `age` or `size`, then all files will
be removed.
This resource type works by generating a file resource for every file
that should be deleted and then letting that resource perform the
actual deletion.
"
# Tidy names are not isomorphic with the objects.
@isomorphic = false
newparam(:path) do
desc "The path to the file or directory to manage. Must be fully
qualified."
isnamevar
munge do |value|
File.expand_path(value)
end
end
newparam(:recurse) do
desc "If target is a directory, recursively descend
into the directory looking for files to tidy."
newvalues(:true, :false, :inf, /^[0-9]+$/)
# Replace the validation so that we allow numbers in
# addition to string representations of them.
validate { |arg| }
munge do |value|
newval = super(value)
case newval
when :true, :inf; true
when :false; false
when Integer; value
when /^\d+$/; Integer(value)
else
raise ArgumentError, _("Invalid recurse value %{value}") % { value: value.inspect }
end
end
end
newparam(:max_files) do
desc "In case the resource is a directory and the recursion is enabled, puppet will
generate a new resource for each file file found, possible leading to
an excessive number of resources generated without any control.
Setting `max_files` will check the number of file resources that
will eventually be created and will raise a resource argument error if the
limit will be exceeded.
Use value `0` to disable the check. In this case, a warning is logged if
the number of files exceeds 1000."
defaultto 0
newvalues(/^[0-9]+$/)
end
newparam(:matches) do
desc <<-'EOT'
One or more (shell type) file glob patterns, which restrict
the list of files to be tidied to those whose basenames match
at least one of the patterns specified. Multiple patterns can
be specified using an array.
Example:
tidy { '/tmp':
age => '1w',
recurse => 1,
matches => [ '[0-9]pub*.tmp', '*.temp', 'tmpfile?' ],
}
This removes files from `/tmp` if they are one week old or older,
are not in a subdirectory and match one of the shell globs given.
Note that the patterns are matched against the basename of each
file -- that is, your glob patterns should not have any '/'
characters in them, since you are only specifying against the last
bit of the file.
Finally, note that you must now specify a non-zero/non-false value
for recurse if matches is used, as matches only apply to files found
by recursion (there's no reason to use static patterns match against
a statically determined path). Requiring explicit recursion clears
up a common source of confusion.
EOT
# Make sure we convert to an array.
munge do |value|
fail _("Tidy can't use matches with recurse 0, false, or undef") if "#{@resource[:recurse]}" =~ /^(0|false|)$/
[value].flatten
end
# Does a given path match our glob patterns, if any? Return true
# if no patterns have been provided.
def tidy?(path, stat)
basename = File.basename(path)
flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
return(value.find {|pattern| File.fnmatch(pattern, basename, flags) } ? true : false)
end
end
newparam(:backup) do
desc "Whether tidied files should be backed up. Any values are passed
directly to the file resources used for actual file deletion, so consult
the `file` type's backup documentation to determine valid values."
end
newparam(:age) do
desc "Tidy files whose age is equal to or greater than
the specified time. You can choose seconds, minutes,
hours, days, or weeks by specifying the first letter of any
of those words (for example, '1w' represents one week).
Specifying 0 will remove all files."
AgeConvertors = {
:s => 1,
:m => 60,
:h => 60 * 60,
:d => 60 * 60 * 24,
:w => 60 * 60 * 24 * 7,
}
def convert(unit, multi)
num = AgeConvertors[unit]
if num
return num * multi
else
self.fail _("Invalid age unit '%{unit}'") % { unit: unit }
end
end
def tidy?(path, stat)
# If the file's older than we allow, we should get rid of it.
(Time.now.to_i - stat.send(resource[:type]).to_i) >= value
end
munge do |age|
unit = multi = nil
case age
when /^([0-9]+)(\w)\w*$/
multi = Integer($1)
unit = $2.downcase.intern
when /^([0-9]+)$/
multi = Integer($1)
unit = :d
else
#TRANSLATORS tidy is the name of a program and should not be translated
self.fail _("Invalid tidy age %{age}") % { age: age }
end
convert(unit, multi)
end
end
newparam(:size) do
desc "Tidy files whose size is equal to or greater than
the specified size. Unqualified values are in kilobytes, but
*b*, *k*, *m*, *g*, and *t* can be appended to specify *bytes*,
*kilobytes*, *megabytes*, *gigabytes*, and *terabytes*, respectively.
Only the first character is significant, so the full word can also
be used."
def convert(unit, multi)
num = { :b => 0, :k => 1, :m => 2, :g => 3, :t => 4 }[unit]
if num
result = multi
num.times do result *= 1024 end
return result
else
self.fail _("Invalid size unit '%{unit}'") % { unit: unit }
end
end
def tidy?(path, stat)
stat.size >= value
end
munge do |size|
case size
when /^([0-9]+)(\w)\w*$/
multi = Integer($1)
unit = $2.downcase.intern
when /^([0-9]+)$/
multi = Integer($1)
unit = :k
else
#TRANSLATORS tidy is the name of a program and should not be translated
self.fail _("Invalid tidy size %{age}") % { age: age }
end
convert(unit, multi)
end
end
newparam(:type) do
desc "Set the mechanism for determining age."
newvalues(:atime, :mtime, :ctime)
defaultto :atime
end
newparam(:rmdirs, :boolean => true, :parent => Puppet::Parameter::Boolean) do
desc "Tidy directories in addition to files; that is, remove
directories whose age is older than the specified criteria.
This will only remove empty directories, so all contained
files must also be tidied before a directory gets removed."
end
# Erase PFile's validate method
validate do
end
def self.instances
[]
end
def depthfirst?
true
end
def initialize(hash)
super
# only allow backing up into filebuckets
self[:backup] = false unless self[:backup].is_a? Puppet::FileBucket::Dipper
end
# Make a file resource to remove a given file.
def mkfile(path)
# Force deletion, so directories actually get deleted.
parameters = {
:path => path, :backup => self[:backup],
:ensure => :absent, :force => true
}
new_file = Puppet::Type.type(:file).new(parameters)
new_file.copy_metaparams(@parameters)
new_file
end
def retrieve
# Our ensure property knows how to retrieve everything for us.
obj = @parameters[:ensure]
if obj
return obj.retrieve
else
return {}
end
end
# Hack things a bit so we only ever check the ensure property.
def properties
[]
end
def generate
return [] unless stat(self[:path])
case self[:recurse]
when Integer, /^\d+$/
parameter = { :max_files => self[:max_files],
:recurse => true,
:recurselimit => self[:recurse] }
when true, :true, :inf
parameter = { :max_files => self[:max_files],
:recurse => true }
end
if parameter
files = Puppet::FileServing::Fileset.new(self[:path], parameter).files.collect do |f|
f == "." ? self[:path] : ::File.join(self[:path], f)
end
else
files = [self[:path]]
end
found_files = files.find_all { |path| tidy?(path) }.collect { |path| mkfile(path) }
result = found_files.each { |file| debug "Tidying #{file.ref}" }.sort { |a,b| b[:path] <=> a[:path] }
if found_files.size > 0
#TRANSLATORS "Tidy" is a program name and should not be translated
notice _("Tidying %{count} files") % { count: found_files.size }
end
# No need to worry about relationships if we don't have rmdirs; there won't be
# any directories.
return result unless rmdirs?
# Now make sure that all directories require the files they contain, if all are available,
# so that a directory is emptied before we try to remove it.
files_by_name = result.inject({}) { |hash, file| hash[file[:path]] = file; hash }
files_by_name.keys.sort { |a,b| b <=> a }.each do |path|
dir = ::File.dirname(path)
resource = files_by_name[dir]
next unless resource
if resource[:require]
resource[:require] << Puppet::Resource.new(:file, path)
else
resource[:require] = [Puppet::Resource.new(:file, path)]
end
end
result
end
# Does a given path match our glob patterns, if any? Return true
# if no patterns have been provided.
def matches?(path)
return true unless self[:matches]
basename = File.basename(path)
flags = File::FNM_DOTMATCH | File::FNM_PATHNAME
if self[:matches].find {|pattern| File.fnmatch(pattern, basename, flags) }
return true
else
debug "No specified patterns match #{path}, not tidying"
return false
end
end
# Should we remove the specified file?
def tidy?(path)
# ignore files that are already managed, since we can't tidy
# those files anyway
return false if catalog.resource(:file, path)
stat = self.stat(path)
return false unless stat
return false if stat.ftype == "directory" and ! rmdirs?
# The 'matches' parameter isn't OR'ed with the other tests --
# it's just used to reduce the list of files we can match.
param = parameter(:matches)
return false if param && ! param.tidy?(path, stat)
tested = false
[:age, :size].each do |name|
param = parameter(name)
next unless param
tested = true
return true if param.tidy?(path, stat)
end
# If they don't specify either, then the file should always be removed.
return true unless tested
false
end
def stat(path)
begin
Puppet::FileSystem.lstat(path)
rescue Errno::ENOENT
debug _("File does not exist")
return nil
rescue Errno::EACCES
#TRANSLATORS "stat" is a program name and should not be translated
warning _("Could not stat; permission denied")
return nil
end
end
end
|