0

我正在尝试使用 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'

欢迎所有建议。

4

1 回答 1

1

您需要遍历其中包含的项目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]对于第一项)。除非您确定列表永远不会包含一个以上的项目(在这种情况下,为什么它是一个列表?),否则您可能希望遍历这些项目。

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

像这样IP的第一行的列:

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

请注意,cluster.id它引用了id中的项目的键clusters。它不是由id设置的{% set id = 1 %}。但是,可以与它进行比较,如果两者相等(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 语句将其限制在一个集群中。

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

于 2021-01-18T23:55:18.727 回答