mirror of
https://github.com/kubevela/kubevela.git
synced 2026-05-24 18:23:11 +00:00
402 lines
11 KiB
Markdown
402 lines
11 KiB
Markdown
# CUE Basic
|
|
|
|
KubeVela use [CUE](https://cuelang.org/) as its template DSL. With the help of CUE, we can define simple but powerful
|
|
template in Definition Objects and build abstraction for applications.
|
|
|
|
## Why CUE ?
|
|
|
|
Why does KubeVela choose CUE? [Points of Cedric Charly](https://blog.cedriccharly.com/post/20191109-the-configuration-complexity-curse/) speaks well,
|
|
let me conclude here.
|
|
|
|
* **CUE is designed for large scale configuration.**
|
|
CUE deliberately opts for the graph unification model used in computational linguistics instead of the traditional
|
|
inheritance model. The graph model can help KubeVela to build a clear view of resources' relationships and dependency.
|
|
For large scale infrastructure, that will be a complex, tightly interconnected graph of
|
|
resources that describes an organization's entire computing environment. In this case, CUE has the ability to understand a
|
|
configuration worked on by engineers across a whole company and to safely change a value that modifies thousands of
|
|
objects in a configuration.
|
|
* **CUE supports first-class code generation and automation.**
|
|
A design goal of CUE is to have code that is straightfoward for humans to write, but is also simple for machines to
|
|
generate. This goal highly consistent with KubeVela which wants to offer abstractions to bridge the gap between concepts
|
|
used by app developers and kubernetes. CUE can integrate with existing tools and workflows naturally while other tools
|
|
would have to build complex custom solutions. CUE can generate Kubernetes definitions from Go code and OpenAPI schemas
|
|
and immediately work with resources directly or build higher level libraries.
|
|
* **CUE integrates very well with Go.**
|
|
KubeVela is built with GO just like most projects of the while Kubernetes system. CUE is also implemented in and
|
|
exposes a rich client API in Go. KubeVela integrates with CUE as its core library and works as a Kubernetes CRD controller.
|
|
With the help of CUE, KubeVela can easily handle data constraint problems.
|
|
|
|
If you want to go deeper I recommend reading [The Logic of CUE](https://cuelang.org/docs/concepts/logic/)
|
|
to understand the theoretical foundation and what makes CUE different from other configuration languages.
|
|
|
|
## CUE in KubeVela
|
|
|
|
Let's go back to discuss how does CUE be used in KubeVela. As you know, KubeVela helps platform builder to build abstraction
|
|
from Kubernetes resources to an application. We will use a [K8s Deployment](https://kubernetes.io/docs/concepts/workloads/controllers/deployment/)
|
|
as example to explain how was it implemented.
|
|
|
|
In KubeVela we usually build a WorkloadDefinition to generate K8s Deployment as it's workload-like resources.
|
|
A complete WorkloadDefinition example like below:
|
|
|
|
```yaml
|
|
apiVersion: core.oam.dev/v1alpha2
|
|
kind: WorkloadDefinition
|
|
metadata:
|
|
name: mydeploy
|
|
spec:
|
|
definitionRef:
|
|
name: deployments.apps
|
|
template: |
|
|
parameter: {
|
|
name: string
|
|
image: string
|
|
}
|
|
output: {
|
|
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
|
|
}]
|
|
}}}
|
|
}
|
|
```
|
|
|
|
In this example, the `template` field is totally CUE, it contains two keywords, `output` and `parameter`.
|
|
The `output` defines what will be rendering out by the template. The `parameter` defines the input parameters which can
|
|
be part of the application.
|
|
|
|
Let's try to write the CUE template step by step.
|
|
|
|
As you see, below is a Deployment YAML, most of the fields can be hidden and only expose `image` and `env` field for end user.
|
|
|
|
```yaml
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
meadata:
|
|
name: mytest
|
|
spec:
|
|
template:
|
|
spec:
|
|
containers:
|
|
- name: mytest
|
|
env:
|
|
- name: a
|
|
value: b
|
|
image: nginx:v1
|
|
metadata:
|
|
labels:
|
|
app.oam.dev/component: mytest
|
|
selector:
|
|
matchLabels:
|
|
app.oam.dev/component: mytest
|
|
```
|
|
|
|
The first step is to convert the YAML to JSON and put the whole json object into the `output` keyword field.
|
|
CUE is a superset of JSON: any valid JSON file is a valid CUE file. It provides some conveniences such as you can omit
|
|
some quotes from field names without special characters.
|
|
|
|
Here is the converted result:
|
|
|
|
```cue
|
|
output: {
|
|
apiVersion: "apps/v1"
|
|
kind: "Deployment"
|
|
metadata: name: "mytest"
|
|
spec: {
|
|
selector: matchLabels: {
|
|
"app.oam.dev/component": "mytest"
|
|
}
|
|
template: {
|
|
metadata: labels: {
|
|
"app.oam.dev/component": "mytest"
|
|
}
|
|
spec: {
|
|
containers: [{
|
|
name: "mytest"
|
|
image: "nginx:v1"
|
|
env: [{name:"a",value:"b"}]
|
|
}]
|
|
}}}
|
|
}
|
|
```
|
|
|
|
Here are all conveniences add by CUE as a superset of JSON:
|
|
|
|
* C-style comments,
|
|
* quotes may be omitted from field names without special characters,
|
|
* commas at the end of fields are optional,
|
|
* comma after last element in list is allowed,
|
|
* outer curly braces are optional.
|
|
|
|
After that we add `parameter` keyword into the template, and use it as a variable reference, this is basic CUE grammar.
|
|
|
|
Fields of keyword `parameter` will be detected by KubeVela and be exposed to users using in application.
|
|
|
|
```cue
|
|
parameter: {
|
|
name: string
|
|
image: string
|
|
}
|
|
output: {
|
|
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
|
|
}]
|
|
}}}
|
|
}
|
|
```
|
|
|
|
Finally, you can put the whole CUE template into the `template` field of WorkloadDefinition object. That's all you need
|
|
to know to create a basic KubeVela capability.
|
|
|
|
## More Advanced Usage of CUE Grammar in Definition
|
|
|
|
In this section, we will introduce some more advanced CUE grammar to use in KubeVela.
|
|
|
|
### Structural parameter
|
|
|
|
If you have some complex type of parameters in your template, and want to define a struct or embed struct as parameters,
|
|
then you could use structural parameter.
|
|
|
|
1. Define a struct type in, it includes a struct, a string and an integer.
|
|
```
|
|
#Config: {
|
|
name: string
|
|
value: int
|
|
other: {
|
|
key: string
|
|
value: string
|
|
}
|
|
}
|
|
```
|
|
|
|
2. Use the struct defined in the `parameter` keyword, and use it as an array list.
|
|
```
|
|
parameter: {
|
|
name: string
|
|
image: string
|
|
configSingle: #Config
|
|
config: [...#Config]
|
|
}
|
|
```
|
|
|
|
3. In `output` keyword, it's referenced the same way with other normal field s.
|
|
```
|
|
output: {
|
|
...
|
|
spec: {
|
|
containers: [{
|
|
name: parameter.name
|
|
image: parameter.image
|
|
env: parameter.config
|
|
}]
|
|
}
|
|
...
|
|
}
|
|
```
|
|
|
|
4. The structural field `config` can be easily used in Application like below:
|
|
```
|
|
apiVersion: core.oam.dev/v1alpha2
|
|
kind: Application
|
|
metadata:
|
|
name: website
|
|
spec:
|
|
components:
|
|
- name: backend
|
|
type: mydeploy
|
|
settings:
|
|
image: crccheck/hello-world
|
|
name: mysvc
|
|
config:
|
|
- name: a
|
|
value: 1
|
|
other:
|
|
key: mykey
|
|
value: myvalue
|
|
```
|
|
|
|
### Conditional Parameter
|
|
|
|
Conditional parameter can be used to decide template condition logic.
|
|
Below is an example that when `useENV=true`, it will render env section, otherwise, it will not.
|
|
|
|
```
|
|
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"}]
|
|
}
|
|
}]
|
|
}
|
|
...
|
|
}
|
|
```
|
|
|
|
### Optional Parameter and Default Value
|
|
|
|
Optional parameter can be optional, that usually works with conditional logic. If some field does not exit, the CUE
|
|
grammar is `if _variable_ != _|_`, the example is like below:
|
|
|
|
```
|
|
parameter: {
|
|
name: string
|
|
image: string
|
|
config?: [...#Config]
|
|
}
|
|
output: {
|
|
...
|
|
spec: {
|
|
containers: [{
|
|
name: parameter.name
|
|
image: parameter.image
|
|
if parameter.config != _|_ {
|
|
config: parameter.config
|
|
}
|
|
}]
|
|
}
|
|
...
|
|
}
|
|
```
|
|
|
|
Default Value is marked with a `*` prefix. It's used like
|
|
|
|
```
|
|
parameter: {
|
|
name: string
|
|
image: *"nginx:v1" | string
|
|
port: *80 | int
|
|
number: *123.4 | float
|
|
}
|
|
output: {
|
|
...
|
|
spec: {
|
|
containers: [{
|
|
name: parameter.name
|
|
image: parameter.image
|
|
}]
|
|
}
|
|
...
|
|
}
|
|
```
|
|
|
|
So if a parameter field is neither a parameter with default value nor a conditional field, it's a required value.
|
|
|
|
### Loop
|
|
|
|
#### Loop for map type
|
|
|
|
```cue
|
|
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
|
|
},
|
|
]
|
|
}]
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Loop for slice
|
|
|
|
```cue
|
|
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
|
|
},
|
|
]
|
|
}]
|
|
}
|
|
}
|
|
```
|
|
|
|
### Import CUE internal packages
|
|
|
|
CUE has [lots of internal packages](https://pkg.go.dev/cuelang.org/go@v0.2.2/pkg) which also can be used in KubeVela.
|
|
|
|
Below is an example that use `strings.Join` to concat string list to one string.
|
|
|
|
```cue
|
|
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, "")
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|