Ansible 複数ファイルを対象にループする方法を整理

Ansible でディレクトリ配下のファイル群をまとめて処理したい場合の方法を整理。

## 環境

- ansible 2.6.1

ディレクトリ構成は以下。 例では *.conf を対象に処理させる。

```
.
├── files
│   ├── conf1.d
│   │   ├── conf1.conf
│   │   └── conf2.conf
│   ├── conf2.d
│   │   └── test.txt
│   └── conf3.d
│       └── sub.d
│           └── sub_conf1.conf
├── templates
│   ├── conf1.d
│   │   ├── conf1.conf
│   │   └── conf2.conf
│   ├── conf2.d
│   │   └── test.txt
│   └── conf3.d
│       └── sub.d
│           └── sub_conf1.conf
├── test
│   └── test.conf
```

# file glob

- [fileglob \- list files matching a pattern — Ansible Documentation](https://docs.ansible.com/ansible/2.5/plugins/lookup/fileglob.html)
	- v 1.4 から利用可能
	- 単一ディレクトリを対象にし、ファイル検索
	- <b>パターンマッチはファイルのみ</b> サポート

戻り値がフルパスとなるため相対パスへ書き換える。

## file glob (files)

```
    - name: local dir files (file glob for files)
      debug: msg="{{ item | replace(playbook_dir + '/', '') }}"
      with_fileglob:
        - "conf1.d/*.conf"
        - "*/*.conf"  # Not Support
        - "**/*.conf" # Not Support
```

### 取得例

```
TASK [local dir files (file glob for files)] ***********************************
 [WARNING]: Unable to find '*' in expected paths (use -vvvvv to see paths)

 [WARNING]: Unable to find '**' in expected paths (use -vvvvv to see paths)

ok: [localhost] => (item=/tmp/loop_match_files/files/conf1.d/conf1.conf) => {
    "msg": "files/conf1.d/conf1.conf"
}
ok: [localhost] => (item=/tmp/loop_match_files/files/conf1.d/conf2.conf) => {
    "msg": "files/conf1.d/conf2.conf"
}
```

## file glob (templates)

<i>files</i> 配下が起点となるため、相対パスで <i>templates</i> ディレクトリから取得する。

```
    - name: local dir files (file glob for templates)
      debug: msg="{{ item | replace(playbook_dir + '/files/../', '') }}"
      with_fileglob:
        - "../templates/conf1.d/*.conf"
```

### 取得例

```
TASK [local dir files (file glob for templates)] *******************************
ok: [localhost] => (item=/tmp/loop_match_files/files/../templates/conf1.d/conf1.conf) => {
    "msg": "templates/conf1.d/conf1.conf"
}
ok: [localhost] => (item=/tmp/loop_match_files/files/../templates/conf1.d/conf2.conf) => {
    "msg": "templates/conf1.d/conf2.conf"
}
```

# with_lines + find

- [lines \- read lines from command — Ansible Documentation](https://docs.ansible.com/ansible/2.5/plugins/lookup/lines.html)
	- コマンド実行結果でループさせる
		- findコマンドの実行結果を利用する

戻り値がフルパスとなるため相対パスへ書き換える。

```
    - name: local dir files (find)
      debug: msg="{{ item | replace(playbook_dir + '/', '') }}"
      with_lines:
        - "find {{ playbook_dir }}/files -type f -name '*.conf'"
        - "find {{ playbook_dir }}/templates -type f -name '*.conf'"
```

### 取得例

```
TASK [local dir files (find)] **************************************************
ok: [localhost] => (item=/tmp/loop_match_files/files/conf1.d/conf1.conf) => {
    "msg": "files/conf1.d/conf1.conf"
}
ok: [localhost] => (item=/tmp/loop_match_files/files/conf1.d/conf2.conf) => {
    "msg": "files/conf1.d/conf2.conf"
}
ok: [localhost] => (item=/tmp/loop_match_files/files/conf3.d/sub.d/sub_conf1.conf) => {
    "msg": "files/conf3.d/sub.d/sub_conf1.conf"
}
ok: [localhost] => (item=/tmp/loop_match_files/templates/conf1.d/conf1.conf) => {
    "msg": "templates/conf1.d/conf1.conf"
}
ok: [localhost] => (item=/tmp/loop_match_files/templates/conf1.d/conf2.conf) => {
    "msg": "templates/conf1.d/conf2.conf"
}
ok: [localhost] => (item=/tmp/loop_match_files/templates/conf3.d/sub.d/sub_conf1.conf) => {
    "msg": "templates/conf3.d/sub.d/sub_conf1.conf"
}
```

# filetree

- [filetree \- recursively match all files in a directory tree — Ansible Documentation](https://docs.ansible.com/ansible/2.5/plugins/lookup/filetree.html)
	- v2.4 から利用可能
	- <i>item.state</i> で種別が取得できる
		- directory / file / link
		- when と組み合わせる事で file のみを対象にできる(条件に合致しない場合は skip)
	- <i>item.path</i> でファイルパスが取得できる
		- when と組み合わせる事で任意のファイル名や拡張子のみを対象にできる(条件に合致しない場合は skip)

> If you provide more than one path, it will implement a with_first_found logic, and will not process entries it already processed in previous paths. 

with_filetree に複数指定した場合、with_first_found ロジックが適用される。( <i>item.path</i> の重複が排除される)

```
    - name: local dir files (filetree)
      debug: msg="{{ item.root | replace(playbook_dir + '/', '') }}{{ item.path }}"
      with_filetree:
        - files/
        - templates/
        - test/
      when: item.state == 'file' and item.path is match(".*\.conf$")
```

### 取得例

上記の例は file/、template/、test/ を指定しているが、
file/ と template/ 配下は同様のファイル構成のため、template/ 配下は<b>処理されない</b>。  
test/ 配下のファイルはファイル名が異なるため処理される。

```
TASK [local dir files (filetree)] **********************************************
skipping: [localhost] => (item={'group': u'wheel', 'uid': 0, 'state': 'directory', 'gid': 0, 'mode': '0755', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf1.d', 'size': 136, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0})
skipping: [localhost] => (item={'group': u'wheel', 'uid': 0, 'state': 'directory', 'gid': 0, 'mode': '0755', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf2.d', 'size': 102, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0})
skipping: [localhost] => (item={'group': u'wheel', 'uid': 0, 'state': 'directory', 'gid': 0, 'mode': '0755', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf3.d', 'size': 102, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0})
ok: [localhost] => (item={'src': u'/tmp/loop_match_files/files/conf1.d/conf1.conf', 'group': u'wheel', 'uid': 0, 'state': 'file', 'gid': 0, 'mode': '0644', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf1.d/conf1.conf', 'size': 0, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0}) => {
    "msg": "files/conf1.d/conf1.conf"
}
ok: [localhost] => (item={'src': u'/tmp/loop_match_files/files/conf1.d/conf2.conf', 'group': u'wheel', 'uid': 0, 'state': 'file', 'gid': 0, 'mode': '0644', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf1.d/conf2.conf', 'size': 0, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0}) => {
    "msg": "files/conf1.d/conf2.conf"
}
skipping: [localhost] => (item={'src': u'/tmp/loop_match_files/files/conf2.d/test.txt', 'group': u'wheel', 'uid': 0, 'state': 'file', 'gid': 0, 'mode': '0644', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf2.d/test.txt', 'size': 0, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0})
skipping: [localhost] => (item={'group': u'wheel', 'uid': 0, 'state': 'directory', 'gid': 0, 'mode': '0755', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf3.d/sub.d', 'size': 102, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0})
ok: [localhost] => (item={'src': u'/tmp/loop_match_files/files/conf3.d/sub.d/sub_conf1.conf', 'group': u'wheel', 'uid': 0, 'state': 'file', 'gid': 0, 'mode': '0644', 'mtime': 1530000000.0, 'owner': 'root', 'path': u'conf3.d/sub.d/sub_conf1.conf', 'size': 0, 'root': u'/tmp/loop_match_files/files/', 'ctime': 1530000000.0}) => {
    "msg": "files/conf3.d/sub.d/sub_conf1.conf"
}
ok: [localhost] => (item={'src': u'/tmp/loop_match_files/test/test.conf', 'group': u'wheel', 'uid': 0, 'state': u'file', 'gid': 0, 'mode': u'0644', 'mtime': 1530000000.0, 'owner': u'root', 'path': u'test.conf', 'size': 0, 'root': u'/tmp/loop_match_files/test/', 'ctime': 1530000000.0}) => {
    "msg": "test/test.conf"
}
```

# 参考URL

- [with\_fileglob does not handle a \`\*\` to match a directory · Issue \#17136 · ansible/ansible · GitHub](https://github.com/ansible/ansible/issues/17136)
- [Ansible Tips \- Qiita](https://qiita.com/tksarah/items/b6f2e5fea0cb66c81111)
- [linux \- Deploying a folder of template files using ansible \- Server Fault](https://serverfault.com/questions/578544/deploying-a-folder-of-template-files-using-ansible)