@stuart-m 's workaround is the right basic structure. If you need to go deep, use find and xargs. I wasn't lucky enough to have his reductive use case!
Do note that Puppet's slowness isn't merely due to the number of files it has to check. It's that Puppet is criminally slow for some tasks.
I tested this on a tree of wordpress directories (Debian 10).
# time find /var/www/*/wordpress/wp-content/ -type f | wc -l
50661
real 0m0.148s
user 0m0.048s
sys 0m0.107s
So there are fifty thousand files in there - this took about a seventh of a second to determine. Now we check for actual file ownership attributes:
# time find /var/www/*/wordpress/wp-content/ -not -group www-data | wc -l
38603
real 0m0.236s
user 0m0.056s
sys 0m0.188s
A bit more in-depth - this took about a quarter second.
Let's change them all:
time find /var/www/*/wordpress/wp-content/ -type f -exec chmod 0640 {} \;
real 0m44.392s
user 0m29.520s
sys 0m14.729s
# time find /var/www/*/wordpress/wp-content/ -type d -exec chmod 2750 {} \;
real 0m4.935s
user 0m3.288s
sys 0m1.666s
OK, fifty seconds. How much of that was shell invocation?
# time bash -c "find /var/www/*/wordpress/wp-content/ -type f -print0 | xargs -0 chmod 0640"
real 0m0.452s
user 0m0.148s
sys 0m0.513s
# time bash -c "find /var/www/*/wordpress/wp-content/ -type d -print0 |xargs -0 chmod 2750 "
real 0m0.149s
user 0m0.024s
sys 0m0.158s
Good, golly, almost all of it was shell forks - actually doing it took just about a second.
I wound up here because a directory : recurse=>true
on the same tree was taking about 800 seconds. That's just too damn slow for something that shell commands can do in one or two seconds.
I don't like wasting I/O but given the timings on this, if your filesystem is local (this is just a $7/mo hosted machine with spinning-rust drives) you may be able to afford to just exec the find/xargs/ch[own/mod] on each run.
EDIT: I wrote a puppet function to "just get it done" and it runs in under 2 seconds for 18 separate instantiations, covering 50,000 files. Roughly a 400x speedup over letting Puppet handle it. Part One, the defined type:
define my_class::permissions_tree (
String $owner = 'root',
String $group = 'root',
String $fmode = '0644',
String $dmode = '0755',
) {
$path = $title
case $os_family {
'Debian' : {
$find = '/usr/bin/find'
$xargs = '/usr/bin/xargs'
$chmod = '/bin/chmod'
$chown = '/bin/chown'
}
}
$permissions_command_owner = "${find} $path \( -not -user ${owner} -or -not -group ${group} \) -print0 | $xargs -0 --replace=found $chown ${owner}:${group} found"
$permissions_command_dirs = "${find} $path \( -type d -and -not -perm ${dmode} \) -print0 | $xargs -0 --replace=found $chmod ${dmode} found"
$permissions_command_files = "${find} $path \( -type f -and -not -perm ${fmode} \) -print0 | $xargs -0 --replace=found $chmod ${fmode} found"
$permissions_command = join([$permissions_command_owner,$permissions_command_dirs,$permissions_command_files], ";")
exec { "permissions_tree_${path}" :
path => [ '/sbin', '/bin', '/usr/sbin', '/usr/bin' ],
command => $permissions_command,
provider => shell,
}
} # permissions_tree
Part 2, use it, with something like:
my_class::permissions_tree { "$some_dir/subdir" :
owner => $my_owner,
group => $my_group,
dmode => '2750',
fmode => '0640',
require => File[$some_dir],
}
The require is just a metaparameter, use whatever you need there. Add your OS paths if it's not Debian.
EDIT 2: added parens needed for booleans with print0