前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >数据约束语言 CUE 是何方神圣?

数据约束语言 CUE 是何方神圣?

作者头像
我是阳明
发布2022-05-22 09:38:00
9160
发布2022-05-22 09:38:00
举报
文章被收录于专栏:k8s技术圈k8s技术圈

CUE 是一门开源的数据约束语言,专门用于处理配置、结构、数据并执行它们。大多数人开始使用 CUE 是因为要做数据验证和生成配置。然后他们继续将 CUE 用于数据模板、运行时输入验证、代码生成、脚本、管道等等。首先你要明白 CUE 并不是一种通常意义的程序语言,它是图灵非完备的编程语言。

CUE 延续了 JSON 超集的思路,额外提供了丰富的类型、表达式、import 语句等常用能力;与 JSONNET 不同,CUE 不支持自定义 function,支持基于 typed feature structure 思路的外置 schema,并通过显式的合一化、分离化操作支持类型和数据的融合,但这样的设定及外置类型推导同样增加了理解难度和编写复杂性。

CUE 项目完全由 Golang 编写,同时背靠 Golang,允许通过 “import” 引入 CUE 提供的必需能力协助用户完成如 encoding、strings、math 等配置编写常用功能。可以说 CUE 既属于 JSON 系的模板语言,同时也带有了很多 Configuration Language 的思考,提供了良好的样本,无论是其语言设计思路,还是基于成熟高级编程语言引入能力的工程方式都值得深入学习。

安装

通过官方二进制安装 CUE

安装包支持多个操作系统,包括 Linux、Window、macOS,可以在 CUE 官方网站 https://cuelang.org/releases 下载。

使用 homebrew 安装

另外,CUE 在 MacOS 和 Linux 上通过 brew 安装。

代码语言:javascript
复制
brew install cue-lang/tap/cue

通过源码安装

首先需要保证安装 Go 1.16 或更高版本,然后执行下面命令即可安装:

代码语言:javascript
复制
go install cuelang.org/go/cmd/cue@latest

安装完成后即可执行 cue 命令:

代码语言:javascript
复制
cue version
cue version v0.4.3-beta.1 darwin/amd64

CUE 命令行

CUE 是 JSON 的超集, 我们可以像使用 JSON 一样使用 CUE,并具备以下特性:

  • C 语言风格的注释
  • 字段名称可以用双引号括起来,注意字段名称中不可以带特殊字符
  • 可选字段末尾是否有逗号
  • 允许数组中最后一个元素末尾带逗号
  • 外大括号可选

请先复制以下信息,保存为一个 first.cue 文件:

代码语言:javascript
复制
a: 1.5
a: float
b: 1
b: int
d: [1, 2, 3]
g: {
 h: "abc"
}
e: string

接下来,我们以上面这个文件为例子,来学习 CUE 命令行的相关指令:

如何格式化 CUE 文件。下面的命令不仅可以格式化 CUE 文件,还能提示错误的模型,相当好用的命令。

代码语言:javascript
复制
cue fmt first.cue

如何校验模型。除了 cue fmt,你还可以使用 cue vet 来校验模型。

代码语言:javascript
复制
$ cue vet first.cue
some instances are incomplete; use the -c flag to show errors or suppress this message

$ cue vet first.cue -c
e: incomplete value string

提示我们:这个文件里的 e 这个变量,有数据类型 string 但并没有赋值。

如何计算/渲染结果。cue eval 可以计算 CUE 文件并且渲染出最终结果。我们看到最终结果中并不包含 a: floatb: int,这是因为这两个变量已经被计算填充。其中 e: string 没有被明确的赋值, 故保持不变.

代码语言:javascript
复制
$ cue eval first.cue
a: 1.5
b: 1
d: [1, 2, 3]
g: {
h: "abc"
}
e: string

如何指定渲染的结果。例如,我们仅想知道文件中 b 的渲染结果,则可以使用该参数 -e

代码语言:javascript
复制
$ cue eval -e b first.cue
1

如何导出渲染结果。cue export 可以导出最终渲染结果。如果一些变量没有被定义执行该命令将会报错。

代码语言:javascript
复制
$ cue export first.cue
e: incomplete value string  

我们更新一下 first.cue 文件,给 e 赋值:

代码语言:javascript
复制
a: 1.5
a: float
b: 1
b: int
d: [1, 2, 3]
g: {
  h: "abc"
}
e: string
e: "abc"

然后,该命令就可以正常工作。默认情况下, 渲染结果会被格式化为 JSON 格式。

代码语言:javascript
复制
$ cue export first.cue
{
    "a": 1.5,
    "b": 1,
    "d": [
        1,
        2,
        3
    ],
    "g": {
        "h": "abc"
    },
    "e": "abc"
}

如何导出 YAML 格式的渲染结果。

代码语言:javascript
复制
$ cue export first.cue --out yaml
a: 1.5
b: 1
d:
- 1
- 2
- 3
g:
  h: abc
e: abc

如何导出指定变量的结果。

代码语言:javascript
复制
$ cue export -e g first.cue
{
    "h": "abc"
}

以上, 你已经学习完所有常用的 CUE 命令行指令。

数据类型

在熟悉完常用 CUE 命令行指令后,我们来进一步学习 CUE 语言。

先了解 CUE 的数据类型。以下是它的基础数据类型:

代码语言:javascript
复制
// float
a: 1.5

// int
b: 1

// string
c: "blahblahblah"

// array
d: [1, 2, 3, 1, 2, 3, 1, 2, 3]

// bool
e: true

// struct
f: {
 a: 1.5
 b: 1
 d: [1, 2, 3, 1, 2, 3, 1, 2, 3]
 g: {
  h: "abc"
 }
}

// null
j: null

如何自定义 CUE 类型?使用 # 符号来指定一些表示 CUE 类型的变量。

代码语言:javascript
复制
#abc: string

我们将上述内容保存到 second.cue 文件。执行 cue export 不会报 #abc 是一个类型不完整的值。

代码语言:javascript
复制
$ cue export second.cue
{}

你还可以定义更复杂的自定义结构,比如:

代码语言:javascript
复制
#abc: {
  x: int
  y: string
  z: {
    a: float
    b: bool
  }
}

CUE 模板

下面,我们开始尝试利用刚刚学习到的知识,来定义 CUE 模版。

  1. 定义结构体变量 parameter.
代码语言:javascript
复制
parameter: {
 name: string
 image: string
}

保存上述变量到文件 deployment.cue.

  1. 定义更复杂的结构变量 template 同时引用变量 parameter.
代码语言:javascript
复制
template: {
 apiVersion: "apps/v1"
 kind:       "Deployment"
 spec: {
  selector: matchLabels: {
   "app.oam.dev/component": parameter.name
  }
  template: {
   metadata: labels: {
    "app.oam.dev/component": parameter.name
   }
   spec: {
    containers: [{
     name:  parameter.name
     image: parameter.image
    }]
   }}}
}

熟悉 Kubernetes 的你可能已经知道,这是 Kubernetes Deployment 的模板。parameter 为模版的参数部分。

添加上述内容到文件 deployment.cue.

  1. 随后, 我们通过更新以下内容来完成变量赋值:
代码语言:javascript
复制
parameter:{
   name: "mytest"
   image: "nginx:v1"
}
  1. 最后, 导出渲染结果为 YAML 格式:
代码语言:javascript
复制
$ cue export deployment.cue -e template --out yaml

apiVersion: apps/v1
kind: Deployment
spec:
  selector:
    matchLabels:
      app.oam.dev/component: mytest
  template:
    metadata:
      labels:
        app.oam.dev/component: mytest
    spec:
      containers:
      - name: mytest
        image: nginx:v1

以上,你就得到了一个 Kubernetes Deployment 类型的模板。

其他用法

设计开放的结构体和数组。如果在数组或者结构体中使用 ...,则说明该对象为开放的。

  • 数组对象 [...string] ,说明该对象可以容纳多个字符串元素。如果不添加 ..., 该对象 [string] 说明数组只能容纳一个类型为 string 的元素。

使用运算符 | 来表示两种类型的值。如下所示,变量 a 表示类型可以是字符串或者整数类型。

代码语言:javascript
复制
a: string | int
  • 使用符号 * 定义变量的默认值。通常它与符号 | 配合使用, 代表某种类型的默认值。如下所示,变量 a 类型为 int,默认值为 1
代码语言:javascript
复制
a: *1 | int
  • 让一些变量可被选填。某些情况下,一些变量不一定被使用,这些变量就是可选变量,我们可以使用 ?: 定义此类变量。如下所示, a 是可选变量, 自定义 #my 对象中 xz 为可选变量, 而 y 为必填字段。
代码语言:javascript
复制
a ?: int

#my: {
x ?: string
y : int
z ?:float
}

选填变量可以被跳过,这经常和条件判断逻辑一起使用。具体来说,如果某些字段不存在,则 CUE 语法为 if _variable_!= _ | _ ,如下所示:

代码语言:javascript
复制
parameter: {
    name: string
    image: string
    config?: [...#Config]
}
output: {
    ...
    spec: {
        containers: [{
            name:  parameter.name
            image: parameter.image
            if parameter.config != _|_ {
                config: parameter.config
            }
        }]
    }
    ...
}
  • 使用运算符 & 来运算两个变量。
代码语言:javascript
复制
a: *1 | int
b: 3
c: a & b

保存上述内容到 third.cue 文件。

你可以使用 cue eval 来验证结果:

代码语言:javascript
复制
$ cue eval third.cue
a: 1
b: 3
c: 3
  • 需要执行条件判断。当你执行一些级联操作时,不同的值会影响不同的结果,条件判断就非常有用。因此,你可以在模版中执行 if..else 的逻辑。
代码语言:javascript
复制
price: number
feel: *"good" | string
// Feel bad if price is too high
if price > 100 {
    feel: "bad"
}
price: 200

保存上述内容到 fourth.cue 文件。

你可以使用 cue eval 来验证结果:

代码语言:javascript
复制
$ cue eval fourth.cue
price: 200
feel:  "bad"

另一个示例是将布尔类型作为参数。

代码语言:javascript
复制
parameter: {
    name:   string
    image:  string
    useENV: bool
}
output: {
    ...
    spec: {
        containers: [{
            name:  parameter.name
            image: parameter.image
            if parameter.useENV == true {
                env: [{name: "my-env", value: "my-value"}]
            }
        }]
    }
    ...
}

使用 For 循环。我们为了避免减少重复代码,常常使用 For 循环。

  • 映射遍历。
代码语言:javascript
复制
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
                },
            ]
        }]
    }
}

  • 切片遍历。
代码语言:javascript
复制
parameter: {
    name:  string
    image: string
    env: [...{name:string,value:string}]
}
output: {
  ...
     spec: {
        containers: [{
            name:  parameter.name
            image: parameter.image
            env: [
                for _, v in parameter.env {
                    name:  v.name
                    value: v.value
                },
            ]
        }]
    }
}

另外,可以使用 "\( _my-statement_ )" 进行字符串内部计算,比如上面类型循环示例中,获取值的长度等等操作。

导入包

比如,使用 strings.Join 方法将字符串数组拼接成字符串。

代码语言:javascript
复制
import ("strings")

parameter: {
	outputs: [{ip: "1.1.1.1", hostname: "xxx.com"}, {ip: "2.2.2.2", hostname: "yyy.com"}]
}
output: {
	spec: {
		if len(parameter.outputs) > 0 {
			_x: [ for _, v in parameter.outputs {
				"\(v.ip) \(v.hostname)"
			}]
			message: "Visiting URL: " + strings.Join(_x, "")
		}
	}
}

你可以在 CUE 模版中通过 kube/<apiVersion> 导入 kubernetes 的包,就像使用 CUE 内部包一样。

比如,Deployment 可以这样使用:

代码语言:javascript
复制
import (
   apps "kube/apps/v1"
)

parameter: {
    name:  string
}

output: apps.#Deployment
output: {
    metadata: name: parameter.name
}

Service 可以这样使用(无需使用别名导入软件包):

代码语言:javascript
复制
import ("kube/v1")

output: v1.#Service
output: {
	metadata: {
		"name": parameter.name
	}
	spec: type: "ClusterIP",
}

parameter: {
	name:  "myapp"
}

甚至已经安装的 CRD 也可以导入使用:

代码语言:javascript
复制
import (
  oam  "kube/core.oam.dev/v1alpha2"
)

output: oam.#Application
output: {
 metadata: {
  "name": parameter.name
 }
}

parameter: {
 name:  "myapp"
}

参考文档

  • https://cuelang.org/docs/
  • https://kubevela.io/docs/platform-engineers/cue/basic
  • https://cuetorials.com
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 k8s技术圈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 安装
  • CUE 命令行
  • 数据类型
  • CUE 模板
  • 其他用法
  • 导入包
  • 参考文档
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档