2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AnsibleAdvent Calendar 2021

Day 10

[Ansible]dictのkeyを一部削除する方法まとめ

Last updated at Posted at 2021-12-09

はじめに

この記事は、Ansible Advent Calendarの10日目の記事になります。

モジュールにdictでパラメータを渡すときに手順の問題で

  1. dictの一部を削除したものをパラメータとして利用
  2. 別作業
  3. dictのすべてをパラメータとして利用

とする必要がありました。

同じdictを利用したかったのですが、公式のfilterからはできるようなものは見つからず・・・

いろいろと方法を探してみたのでメリットデメリット含めてまとめたものを共有したいと思います。

方法

dictのkeyを削除する方法としては、以下の通り5種類ありました。
#ほかにも方法があるかもしれずご存じの方いればお教えください。

おすすめとしては、5のカスタムフィルタを作成するか、フィルタを作成しないポリシーであれば2のjinjaテンプレートを使う方法でいいのかなと思います。

なお、今回は、5のカスタムフィルタで対応しました。

# 方法 変数空間を汚すか メリット デメリット
1 dictをゼロから作成 Yes keyごとにdict代入しているかがログに出るのでデバッグしやすい loopを使っており、タスクが3つに分かれるため、行数が長くなる
2 jinjaテンプレートでkey削除 No カスタマイズ性が高い jinjaテンプレートをPlaybookで使うため、わかりづらい
3 keyにomit代入 No Ansibleが提供しているfilterのみで完結 特になし。複雑なdictへの対応が難しい?
4 list変換して再度dict化 No Ansibleが提供しているfilterのみで完結 変数の動きがわかりづらい
5 カスタムフィルターを作成 No Playbookがシンプルになる カスタムフィルタを作成する必要がある
6 JSONQueryフィルターを利用 No 柔軟性が高い jsmepathをインストールしておく必要がある

dict内のaというkeyを削除して表示したときの各方法のPlaybookは下記のとおりです。

なお、方法5のカスタムフィルターについてはPlaybook実行フォルダ配下にfilter_plugins/remove_dict_key.pyを作成しています。

playbook
playbook.yml
---
- name: test
  hosts: localhost
  gather_facts: no

  vars:
    dict:
      a: test1
      b: test2
      c: test3

  tasks:
    - name: 元dictをコピー
      set_fact:
        d1: "{{ dict }}"
        d2: "{{ dict }}"
        d3: "{{ dict }}"
        d4: "{{ dict }}"
        d5: "{{ dict }}"
        d6: "{{ dict }}"

    # 1. dictをゼロから作成  
    - name: ①:空dict作成
      set_fact:
        d1_: {}
    - name: ①:空dictにkeyを追加
      set_fact:
        d1_: "{{ d1_ | combine({ item.key: item.value }) }}"
      when: item.key not in ['a']
      with_dict: "{{ d1 }}"
    - name: ①:表示(d1_は変数空間に残る)
      debug:
        msg: "{{ d1_ }}"
    # TASK [①:表示(d1_は変数空間に残る)] *********************************************************************************************************************************************************************************************************
    # ok: [localhost] => {
    #     "msg": {
    #         "b": "test2",
    #         "c": "test3"
    #     }
    # }

    # 2. jinjaテンプレートでkey削除
    - name: ②jinja内で変数作成してkeyを除外して表示
      debug:
        msg: |
          {% set d2_=d2.copy() %}
          {% set _r=d2_.pop('a') %}
          {{ d2_ }}

    - name: ②d2は変数空間に残らない
      debug:
        msg: "{{ d2_ }}"
      ignore_errors: yes
    # TASK [②jinja内で変数作成してkeyを除外して表示] *********************************************************************************************************************************************************************************************
    # ok: [localhost] => {
    #     "msg": {
    #         "b": "test2",
    #         "c": "test3"
    #     }
    # }
    # TASK [②d2は変数空間に残らない] **************************************************************************************************************************************************************************************************************
    # fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'd2_' is undefined\n\nThe error appears to be in '/mnt/c/Users/y_miyashita.IT-PC-2012-0517/git/act_training/test.yml': line 41, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n    - name: ②d2は変数空間に残らない\n      ^ here\n"}
    # ...ignoring

    # 3. keyにomit代入
    - name: ③keyの値をomitにして表示
      debug:
        msg: |
          {{ d3 | combine({'a': omit}) }}
    # TASK [③keyの値をomitにして表示] *************************************************************************************************************************************************************************************************************
    # ok: [localhost] => {
    #     "msg": {
    #         "b": "test2",
    #         "c": "test3"
    #     }
    # }

    # 4. list変換して再度dict化
    - name: ④list変換・再度dict化して表示
      debug:
        msg: |
          {{ dict | dict2items | rejectattr("key", "eq", "a") | list | items2dict }}
      # TASK [④list変換・再度dict化して表示] ******************************************************************************************************************************************************************************************************
      # ok: [localhost] => {
      #     "msg": {
      #         "b": "test2",
      #         "c": "test3"
      #     }
      # }

      # 5. カスタムフィルターを作成
    - name: ⑤フィルターを利用して表示
      debug:
        msg: |
          {{ d5 | remove_dict_key('a') }}

    - name: ⑤d5に影響はない
      debug:
        msg: "{{ d5 }}"
    # TASK [⑤フィルターを利用して表示] ***************************************************************************************************************************************************************************************
    # ok: [localhost] => {
    #     "msg": {
    #         "b": "test2",
    #         "c": "test3"
    #     }
    # }
    # TASK [⑤d5に影響はない] **********************************************************************************************
    # ok: [localhost] => {
    #     "msg": {
    #         "a": "test1",
    #         "b": "test2",
    #         "c": "test3"
    #     }
    # }

    # 6. JSON Queryを利用
    - name: ⑥JSON Queryを利用して表示
      debug:
        msg: |
          {{ d6 | json_query('{b: b, c: c}') }}
    # TASK [⑥JSON Queryを利用して表示] ************************************************************************************
    # ok: [localhost] => {
    #     "msg": {
    #         "b": "test2",
    #         "c": "test3"
    #     }
    # }
filter
filter_plugins/remove_dict_key.py
#!/usr/bin/env python

from ansible.errors import AnsibleFilterError

class FilterModule(object):
    def filters(self):
        return {
            'remove_dict_key': self.remove_dict_key,
        }

    def remove_dict_key(self, d, key):
        if not isinstance(d, dict):
            raise AnsibleFilterError("|failed expects a dictionary")

        if key in d:
            del d[key]

        return d

さいごに

すでに定義しているdictをいじることは結構あると思うので、プラグインで定義しておくと後々有効活用できそうで良いかなと思います。

3のcombineを使う方法では、null代入しかできないと思っていましたが、後々調べてみるとomitでも問題なさそうなので気軽にkey削除する場合はその方法でもいいですね!

余談ですが、今回filterを作成する際にGitHub Copilotを有効にしていたのですがdef remove_dictと打ったタイミングで、関数の中身が丸々が候補として出てきており、同じようなfilterを作っている人は多いんだなと思った反面GitHub Copilotすごい!と改めて痛感しました。

参考リンク

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?