Puppetでfileを再帰的に設定するrecurse属性

[Puppet Advent Calendar 2015](http://qiita.com/advent-calendar/2015/puppet)の16日目です。だいたい３分の2くらいきました、Tipsで支援です。

# recurse属性
Puppetのfileリソースでディレクトリを定義するとき、recurse属性を使うと再帰的な設定ができます。例えば、ディレクトリ配下のオーナー、グループ、パーミッションを一括で設定するとか、マスターサーバ上の特定のディレクトリ配下をまとめてコピー・同期するとかです。

# サンプル
サンプル紹介です。

## 定義例
### クラス
- (1)/root/piyoの配下のgroup,mode,ownerを設定する例(recurese=>true)
- (2)/root/piyotoolの配下を/etc/puppet/modules/sample/files/piyotoolからコピーする例(recurese=>remote,source)
- (3)/root/piyocontentsの配下を/etc/puppet/modules/sample/files/piyocontentsと同期する例(recurese=>true, source, purge=>true, force=>true)

※コピー元の/etc/puppet/modules/は、modulesのパス設定が/etc/puppet/modules/の場合です。

```puppet:/etc/puppet/modules/sample/manifests/recurse.pp
class sample::recurse (
) {
  
  # (1)  
  file { '/root/piyo':
    ensure       => 'directory',
    group        => 'root',
    mode         => '0644',
    owner        => 'root',
    recurse      => 'true',
  }
  
  # (2)
  file { '/root/piyotool':
    ensure       => 'directory',
    group        => 'root',
    mode         => '0755',
    owner        => 'root',
    recurse      => 'remote',
    source       => 'puppet:///modules/sample/piyotool',
  }
  
  # (3)
  file { '/root/piyocontents':
    ensure       => 'directory',
    force        => 'true',
    group        => 'root',
    ignore       => '\.gitignore',
    mode         => '0644',
    owner        => 'root',
    purge        => 'true',
    recurse      => 'true',
    source       => 'puppet:///modules/sample/piyocontents',
  }

}
```

(2)でrecurce => remoteにしているのはtrueに設定するより速いためです。ただし、remoteにすると(3)で使っているpurge属性が無効になるため(3)ではrecurce => trueです。

> Using remote will disable the purge attribute, but results in faster catalog application than recurse => true.

(3)はpurge => trueにすることでsourceに指定しているディレクトリにないファイルを削除することができる例です（同期）。また通常サブディレクトリは消えませんが、force => trueにすることでsource側にないサブディレクトリも消せます。また、コピーする際にsource側にコピー不要なファイルがある場合は、ignoreで無視することができます。例では、.gitignoreをコピーしないようにしています。マニフェストをGit管理している場合、マスター配置時にGit用のファイルが紛れ混んでいても、エージェント側には配布しないようにするとかです。

### classを使うsite定義

```puppet:/etc/puppet/manifests/site.pp
node default {
  class { 'sample::recurse': }
}
```

### sourceに指定しているディレクト

```
[root@ed1187bc1358 /]# tree -fpug /etc/puppet/modules/sample/files/
/etc/puppet/modules/sample/files
├── [drwxr-xr-x root     root    ]  /etc/puppet/modules/sample/files/piyocontents
│   ├── [drwxr-xr-x root     root    ]  /etc/puppet/modules/sample/files/piyocontents/img
│   │   └── [-rw-r--r-- root     root    ]  /etc/puppet/modules/sample/files/piyocontents/img/img.png
│   └── [-rw-r--r-- root     root    ]  /etc/puppet/modules/sample/files/piyocontents/index.html
└── [drwxr-xr-x root     root    ]  /etc/puppet/modules/sample/files/piyotool
    ├── [drwxr-xr-x root     root    ]  /etc/puppet/modules/sample/files/piyotool/conf
    │   └── [-rw-r--r-- root     root    ]  /etc/puppet/modules/sample/files/piyotool/conf/my.conf
    └── [-rw-r--r-- root     root    ]  /etc/puppet/modules/sample/files/piyotool/mytool.sh

4 directories, 4 files
```

## 実行例

### Puppet適用前の/rootの状態

```
[root@ed1187bc1358 /]# tree -fpug /root
/root
├── [-rw------- root     root    ]  /root/anaconda-ks.cfg
├── [-rw-r--r-- root     root    ]  /root/install.log
├── [-rw-r--r-- root     root    ]  /root/install.log.syslog
└── [drwxr-xr-x root     root    ]  /root/piyo
    └── [-rwxrwxrwx root     root    ]  /root/piyo/file.txt

1 directory, 4 files
```

### puppet適用
それぞれ、再帰的な設定、ディレクトリコピー・同期がされます。
/root/piyocontents/.gitignoreはignore => '\.gitignore'で無視対象に該当するためコピーされないです。

```
[root@ed1187bc1358 /]# puppet agent -t -v --server `uname -n`
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for ed1187bc1358
Info: Applying configuration version '1449926223'
Notice: /Stage[main]/Sample::Recurse/File[/root/piyocontents]/ensure: created
Notice: /Stage[main]/Sample::Recurse/File[/root/piyocontents/index.html]/ensure: defined content as '{md5}d41d8cd98f00b204e9800998ecf8427e'
Notice: /Stage[main]/Sample::Recurse/File[/root/piyotool/]/ensure: created
Notice: /Stage[main]/Sample::Recurse/File[/root/piyotool/conf]/ensure: created
Notice: /Stage[main]/Sample::Recurse/File[/root/piyotool/mytool.sh]/ensure: defined content as '{md5}d41d8cd98f00b204e9800998ecf8427e'
Notice: /Stage[main]/Sample::Recurse/File[/root/piyotool/.gitignore]/ensure: defined content as '{md5}d41d8cd98f00b204e9800998ecf8427e'
Notice: /Stage[main]/Sample::Recurse/File[/root/piyotool/conf/my.conf]/ensure: defined content as '{md5}d41d8cd98f00b204e9800998ecf8427e'
Notice: /Stage[main]/Sample::Recurse/File[/root/piyocontents/img]/ensure: created
Notice: /Stage[main]/Sample::Recurse/File[/root/piyocontents/img/img.png]/ensure: defined content as '{md5}d41d8cd98f00b204e9800998ecf8427e'
Notice: /Stage[main]/Sample::Recurse/File[/root/piyo/file.txt]/mode: mode changed '0777' to '0644'
Notice: Finished catalog run in 0.39 seconds
```

### Puppet適用後の/rootの状態

```
[root@ed1187bc1358 /]# tree -fpug /root
/root
├── [-rw------- root     root    ]  /root/anaconda-ks.cfg
├── [-rw-r--r-- root     root    ]  /root/install.log
├── [-rw-r--r-- root     root    ]  /root/install.log.syslog
├── [drwxr-xr-x root     root    ]  /root/piyo
│   └── [-rw-r--r-- root     root    ]  /root/piyo/file.txt
├── [drwxr-xr-x root     root    ]  /root/piyocontents
│   ├── [drwxr-xr-x root     root    ]  /root/piyocontents/img
│   │   └── [-rw-r--r-- root     root    ]  /root/piyocontents/img/img.png
│   └── [-rw-r--r-- root     root    ]  /root/piyocontents/index.html
└── [drwxr-xr-x root     root    ]  /root/piyotool
    ├── [drwxr-xr-x root     root    ]  /root/piyotool/conf
    │   └── [-rwxr-xr-x root     root    ]  /root/piyotool/conf/my.conf
    └── [-rwxr-xr-x root     root    ]  /root/piyotool/mytool.sh
```

## purge属性のテスト
purge未指定の/root/piyotool, purgeをtrueに設定している/root/piyocontentsに適当なファイルを作って、再度Puppetを適用すると、/root/piyocontents側はsourceにないファイルが消えます。特定のディレクトリ配下に管理外のファイルがあった場合に、ミドルウェア・アプリの動作に影響する場合などに消すとかに使えます。

```
[root@ed1187bc1358 /]# touch /root/piyotool/purge_test.txt
[root@ed1187bc1358 /]# touch /root/piyocontents/purge_test.txt

[root@ed1187bc1358 /]# puppet agent -t -v --server `uname -n`
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for ed1187bc1358
Info: Applying configuration version '1449926223'
Info: Computing checksum on file /root/piyocontents/purge_test.txt
Info: /Stage[main]/Sample::Recurse/File[/root/piyocontents/purge_test.txt]: Filebucketed /root/piyocontents/purge_test.txt to puppet with sum d41d8cd98f00b204e9800998ecf8427e
Notice: /Stage[main]/Sample::Recurse/File[/root/piyocontents/purge_test.txt]/ensure: removed
Notice: Finished catalog run in 0.18 seconds
```

# recurse属性使用時の注意点
いろいろできるrecurse属性ですが、recurse対象になるディレクトリ配下に誰か（OS、ミドルウェア、アプリ、人）がファイルを大量に作っていないか注意が必要です。再帰的に定義を反映するため、ファイルが多いと時間がかかるためです。

例えば、/root/piyo/に無慈悲にも5000ファイルも作られた場合、30秒以上余計にかかります。

```
[root@ed1187bc1358 /]# time puppet agent -t -v --server `uname -n`
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for ed1187bc1358
Info: Applying configuration version '1449926223'
Notice: Finished catalog run in 0.17 seconds

real	0m1.771s
user	0m0.990s
sys	0m0.300s

[root@ed1187bc1358 /]# for i in {1..5000} ; do name=$(printf file_%05d $i); touch /root/piyo/$name.txt ; done
[root@ed1187bc1358 /]# ls -l /root/piyo | tail
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04992.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04993.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04994.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04995.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04996.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04997.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04998.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_04999.txt
-rw-r--r-- 1 root root 0 Dec 12 14:10 file_05000.txt
-rw-r--r-- 1 root root 0 Dec 12 12:59 file.txt

[root@ed1187bc1358 /]# time puppet agent -t -v --server `uname -n` > log.log

real	0m35.353s
user	0m27.560s
sys	0m2.110s
[root@ed1187bc1358 /]# tail log.log 
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for ed1187bc1358
Info: Applying configuration version '1449926223'
Notice: Finished catalog run in 24.17 seconds
[root@ed1187bc1358 /]# time puppet agent -t -v --server `uname -n`
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for ed1187bc1358
Info: Applying configuration version '1449926223'
Notice: Finished catalog run in 24.38 seconds

real	0m35.738s
user	0m27.870s
sys	0m2.210s
```

やりたいことが明確な場合、recurselimit属性で再帰反映の深さを制限するとか、execとrefreshonlyを組み合わせて特定のタイミングだけコマンド実行するとかにした方が良い場合もあります。初回インストール時に、解凍したファイルの権限をコマンドで変えられればよいとかです。execとrefreshonlyについては、4日目の[exec - refreshonly の使い所](http://qiita.com/udzura/items/aac88dc71f7db0fff9dc)で@udzuraさんが紹介されています。

# 参考

- https://docs.puppetlabs.com/references/latest/type.html#file