§ A6 · Track A · CUE from zero
Choices and defaults
Offer a short list of values, mark one as the default, and select it from the command line.
On this page
In HCL, a default and the list of allowed values are two separate statements.
variable "class" {
type = string
default = "standard"
validation {
condition = contains(["standard", "ia", "glacier"], var.class)
error_message = "class must be standard, ia or glacier."
}
}
Change the list and the default does not follow. A lookup(var.bucket, "class", "standard") elsewhere repeats the default a third time.
In CUE, a | between values offers a choice. A * in front of one choice marks the default: the value the field takes when nothing else is written.
Warm-up
From earlier lessons
Two lines give name the values "logs" and "media". What does pravo eval print?
An error that names both values and both lines. A field written twice must agree (A3).
A port is >=1 & <=65535 and a second line sets it to 70000. Is the value accepted?
No. A bound is a constraint, and 70000 is outside it (A5).
Offer a choice with a default
Work in an empty directory. Write estate.cue. The bucket has a class with three choices.
package estate
bucket: {
name: "logs"
class: *"standard" | "ia" | "glacier"
}
bucket: {
name: "logs"
class: "standard"
}
Nothing sets class, so it takes the default, "standard". The other two choices stay allowed. The eight lines of HCL above say what this one line says.
Write the field again with one of the other choices. A field written twice adds a constraint.
package estate
bucket: {
name: "logs"
class: *"standard" | "ia" | "glacier"
}
bucket: class: "ia"
bucket: {
name: "logs"
class: "ia"
}
The new value matches one choice, so the default is not used.
Break it
Write a value that is not one of the choices.
package estate
bucket: {
name: "logs"
class: *"standard" | "ia" | "glacier"
}
bucket: class: "cold"
Error: evaluating .:
bucket.class: 3 errors in empty disjunction:
bucket.class: conflicting values "glacier" and "cold":
./estate.cue:5:30
./estate.cue:8:16
bucket.class: conflicting values "ia" and "cold":
./estate.cue:5:23
./estate.cue:8:16
bucket.class: conflicting values "standard" and "cold":
./estate.cue:5:10
./estate.cue:8:16
Read the error from the top.
- The path is
bucket.class. - "3 errors in empty disjunction" means pravo tested
"cold"against each of the three choices. No choice is left. - Each error pairs one choice with your value.
- Each error gives two locations. Line 5 wrote the choice. Line 8 wrote
"cold".
Take a value from the command line
pravo has no workspaces. Select the environment with -t env=prod.
A tag is a field marked @tag(name). The flag -t name=value supplies its value. Add an env field with three choices and a default.
package estate
env: *"dev" | "staging" | "prod" @tag(env)
bucket: {
name: "logs"
class: *"standard" | "ia" | "glacier"
stage: env
}
stage: env copies the value of env into the bucket. A11 covers this.
env: "dev"
bucket: {
name: "logs"
class: "standard"
stage: "dev"
}
env: "prod"
bucket: {
name: "logs"
class: "standard"
stage: "prod"
}
Without a tag, env takes its default. With -t env=prod, the value is "prod". Now ask for an environment that does not exist.
Error: evaluating .:
env: 3 errors in empty disjunction:
env: conflicting values "dev" and "qa":
./estate.cue:3:7
env: conflicting values "prod" and "qa":
./estate.cue:3:27
env: conflicting values "staging" and "qa":
./estate.cue:3:15
The flag does not skip the rules. -t env=qa is the same as writing env: "qa" in a file. It meets the same three choices, and none matches. The refusal happens during evaluation, before any provider starts.
Two defaults cancel each other
A second file marks a different choice as the default for class. Each file marks one choice with *, and they disagree.
package estate
bucket: class: *"ia" | "standard" | "glacier"
Decide before you open the answer.
Predict Which default wins?
Neither. The two defaults cannot both hold, so no default remains. The field keeps all three choices.
env: "dev"
bucket: {
name: "logs"
class: "standard" | "ia" | "glacier"
stage: "dev"
}
Error: the value is not concrete:
bucket.class: incomplete value "standard" | "ia" | "glacier"
pravo eval succeeds and prints the three choices without a default. The flag -c fails unless every value is concrete. It reports incomplete value, because pravo cannot pick one choice. C2 lists every cause of an incomplete value.
Pitfall
Mark the default once per field, in one place. Every other file writes a plain value or a plain list of choices.
Remove the second file. The default works again.
env: "dev"
bucket: {
name: "logs"
class: "standard"
stage: "dev"
}
Exercise
Add a region
Add a top-level field region to estate.cue. It offers eu-west-1, eu-central-1 and us-east-1. The default is eu-west-1. A tag named region fills it.
The check runs pravo eval -e region with no tag and with -t region=us-east-1. It also runs -t region=ap-south-1, which must be refused.
test "$(pravo eval -e region 2>/dev/null)" = '"eu-west-1"' || { echo "with no tag, region should be eu-west-1"; exit 1; }
test "$(pravo eval -t region=us-east-1 -e region 2>/dev/null)" = '"us-east-1"' || { echo "-t region=us-east-1 should select us-east-1"; exit 1; }
if pravo eval -t region=ap-south-1 > /dev/null 2>&1; then echo "ap-south-1 should be refused"; exit 1; fi
echo ok
Run it on the start state. It fails.
with no tag, region should be eu-west-1
Hint 1
Follow the env field. Put * in front of one choice and @tag(region) after the last choice.
Solution
package estate
env: *"dev" | "staging" | "prod" @tag(env)
region: *"eu-west-1" | "eu-central-1" | "us-east-1" @tag(region)
bucket: {
name: "logs"
class: *"standard" | "ia" | "glacier"
stage: env
}
The refusal reads like the one for env.
Error: evaluating .:
region: 3 errors in empty disjunction:
region: conflicting values "eu-central-1" and "ap-south-1":
./estate.cue:5:24
region: conflicting values "eu-west-1" and "ap-south-1":
./estate.cue:5:10
region: conflicting values "us-east-1" and "ap-south-1":
./estate.cue:5:41
What | and * do
A list of values joined by | is a disjunction. The field can be any one of them. Writing a value narrows the list to the choices that value matches.
A default is selected only when nothing narrows the list further. Four fields show the cases.
package estate
a: *1 | 2 | 3
b: *1 | 2 | 3
b: 2
c: *1 | *2 | 3
d: 1 | 2 | 3
a: 1
b: 2
c: 1 | 2
d: 1 | 2 | 3
Field a takes its default. Field b takes the value written, because that value is one of the choices. Field c marks two defaults, so both stay as choices. Field d has no default and stays open.
Recap
- A
|offers choices. The value must be one of them. - A
*marks the default. Mark it once per field. - A
@tag(name)field takes its value from-t name=value. The choices still apply.
Check yourself
What does class: *"standard" | "ia" hold when nothing else is written?
The default, "standard".
Why does -t env=qa fail when env offers dev, staging and prod?
A tag value is unified with the field like any other value. The value "qa" matches none of the choices.
The next lesson decides what happens to a field that nobody sets.