CUE(Configure, Unify, Execute)是一种声明式配置语言,由 Google 开发,专注于数据验证、模板生成和配置管理。它的核心设计目标是:
有许多类似CUE的项目,或专注于配置生成,或专注模板构建,例如python的jinja2、Pydantic,OpenAPI Validate(基于JSON Schema),但CUE在此领域集成诸多优势脱颖而出,表现在:
CUE 支持复杂类型和复合数据解构。而OpenAPI Validate基于JSON Schema,类型有限;Pydantic支持丰富类型,但其基于Python注解,无cli工具等配套生态,与其他语言(如golang)难以结合。
CUE原生支持多&操作符来合并多源数据,类似Class的多重继承,对于复杂配置项的整合十分方便。而JSON Schema不支持合并,Pydantic可以通过update操作进行合并,但需手动声明维护,且无冲突检测机制。
原生支持YAML/JSON,通过社区工具cue2ini等可以转换成ini,或者其他配置语言。JSON Schema仅作为校验、不生成配置。Pydantic可以方便的生成静态配置,但难以实现条件化模板(如根据环境变量动态生成字段),需要借助额外的Python逻辑来辅助实现。
对于jinja2,其仅作为一个模板语言实现字符串替换,对于校验、数据合并等,完全依赖Python实现。总的来说,CUE在YAML配置自动生成方面,具有诸多的核心优势使我们不得不考虑使用,下面是简单的使用示例:
CUE有一个CLI命令行工具,mac执行 brew install cue-lang/tap/cue可自动安装,执行cue version校验安装是否成功:
➜ cue version
cue version v0.12.1
go version go1.24.2
-buildmode exe
-compiler gc
-trimpath true
CGO_ENABLED 0
GOARCH arm64
GOOS darwin
GOARM64 v8.0
cue.lang.version v0.12.0
编写一个多环境配置的example.cue模板文件:
// 基础配置
Base: {
env: "dev" | "prod"
apiUrl: string
timeout: int | *5000
}
// 环境覆盖
Prod: Base & {
env: "prod"
apiUrl: "https://api.example.com"
timeout: 10000
}
// 输出最终配置
output: Prod
使用cue CLI进行渲染,可以看到已经输出YAML:
➜ cue export example.cue -e output --out yaml
env: prod
apiUrl: https://api.example.com
timeout: 10000
基本类型
// 字符串
name: "Alice"
// 数字(整数/浮点数)
age: 30
price: 9.99
// 布尔值
enabled: true
// 空值
nothing: null
复合类型
// 结构体
user: {
name: string // 类型约束
age: int
email?: string // 可选字段
}
// 列表
ports: [80, 443, 8080]
users: [{name: "Alice"}, {name: "Bob"}]
高级特性
// 类型约束
#Config: {
timeout: int & >=1 & <=10 // 1~10的整数
url: string & =~"^https://" // 必须https开头
}
// 默认值
settings: {
port: int | *8080 // 默认8080
env: "dev" | "prod" | *"dev"
}
// 条件逻辑
config: {
env: "dev" | "prod"
if env == "prod" {
replicas: 5
} else {
replicas: 1
}
}
循环逻辑
parameter: {
name: string
image: string
env: [string]: string
}
output: {
spec: {
containers: [{
name: parameter.name
image: parameter.image
env: [
for k, v in parameter.env {
name: k
value: v
},
]
}]
}
}
// 类型遍历
#a: {
"hello": "Barcelona"
"nihao": "Shanghai"
}
for k, v in #a {
"\(k)": {
nameLen: len(v)
value: v
}
}
// 切片遍历
parameter: {
env: [...{name:string,value:string}]
}
output: {
env: [
for _, v in parameter.env {
name: v.name
value: v.value
}
]
}
循环与条件判断的结合
parameter: [
{
name: "empty"
}, {
name: "xx1"
},
]
// 循环内使用条件判断
dataFrom: [ for _, v in parameter {
if v.name != "empty" {
name: v.name
}
}]
/* 输出:
[{}, {
name: "xx1"
}]
*/
// 将条件判断作为循环的条件
dataFrom: [ for _, v in parameter if v.name != "empty" {
name: v.name
}]
/* 输出:
[{
name: "xx1"
}]
*/
数据合并与冲突
// merge
base: { timeout: 30, log: "info" }
prod: { log: "error" }
result: base & prod // {timeout:30, log:"error"}
// 冲突
a: { port: 8080 }
b: { port: 80 }
combined: a & b // 错误:port冲突
模板化与复用
// 模板定义
#Deployment: {
apiVersion: "apps/v1"
kind: "Deployment"
spec: {
replicas: int & >=1
template: {
spec: containers: [{
name: string
image: string
}]
}
}
}
// 实例化
myApp: #Deployment & {
metadata: name: "web"
spec: {
replicas: 3
template: {
spec: containers: [{
name: "nginx"
image: "nginx:latest"
}]
}
}
}
校验与输出:
// 校验
cue vet config.cue # 检查配置合法性
// 渲染输出
cue export config.cue -e myApp --out yaml > deploy.yaml
值: 值为具体的数据实例,如:
name: "Alice" // 字符串值
port: 8080 // 整数值
enabled: true // 布尔值
// 默认值:用 | * 指定
port: int | *8080 // 未指定时默认为8080
// 可选字段使用?
user: {
name: string
email?: string // 可省略
}
约束: 对值的限定规则,如:
// 类型约束
age: int // 必须为整数
// 范围约束
timeout: >0 & <10 // 必须大于0且小于10
// 正则约束
email: =~"^\\w+@\\w+\\.\\w+$"
合并:合并策略中,包含 &(Unification)、 | (Disjunction)等.
使用"&"会要求数据同时满足两侧的所有约束,两侧字段必须兼容,即类型相同或可统一。以下是"&"的行为:
默认值被覆盖的一个典型情况:
base: {
port: int | *8080 // 默认值 8080
env: string | *"dev"
}
prod: {
port: 80 // 具体值(非默认值)
env: "prod" // 具体值
}
config: base & prod // 结果: {port: 80, env: "prod"}
"|"则表示“或”关系:值必须满足其中任意一个分支的约束,但CUE是逻辑自洽的,当发生多个值冲突时,也会报错,如:
port: 8080 | 80 // 错误:两个具体值冲突,无法自动选择
// 上述语句单独写,CUE无法确认到底应该选择哪个值。
// 若将8080与80看做合法的枚举值,则可以将上述语句嵌入结构体或模板中,并在上下文中提供实例化的数值,如:
#Server: {
// 约束端口为 80 或 8080,默认8080
port: 80 | 8080 | *8080
// 根据端口自动生成协议
protocol: if port == 80 { "http" } else { "http-alt" }
}
// 实例化
web: #Server & { port: 80 } // 结果: {port:80, protocol:"http"}
api: #Server & { port: 8080 } // 结果: {port:8080, protocol:"http-alt"}
它适用于下述情况:
// 具体值 vs 默认值
port: 8080 | *80 // 若用户未指定,port=80;若用户指定8080,则port=8080
// 约束 vs 默认值
timeout: int & >0 | *30 // 用户未指定时=30;若指定则必须为正整数
// 纯约束的组合
env: "dev" | "prod" // 只能是 "dev" 或 "prod",这里的"dev"、"prod"同样为可选择的枚举值
age: int | float // 可以是任意整数或浮点数
模式匹配 ...(Ellipsis): 这个操作符适用于两种情况:
// 允许任意额外字段(需符合类型约束)
#OpenSchema: {
name: string
... // 允许其他字段
[_:string]: int | string // 额外字段只能是int或string
}
// 允许列表扩展
numbers: [1, 2, ...] // 表示以1,2开头的任意长度列表
针对一个动态配置系统,模拟前端用户提交表单,后端生成yaml:
// template.cue
FlatInput: {
user_name: string | *"Any"
item_count: int | *0
priority: string | *"normal"
}
output: {
person: {
name: FlatInput.user_name
details: {
count: FlatInput.item_count
config: {
priority: FlatInput.priority
}
}
}
}
用户输入:
// data.json
{"FlatInput": {
"user_name": "Alice",
"item_count": 3,
"priority": "high"
}}
执行命令cue export data.json -e output template.cue --out yaml 输出渲染结果:
person:
name: Alice
details:
count: 3
config:
priority: high