Ansible Tips: copyモジュールで(Chef cookbook_fileリソースの様に)ファイル配布元を自動的に選択させる例


目的1: copy元を自動的に選択

Ansibleのcopyモジュールに相当するChefのcookbook_fileリソースでは、copy元は、COOKBOOK_NAME/files/下で以下に最初にマッチしたファイルが使用される。

  1. /host-\$fqdn/\$source
  2. /\$platform-\$platform_version/\$source
  3. /\$platform/\$source
  4. /default/\$source
  5. /\$source

https://docs.chef.io/resources/cookbook_file/
https://docs.chef.io/resources/cookbook_file/#cookbook_file_specificity

しかしansibleのcopyモジュールには、その様なcopy元自動選択機能はない。
その様な機能を実装するにはどうするか。

目的2 ファイル配布元をinventory_dir/files下とする

Ansible copyモジュールのファイル配布元は、相対パス指定の場合、playbook_dir/files, playbook_dir 下から探される様子。

しかし、playbookはロジック、inventoryはデータと切り分けをしたい場合、配布元はplaybook_dir下よりinventory_dir下が望ましい。

追記/訂正

ansible-playbookコマンドの-iオプションで指定可能なインベントリーには、-i inventory_dir の様にディレクトリーを指定することも可能。
インベントリーファイルそのものやinventory_dir/host_varsやinventory_dir/group_varsを配置して利用可能。
しかし、inventory_dir/filesの様に本来のインベントリー情報以外のファイルが含まれると、エラーとなってしまう。その為、ディレクトリー指定を考慮するならば、inventory_dirの外部にに置いたほうが良い。

最終目的

Chefのcookbook_fileリソースの様に、以下の順序でcopy元を探索し、最初に見つかったファイルを配布したい。

  1. "{{ inventory_dir }}/files/{{ inventory_hostname }}" 下
  2. "{{ inventory_dir }}/files/{{ ansible_distribution }}-{{ ansible_distribution_version }}" 下
  3. "{{ inventory_dir }}/files/{{ ansible_distribution }}" 下
  4. "{{ inventory_dir }}/files/default" 下
  5. "{{ inventory_dir }}/files" 下

検証環境

  • ansible 2.9.4

inventory_dirイメージ

inventory_dir
│
├── hosts             # inventory file
│
├── host_vars         # host_varsはinventory_dir下を使用している
│   ├── host01
│   │   └── main.yml
│   └── host02
│       └── main.yml
│
├── group_vars        # group_varsもinventory_dir下を使用している
│   └── all
│       └── main.yml
│
└── files             # **** ファイル配布元もinventory_dir下に置きたい ****
    ├── default     # 個別のノードで指定しない場合のデフォルト(Chef準拠)
    │   └── etc
    │       └── hosts
    ├── host01        # 個別のノードで指定する場合(Chef準拠)
    │   └── etc
    │       └── hosts

対応するtask例

  - name: copy files from inventory_dir/files
    copy:
      src: "{{ lookup('first_found', paths|map('regex_replace','$',item)|list ) }}"
      dest: "{{ item }}"
    vars:
      paths:
      - "{{ inventory_dir }}/files/{{ inventory_hostname }}"
      - "{{ inventory_dir }}/files/{{ ansible_distribution }}-{{ ansible_distribution_version }}"
      - "{{ inventory_dir }}/files/{{ ansible_distribution }}"
      - "{{ inventory_dir }}/files/default"
      - "{{ inventory_dir }}/files"
    loop:
    - "/etc/hosts"
TASK [copy files from inventory_dir/files] *************************************************************************************
ok: [host01] => (item=/etc/hosts)
  • lookup('first_found', ... にて、コントローラーノード上でリスト順で最初に見つかったファイルをソースとする。
    • with_first_found: を用いると、ファイル名でのloopを行うには、ファイルを分けてimport_tasksなどを使用しなければいけなくなる。
    • jinja2によるfor loopと組み合わせる事でも対応可能。(可読性は落ちるが、柔軟な処理が可能)
  • mapフィルターにて、Arrayであるpathsの各要素に操作を行う。
    • 操作を行うフィルターへのパラメーターは、map内での第2パラメーター以降で設定する。
      • つまり、各要素に | regex_replace('$',item) というフィルターを掛け、各要素の最後にファイル名を付与する
    • 最後にlistフィルターでArrayに戻す。
  • pathsに探索順にパスを定義
    • inventory_dir や inventory_hostname は Ansible が設定する値
  • copy対象ファイルはloop:にて回す
    • パス情報の管理を容易とするために絶対パス指定としている

参照

first_found – return first file found from list
https://docs.ansible.com/ansible/latest/plugins/lookup/first_found.html

ansible 1.6 > using with_first_found in a with_items loop?
https://stackoverrun.com/ja/q/6205524

map(*args, **kwargs)
https://jinja.palletsprojects.com/en/2.11.x/templates/#map

Regular Expression Filters
https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html#regular-expression-filters

Ansible Jinja2 filters 正規表現で変数の文字列を置換、抽出する
https://qiita.com/tbuchi888/items/80fded8d11366e967290