Использование переменных в jinja2 внутри шаблона уценки
Я пытаюсь создать шаблон файла уценки, используя jinja, и значения переменных хранятся в файле .yml (своего рода инвентарь хостов).
моя проблема в том, что я думаю, что таблица уценки, которую я пытаюсь заполнить, не упрощает задачу, и, поскольку я пробовал много альтернатив, используя инструменты и функции jinja2, и до сих пор не добился успеха, я обращаюсь к этому вопросу к сообществу в надежде получить некоторое представление или советы по преодолению проблемы:
мой файл уценки содержит такую таблицу, как:
## Servers
### Cluster 1
| | IP | FQDN |
|-------|----|------|
| | | |
мой файл значений .yml выглядит следующим образом:
servers:
clusters:
- id: 1
test: X.X.X.X
nodes:
- X.X.X.X
- X.X.X.X
- X.X.X.X
чтобы получить правильные значения для заполнения таблицы, я написал это:
{% set id = 1 %}
| | IP | FQDN |
|-------|----|------|
| test | {{servers.clusters.id.test}} | |
{% for node in servers.clusters.id.nodes %}|node{{node.id}}|{{node.ip}}|{{node.fqdn}}|
{% endfor %}
но, похоже, это не работает, и ошибка не очень явная (конечно, для новичка jinja2):
File "[PATH]/filename.md", line 34, in top-level template code
| test | {{server.clusters.id.test}} | |
File "/usr/lib/python3.8/site-packages/jinja2/environment.py", line 471, in getattr
return getattr(obj, attribute)
jinja2.exceptions.UndefinedError: 'list object' has no attribute 'id'
Все предложения приветствуются.
1 ответ
Вам нужно перебирать элементы, содержащиеся в нем, или ссылаться на него по индексу, поскольку это список.
Хитрость заключается в том, чтобы понять структуру данных, возвращаемых парсером YAML, и как получить доступ к этой структуре из Jinja.
Выражения Jinja в основном представляют собой просто код Python с небольшими отличиями. Например, Jinja предоставляет ярлык, который позволяет вам получать доступ к словарям с использованием точечного синтаксиса. Обычно в Python это делается для извлечения значения. Тем не менее, Jinja также поддерживает действие «Под капотом», которое на самом деле вызывает
mydict.keyname
но когда это не удается, он пытается
mydict['keyname']
как запасной вариант. Если оба терпят неудачу, возникает первая ошибка, которую вы видите (
'list object' has no attribute 'id'
).
Обратите внимание, что элемент содержит список (как указано
-
), поэтому ошибка относится к «объекту списка». Вы не можете получить доступ к элементам списка, используя либо
mylist.keyname
или же
mylist['keyname']
. Вам нужно либо пройтись по списку, либо сослаться на конкретный элемент по его индексу по номеру (
mylist[0]
по первому пункту). Если вы не уверены, что список никогда не будет содержать более одного элемента (в таком случае, почему это список?), тогда вы, вероятно, захотите просмотреть элементы в цикле.
Похоже, вы пытаетесь включить данные только для одного элемента с is . Если это всегда будет первым элементом в списке, вы можете сделать:
servers.clusters.[0].test
. Однако, если вы не можете быть уверены в этом, вам нужно будет пройтись по всем элементам и обернуть фактический оператор в оператор if, который проверяет, что id равен предыдущему набору переменных.
Вот так для
IP
столбец первой строки:
{% for cluster in servers.clusters %}{% if cluster.id == id %}{{ cluster.test }}{% endif %}{% endfor %}
Обратите внимание, что это ссылка на ключ элемента в . Это не установлено
{% set id = 1 %}
. Однако его можно сравнить с ним, и если они равны (оба содержат значение
1
в этом случае), тогда выражение имеет значение True и выражение, содержащееся внутри, выполняется. Когда они не равны, они игнорируются.
У вас такая же проблема со второй строкой. Однако,
nodes
также содержит список строк. Ни у одного из этих элементов нет атрибутов, поэтому вы просто визуализируете
node
во втором цикле (вместо
node.id
,
node.ip
а также
node.fqdn
). Поэтому я предполагаю, что вы хотите что-то вроде этого:
{% for cluster in servers.clusters %}{% if cluster.id == id %}|{% for node in cluster.nodes %} {{ node }} |{% endfor %}{% endif %}{% endfor %}
Конечно, вы можете объединить все это вместе и выполнить цикл только один раз:
{% set id = 1 %}
| | IP | FQDN |
|-------|----|------|
{% for cluster in servers.clusters %}{% if cluster.id == id %}| test | {{ cluster.test }} | |
|{% for node in cluster.nodes %} {{ node }} |{% endfor %}
{% endif %}
{% endfor %}
Естественно, если вы хотите совместить все
clusters
в одной таблице вы бы удалили
if
проверить и иметь одну строку для каждого кластера. Но это будет другой стол, чем тот, который вы просили.
Если вам нужна отдельная таблица для каждого кластера, у вас есть несколько вариантов. Вы можете использовать тот же подход и просто переопределить
id
переменная. Как это:
### Cluster 1
{% set id = 1 %}
| | IP | FQDN |
|-------|----|------|
{% for cluster in servers.clusters %}{% if cluster.id == id %}| test | {{ cluster.test }} | |
|{% for node in cluster.nodes %} {{ node }} |{% endfor %}
{% endif %}
{% endfor %}
### Cluster 2
{% set id = 2 %}
| | IP | FQDN |
|-------|----|------|
{% for cluster in servers.clusters %}{% if cluster.id == id %}| test | {{ cluster.test }} | |
|{% for node in cluster.nodes %} {{ node }} |{% endfor %}
{% endif %}
{% endfor %}
Обратите внимание, что единственная разница между ними — это первые две строки:
### Cluster 2
{% set id = 2 %}
Все остальное то же самое. Но если вы просто повторяете один и тот же код, это не очень эффективно. Любые будущие изменения необходимо будет вносить для каждого кластера. Вместо этого просто заверните все это в цикл:
{% for cluster in servers.clusters %}
### Cluster {{ cluster.id }}
| | IP | FQDN |
|-------|----|------|
| test | {{ cluster.test }} | |
|{% for node in cluster.nodes %} {{ node }} |{% endfor %}
{% endfor %}
Обратите внимание, что цикл охватывает все, включая заголовок. Затем заголовок получает
cluster.id
. А поскольку тело таблицы будет повторяться для каждого кластера, нам не нужен оператор if, ограничивающий его только одним кластером.
Важно отметить, что этот подход будет работать только в том случае, если данные для каждого кластера имеют одинаковый формат/структуру.