降价模板中 jinja2 中的变量用法

Variable usage in jinja2 inside markdown template

我正在尝试使用 jinja 创建 markdown 文件的模板,并且变量的值存储在 .yml 文件中(一种主机清单)。

我的问题是我认为我尝试填写的降价 table 并不容易,因为我已经尝试了很多使用 jinja2 工具和功能的替代方案但仍然没有成功我正在解决这个问题希望获得一些见解或技巧来解决问题的社区:

我的降价文件包含 table 例如:

## 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

为了检索正确的值来填充 table 我写了这个 :

{% 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'

欢迎所有建议。

您需要循环遍历 clusters 中包含的项目或通过索引引用它,因为它是一个列表。

诀窍是了解 YAML 解析器返回的数据结构以及如何从 Jinja 中访问该结构。

Jinja 表达式大多只是 Python 代码,有一些细微差别。例如,Jinja 提供了一个快捷方式,允许您使用点语法访问字典。通常,在 Python 中,人们会做 mydict['keyname'] 来检索一个值。然而,Jinja 也支持做 mydict.keyname 在它实际上调用 mydict.keyname 的引擎盖下,但当它失败时,它会尝试 mydict['keyname'] 作为后备。如果两者都失败,它会引发第一个错误,这就是您所看到的 ('list object' has no attribute 'id').

请注意,clusters 项包含一个列表(如 - 所示),这就是错误指向 'list object' 的原因。您不能使用 mylist.keynamemylist['keyname'] 访问列表的项目。您要么需要遍历列表,要么需要按编号索引引用特定项目(第一项为 mylist[0])。除非您确定该列表永远不会包含一个以上的项目(在这种情况下,为什么它是一个列表?)那么您可能希望遍历这些项目。

您似乎正试图仅包含 id1 的单个项目的数据。如果那将始终是列表中的第一项,您可以这样做:servers.clusters.[0].test。但是,如果您不能确定这一点,那么您将需要遍历所有项目并将实际语句包装在 if 语句中,该语句检查 id 是否等于先前设置的变量 id.

第一行的 IP 列像这样:

{% for cluster in servers.clusters %}{% if cluster.id == id %}{{ cluster.test }}{% endif %}{% endfor %}

请注意,cluster.id 引用了 clusters 中项目的键 id。它不是 {% set id = 1 %} 设置的 id。但是,它可以与它进行比较,如果两者相等(在本例中都包含值 1),则表达式为 True 并执行其中包含的表达式。当两者不相等时,则忽略。

第二行也有同样的问题。但是,nodes 还包含一个字符串列表。这些项目中的任何一个都没有属性,因此您只需在第二个循环中呈现 node(而不是 node.idnode.ipnode.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 合并为一个 table,您将删除 if 检查并为每个集群设置一行。但这与您要求的 table 不同。

如果你想为每个集群单独 table,你有几个选择。您可以使用相同的方法并简单地重新定义 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 %}

请注意,循环包装了所有内容,包括 header。 header 然后得到 cluster.id。而且,由于 table 的 body 将针对每个集群重复,我们不需要 if 语句将其限制为仅一个集群。

重要的是要注意,只有当每个集群的数据都在相同的 format/structure.

中时,此方法才有效