Cue,是一種開源語言,用于定義轨功,生成和驗證各種數(shù)據(jù):配置榨乎,API,數(shù)據(jù)庫模式恨搓,代碼......。它能夠?qū)?shù)據(jù)的結(jié)構(gòu)、約束固棚、數(shù)值作為同一層級成員,從而簡化配置文件的生成仙蚜。
Cue教程
Cue格式說明
- 使用
//
進行單行注釋 - 對象被稱為結(jié)構(gòu)體
- 對象成員稱為結(jié)構(gòu)字段
- 對于沒有特殊字符的字段名此洲,可以省略引號
- 結(jié)構(gòu)字段后面無需
,
- 在列表中的最后一個元素后放置
,
- 最外層的
{}
可省略
例子:
str: "hello world"
num: 42
flt: 3.14
// Special field name (and a comment)
"k8s.io/annotation": "secure-me"
// lists can have different element types
list: [
"a", "b", "c",
1,
2,
3,
]
obj: {
foo: "bar"
// reuse another field?!
L: list
}
Cue 結(jié)構(gòu)、約束委粉、數(shù)據(jù)
// 結(jié)構(gòu)
album: {
title: string
year: int
live: bool
}
// 約束
album: {
title: string
year: >1950
live: false
}
// 數(shù)據(jù)
album: {
title: "Houses of the Holy"
year: 1973
live: false
}
Cue的最佳實踐:從開放的結(jié)構(gòu)模式開始呜师,限制上下文可能性,最終具體到數(shù)據(jù)實例贾节。
Cue哲學:為了保證唯一性汁汗,Cue的數(shù)據(jù)不會被覆蓋趟紊。
Cue核心規(guī)則
- 數(shù)據(jù)可被重復定義,但必須值保持一致
- 結(jié)構(gòu)字段可以被更強限制覆蓋
- 結(jié)構(gòu)的字段會被合并碰酝,如果是列表霎匈,必須嚴格匹配
- 規(guī)則可被遞規(guī)應用
hello: "world"
hello: "world"
// set a type
s: { a: int }
// set some data
s: { a: 1, b: 2 }
// set a nested field without curly braces
s: c: d: 3
// lists must have the same elements
// and cannot change length
l: ["abc", "123"]
l: [
"abc",
"123"
]
結(jié)構(gòu)
- 結(jié)構(gòu)并不會輸出
- 它的值可能是不確認、不完整的
- 字段必須完全
使用#mydef
來定義結(jié)構(gòu)送爸,使用...
來定義一個開放的結(jié)構(gòu)體
#Album: {
artist: string
title: string
year: int
// ... uncomment to open, must be last
}
// This is a conjunction, it says "album" has to be "#Album"
album: #Album & {
artist: "Led Zeppelin"
title: "Led Zeppelin I"
year: 1969
// studio: true (uncomment to trigger error)
}
#Person: {
name: string
... // open struct
}
Jim: #Person & {
name: "jim"
age: 12
}
約束
約束與數(shù)值使用&
字符進行連接時铛嘱,會將值進行校驗
// conjunctions on a field
n: int & >0 & <100
n: 23
// conjuction of schemas
val: #Def1 & #Def2
val: { foo: "bar", ans: 42 }
#Def1: {
foo: string
ans: int
}
#Def2: {
foo: =~ "[a-z]+"
ans: >0
}
替換
使用|
可以實現(xiàn)支持多種結(jié)構(gòu)。同時它也可以為出錯值設置替換值
// disjunction of values (like an enum)
hello: "world" | "bob" | "mary"
hello: "world"
// disjunction of types
port: string | int
port: 5432
// disjunction of schemas
val: #Def1 | #Def2
val: { foo: "bar", ans: 42 }
#Def1: {
foo: string
ans: int
}
#Def2: {
name: string
port: int
}
默認值與可選
使用*
來設置默認值袭厂, ?
設置可選字段
s: {
// field with a default
hello: string | *"world" | "apple"
// an optional integer
count?: int
}
開放模式與封閉模式
開放模式意味著結(jié)構(gòu)可以擴展墨吓,關閉模式意味著不能擴展。 默認情況下纹磺,結(jié)構(gòu)是開放模式帖烘,定義是封閉模式。 可以通過定義的最后添加...
來申明開放模式定義橄杨;另外通過過close強制為結(jié)構(gòu)體設置為關閉模式
// Open definition
#d: {
foo: "bar"
... // must be last
}
// Closed struct
s: close({
foo: "bar"
})
jim: {
name: "Jim"
}
jim: {
age: 12
}
推薦從基礎定義開始秘症,復用定義
在編寫Cue時,推薦從基礎定義開始式矫,這樣能夠有更好的復用能力乡摹。
#Base: {
name: string
kind: string
... // so it can be extended
}
#Meta: {
// string and a semver regex
version: string & =~"^v[0-9]+\\.[0-9]+\\.[0-9]+$"
// list of strings
labels: [...string]
}
#Permissions: {
role: string
public: bool | *false
}
// Building up a schema using a conjunction and embedding
#Schema: #Base & {
// "embed" meta and permissions
#Meta
#Permissions
// with no '...' this is final
}
value: #Schema & {
name: "app"
kind: "deploy"
version: "v1.0.42"
labels: ["server", "prod"]
role: "backend"
// public: false (by default)
}
使用"""
來定義多行字符串
str1: #"avoid using \ to "escape""#
str2: """
a nested multiline
string goes here
"""
List
List 可被定義為開放模式,這樣便可與其它數(shù)據(jù)進行合并采转,
empty: []
any: [...]
ints: [...int]
nested: [...[...string]]
opened: ints & [1,2,...]
closed: ints & [1,2,3]
// list of for constrained ints
ip: 4 * [uint8]
// sets the first element
tendot: ip & [10, ...uint8]
// uses constraint as second element
one72: ip & [172, >=16 & <=32, ...]
mixed: any & [...] & ["a",1, { foo: "bar" }]
join: [1,2] + [3,4]
Join: opened & join
Struct
結(jié)構(gòu)體是Cue的主要內(nèi)容聪廉,也是最終數(shù)據(jù)的輸出。如上介紹故慈,默認情況下它是開放模式板熊。除了使用Json類型形式進行設置值,還可通過級聯(lián):
來設置察绷,如a: hello: "world"
// an open struct
a: {
foo: "bar"
}
// shorthand nested field
a: hello: "world"
// a closed struct
b: close({
left: "right"
})
模式匹配約束
模式匹配允許您為與模式匹配的標簽指定約束干签。可以將約束應用于字符串標簽克婶,并使用標識符來設置字段筒严。
#schema: {
name: string
ans: string
num: int | *42
}
// match elem fields and alias labels to Name,
// unify with schema, set name to Name by label
elems: [Name=_]: #schema & { name: Name }
elems: {
one: {
ans: "solo"
num: 1
}
two: {
ans: "life"
}
}
elems: other: { ans: "id", num: 23 }
表達式
- 引用字段丹泉,使用
\(**)
顯用其它字段
container: {
repo: "docker.io/cuelang"
image: "cue"
version: "v0.3.0"
full: "\(repo)/\(image):\(version)"
}
name: "Tony"
msg: "Hello \(name)"
// conver string to bytes
b: '\(msg)'
// convert bytes to string
s: "\(b)"
- Cue也能夠為通過
\(**)
來設置key
apps: ["nginx", "express", "postgres"]
#labels: [string]: string
stack: {
for i, app in apps {
"\(app)": {
name: app
labels: #labels & {
app: "foo"
tier: "\(i)"
}
}
}
}
- List遍歷
遍歷List數(shù)據(jù)格式如下:[ for key, val in <iterable> [condition] { production } ]
nums: [1,2,3,4,5,6]
sqrd: [ for _, n in nums { n*n } ]
even: [ for _, n in nums if mod(n,2) == 0 { n } ]
listOfStructs: [ for p, n in nums {
pos: p
val: n
}]
extractVals: [ for p, S in listOfStructs { S.val } ]
- 條件控制語句
沒有else情萤,所有判斷都會被執(zhí)行
app: {
name: string
tech: string
mem: int
if tech == "react" {
tier: "frontend"
}
if tech != "react" {
tier: "backend"
}
if mem < 1Gi {
footprint: "small"
}
if mem >= 1Gi && mem < 4Gi {
footprint: "medium"
}
if mem >= 4Gi {
footprint: "large"
}
}
標準庫
Cue的標準庫中包含了很多的幫助包(helper packages)。
- Encoding
package stdlib
import (
"encoding/json"
)
data: """
{
"hello": "world",
"list": [ 1, 2 ],
"nested": {
"foo": "bar"
}
}
"""
jval: json.Unmarshal(data)
val: {
hello: "world"
list: [1,2]
nested: foo: "bar"
}
cjson: json.Marshal(val)
- Strings
package stdlib
import "strings"
s: "HelloWorld"
u: strings.ToUpper(s)
l: strings.ToLower(s)
line: "Cue stands for configure, unify, execute"
words: strings.Split(line, " ")
lined: strings.Join(words, " ")
haspre: strings.HasPrefix(line, "Cue")
index: strings.Index(line, "unify")
- List
package stdlib
import "list"
l1: [1,2,3,4,5]
l2: ["c","b","a"]
// constrain length
l2: list.MinItems(1)
l2: list.MaxItems(3)
// slice a list
l3: list.Slice(l1, 2,4)
// get the sum and product
sum: list.Sum(l1)
prd: list.Product(l1)
// linear search for list (no binary)
lc: list.Contains(l1, 2)
// sort a list
ls: list.Sort(l2, list.Ascending)
l2s: list.IsSorted(l2, list.Ascending)
lss: list.IsSorted(ls, list.Ascending)
// Flatten a list
ll: [1,[2,3],[4,[5]]]
lf: list.FlattenN(ll, 1)
- Constrain
package stdlib
import (
"net"
"time"
)
// string with ip format
ip: net.IPv4
ip: "10.1.2.3"
// string with time format
ts: time.Format(time.ANSIC)
ts: "Mon Jan 2 15:04:05 2006"
使用Cue制作腳本命令工具
Cue 擁有制作腳本命令工具的功能摹恨,它有一個工具層筋岛,可用來執(zhí)行腳本、讀寫文件以及網(wǎng)絡訪問等晒哄。
規(guī)范:
- 腳本文件以
_tool.cue
結(jié)尾 - 執(zhí)行命令為
cue cmd <name>
orcue <name>
例子:
- 腳本文件名為
ex_tool.cue
package foo
import (
"tool/cli"
"tool/exec"
"tool/file"
)
// moved to the data.cue file to show how we can reference "pure" Cue files
// city: "Amsterdam"
// A command named "prompter"
command: prompter: {
// save transcript to this file
var: file: *"out.txt" | string @tag(file) // you can use "-t flag=filename.txt" to change the output file, see "cue help injection" for more details
// prompt the user for some input
ask: cli.Ask & {
prompt: "What is your name?"
response: string
}
// run an external command, starts after ask
echo: exec.Run & {
// note the reference to ask and city here
cmd: ["echo", "Hello", ask.response + "!", "Have you been to", city + "?"]
stdout: string // capture stdout, don't print to the terminal
}
// append to a file, starts after echo
append: file.Append & {
filename: var.file
contents: echo.stdout // becuase we reference the echo task
}
// also starts after echo, and concurrently with append
print: cli.Print & {
text: echo.stdout // write the output to the terminal since we captured it previously
}
}
- prompter為命令名
- ask/echo/append/print為唯一標識
- cli.Ask/exec.Run/file.Append為函數(shù),
- &{...}為函數(shù)參數(shù)
- 創(chuàng)建data.cue
package foo
city: "Amsterdam"
- 運行:
cue cmd prompter
orcue prompter
$ cue cmd prompter
What is your name? he
Hello he! Have you been to Amsterdam?
$ cat out.txt
Hello he! Have you been to Amsterdam?
Tips
- A & B === B & A
- A === A
- 路徑短寫:{a : {b: {c: 5}}} == a b c: 5
- 多種類型:a | b | c
- 默認值:number | *1
- 算術(shù): 4 + 5
- 變量引用:"Hello (person)"
- 列表遍歷:[ x for x in y ]
- cue 執(zhí)行 當前目錄下的cue文件及父目錄下同一個package的cue文件
- cue ./... 以上目錄 + 遍歷當前目錄的子目錄下的cue文件
- _開頭的變量不會在輸出結(jié)果中顯示睁宰,作為局部變量
- [Name=_] 可用來定義一個模板肪获,其中Name匹配任意字段。例如:
application: [Name=_]: { name: string | *Name }
- | 可判斷是否存在柒傻。例如:if _variable != | { // ... }
- 定義映射:map: [string]: string
- 定義切片:slice: [...{name:string,value:string}]
實踐
- 使用
cue import
將已有的yaml轉(zhuǎn)成Cue
語言
$ cue import ./... -p kube -l '"\(strings.ToCamel(kind))" "\(metadata.name)"' -fR
- 引入k8s資源的模塊
$ go mod init main
$ cue get go k8s.io/api/extensions/v1beta1 -v
- 導入k8s資源模塊孝赫,并創(chuàng)建資源
package kube
import (
"k8s.io/api/core/v1"
"k8s.io/api/extensions/v1beta1"
)
service <Name>: v1.Service
deployment <Name>: v1beta1.Deployment