Contents
1. はじめに
2. デフォルト動作
3. 実装
4. 終わりに
1. はじめに
Ansible Role 内でansible.builtin.copy
やansible.builtin.template
モジュールを使う場合, Role 内のfiles/
or templates/
配下のファイルが優先して使われます。
Role 内のディレクトリで見つからなかった場合は, 「親のRole」, 「実行task のディレクトリ内」, 「実行play のディレクトリ内」の順に探索します。(したがって, Play の階層の深いところから順に探索するようになっています)
Role 内でansible.builtin.copy
やansible.builtin.template
モジュールを使用する場合, Role 内のディレクトリにファイルを配置するのが一般的と考えられるため, この挙動は自然と言えます。
(ドキュメントのリンクはこちら)
本記事はこれに反し, Ansible Role 内のfiles/
or templates/
以下に配置したファイルを維持しつつ, Roleを呼び出したPlaybook 側(すなわち, 親側)のfiles/
or templates
以下に該当ファイルがあればそれを使用したい, といった場合を想定しています。
ただし,
Role 実行時のファイル探索の挙動自体を変更するものではなく, Role の実装により対処しています
したがって, 本記事は次のようなわりと特殊な状況向けです。
- 自身がRole の作成者/管理者 である
- Role がデフォルトで使用するファイル・テンプレートは, Role 内に配置しておきたい
- しかし, Role の実行対象によってはファイルを差し替える必要がある。もしくは, 将来的な差し替えを想定しなければならない
- 当該ファイルをRole 利用者が(任意に)置き換えても, Role の実行自体に影響はなく, セキュリティ上の懸念もない
※ もちろん, 2番目の要求がなければ, 単にRole 内のfiles/
or templates/
ディレクトリ内にファイルを置かなければ良いでしょう。
サンプルコードはこちらで公開しています。
2. デフォルト動作
まずは, デフォルトの動作を確認しましょう。簡単なPlaybook と Role を二つ用意します。
片方のRole では, Role 内にfiles/
ディレクトリとtemplates
ディレクトリが含まれます。
一方のRole では, 処理は全く同じですが, files/
ディレクトリ とtemplates
ディレクトリを含まない状態としています。
ディレクトリ構成は以下の通りです。
├── ansible.cfg
├── files
│ └── test_file
├── inventory
├── playbook.yaml
├── roles
│ ├── myns.myrole.test_role
│ │ ├── README.md
│ │ ├── defaults
│ │ │ └── main.yml
│ │ ├── files
│ │ │ └── test_file
│ │ ├── meta
│ │ │ └── main.yml
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── test_template.j2
│ └── myns.myrole.test_role_2
│ ├── README.md
│ ├── defaults
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ └── tasks
│ └── main.yml
└── templates
└── test_template.j2
Playbook と それぞれのファイルの中身をみていきましょう。
まずは test_role
および test_role_2
のmain.yml
です。
roles/myns.myrole.test_role/tasks/main.yml
(roles/myns.myrole.test_role_2/tasks/main.yml も同内容)
---
- name: Copy file with relative path
ansible.builtin.copy:
src: test_file
dest: /tmp/test_file
- name: Get File Contents
ansible.builtin.command:
cmd: /bin/cat /tmp/test_file
register: cat_test_file_result
changed_when: false
- name: Use Templates with relative path
ansible.builtin.template:
src: test_template.j2
dest: /tmp/test_template
- name: Get Template Contents
ansible.builtin.command:
cmd: /bin/cat /tmp/test_template
register: cat_test_template_result
- name: Print File Contents
ansible.builtin.debug:
msg: |
File Contents:
{{ cat_test_file_result.stdout }}
Template Contents:
{{ cat_test_template_result.stdout }}
...
単純にansible.builtin.copy
, ansible.builtin.template
モジュールを使用し, 内容を出力させているだけです。
Playbook は下記の通り, 単純にRole を呼び出しているだけです。
playbook.yaml
---
- name: Test File and Template module demo
hosts: localhost
connection: local
gather_facts: false
roles:
- myns.myrole.test_role
- myns.myrole.test_role_2
...
次に, Role 内, Project root それぞれのfiles/
, templates/
ディレクトリ内のファイルをみてみましょう。
まずは Role (myns.myrole.test_role)の方です。
roles/myns.myrole.test_role/files/test_file
This is file in Role files/ directory!
roles/myns.myrole.test_role/templates/test_template.j2
This is Template in Role templates/ directory!
{{ foo }}
{{ hoge }}
Project root 直下のファイルは下記の通りです。
files/test_file
This is file in Role files/ directory!
templates/test_template.j2
This is file in Project's templates/ directory!
{{ foo }}
{{ hoge }}
それでは, Playbook を実行してみましょう。出力を見やすくするため,ansible.cfg
内で, 以下の行を追加しています。
ansible.cfg
[defaults]
callback_format_pretty = false
callback_result_format = yaml
$ ansible-playbook playbook.yaml
PLAY [Test File and Template module demo] *************************************************************************************************************************************************************************************
TASK [myns.myrole.test_role : Debug] ******************************************************************************************************************************************************************************************
ok: [localhost] =>
msg: Execute Role Tasks
(略)
TASK [myns.myrole.test_role : Print File Contents] ****************************************************************************************************************************************************************************
ok: [localhost] =>
msg: |
File Contents:
This is file in Role files/ directory!
Template Contents:
This is Template in Role templates/ directory!
bar
huga
(略)
TASK [myns.myrole.test_role_2 : Print File Contents] **************************************************************************************************************************************************************************
ok: [localhost] =>
msg: |
File Contents:
This is file in Project's files/ directory!
Template Contents:
This is file in Project's templates/ directory!
bar
huga
test_role_1
の実行にはRole ファイルが, test_role_2
の実行では, Project root のファイルが使用されていることがわかります。
3. 実装
複数の実装を考えますが, いずれも 絶対パスでsrcを指定する ことが重要です。
絶対パスであれば, role/playbook の親子関係による相対パスの位置を全て無視でます。
ただし, 絶対パスをハードコーディングしてしまうと, 移植性が低下し, Ansible Execution Environment を用いた実行を考慮する際などに余計なハードルが発生してしまいます。
そこで, Ansible の特殊変数の一つである, ansible_search_path
を使用します。
この変数は後の例で見るように, 実行時のファイル探索(絶対)パスを順番に格納します。
3-1. 変数
まずはsrc
パラメータを変数化してしまい, 実行時に選択する方法を見てみましょう。
デフォルト動作の確認で使用したRole やPlaybook を少し変更することで実装します。
Role 側のタスクは次の通りです。
---
- name: Print Search Path
ansible.builtin.debug:
var: ansible_search_path
- name: Copy file
ansible.builtin.copy:
src: "{{ file_src }}"
dest: /tmp/test_file
- name: Get File Contents
ansible.builtin.command:
cmd: /bin/cat /tmp/test_file
register: cat_test_file_result
changed_when: false
- name: Use Templates
ansible.builtin.template:
src: "{{ template_src }}"
dest: /tmp/test_template
- name: Get Template Contents
ansible.builtin.command:
cmd: /bin/cat /tmp/test_template
register: cat_test_template_result
- name: Print File Contents
ansible.builtin.debug:
msg: |
File Contents:
{{ cat_test_file_result.stdout }}
Template Contents:
{{ cat_test_template_result.stdout }}
...
ご覧の通り, ansible.builtin.copy
, ansible.builtin.template
モジュールのsrc
パラメータを変数で指定しています。
確認のため, ansible_search_path
を出力するデバッグコードをはじめに挿入しています。
呼び出し側のコードは下記の通りです。
---
- name: Test File and Template module demo
hosts: localhost
connection: local
gather_facts: false
roles:
- name: myns.myrole.test_role
# Project Root のfiles/ , templates/ 以下の絶対パスを参照する
vars:
file_src: "{{ ansible_search_path[-1] }}/files/test_file"
template_src: "{{ ansible_search_path[-1] }}/templates/test_template.j2"
# Role 内のdefaults/main.yaml の変数に指定したパスを参照する。(こっちは相対パス)
- name: myns.myrole.test_role
...
ansible_search_path
の最後のアイテムが, 呼び出し側のPlaybook のディレクトリとなることを利用し, 変数を設定しています。
また, Role 内のdefaults/main.yml
に相対パスを設定することで, 変数を設定しなかった場合Role 内のfiles, templates を使用するようにしています。
file_src: test_file
template_src: test_template.j2
それでは実行してみましょう。
$ ansible-playbook playbook.yaml
PLAY [Test File and Template module demo] *************************************************************************************************************************************************************************************
TASK [myns.myrole.test_role : Print Search Path] ******************************************************************************************************************************************************************************
ok: [localhost] =>
ansible_search_path:
- /home/user/override_role_files/by_variables/roles/myns.myrole.test_role
- /home/user/override_role_files/by_variables/roles/myns.myrole.test_role/tasks
- /home/user/override_role_files/by_variables
(略)
TASK [myns.myrole.test_role : Print File Contents] ****************************************************************************************************************************************************************************
ok: [localhost] =>
msg: |
File Contents:
This is file in Project's files/ directory!
Template Contents:
This is file in Project's templates/ directory!
bar
huga
(略)
TASK [myns.myrole.test_role : Print File Contents] ****************************************************************************************************************************************************************************
ok: [localhost] =>
msg: |
File Contents:
This is file in Role files/ directory!
Template Contents:
This is Template in Role templates/ directory!
bar
huga
想定通り, 1回目のはPlaybook 側のファイルを, 2回目は Role 側のファイルを参照していることがわかります。
3-2. ファイルの存在チェックのロジックを実装する
次の方法は, 実行時にファイルの存在チェックを行い, Project Root 側のファイルが存在すれば, そちらを使用する判定条件を実装します。
---
- name: Print Search Path
ansible.builtin.debug:
var: ansible_search_path
- name: Check Project Root File Existence
ansible.builtin.stat:
path: "{{ ansible_search_path[-1] }}/files/test_file"
register: _file_stat
delegate_to: localhost
run_once: true
- name: Copy file if Project Root Files Exist
ansible.builtin.copy:
src: "{{ ansible_search_path[-1] }}/files/test_file"
dest: /tmp/test_file
when: _file_stat.stat.exists
- name: Copy file
ansible.builtin.copy:
src: test_file
dest: /tmp/test_file
when: not _file_stat.stat.exists
- name: Get File Contents
ansible.builtin.command:
cmd: /bin/cat /tmp/test_file
register: cat_test_file_result
changed_when: false
- name: Check Project Root Templates Existence
ansible.builtin.stat:
path: "{{ ansible_search_path[-1] }}/templates/test_template.j2"
register: _template_stat
delegate_to: localhost
run_once: true
- name: Deploy file if Project Root Files Exist
ansible.builtin.template:
src: "{{ ansible_search_path[-1] }}/templates/test_template.j2"
dest: /tmp/test_template
when: _template_stat.stat.exists
- name: Use Templates
ansible.builtin.template:
src: test_template.j2
dest: /tmp/test_template
when: not _template_stat.stat.exists
- name: Get Template Contents
ansible.builtin.command:
cmd: /bin/cat /tmp/test_template
register: cat_test_template_result
- name: Print File Contents
ansible.builtin.debug:
msg: |
File Contents:
{{ cat_test_file_result.stdout }}
Template Contents:
{{ cat_test_template_result.stdout }}
...
2番目のタスクで, ローカルホストのProject Root 配下のファイルの存在チェックをしています。
この結果を使用し, 使用するsrc
パラメータを分岐させています。set_fact
を実行して変数に格納した方が読みやすいかもしれません。
(略)
- name: Set Fact
ansible.builtin.set_fact:
file_src: "{{ ansible_search_path[-1] }}/files/test_file"
when: _file_stat.stat.exists
- name: Copy file
ansible.builtin.copy:
src: "{{ file_src | default('test_file') }}"
dest: /tmp/test_file
(略)
それでは実行してみましょう。呼び出し側のコードは省略します。(単にRole を呼び出しているだけです。)
$ tree
├── ansible.cfg
├── files
│ └── test_file
├── inventory
├── playbook.yaml
├── roles
│ └── myns.myrole.test_role
│ ├── README.md
│ ├── defaults
│ │ └── main.yml
│ ├── files
│ │ └── test_file
│ ├── meta
│ │ └── main.yml
│ ├── tasks
│ │ └── main.yml
│ └── templates
│ └── test_template.j2
└── templates
└── test_template.j2
$ ansible-playbook playbook.yaml
TASK [myns.myrole.test_role : Print Search Path] ******************************************************************************************************************************************************************************
ok: [localhost] =>
ansible_search_path:
- /home/user/override_role_files/by_existence_check/roles/myns.myrole.test_role
- /home/user/override_role_files/by_existence_check/roles/myns.myrole.test_role/tasks
- /home/user/override_role_files/by_existence_check
(略)
ok: [localhost] =>
msg: |
File Contents:
This is file in Project's files/ directory!
Template Contents:
This is file in Project's templates/ directory!
bar
huga
Project Root 側にファイルがある場合は, 想定通りですね。
それでは, Project Root 側にファイルがないパターンも実行してみましょう。
$ tree
.
├── ansible.cfg
├── inventory
├── playbook.yaml
└── roles
└── myns.myrole.test_role
├── README.md
├── defaults
│ └── main.yml
├── files
│ └── test_file
├── meta
│ └── main.yml
├── tasks
│ └── main.yml
└── templates
└── test_template.j2
$ ansible-playbook playbook.yaml
TASK [myns.myrole.test_role : Print Search Path] ******************************************************************************************************************************************************************************
ok: [localhost] =>
ansible_search_path:
- /home/user/override_role_files/by_existence_check_not_exists/roles/myns.myrole.test_role
- /home/user/override_role_files/by_existence_check_not_exists/roles/myns.myrole.test_role/tasks
- /home/user/override_role_files/by_existence_check_not_exists
(略)
TASK [myns.myrole.test_role : Print File Contents] ****************************************************************************************************************************************************************************
ok: [localhost] =>
msg: |
File Contents:
This is file in Role files/ directory!
Template Contents:
This is Template in Role templates/ directory!
bar
huga
想定どおり, Role 内のファイルが使われました。
4. 終わりに
以上, Role 内のデフォルトのfiles/
, templates/
を上書きする実装方法を紹介しました。
デフォルトの挙動を定義しつつ, ユーザーに上書きも許可したい, といった状況は割とよくあるのではないかと思います。
通常の挙動と異なるためドキュメントで明記することは必須ですが, 同様の状況への処方箋となれば幸いです。