kubevela.github.io/docs/platform-engineers/cue/basic.md

586 lines
13 KiB
Markdown

---
title: CUE Basic
---
In this section, we will explain more about how KubeVela use CUE to encapsulate and abstract a given capability in Kubernetes in detail.
We'll start from some basic CUE knowledge first, if you don't have any idea of [KubeVela Concept](../../getting-started/core-concept.md), don't worry! This doc is available for anyone who want to learn how to use CUE in practice quickly.
## Overview
The reasons for KubeVela supports CUE as a first-class solution to design abstraction can be concluded as below:
- **CUE is designed for large scale configuration.** 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. This aligns very well with KubeVela's original goal to define and ship production level applications at web scale.
- **CUE supports first-class code generation and automation.** CUE can integrate with existing tools and workflows naturally while other tools would have to build complex custom solutions. For example, generate OpenAPI schemas with Go code. This is how KubeVela build developer tools and GUI interfaces based on the CUE templates.
- **CUE integrates very well with Go.**
KubeVela is built with GO just like most projects in Kubernetes system. CUE is also implemented in and exposes a rich API in Go. KubeVela integrates with CUE as its core library and works as a Kubernetes controller. With the help of CUE, KubeVela can easily handle data constraint problems.
> Pleas also check [The Configuration Complexity Curse](https://blog.cedriccharly.com/post/20191109-the-configuration-complexity-curse/) and [The Logic of CUE](https://cuelang.org/docs/concepts/logic/) for more details.
## Prerequisites
Please make sure below CLIs are present in your environment:
* [`cue` v0.2.2+](https://cuelang.org/docs/install/).
* [`vela` v1.0.0+](../../install.mdx).
:::caution
Versions prior to KubeVela 1.6.0 use the v0.2.2 version of CUE, KubeVela 1.6.0+ uses the v0.5.0-alpha.1 version of CUE. Please use the CUE tool that matches your KubeVela version.
:::
## CUE CLI Basic
Below is the basic CUE data, you can define both schema and value in the same file with the almost same format:
```cue
a: 1.5
a: float
b: 1
b: int
d: [1, 2, 3]
g: {
h: "abc"
}
e: string
```
CUE is a superset of JSON, we can use it like json with following convenience:
* 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.
CUE has powerful CLI commands. Let's keep the data in a file named `first.cue` and try.
* Format the CUE file. If you're using Goland or similar JetBrains IDE,
you can [configure save on format](https://wonderflow.info/posts/2020-11-02-goland-cuelang-format/) instead.
This command will not only format the CUE, but also point out the wrong schema. That's very useful.
```shell
cue fmt first.cue
```
* Schema Check, besides `cue fmt`, you can also use `cue vet` to check schema.
```shell
cue vet first.cue
```
* Calculate/Render the result. `cue eval` will calculate the CUE file and render out the result.
You can see the results don't contain `a: float` and `b: int`, because these two variables are calculated.
While the `e: string` doesn't have definitive results, so it keeps as it is.
```shell
cue eval first.cue
```
```cue
a: 1.5
b: 1
d: [1, 2, 3]
g: {
h: "abc"
}
e: string
```
* Render for specified result. For example, we want only know the result of `b` in the file, then we can specify the parameter `-e`.
```shell
cue eval -e b first.cue
```
```console
1
```
* Export the result. `cue export` will export the result with final value. It will report an error if some variables are not definitive.
```shell
cue export first.cue
```
```console
e: cannot convert incomplete value "string" to JSON:
./first.cue:9:4
```
We can complete the value by giving a value to `e`, for example:
```shell
echo "e: \"abc\"" >> first.cue
```
Then, the command will work. By default, the result will be rendered in json format.
```shell
cue export first.cue
```
```json
{
"a": 1.5,
"b": 1,
"d": [
1,
2,
3
],
"g": {
"h": "abc"
},
"e": "abc"
}
```
* Export the result in YAML format.
```shell
cue export first.cue --out yaml
```
```yaml
a: 1.5
b: 1
d:
- 1
- 2
- 3
g:
h: abc
e: abc
```
* Export the result for specified variable.
```shell
cue export -e g first.cue
```
```cue
{
"h": "abc"
}
```
For now, you have learned all useful CUE cli operations.
## CUE Language Basic
* Data structure: Below is the basic data structure of CUE.
```cue
// 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"
}
}
// byte
g: `byte`
// null
j: null
```
* Define a custom CUE type. You can use a `#` symbol to specify some variable represents a CUE type.
```
#abc: string
```
Let's name it `second.cue`. Then the `cue export` won't complain as the `#abc` is a type not incomplete value.
```shell
cue export second.cue
```
```json
{}
```
You can also define a more complex custom struct, such as:
```cue
#abc: {
x: int
y: string
z: {
a: float
b: bool
}
}
```
It's widely used in KubeVela to define templates and do validation.
## CUE Templating and References
Let's try to define a CUE template with the knowledge just learned.
1. Define a struct variable `parameter`.
```cue
parameter: {
name: string
image: string
}
```
Let's save it in a file called `deployment.cue`.
2. Define a more complex struct variable `template` and reference the variable `parameter`.
```cue
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
}]
}}}
}
```
People who are familiar with Kubernetes may have understood that is a template of K8s Deployment. The `parameter` part
is the parameters of the template.
Add it into the `deployment.cue`.
3. Then, let's add the value by adding following code block:
```cue
parameter:{
name: "mytest"
image: "nginx:v1"
}
```
4. Finally, let's export it in yaml:
```shell
cue export deployment.cue -e template --out yaml
```
```yaml
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: mytest
image: nginx:v1
metadata:
labels:
app.oam.dev/component: mytest
selector:
matchLabels:
app.oam.dev/component: mytest
```
## Advanced CUE Schematic
* Using the `(key)` to use the value of the object as the new object name.
```
hello: "world"
(hello): "world2"
"my-\(hello)": "world3"
```
:::caution
In CUE v0.2.2, you need to use `"\(hello)"` to refer to objects. In high versions of CUE, it can be simplified to `(hello)`.
:::
* Open struct and list. Using `...` in a list or struct means the object is open.
- A list like `[...string]` means it can hold multiple string elements.
If we don't add `...`, then `[string]` means the list can only have one `string` element in it.
- A struct like below means the struct can contain unknown fields.
```cue
{
abc: string
...
}
```
* Operator `|`, it represents a value could be both case. Below is an example that the variable `a` could be in string or int type.
```cue
a: string | int
```
* Default Value, we can use `*` symbol to represent a default value for variable. That's usually used with `|`,
which represents a default value for some type. Below is an example that variable `a` is `int` and it's default value is `1`.
```cue
a: *1 | int
```
* Optional Variable. In some cases, a variable could not be used, they're optional variables, we can use `?:` to define it.
In the below example, `a` is an optional variable, `x` and `z` in `#my` is optional while `y` is a required variable.
```cue
a ?: int
#my: {
x ?: string
y : int
z ?:float
}
```
:::caution
Note that `?:` and `*` cannot be used at the same time. If a variable has been specified as an optional variable, its default value will no longer take effect.
:::
Optional variables can be skipped, that usually works together with conditional logic.
Specifically, if some field does not exist, the CUE grammar is `if _variable_ != _|_`, the example is like below:
```cue
#Config: {
name: string
value: string
}
parameter: {
name: string
image: string
config?: [...#Config]
}
output: {
...
spec: {
containers: [{
name: parameter.name
image: parameter.image
if parameter.config != _|_ {
config: parameter.config
}
}]
}
...
}
```
* Operator `&`, it used to calculate two variables.
```cue
a: *1 | int
b: 3
c: a & b
```
Saving it in `third.cue` file.
You can evaluate the result by using `cue eval`:
```shell
cue eval third.cue
```
```cue
a: 1
b: 3
c: 3
```
* Conditional statement, it's really useful when you have some cascade operations that different value affects different results.
So you can do `if..else` logic in the template.
```cue
price: number
feel: *"good" | string
// Feel bad if price is too high
if price > 100 {
feel: "bad"
}
price: 200
```
Saving it in `fourth.cue` file.
You can evaluate the result by using `cue eval`:
```shell
cue eval fourth.cue
```
```cue
price: 200
feel: "bad"
```
Another example is to use bool type as parameter.
```cue
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 Loop: if you want to avoid duplicate, you may want to use for loop.
- Loop for Map
```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 type
```cue
#a: {
"hello": "Barcelona"
"nihao": "Shanghai"
}
for k, v in #a {
"\(k)": {
nameLen: len(v)
value: v
}
}
```
Note that we use `"\( _my-statement_ )"` for inner calculation in string, it's very useful!
- 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
},
]
}]
}
}
```
- If clause in Loop {}
```cue
parameter: [
{
name: "empty"
}, {
name: "xx1"
},
]
dataFrom: [ for _, v in parameter {
if v.name != "empty" {
name: v.name
}
}]
```
the result is:
```console
cue eval ../blog/a.cue -e dataFrom
[{}, {
name: "xx1"
}]
```
- If clause in Loop with condition
```cue
parameter: [
{
name: "empty"
}, {
name: "xx1"
},
]
dataFrom: [ for _, v in parameter if v.name != "empty" {
name: v.name
}]
```
the result is:
```console
cue eval ../blog/a.cue -e dataFrom
[{
name: "xx1"
}]
```
## Import CUE Internal Packages
CUE has many [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:\n" + strings.Join(_x, "\n")
}
}
}
```
Great, now you've learned all of the basic skills and best practices about how to use CUE!
If you want to learn more details of CUE, you can refer to [the CUE official documentation](https://cuelang.org/) or [the well documented tutorials](https://cuetorials.com/introduction/).
In the following sections, we'll start to learn how KubeVela use CUE to glue resources. Make sure you have learned the [core concepts](../../getting-started/core-concept.md) of KubeVela before that.
## Next
* Learn how to use CUE to extend KubeVela by [Managing Definition](./definition-edit.md).