Ansible: удалить элемент из словаря - PullRequest
0 голосов
/ 24 января 2020

У меня есть словарь с ключевыми значениями, и немногие значения являются списками. Я хочу удалить элемент из списка внутри словаря. Dict выглядит следующим образом: вложенные элементы.

Dict выглядит следующим образом:

title: Some Title
metadata : 
   manifest-version: 1.0
   key: value
installers:
   - name: someName1
     version: 1.0
   - name: someName2
     version: 2.0
   - name: someName3
     packages:
       - fileName: fileName1
         version: 1.1.1
       - fileName: fileName2
         version: 2.2.2
   - name: service
     type: install
     packages:
       - name: serviceName1
         manifest: someManifest2
         source: abcd.tgz
         files:
           - file1.tar
           - file2.tar
       - name: serviceName2
         manifest: someManifest2
         source: efgh.tgz
         files:
           - file3.tar
           - file4.tar
       - name: serviceName3
         manifest: someManifest3
         source: ijkl.tgz
         files:
           - file5.tar
           - file6.tar

Как удалить элемент, имеющий name: serviceName3? или Могу ли я скопировать этот диктофон в другой диктофон без указанного ниже элемента?

Окончательный вариант не должен содержать следующий элемент:

- name: serviceName2
  manifest: someManifest2
  source: efgh.tgz
  files:
    - file3.tar
    - file4.tar

1 Ответ

1 голос
/ 26 января 2020

Простое решение

Давайте создадим список словарей, которые должны быть удалены

  vars:
    ritem:
      - name: serviceName2
        manifest: someManifest2
        source: efgh.tgz
        files:
          - file3.tar
          - file4.tar

Задачи, приведенные ниже, выполняют работу

    - set_fact:
        inst2: "{{ installers|selectattr('packages', 'defined')|list }}"
    - set_fact:
        inst1: "{{ installers|difference(inst2) }}"
    - set_fact:
        inst4: "{{ inst4|default(inst1) + [
                   item|combine({'packages': item.packages|difference(ritem)})] }}"
      loop: "{{ inst2 }}"
    - debug:
        var: inst4

give

    "inst4": [
        {
            "name": "someName1", 
            "version": 1.0
        }, 
        {
            "name": "someName2", 
            "version": 2.0
        }, 
        {
            "name": "someName3", 
            "packages": [
                {
                    "fileName": "fileName1", 
                    "version": "1.1.1"
                }, 
                {
                    "fileName": "fileName2", 
                    "version": "2.2.2"
                }
            ]
        }, 
        {
            "name": "service", 
            "packages": [
                {
                    "files": [
                        "file1.tar", 
                        "file2.tar"
                    ], 
                    "manifest": "someManifest2", 
                    "name": "serviceName1", 
                    "source": "abcd.tgz"
                }, 
                {
                    "files": [
                        "file5.tar", 
                        "file6.tar"
                    ], 
                    "manifest": "someManifest3", 
                    "name": "serviceName3", 
                    "source": "ijkl.tgz"
                }
            ], 
            "type": "install"
        }
    ]

Форматирование словарей

Простое решение не работает, если словари не отформатированы (отсортированы) точно так же. Например, если порядок файлов отличается, элемент не будет удален

  vars:
    ritem:
      - name: serviceName2
        manifest: someManifest2
        source: efgh.tgz
        files:
          - file4.tar
          - file3.tar

Это можно исправить с помощью нескольких пользовательских фильтров. Давайте создадим список rhash для упрощения сравнения словарей и поместим его в переменные

rhash: "{{ ritem|map('dict_flatten')|map('hash')|list }}"

Приведенные ниже задачи дают тот же результат

    - set_fact:
        inst3: "{{ inst3|default([]) + [
                   filter|
                   list_select_list_bool(item.packages, negative=True)|
                   list] }}"
      vars:
        filter: "{{ item.packages|
                    map('dict_flatten')|
                    map('hash')|
                    map('bool_in', rhash)|
                    list }}"
      loop: "{{ inst2 }}"
    - set_fact:
      inst4: "{{ inst4|default(inst1) + [
                 item.0|combine({'packages': item.1})] }}"
      loop: "{{ inst2|zip(inst3)|list }}"

    - debug:
        var: inst4

Пользовательские фильтры

$ cat filter_plugins/filers.py
def bool_in(x, l):
    return (x in l)

def dict_flatten(d, separator='.'):
    out = {}
    def flatten(x, name=''):
        if type(x) is dict:
            for a in x:
                flatten(x[a], name + a + separator)
        elif type(x) is list:
            i = 0
            for a in sorted(x):
                flatten(a, name + str(i) + separator)
                i += 1
        else:
            out[name[:-1]] = x
    flatten(d)
    return out

def list_select_list_bool(b, l, negative=False):
    l2=[]
    for bi,li in zip(b,l):
        if negative:
            if not bi:
                l2.append(li)
        else:
            if bi:
                l2.append(li)
    return l2

class FilterModule(object):

    def filters(self):
        return {
            'bool_in': bool_in,
            'dict_flatten': dict_flatten,
            'list_select_list_bool': list_select_list_bool
            }
...