Ansible: データを(set_factを使用せず)間接参照可能なままで更新して利用

備忘

# 使用時に評価するlazyな変数

Ansibleでは、play varsやtask varsなどで設定した変数において、{{ }} によるjinja2書式を用いて、他の変数を参照させることが可能。

## lazyな変数の例


```yaml
- hosts: all

  vars:
    foo: "{{ bar }}"
    bar: baz

  tasks:
  - debug: var=foo
  - debug: var=foo
    vars:
      bar: hoge
  - debug: var=foo
    vars:
      bar: fuga
```
↓
```
TASK [debug] ***************************************************************************************************************************
ok: [localhost] => {
    "foo": "baz"
}

TASK [debug] ***************************************************************************************************************************
ok: [localhost] => {
    "foo": "hoge"
}

TASK [debug] ***************************************************************************************************************************
ok: [localhost] => {
    "foo": "fuga"
}
```


# set_factの問題: lazyな変数を展開してしまう
set_factは、使用時に評価するlazyな変数など、データ中にjinja2箇所があると、これを展開し、静的な値としてしまう。

https://docs.ansible.com/ansible/latest/collections/ansible/builtin/set_fact_module.html#notes

> Because of the nature of tasks, set_fact will produce ‘static’ values for a variable. Unlike normal ‘lazy’ variables, the value gets evaluated and templated on assignment.

## set_factがlazyな変数を展開してしまう例

```yaml
- hosts: all

  vars:
    bar: baz

  tasks:

  - set_fact:
      foo: "{{ bar }}"

  - debug: var=foo
  - debug: var=foo
    vars:
      bar: hoge
  - debug: var=foo
    vars:
      bar: fuga
```
↓

```
TASK [debug] ***************************************************************************************************************************
ok: [localhost] => {
    "foo": "baz"
}

TASK [debug] ***************************************************************************************************************************
ok: [localhost] => {
    "foo": "baz"
}

TASK [debug] ***************************************************************************************************************************
ok: [localhost] => {
    "foo": "baz"
}
```




# 課題: 加工したデータを、lazyな変数を展開せずに、その後のtasksで利用するには？


例えば、dictionaryデータの一部を書き換えるにはcombineが使用可能。
しかし、作成したデータを利用するためにset_factを使用すると、データ中のjinja2箇所が展開され、使用時に評価できなくなってしまう。

https://qiita.com/hiroyuki_onodera/items/a3aae117bb0895cf0306


# 対応: task varsやblock varsを利用

task varsやblock varsを用いることで、展開しないでその後のtasksで利用できる。


## Playbook例

ここではblock varsを利用する例を示す。

```yaml:example.yml
---
- name: dataを更新し、lazy vars を展開しないでその後のtasksで利用
  gather_facts: false
  hosts: localhost

  vars:
    data1:
      static_var: 更新元
      lazy_var: "{{ arg1|default(omit) }}"

  tasks:

  - name: 元のデータ
    debug: var=data1
    vars:
      arg1: task varsによる値1

  - name: 元データのstatic_varを更新してdata2とする (このblockのnameはログに表示されない)
    vars:
      dict2:
        static_var: 更新先
      data2: "{{ data1|combine(dict2, recursive=true) }}"
    block:

    # block内
    - name: 元データのstatic_varを更新したdata2
      debug: var=data2
      vars:
        arg1: task varsによる値2

  - name: 元データのlazy_varを更新してdata3とする (このblockのnameはログに表示されない)
    vars:
      dict3:
        lazy_var: "{{ arg3 }}"
      data3: "{{ data1|combine(dict3, recursive=true) }}"
    block:

    # block内
    - name: 元データのlazy_varを更新したdata3
      debug: var=data3
      vars:
        # arg1: "task varsによる値1"    # もしdata1のjinja2箇所に|defaultがなければ、data3にはdata1|...としてdata1が含まれるので、使用されないがarg1が設定されている必要がある
        arg3: task varsによる値3
```

- 上のコメントに記載したが、lazy vars箇所を上書きする際には、使用時に元変数も必要であることに注意が必要。ただし、そもそも間接参照可能なのだから、その変更が必要となるケースはあまり無いと思えるが。

## 結果

```
$ ansible-playbook vars.yml  
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [dataを更新し、lazy vars を展開しないでその後のtasksで利用] *****************************************************************************************************************************

TASK [元のデータ] ****************************************************************************************************************************************************************************
ok: [localhost] => {
    "data1": {
        "lazy_var": "task varsによる値1",
        "static_var": "更新元"
    }
}

TASK [元データのstatic_varを更新したdata2] ***************************************************************************************************************************************************
ok: [localhost] => {
    "data2": {
        "lazy_var": "task varsによる値2",
        "static_var": "更新先"
    }
}

TASK [元データのlazy_varを更新したdata3] *****************************************************************************************************************************************************
ok: [localhost] => {
    "data3": {
        "lazy_var": "task varsによる値3",
        "static_var": "更新元"
    }
}

PLAY RECAP ***********************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
```

- なお、展開するset_factと異なり、データ作成に使用する変数名と同じ変数名で参照するように設定すると、自己参照となり実行時にエラーとなる。
    - 例えば上の例では、data1を加工したデータを、data1という名前で参照させることはできない。
- インデントが深くなるのを避けるなら、別ファイルになってしまうがinclude_tasks:などで逃げてもよいかもしれない。

## 検証環境
- Fedora Linux 36 (Workstation Edition)
- ansible                           7.0.0
- ansible-core                      2.14.0

## 参考
- https://docs.ansible.com/ansible/latest/collections/ansible/builtin/set_fact_module.html
- https://qiita.com/hiroyuki_onodera/items/a3aae117bb0895cf0306
- https://tekunabe.hatenablog.jp/entry/2020/02/09/ansible_replace_set_fact