我正在尝试使用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.keyname
或mylist['keyname']
访问列表中的项。您要么需要循环遍历列表,要么通过索引和编号引用特定的项(第一个项为mylist[0]
)。除非您确定列表永远不会包含多于一个的项(在这种情况下为什么它是一个列表?),否则您可能需要循环遍历这些项。
看起来您试图只包含id
是1
的单个项目的数据。如果它总是列表中的第一项,您可以这样做: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.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语句将其限制为仅一个集群。
需要注意的是,这种方法只在每个集群的数据格式/结构相同的情况下才有效。