5

I'm using Puppet 3 on Amazon Linux 2012.09, one of my manifests sets up and reconfigs some directories. One of the tasks is just changing the folder owner and group recursivelt to another user - however, this takes over a 60 seconds to complete and there is barely anything in the directory - the chown myuser:myuser /var/lib/jenkins in the terminal take less than a second.

My question is: Is there a better/faster way to change directory ownership recursively in Puppet?

Thanks

 file {'/var/lib/jenkins':
   ensure  => 'directory',
   owner   => myuser,
   group   => myuser,
   recurse => true,
   require => Package['jenkins'],
 }
Stuart M
  • 11,458
  • 6
  • 45
  • 59
user1741694
  • 237
  • 4
  • 13

2 Answers2

4

I see this slowness too, and it appears to be due to Puppet checking each file under /var/lib/jenkins individually to ensure it has the correct owner permissions, which takes time since there's a lot of files under $JENKINS_HOME.

I worked around it on our Jenkins server by instead running a simple chown -R command (with exec) whenever the top-level directory is not owned by the desired user:

define modify_owner() {
  exec { "modify_owner_${title}" :
    command => "/bin/chown -R ${user}:${user} '${title}'",
    onlyif => "/usr/bin/stat -c %U '${title}' | grep '^${default_user}$'"
  }
}

modify_owner { ['/var/lib/jenkins', '/var/log/jenkins', '/var/cache/jenkins']: }

$user/$user is the owner/group combo I want these directories to be owned by. This brought my Puppet times back down to normal levels.

(Note: I used stat -c %U but you may need to tweak the exact formatting options depending on your OS. This command printed the owner's textual name and worked for me on Linux.)

Eliran Malka
  • 15,821
  • 6
  • 77
  • 100
Stuart M
  • 11,458
  • 6
  • 45
  • 59
1

@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

Bill McGonigle
  • 215
  • 1
  • 9