在markdown模板中jinja2的变量使用



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

我的问题是,我认为我试图填写的markdown表并没有使它变得容易,因为我已经尝试了许多使用jinja2工具和功能的替代方案,但仍然没有成功,我正在向社区解决这个问题,希望得到一些洞察力或技巧来解决这个问题:

我的markdown文件包含如下表:

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

欢迎大家提出建议。

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

技巧在于理解YAML解析器返回的数据的结构,以及如何从Jinja中访问该结构。

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

注意clusters项包含一个列表(如-所示),这就是为什么错误指的是一个"列表对象"。不能使用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.id,node.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合并到一个表中,您将删除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语句将其限制为仅一个集群。

需要注意的是,这种方法只在每个集群的数据格式/结构相同的情况下才有效。

最新更新