Ansible: 不特定数のvarsファイルの内容をマージして使用する例3つとChefの場合


#課題
いくつあるか分からないvarsファイルの内容を、ディレクトリー(Hash)に関してマージして使用したい。
例えば、いくつあるか分からないvars*.ymlの内容をマージして使用する。


```yaml:vars/vars1.yml
---
foo:
  bar1: baz1
  bar2: baz2
```

```yaml:vars/vars2.yml
---
foo:
  bar3: baz3
  bar4: baz4
```

この様な場合、以下の様にマージして使用したい。

```yaml
foo:
  bar1: baz1
  bar2: baz2
  bar3: baz3
  bar4: baz4
```

なお、自動的に読み込まれる host_vars/<対象host>/, group_vars/all/, group_vars/<対象group名>/ 等の下におけば、不特定数のvarsファイルは読み込まれるが、同じキーの場合には次々と上書きしてしまう。


#実装例1

```yaml:tasks/merge1.yml 
---
- hosts: all
  tasks:

    - name: set_fact merged_vars = {}
      set_fact:
        merged_vars: {}     # 空のHashに読み込んだ内容を追加していく

    - include: merge2.yml   # ループ内容は別ファイルで
      with_fileglob:        # 指定されたローカルファイルに対してループ
        - ../vars/vars*.yml # 変数ファイルを正規表現指定

    - debug: msg="{{ merged_vars }}"
```

```yaml:tasks/merge2.yml 
---
- name: include_vars {{ item }} to tmp_vars
  include_vars: 
    file: "{{ item }}"  # 読み込むファイル
    name: tmp_vars      # tmp_vars下に読み込み

- name: merge tmp_vars into merged_vars
  set_fact:
    merged_vars: "{{ merged_vars | combine(tmp_vars, recursive=True) }}"  # マージ本体。merged_varsに追加マージ
```

#実行例

```
TASK [set_fact merged_vars = {}] *********************************************************
ok: [ansiblepush-centos-7]

TASK [include] ***************************************************************************
included: /xxx/tasks/merge2.yml for ansiblepush-centos-7
included: /xxx/tasks/merge2.yml for ansiblepush-centos-7

TASK [include_vars /xxx/tasks/../vars/vars2.yml to tmp_vars] ***
ok: [ansiblepush-centos-7]

TASK [merge tmp_vars into merged_vars] ***************************************************
ok: [ansiblepush-centos-7]

TASK [include_vars /xxx/tasks/../vars/vars1.yml to tmp_vars] ***
ok: [ansiblepush-centos-7]

TASK [merge tmp_vars into merged_vars] ***************************************************
ok: [ansiblepush-centos-7]

TASK [debug] *****************************************************************************
ok: [ansiblepush-centos-7] => {
    "msg": {
        "foo": {
            "bar1": "baz1", 
            "bar2": "baz2", 
            "bar3": "baz3", 
            "bar4": "baz4"
        }
    }
}
```
この例ではvarsX.ymlの指定を../vars/vars*.ymlとしているが、これはfilesもしくはtasksからの相対パス。指定をvars*.ymlとして、ファイルをfiles/(やtasks/)に配置しても良い。
キーが同じ場合には後から読まれたほうが上書きする。
どちらから読むかは不明。1ステップ追加して取得したvarsファイル名をsortして使用すれば順位は一意に決められそうだが。
ブロックをループできないという理解なので2ファイルに分けている。(Ansibleでは少し制御をしようとすると途端に醜くなる。)

もっと良い方法があれば教えてください。

#追記1: hash_behaviour = merge による対応(実装例2)
.ansible.cfgにhash_behaviour = mergeを指定する対応をコメントいただきました。影響を管理できるなら最も容易な方法になります。
その場合、varsファイルは、自動的に読み込まれるhost_vars/<対象host>/やgroup_vars/all/等におけば良いでしょう。

#追記2: 実装例3(playbookを分割しない)

以下の手法を借り、include_vars + with_fileglob の各回の取り込み内容を保存し、それらをマージする。

[Ansibleのplaybookからコピペをなくすset_factモジュールの使い方: おまけループの中のset_fact](https://qiita.com/yunano/items/3bec328b42715a8ea006#おまけループの中のset_fact)

ただ、register:の結果を使用するので、見通しは悪い。

##playbook

```yaml:tasks/merge1.yml
---
- hosts: all
  tasks:

    - name: set_fact merged_vars = {}
      set_fact:
        merged_vars: {}
    
    - name: include_vars to result
      include_vars:
        file: "{{ item }}"
        name: tmp_vars                    # tmp_vars下に取り込むが各ループで上書きされるので直接は使用しない
      with_fileglob:
        - ../vars/vars*.yml
      register: include_vars_result       # 各ループで取り込んだ内容を含む結果を保存
    
    - name: merge each result.results.ansible_facts into merged_vars
      set_fact:
        merged_vars: "{{ merged_vars | combine( item.ansible_facts.tmp_vars, recursive=True) }}"  # マージ本体
      with_items: "{{ include_vars_result.results }}"
      loop_control:
        label: "{{ item.ansible_facts }}" # 実行時ログに不要な情報が表示されない様にする
    
    - debug: msg="{{ merged_vars }}"
```


##実行結果

```
TASK [set_fact merged_vars = {}] ***********************************************************************************************
ok: [ansiblepush-centos-7]

TASK [include_vars to result] **************************************************************************************************
ok: [ansiblepush-centos-7] => (item=/xxx/tasks/../vars/vars2.yml)
ok: [ansiblepush-centos-7] => (item=/xxx/tasks/../vars/vars1.yml)

TASK [merge each result.results.ansible_facts into merged_vars] ****************************************************************
ok: [ansiblepush-centos-7] => (item={u'tmp_vars': {u'foo': {u'bar3': u'baz3', u'bar4': u'baz4'}}})
ok: [ansiblepush-centos-7] => (item={u'tmp_vars': {u'foo': {u'bar1': u'baz1', u'bar2': u'baz2'}}})

TASK [debug] *******************************************************************************************************************
ok: [ansiblepush-centos-7] => {
    "msg": {
        "foo": {
            "bar1": "baz1", 
            "bar2": "baz2", 
            "bar3": "baz3", 
            "bar4": "baz4"
        }
    }
}
```

#蛇足: Chefなら

##Ruby DSL定義で、キーを分けて代入すればマージされる。

###attributes

```ruby:attributes/var1.rb
default['foo']['bar1'] = 'baz1'
default['foo']['bar2'] = 'baz2'
```

```ruby:attributes/var2.rb
default['foo']['bar3'] = 'baz3'
default['foo']['bar4'] = 'baz4'
```

###recipe

```ruby:recipes/default.rb 
log node['foo']
```

###実行結果

```
Recipe: merge_chef::default
  * log[{"bar1"=>"baz1", "bar2"=>"baz2", "bar3"=>"baz3", "bar4"=>"baz4"}] action write
```





#参考

- [include_vars - Load variables from files, dynamically within a task](https://docs.ansible.com/ansible/2.5/modules/include_vars_module.html)
- [[小ネタ] Ansibleでnestedなdict変数を正しくdeep mergeする方法2つ
](https://qiita.com/jrsyo/items/43eb15309425a824b994)