如何更新包含Jinja2变量的多段YAML文件且保留原有Jinja语法
问题场景
我遇到的使用场景和Stack Overflow上的《How can I update a .yml file, ignoring preexisting Jinja syntax, using Python?》问题类似。我尝试按照该帖子给出的答案操作,但没有一个能适配我的YAML语法,我的YAML内容如下:
kind: Service metadata: name: {{ .Chart.Name }} labels: app: {{ .Chart.Name }} service: {{ .Chart.Name }} spec: {{- if eq .Values.global.node.networkIpVersion "ipv6" }} ipFamily: IPv6 {{- end }} type: ClusterIP ports: - name: http2-8080 protocol: TCP port: 8080 targetPort: 8080 selector: app: {{ .Chart.Name }} --- apiVersion: v1 kind: Service metadata: name: {{ .Chart.Name }}-traffic labels: app: {{ .Chart.Name }} service: {{ .Chart.Name }}-traffic spec: ports: - name: http-2112 port: {{ .Values.global.monitor.prometheusExporter.trafficListenPort }} protocol: TCP targetPort: 2112 selector: app: {{ .Chart.Name }}
除此之外,我的YAML文件内还包含多段模板内容。我原本是要在Node.js环境下实现该需求,但没有找到对应语言的解决方案,因此转向Python寻求解法。
实现方案
核心思路是先将所有Jinja语法块替换为可被YAML解析的唯一占位符,修改完YAML结构后再反向替换回原Jinja内容,既保留原模板语法,又能正常操作YAML字段,推荐使用ruamel.yaml库,可完美保留原文件的缩进、换行、多文档结构。
依赖安装
pip install ruamel.yaml
示例代码
import re from ruamel.yaml import YAML # 匹配所有Jinja语法块的正则,支持{{}}、{{- -}}、{% %}等格式 jinja_pattern = re.compile(r'\{\{.*?\}\}|\{%.*?%\}|\{#.*?#\}', re.DOTALL) def update_jinja_yaml(file_path, update_func): # 读取原文件内容 with open(file_path, 'r', encoding='utf-8') as f: content = f.read() # 替换Jinja块为唯一占位符,存储映射关系 placeholder_map = {} counter = 0 def replace_jinja(match): nonlocal counter placeholder = f"__JINJA_PLACEHOLDER_{counter}__" placeholder_map[placeholder] = match.group(0) counter += 1 return placeholder processed_content = jinja_pattern.sub(replace_jinja, content) # 解析处理后的YAML,支持多文档 yaml = YAML() yaml.preserve_quotes = True yaml.indent(mapping=2, sequence=4, offset=2) docs = list(yaml.load_all(processed_content)) # 执行用户自定义的更新逻辑,比如修改端口、新增字段等 update_func(docs) # 将修改后的YAML转成字符串 from io import StringIO output_stream = StringIO() yaml.dump_all(docs, output_stream) modified_content = output_stream.getvalue() # 反向替换占位符为原Jinja内容 for placeholder, jinja_content in placeholder_map.items(): modified_content = modified_content.replace(placeholder, jinja_content) # 写回文件 with open(file_path, 'w', encoding='utf-8') as f: f.write(modified_content) # 示例:给第一个Service新增一个端口 def my_update_logic(docs): # docs[0]对应第一个Service文档,docs[1]对应第二个 new_port = { "name": "http-9090", "protocol": "TCP", "port": 9090, "targetPort": 9090 } docs[0]['spec']['ports'].append(new_port) # 调用方法 if __name__ == "__main__": update_jinja_yaml("你的模板文件路径.yaml", my_update_logic)
方案说明
- 正则支持匹配
{{ 变量 }}、{% 控制语句 %}、{# 注释 #}三类Jinja语法,覆盖绝大多数模板场景 - 保留原文件的所有格式、缩进、多文档分隔符,不会破坏原有结构
- 自定义更新逻辑完全解耦,你可以根据自己的需求修改任意YAML字段
内容的提问来源于stack exchange,提问作者user3170450
相关产品推荐
相关产品推荐

