無印吉澤

Site Reliability Engineering(SRE)、ソフトウェア開発、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

Ansible で文字列比較の結果を boolean 変数に代入する方法

f:id:muziyoshiz:20160331232512p:plain:w300

きっかけ

今回は、時々つまづく Ansible の変数の仕様に関するメモです。

最近、Amazon Linux 1(そろそろ完全に廃止しないといけない……)と Amazon Linux 2 で処理を分岐させたいことがあり、同僚にこういう書き方でできるよと教えてもらいました。

- name: Task for Amazon Linux 2
  debug: msg="Task for Amazon Linux 2"
  when: (ansible_distribution_file_variety == "Amazon" and ansible_distribution_major_version == "2")

以前は ansible_distribution_major_version がいずれも NA になっていたようなのですが、現在は Amazon Linux 2 なら "2" が返されるようです。

ただ、毎回この長い when を書くのは面倒なので、以下のように playbook の冒頭で定義するようにしてみました。

- hosts: hogehoge
  vars:
    amzn2: (ansible_distribution_file_variety == "Amazon" and ansible_distribution_major_version == "2")
  tasks:
    - name: Task for Amazon Linux 2
      debug: msg="Task for Amazon Linux 2"
      when: amzn2
    - name: Task NOT for Amazon Linux 2
      debug: msg="Task NOT for Amazon Linux 2"
      when: not amzn2

しかし、この書き方をすると、以下の DEPRECATION WARNING が出てしまいます。

TASK [Task for Amazon Linux 2] ***************************************************************************************
[DEPRECATION WARNING]: evaluating (ansible_distribution_file_variety == "Amazon" and
ansible_distribution_major_version == "2") as a bare variable, this behaviour will go away and you might need to add
|bool to the expression in the future. Also see CONDITIONAL_BARE_VARS configuration toggle.. This feature will be
removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
ok: [hogehoge] => {
    "msg": "Task for Amazon Linux 2"
}

TASK [Task NOT for Amazon Linux 2] ***********************************************************************************
skipping: [hogehoge]

なるほど。じゃあ、面倒ですけど、警告文にあるように |bool を書き足します。

- hosts: hogehoge
  vars:
    amzn2: (ansible_distribution_file_variety == "Amazon" and ansible_distribution_major_version == "2")
  tasks:
    - name: Task for Amazon Linux 2
      debug: msg="Task for Amazon Linux 2"
      when: amzn2|bool
    - name: Task NOT for Amazon Linux 2
      debug: msg="Task NOT for Amazon Linux 2"
      when: not amzn2|bool

すると、DEPRECATION WARNING は出なくなったのですが、実行結果が期待と真逆になってしまいました……なんで?

TASK [Task for Amazon Linux 2] ***************************************************************************************
skipping: [hogehoge]

TASK [Task NOT for Amazon Linux 2] ***********************************************************************************
ok: [hogehoge] => {
    "msg": "Task NOT for Amazon Linux 2"
}

結論

文字列比較の結果を代入する際に "{{ }}" で囲むと、文字列ではなく boolean として代入されます。DEPRECATION WARNING にある |bool は不要です。

上の例で言うと、

  vars:
    amzn2: "{{ ansible_distribution_file_variety == \"Amazon\" and ansible_distribution_major_version == \"2\" }}"

と書くと、以下のように DEPRECATION WARNING が出ず、実行結果も期待通りになりました。

TASK [Task for Amazon Linux 2] ***************************************************************************************
ok: [hogehoge] => {
    "msg": "Task for Amazon Linux 2"
}

TASK [Task NOT for Amazon Linux 2] ***********************************************************************************
skipping: [hogehoge]

実験

文字列比較の内容は何でもいいので、("1" == "1") という単純な条件式で実験します。以下の実験は、Ansible 2.9 の最新版(2.9.6)で行いました。

いろいろな方法での代入

まず、代入した値をそのまま debug モジュールで出力してみます。

playbook:

- hosts: localhost
  connection: local
  vars:
    var1: True
    var2: False
    var3: "True"
    var4: "False"
    var5: ("1" == "1")
    var6: ("1" == "1")|bool
    var7: "{{ ('1' == '1') }}"
    var8: "{{ ('1' == '1')|bool }}"
  tasks:
    - name: Print vars
      debug: msg="{{ item }}"
      with_items:
        - "{{ var1 }}"
        - "{{ var2 }}"
        - "{{ var3 }}"
        - "{{ var4 }}"
        - "{{ var5 }}"
        - "{{ var6 }}"
        - "{{ var7 }}"
        - "{{ var8 }}"

結果:

% ansible-playbook test.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 [localhost] *****************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

TASK [Print vars] ****************************************************************************************************
ok: [localhost] => (item=True) => {
    "msg": true
}
ok: [localhost] => (item=False) => {
    "msg": false
}
ok: [localhost] => (item=True) => {
    "msg": true
}
ok: [localhost] => (item=False) => {
    "msg": false
}
ok: [localhost] => (item=("1" == "1")) => {
    "msg": "(\"1\" == \"1\")"
}
ok: [localhost] => (item=("1" == "1")|bool) => {
    "msg": "(\"1\" == \"1\")|bool"
}
ok: [localhost] => (item=True) => {
    "msg": true
}
ok: [localhost] => (item=True) => {
    "msg": true
}

PLAY RECAP ***********************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
変数名 代入方法 結果
var1 True を代入 boolean true
var2 False を代入 boolean false
var3 "True" という文字列を代入 boolean true
var4 "False" という文字列を代入 boolean false
var5 条件式を代入 条件式の文字列
var6 条件式を |bool 付きで代入 条件式の文字列
var7 条件式を "{{ }}" で囲んで代入 boolean true
var8 条件式に |bool を付けた上で "{{ }}" で囲んで代入 boolean true

条件式を "{{ }}" で囲めば、その結果が boolean として代入されるし、囲まなければ文字列として代入されることがわかります。いずれにせよ、代入の時点で |bool の有無は全く関係ないようです。

when 節への文字列の渡し方

代入時に boolean になっている場合は当然成功するので、ここからは代入時に文字列になってしまった場合のみを考えます。

|bool の有無と、結果が True になるかどうかの4通りで実験しました。Playbook が長くなるので、以下には var1 のタスクだけ記載しています。

---
- hosts: localhost
  connection: local
  vars:
    var1: ("1" == "1")
    var2: ("1" == "1")|bool
    var3: ("1" == "2")
    var4: ("1" == "2")|bool
  tasks:
    - name: 1. var1 should be printed
      debug: msg="{{ var1 }}"
      when: var1

    - name: 2. var1 should NOT be printed
      debug: msg="{{ var1 }}"
      when: not var1

    - name: 3. var1 should be printed
      debug: msg="{{ var1 }}"
      when: var1|bool

    - name: 4. var1 should NOT be printed
      debug: msg="{{ var1 }}"
      when: not var1|bool

    - name: 5. var1 should NOT be printed
      debug: msg="{{ var1 }}"
      when: not (var1|bool)

    - name: 6. var1 should be printed
      debug: msg="{{ var1 }}"
      when: "{{ var1 }}"

    - name: 7. var1 should NOT be printed
      debug: msg="{{ var1 }}"
      when: "{{ not var1 }}"

# Template error
#    - name: 8. var1 should NOT be printed
#      debug: msg="{{ var1 }}"
#      when: not "{{ var1 }}"

    - name: 9. var1 should be printed
      debug: msg="{{ var1 }}"
      when: "{{ var1|bool }}"

    - name: 10. var1 should NOT be printed
      debug: msg="{{ var1 }}"
      when: "{{ not var1|bool }}"

    - name: 11. var1 should NOT be printed
      debug: msg="{{ var1 }}"
      when: not "{{ var1|bool }}"

結果はこうなりました。

% ansible-playbook test.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 [localhost] *****************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************
ok: [localhost]

TASK [1. var1 should be printed] *************************************************************************************
[DEPRECATION WARNING]: evaluating u'var1' as a bare variable, this behaviour will go away and you might need to add
|bool to the expression in the future. Also see CONDITIONAL_BARE_VARS configuration toggle. This feature will be
removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
ok: [localhost] => {
    "msg": "(\"1\" == \"1\")"
}

TASK [2. var1 should NOT be printed] *********************************************************************************
skipping: [localhost]

TASK [3. var1 should be printed] *************************************************************************************
skipping: [localhost]

TASK [4. var1 should NOT be printed] *********************************************************************************
ok: [localhost] => {
    "msg": "(\"1\" == \"1\")"
}

TASK [5. var1 should NOT be printed] *********************************************************************************
ok: [localhost] => {
    "msg": "(\"1\" == \"1\")"
}

TASK [6. var1 should be printed] *************************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{
var1 }}
ok: [localhost] => {
    "msg": "(\"1\" == \"1\")"
}

TASK [7. var1 should NOT be printed] *********************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{
not var1 }}
skipping: [localhost]

TASK [9. var1 should be printed] *************************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{
var1|bool }}
skipping: [localhost]

TASK [10. var1 should NOT be printed] ********************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: {{
not var1|bool }}
ok: [localhost] => {
    "msg": "(\"1\" == \"1\")"
}

TASK [11. var1 should NOT be printed] ********************************************************************************
[WARNING]: conditional statements should not include jinja2 templating delimiters such as {{ }} or {% %}. Found: not
"{{ var1|bool }}"
skipping: [localhost]

まとめると、1番の when: varX または 6番の when: {{ varX }} の書き方をすると、DEPRECATION WARNING は出るが、想定通りの結果になりました。それ以外は、文字列の中身に関わらず同じ結果になりました。

when の書き方 var1 var2 var3 var4
1. varX ok ok skipping skipping
2. not varX skipping skipping skipping skipping
3. varX|bool skipping skipping skipping skipping
4. not varX|bool ok ok ok ok
5. not (varX|bool) ok ok ok ok
6. {{ varX }} ok ok skipping skipping
7. {{ not varX }} skipping skipping skipping skipping
9. {{ varX|bool }} skipping skipping skipping skipping
10. {{ not varX|bool }} ok ok ok ok
11. not {{ varX|bool }} skipping skipping skipping skipping

結局、この DEPRECATION WARNING で |bool を付けろ、と言っている意味はわからずじまいでした。誰か詳しい人いたら教えてください……。

[DEPRECATION WARNING]: evaluating (ansible_distribution_file_variety == "Amazon" and ansible_distribution_major_version == "2") as a bare variable, this behaviour will go away and you might need to add |bool to the expression in the future. Also see CONDITIONAL_BARE_VARS configuration toggle.. This feature will be removed in version 2.12. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.

過去の「Ansible のこの仕様なんなん?」と思って書いた記事

muziyoshiz.hatenablog.com

muziyoshiz.hatenablog.com

muziyoshiz.hatenablog.com