Вопрос или проблема
У меня есть список словарей:
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
Объяснение
- Создание нового списка: Используется переменная
new_sites
, которая инициализируется как пустой список. - Проход по элементам: Цикл
loop
позволяет нам пройти по каждому элементу спискаsites
. - Использование
combine
: Каждый элементitem
комбинируется с новым словарем, содержащим необходимые поля (site_subnet
иtest1
). - Конкатенация: С помощью
+
добавляем обновлённый элемент к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, как инструмент автоматизации, может вызывать сложности при манипуляциях с данными структур, поэтому важно тщательно проверять используемые методы и подходы. Ваша задача — это классический пример, когда создание нового списка с нужными значениями оказывается гораздо более простым и надёжным методом, чем попытка изменить существующий объект.