Использование переменных в 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, ограничивающий его только одним кластером.

Важно отметить, что этот подход будет работать только в том случае, если данные для каждого кластера имеют одинаковый формат/структуру.

Другие вопросы по тегам