§ A12 · Track A · CUE from zero
Conditions and strings
Include a field only when a condition holds. Build strings from values. See why a condition needs a concrete value.
On this page
HCL says "only in prod" with a ternary. A resource that exists only in prod takes a count of one or zero.
resource "aws_kms_key" "logs" {
count = var.env == "prod" ? 1 : 0
}
locals {
retention = var.env == "prod" ? 90 : 7
}
A nested block that exists only in prod needs a dynamic block over a list of one or zero items.
In CUE, an if inside a struct includes the fields in its braces when the condition is true. When the condition is false, the fields are not there at all.
Warm-up
From earlier lessons
One line sets retention to 7 and a second line sets it to 90. What does pravo eval print?
An error that names both values. Two different concrete values cannot both hold (A3).
A field offers *7 | int, and a second line sets it to 90. What does pravo eval print?
The value 90. A default gives way to any value that matches the other choice (A6).
Include a field only in prod
Work in an empty directory. Write estate.cue. The env field is the tag from A6.
package estate
env: *"dev" | "staging" | "prod" @tag(env)
bucket: {
name: "estate-logs-\(env)"
retention: *7 | int
if env == "prod" {
retention: 90
kms_key: "alias/estate-logs"
}
}
env: "dev"
bucket: {
name: "estate-logs-dev"
retention: 7
}
env: "prod"
bucket: {
name: "estate-logs-prod"
retention: 90
kms_key: "alias/estate-logs"
}
In dev the if adds nothing, and the default keeps retention at 7. In prod the if adds kms_key and sets retention to 90.
The condition env == "prod" is a comparison. You can also write !=, <, >, &&, || and !. There is no else. To cover the other case, write a second if with the opposite condition.
Break it
A condition adds fields. It never replaces a value. Give retention a plain 7 instead of a default.
package estate
env: *"dev" | "staging" | "prod" @tag(env)
bucket: {
name: "estate-logs-\(env)"
retention: 7
if env == "prod" {
retention: 90
kms_key: "alias/estate-logs"
}
}
env: "dev"
bucket: {
name: "estate-logs-dev"
retention: 7
}
Error: evaluating .:
bucket.retention: conflicting values 90 and 7:
./estate.cue:5:9
./estate.cue:7:13
./estate.cue:8:2
./estate.cue:9:14
dev works, because the if is not included. In prod the if adds 90 to a field that already holds 7. That is the conflict from A3. Read the error from the top.
- The path is
bucket.retention. - The values are
90and7. - The four locations are line 5 (the struct), line 7 (
7), line 8 (theif) and line 9 (90).
Restore the default, so the value 7 can give way.
package estate
env: *"dev" | "staging" | "prod" @tag(env)
bucket: {
name: "estate-logs-\(env)"
retention: *7 | int
if env == "prod" {
retention: 90
kms_key: "alias/estate-logs"
}
}
Build strings from values
A string can contain \( and ). The expression between them is replaced by its value. This is interpolation. You have used it since A11.
package estate
region: "eu-west-1"
port: 8443
url: "https://\(env).example.com:\(port + 1)"
arn: "arn:aws:s3:::\(bucket.name)"
note: """
bucket: \(bucket.name)
region: \(region)
"""
"https://dev.example.com:8444"
"arn:aws:s3:::estate-logs-dev"
"""
bucket: estate-logs-dev
region: eu-west-1
"""
The expression can be a field, a sum or any other value. Numbers and booleans become text. Three quotes open a string that spans several lines. Interpolation works inside it.
Choose a value by name
An if adds fields. To choose one value out of several, index a struct by the key.
package estate
days: {dev: 7, staging: 30, prod: 90}[env]
7
30
An if suits a field that may be absent. A lookup suits a value that always exists and changes.
Remove strings.cue. The rest of the lesson uses estate.cue only.
A condition needs a concrete value
An if can only decide when its condition is concrete. Remove the tag and leave env as a type.
package estate
env: string
bucket: {
name: "estate-logs-\(env)"
retention: *7 | int
if env == "prod" {
retention: 90
kms_key: "alias/estate-logs"
}
}
env: string
bucket: {
name: "estate-logs-\(env)"
retention: *7 | int
if env == "prod" {
retention: 90
kms_key: "alias/estate-logs"
}
}
pravo eval does not fail. It prints the if as it is written, because env is still a type. This is the difference between a type and a value from A2. The flag -c makes the gap an error.
Error: the value is not concrete:
env: incomplete value string:
./estate.cue:3:6
bucket: non-concrete value string in operand to ==:
./estate.cue:8:5
./estate.cue:3:6
The first error says env is incomplete: it is still string. The second says the if cannot compare a string with "prod". Fix both by giving env a value: a default, a tag or a line in a file.
Exercise
Two conditions
Restore the tag. Then change estate.cue so that these three runs print what is listed.
| Command | bucket.retention |
bucket.kms_key |
|---|---|---|
pravo eval |
7 | absent |
pravo eval -t env=staging |
30 | absent |
pravo eval -t env=prod |
90 | "alias/estate-logs" |
package estate
env: *"dev" | "staging" | "prod" @tag(env)
bucket: {
name: "estate-logs-\(env)"
retention: *7 | int
}
The check runs each command of the table. A field that is absent is an error: pravo eval -e bucket.kms_key fails in dev.
want() { [ "$(pravo eval $1 2>/dev/null)" = "$2" ] || { echo "pravo eval $1 should print $2"; exit 1; }; }
absent() { if pravo eval $1 > /dev/null 2>&1; then echo "pravo eval $1 should fail"; exit 1; fi; }
want "-e bucket.retention" 7
want "-t env=staging -e bucket.retention" 30
want "-t env=prod -e bucket.retention" 90
want "-t env=prod -e bucket.kms_key" '"alias/estate-logs"'
absent "-e bucket.kms_key"
absent "-t env=staging -e bucket.kms_key"
echo ok
Run it on the start state. It fails.
pravo eval -t env=staging -e bucket.retention should print 30
Hint 1
Write one if for staging and one for prod. Put kms_key only in the second.
Hint 2
Compare the first example on this page. The default *7 | int lets both conditions set a different value.
Solution
package estate
env: *"dev" | "staging" | "prod" @tag(env)
bucket: {
name: "estate-logs-\(env)"
retention: *7 | int
if env == "staging" {
retention: 30
}
if env == "prod" {
retention: 90
kms_key: "alias/estate-logs"
}
}
env: "staging"
bucket: {
name: "estate-logs-staging"
retention: 30
}
"alias/estate-logs"
Recap
- An
ifincludes its fields when the condition is true. There is noelse. - A condition adds to a field and never replaces it. Use a default so a later value can win.
- A condition needs a concrete value.
pravo eval -cfinds the ones that are still types.
Check yourself
What does retention: 7 plus if env == "prod" { retention: 90 } do in prod?
It fails with conflicting values 90 and 7. Make the first value a default: *7 | int.
Why does pravo eval succeed when env is string, and pravo eval -c fail?
Without -c, an undecided if is printed as written. The flag -c requires every value to be concrete.
The next lesson imports the standard library and shares a definition between directories.