§ Learn · Track A · Companions
CUE cheat sheet
Every construct of Track A on one page. Each has a short example and the output that pravo prints.
On this page
Each section holds one file and the output of pravo eval. Work in an empty directory. Each file replaces the previous estate.cue. The lessons explain the reasons. This page only shows the shapes.
Symbols
| Write | Name | Lesson |
|---|---|---|
a: 1 |
field | A1 |
a: b: 1 |
nested fields in one line | A1 |
int, string, _ |
a type is a value | A2 |
a: int then a: 3 |
unification | A3 |
& |
combine two values | A5 |
>=1, <10, !=0 |
bound | A5 |
=~"re", !~"re" |
regular expression match | A5 |
a | b |
disjunction, a choice | A6 |
*a | b |
default | A6 |
@tag(env) |
tag, set with -t env=prod |
A6 |
a!: int |
required field | A7 |
a?: int |
optional field | A7 |
a: int |
regular field | A7 |
#Name: {...} |
definition, a closed shape | A8 |
{a: int, ...} |
an open struct | A8 |
{#Other, a: int} |
embedding | A8 |
[string]: T |
pattern | A9 |
for k, v in x {...} |
comprehension | A10 |
a: b |
reference | A11 |
let x = 1 |
let | A11 |
_x: 1 |
hidden field | A11 |
if c {...} |
a condition | A12 |
"\(x)" |
interpolation | A12 |
package p, import "x" |
package and import | A13 |
Data
A struct holds fields. A list holds values. A string, a number, a bool and null are the scalars. A1
package estate
tags: {owner: "platform"} // a struct
ports: [80, 443] // a list
a: b: c: 1 // nested fields in one line
who: tags.owner // select a field
first: ports[0] // index a list
name: "logs" // a string
size: 3 // a number
ratio: 0.5
ready: true // a bool
none: null
tags: {
owner: "platform"
}
ports: [80, 443]
a: {
b: {
c: 1
}
}
who: "platform"
first: 80
name: "logs"
size: 3
ratio: 0.5
ready: true
none: null
Types and constraints
A type is a value. & combines values. A bound or a match is a constraint that narrows a type. A2, A5
package estate
count: int // a type is a value
label: string
ready: bool
price: number
hosts: [...string] & ["a", "b"] // a list of strings
anything: _ // any value
port: int & >=1 & <=65535 & 8080 // bounds narrow a type
code: =~"^[a-z]{3}$" & "abc" // a match narrows a string
text: !~"^[0-9]+$" & "abc" // a non-match
rest: !=0 & 7 // an inequality
count: int
label: string
ready: bool
price: number
hosts: ["a", "b"]
anything: _
port: 8080
code: "abc"
text: "abc"
rest: 7
A field that holds a type is not concrete. It is incomplete until a value narrows it.
Writing a field twice
Copies of a field combine. The more specific value wins. Structs merge. Order and file do not matter. A3, A4
package estate
size: int // copies combine
size: 3 // the more specific value wins
tags: owner: "platform" // structs merge
tags: team: "infra"
size: 3
tags: {
owner: "platform"
team: "infra"
}
Two different concrete values are a conflict. The result is bottom: no value satisfies both, and pravo prints an error.
Choices and defaults
| offers choices. * marks the default. @tag takes its value from the command line. A6
package estate
class: "standard" | "ia" // a choice, undecided
tier: *"standard" | "ia" // a choice with a default
env: *"dev" | "prod" @tag(env) // a tag
class: "standard" | "ia"
tier: "standard"
env: "dev"
"prod"
Required, optional, regular
A required field must be supplied. An optional field may stay unset. A regular field is a field. A7
package estate
bucket: {
name: string // regular
owner!: string // required
note?: string // optional
}
bucket: {
name: string
owner!: string
}
bucket: {
name: string
owner!: string
note?: string
}
Definitions
A definition starts with #. It is closed: a value built from it may hold only declared fields. ... opens it. Embedding includes the fields of another definition. A8
package estate
#Bucket: {name!: string, versioning: *false | bool} // a closed shape
logs: #Bucket & {name: "logs"}
#Loose: {name: string, ...} // an open shape
extra: #Loose & {name: "x", anything: 1}
#Tagged: {owner!: string}
#Owned: {#Tagged, name!: string} // embedding
owned: #Owned & {name: "n", owner: "me"}
logs: {
name: "logs"
versioning: false
}
extra: {
name: "x"
anything: 1
}
owned: {
name: "n"
owner: "me"
}
pravo eval omits definitions. Name one with -e '#Bucket'.
Patterns
A pattern applies a rule to every field of a struct, now or added later. Name= names the key. A9
package estate
buckets: [string]: {size: *1 | int} // a rule for every entry
buckets: logs: {}
nodes: [Name=string]: {name: Name} // Name stands for the key
nodes: web: {}
labels: [string]: string // a map of strings
labels: owner: "platform"
buckets: {
logs: {
size: 1
}
}
nodes: {
web: {
name: "web"
}
}
labels: {
owner: "platform"
}
A pattern constrains entries that exist. It does not create them.
Comprehensions
for builds fields or list items from other data. if filters. A10
package estate
zones: {a: 1, b: 2}
subnets: {for z, n in zones {"net-\(z)": n * 10}} // fields from a struct
doubled: [for n in [1, 2, 3] {n * 2}] // a list from a list
big: [for n in [1, 2, 3] if n > 1 {n}] // with a filter
zones: {
a: 1
b: 2
}
subnets: {
"net-a": 10
"net-b": 20
}
doubled: [2, 4, 6]
big: [2, 3]
The key you build is the identity of the entry. Choose keys that do not move.
Names
A field reads another by name. A let and a hidden field name a helper and stay out of the output. A11
package estate
base: "10.0"
cidr: "\(base).0.0/16" // a reference
let prefix = "estate" // a let: not printed
_zone: "eu-west-1" // a hidden field: not printed
name: "\(prefix)-logs"
region: _zone
base: "10.0"
cidr: "10.0.0.0/16"
name: "estate-logs"
region: "eu-west-1"
A field must not share a name with the value it reads. region: region refers to itself.
Conditions and strings
if includes fields when its condition is true. There is no else. \( ) puts a value into a string. A struct indexed by a key looks a value up. A12
package estate
env: *"dev" | "prod" @tag(env)
retention: *7 | int
if env == "prod" {retention: 90} // a condition
name: "estate-\(env)" // interpolation
full: name + "-bucket" // join strings
days: {dev: 7, prod: 90}[env] // a lookup
note: """
one
two
"""
env: "dev"
retention: 7
name: "estate-dev"
full: "estate-dev-bucket"
days: 7
note: """
one
two
"""
retention: 90
env: "prod"
name: "estate-prod"
full: "estate-prod-bucket"
days: 90
note: """
one
two
"""
Packages and the standard library
A directory is a package. import brings in another one. Validators join a value with &. Functions compute a value. A13
package estate
import (
"encoding/json"
"list"
"net"
"strings"
)
cidr: net.IPCIDR & "10.0.1.0/24" // a validator
zones: list.MinItems(1) & ["a"] // a validator
up: strings.ToUpper("logs") // a function
doc: json.Marshal({a: 1})
n: len(zones)
cidr: "10.0.1.0/24"
zones: ["a"]
up: "LOGS"
doc: "{\"a\":1}"
n: 1
Your own package is imported by module path and directory.
import "estate.example/platform"
subnet: platform.#Subnet & {zone: "a", cidr: "10.0.1.0/24"}
Commands
| Command | Does |
|---|---|
pravo eval |
Prints the evaluated specification. |
pravo eval -e path |
Prints one path or expression. |
pravo eval -c |
Fails unless every value is concrete. |
pravo eval -O |
Also prints optional fields. |
pravo eval -t env=prod |
Sets a tag. |
pravo validate |
Checks the specification against provider schemas. |
pravo fmt |
Rewrites the .cue files of the directory in the standard layout. |
C4 explains each flag.
package estate
size:int
tags: {owner:"platform",team:"infra"}
name: "logs"
estate.cue
package estate
size: int
tags: {owner: "platform", team: "infra"}
name: "logs"
Errors
An error names the path, the reason and the places that took part. C1 reads one line by line. The error catalogue lists the errors of pravo itself.
| Message | Meaning | Lesson |
|---|---|---|
conflicting values 4 and 3 |
Two concrete values for one field. | A3 |
invalid value 70000 (out of bound <=65535) |
A value outside a bound. | A5 |
3 errors in empty disjunction |
No choice matches the value. | A6 |
field is required but not present |
A field marked ! has no value. |
A7 |
field not allowed |
A closed definition has no such field. | A8 |
reference "bse" not found |
No field or let of that name is in reach. |
A11 |
unreferenced alias or let clause x |
A let that nothing reads. |
A11 |
non-concrete value string in operand to == |
A condition on a value that is still a type. | A12 |
imported and not used: "strings" |
An import that nothing uses. | A13 |
does not satisfy net.IPCIDR |
A validator refused the value. | A13 |
incomplete value string |
With -c: the field is still a type. |
C2 |
no tag for "stage" |
-t names a tag that no field declares. |
C4 |