合并与覆盖规则¶
本文阐述 DNSBuilder 在配置合并时的“覆盖”语义,帮助你在使用 include、标准模板 ref/std:、mixins 等功能时预测最终结果
合并模型概览¶
- 基本行为:
- 字典:递归合并(相同键继续进入下一层)
- 列表:按“唯一项并集”合并(以字符串化结果判断唯一性)
- 字典/列表混合:尝试将两者都“归一化为字典”再合并;失败则进行覆盖
- 其他类型:子值覆盖父值
- 不变性:对“父”进行深拷贝后生成结果,源数据不会被修改
字典递归合并¶
- 同键字典会进入下一层继续
deep_merge;不存在的键直接拷贝子值
示例:
parent:
service:
command: ["named", "-g"]
env:
A: a
child:
service:
env:
B: b
结果:
service:
command: ["named", "-g"]
env:
A: a
B: b
列表并集合并¶
- 列表合并采用“字符串化去重”的并集策略:
- 先收集父列表的字符串化集合(
{str(item)}) - 逐项遍历子列表,若字符串化结果不在集合中则追加
- 先收集父列表的字符串化集合(
- 注意:
- 两个结构不同但字符串化后相等的项会被视为重复,从而不追加
- 合并后项的顺序为“父项在前,子新项追加在后”
示例:
parent:
util: ["vim", "dnsutils"]
child:
util: ["dnsutils", "tcpdump"]
结果:
util: ["vim", "dnsutils", "tcpdump"]
字典/列表混合合并¶
- 当父为字典、子为列表或反之:
- 先对两者各自调用
_normalize_to_dict,将“列表形式的KEY=VALUE”归一化为字典 - 成功后对这两个字典执行浅合并(子覆盖同键)
- 若任一方归一化失败(抛
TypeError),则直接使用“子覆盖父”
- 先对两者各自调用
示例:
parent:
environment:
HTTP_USER: root
child:
environment:
- "HTTP_PASS=root"
- "VAR_ONLY"
结果(归一化后浅合并):
environment:
HTTP_USER: root
HTTP_PASS: root
VAR_ONLY: null
标量与其他类型覆盖¶
- 既非字典也非列表的类型,统一采用“子覆盖父”的策略
- 例如:字符串、数字、布尔、空值等直接替换父值
合并的应用场景¶
- include 预处理:
- 处理配置(尤其是同名配置的合并,主要为相同名称的镜像或服务)
- 服务解析与模板混入:
- 将父模板(
ref:或std:)与mixins通过deep_merge与子服务合并,子服务的同键覆盖父/mixin 键
- 将父模板(
注意事项¶
- 统一用法:
- 需要“追加”列表项时,确保同项字符串表示不同,以避免被视为重复
- 对环境变量或
KEY=VALUE列表,优先考虑使用列表形式,可被自动归一化与合并
- 使用建议
- 对于有多种格式的属性(例如
command: str | list),请考虑清楚后续是覆盖还是添加以合理使用某种格式- 如果后续服务继承后需要覆盖
command行为,则直接使用str - 如果后续有不同的服务继承,并添加不同的一些参数,可以考虑使用
list
- 如果后续服务继承后需要覆盖
- 对于有多种格式的属性(例如
示例:include 合并与覆盖¶
# a.yml
builds:
bind:
image: bind
environment:
- "HTTP_USER=root"
# b.yml
builds:
bind:
environment:
- "HTTP_PASS=root"
util: ["vim"]
# 主配置
include:
- a.yml
- b.yml
builds:
bind:
util: ["dnsutils"]
environment:
HTTP_USER: admin # 覆盖同键
合并结果(关键片段):
builds:
bind:
image: bind
util: ["vim", "dnsutils"]
environment:
HTTP_USER: admin
HTTP_PASS: root