В Ansible добавьте элемент ко всем словарям в списке словарей, но умножьте его на существующую запись.

Вопрос или проблема

У меня есть список словарей:

vars:
  sites:
    - name: "Tampa"
      site_number: 0
    - name: "Miami"
      site_number: 1
    - name: "Daytona"
      site_number: 2

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

- set_fact:
    sites: >-
      {{
        sites | map('combine', {
          'site_subnet': '172.16.0.' + (item.site_number*8)|string + '/29',
          'test1': item.site_number
        }) | list
      }}
  loop: "{{ sites }}"
- debug: var=sites

Теперь результирующий вывод показывает, что sites[n].test1 всегда равен 2, и, конечно, site_subnet всегда равен 172.16.0.16/29. В чем ошибка в моем коде?

Ansible в целом не является отличным инструментом для модификации структур данных. Вы можете получить то, что хотите, создав новую переменную, вот так:

- hosts: localhost
  gather_facts: false
  vars:
    sites:
      - name: "Tampa"
        site_number: 0
      - name: "Miami"
        site_number: 1
      - name: "Daytona"
        site_number: 2
  tasks:
    - set_fact:
        new_sites: >-
          {{
            new_sites + [item | combine({
              'site_subnet': '172.16.0.' ~ (item.site_number*8) ~ '/29',
              'test1': item.site_number
            })]
          }}
      loop: "{{ sites }}"
      vars:
        new_sites: []

    - debug:
        var: new_sites

Это приводит к следующему выводу (что, как я думаю, вы хотите):

TASK [debug] *************************************************************************************************************************************************************
ok: [localhost] => {
    "new_sites": [
        {
            "name": "Tampa",
            "site_number": 0,
            "site_subnet": "172.16.0.0/29",
            "test1": 0
        },
        {
            "name": "Miami",
            "site_number": 1,
            "site_subnet": "172.16.0.8/29",
            "test1": 1
        },
        {
            "name": "Daytona",
            "site_number": 2,
            "site_subnet": "172.16.0.16/29",
            "test1": 2
        }
    ]
}

Для любой сложной логики я всегда нахожу более удобным использовать пользовательский фильтр. Я бы добавил следующее в filter_plugins/filters.py:

def filter_update_sites(sites):
    return [
        site | {
            "site_subnet": f"172.16.0.{site['site_number']*8}/29"
        }
        for site in sites
    ]

class FilterModule:
    def filters(self):
        return {
            "update_sites": filter_update_sites,
        }

И затем написать плейбук вот так:

- hosts: localhost
  gather_facts: false
  vars:
    sites:
      - name: "Tampa"
        site_number: 0
      - name: "Miami"
        site_number: 1
      - name: "Daytona"
        site_number: 2
  tasks:
    - set_fact:
        new_sites: "{{ sites | update_sites }}"

    - debug:
        var: new_sites

Что производит:

TASK [debug] *************************************************************************************************************************************************************
ok: [localhost] => {
    "new_sites": [
        {
            "name": "Tampa",
            "site_number": 0,
            "site_subnet": "172.16.0.0/29"
        },
        {
            "name": "Miami",
            "site_number": 1,
            "site_subnet": "172.16.0.8/29"
        },
        {
            "name": "Daytona",
            "site_number": 2,
            "site_subnet": "172.16.0.16/29"
        }
    ]
}

Основываясь на Как сохранить добавление новых ключей в словарь при использовании set_fact с with_items? (аннот: где также упоминается Пользовательский фильтр), минимальный пример плейбука

---
- hosts: localhost
  become: false
  gather_facts: false

  vars:

    sites:
      - name: "Tampa"
        site_number: 0
      - name: "Miami"
        site_number: 1
      - name: "Daytona"
        site_number: 2

  tasks:

   - debug:
       msg: "{{ sites[item.site_number] | combine(_append_dict) }}"
     loop: "{{ sites }}"
     vars:
       _append_dict:
         site_subnet: "172.16.0.{{ (item.site_number * 8) | string }}/29"
         test: "{{ item.site_number }}"

приведет к выводу

TASK [debug] ******************************************************
ok: [localhost] => (item={'name': 'Tampa', 'site_number': 0}) =>
  msg:
    name: Tampa
    site_number: 0
    site_subnet: 172.16.0.0/29
    test: '0'
ok: [localhost] => (item={'name': 'Miami', 'site_number': 1}) =>
  msg:
    name: Miami
    site_number: 1
    site_subnet: 172.16.0.8/29
    test: '1'
ok: [localhost] => (item={'name': 'Daytona', 'site_number': 2}) =>
  msg:
    name: Daytona
    site_number: 2
    site_subnet: 172.16.0.16/29
    test: '2'

Ответ или решение

Вопрос, поставленный вами о модификации списка словарей (dict) в Ansible, требует детального подхода для решения проблемы добавления элементов и корректного использования значений. Давайте разберёмся в данной задаче.

Исходные Данные

У нас есть список словарей, представленных в переменной sites:

vars:
  sites:
    - name: "Tampa"
      site_number: 0
    - name: "Miami"
      site_number: 1
    - name: "Daytona"
      site_number: 2

Задача

Необходимо добавить новый элемент в каждый словарь, но при этом умножить его на существующий элемент site_number. Например, для Tampa, Miami и Daytona мы хотим добавить site_subnet на основе значения site_number.

Проблема

При использовании вашего первоначального кода:

- set_fact:
    sites: >-
      {{
        sites | map('combine', {
          'site_subnet': '172.16.0.' + (item.site_number*8)|string + '/29',
          'test1': item.site_number
        }) | list
      }}
  loop: "{{ sites }}"

возникает проблема, связанная с тем, что Ansible не может обрабатывать переменные в блоке loop ожидаемым образом. В результате, item ссылается на одно и то же значение.

Решение

Для корректного добавления элемента site_subnet с использованием умноженного значения site_number, лучше всего создать новый список словарей. Это можно достичь, применив модуль set_fact с использованием цикла:

Пример Решения

- hosts: localhost
  gather_facts: false
  vars:
    sites:
      - name: "Tampa"
        site_number: 0
      - name: "Miami"
        site_number: 1
      - name: "Daytona"
        site_number: 2
  tasks:
    - set_fact:
        new_sites: >-
          {{
            new_sites + [item | combine({
              'site_subnet': '172.16.0.' ~ (item.site_number * 8) ~ '/29',
              'test1': item.site_number
            })]
          }}
      loop: "{{ sites }}"
      vars:
        new_sites: []

    - debug:
        var: new_sites

Объяснение

  1. Создание нового списка: Используется переменная new_sites, которая инициализируется как пустой список.
  2. Проход по элементам: Цикл loop позволяет нам пройти по каждому элементу списка sites.
  3. Использование combine: Каждый элемент item комбинируется с новым словарем, содержащим необходимые поля (site_subnet и test1).
  4. Конкатенация: С помощью + добавляем обновлённый элемент к new_sites.

Результат

После выполнения данного кода, вывод debut покажет, что новый список new_sites будет содержать правильные значения для каждого элемента:

ok: [localhost] => {
    "new_sites": [
        {
            "name": "Tampa",
            "site_number": 0,
            "site_subnet": "172.16.0.0/29",
            "test1": 0
        },
        {
            "name": "Miami",
            "site_number": 1,
            "site_subnet": "172.16.0.8/29",
            "test1": 1
        },
        {
            "name": "Daytona",
            "site_number": 2,
            "site_subnet": "172.16.0.16/29",
            "test1": 2
        }
    ]
}

Заключение

Ansible, как инструмент автоматизации, может вызывать сложности при манипуляциях с данными структур, поэтому важно тщательно проверять используемые методы и подходы. Ваша задача — это классический пример, когда создание нового списка с нужными значениями оказывается гораздо более простым и надёжным методом, чем попытка изменить существующий объект.

Оцените материал
Добавить комментарий

Капча загружается...