§ A4 · Track A · CUE from zero
Order does not matter
Lines and files can come in any order. The result is the same, so there is no precedence list to learn.
On this page
In HCL, where a value is read decides which value wins. These two commands differ only in the order of their arguments.
tofu plan -var-file=base.tfvars -var-file=prod.tfvars # prod.tfvars wins where both set a name
tofu plan -var-file=prod.tfvars -var-file=base.tfvars # base.tfvars wins
OpenTofu reads variable values from several sources and keeps the last one it reads. The order, from first to last:
TF_VAR_environment variables.terraform.tfvars, thenterraform.tfvars.json.*.auto.tfvarsand*.auto.tfvars.jsonfiles, in file-name order.-varand-var-fileoptions, in the order given.
Override files add a second order for configuration.
CUE has no precedence list. Every line and every file adds constraints, and unification gives the same result in any order. The files of one directory that start with the same package line form one package. pravo evaluates the package as a whole.
Warm-up
From earlier lessons
From A1: how do you write a struct with one field, tags holding owner, on one line?
tags: owner: "web". A struct with one field folds into a path.
From A2: is >=1 a concrete value?
No. It allows every number from 1 upward. A concrete value allows exactly one.
Start from the estate
Work in the directory from A3. If you start here, write these two files.
package estate
buckets: {
logs: {
name: "logs"
versioning: true // keep old versions
encrypted: true
retention_days: 365
tags: owner: "platform"
}
uploads: {
name: "uploads"
versioning: false
encrypted: true
retention_days: 30
tags: owner: "web"
}
}
rules: {
https: {
port: 443
protocol: "tcp"
}
ssh: {
port: 22
protocol: "tcp"
}
}
package estate
buckets: uploads: tags: cost_center: "4711"
buckets: uploads: tags: env: "prod"
Save the output. You compare against it later.
Swap two lines
Add a new field to uploads in a scratch file. One line gives it a type and one line gives it a value.
package estate
buckets: uploads: class: string
buckets: uploads: class: "standard"
"standard"
Swap the two lines.
package estate
buckets: uploads: class: "standard"
buckets: uploads: class: string
"standard"
The value is the same. In HCL, swapping two assignments to one name changes which one wins. In CUE, a type and a value that agree give the value in either order.
Note
If the two lines name different fields, the data is the same in both orders. pravo eval may print the fields in the order you wrote them. The printed order is not part of the value.
Remove the scratch file.
Rename a file
Files behave like lines. pravo reads the files of a directory in the order of their names. Rename estate.cue so that it sorts after team.cue, and compare the output with the output you saved.
same
Put the name back.
Two files that name the same field still have to agree. Order cannot make a conflict disappear.
Predict
You write retention_days three times: with 365, with >=90 and with int. The three lines can appear in six orders. Decide before you open the answer.
Predict How many different values do the six orders give?
One. Every order gives 365. Writing a field several times is the same as joining the values with &. The six expressions below are the six orders of the three lines.
365
365
365
365
365
365
Break it
A file that names another package cannot join the estate. Write prod.cue with package prod, as if the package were named after the file.
package prod
buckets: logs: retention_days: >=365
Error: loading .:
prod.cue is package prod; the other files are package estate
Read the error from left to right.
- Stage.
loadingsays that pravo could not assemble the files into one package. It reported the error before it evaluated any value. - Message.
prod.cue is package prod; the other files are package estatenames the file, its package and the package of the other files. A directory holds one package. - Location. The problem is the
packageline of a whole file, so there is no line number.
Change the first line of prod.cue to package estate.
package estate
buckets: logs: retention_days: >=365
same
The third file adds a bound that logs already meets, so the data is unchanged.
The opposite mistake is reported too. Delete the first line of prod.cue.
buckets: logs: retention_days: >=365
Error: loading .:
prod.cue has no package clause; the other files are package estate
A file without a package line cannot join the estate. pravo names the file and stops before it evaluates a value. Remove the file.
Exercise
Split the estate into three files
Split estate.cue into buckets.cue and rules.cue. With team.cue you have three files. Delete estate.cue. The output must not change, whatever the order of the files.
Save the output before you split.
The check copies the three files into a scratch directory in three different orders and compares each output with before.txt.
[ ! -f estate.cue ] || { echo "estate.cue still exists"; exit 1; }
set -- *.cue
[ $# -eq 3 ] || { echo "expected three .cue files, found $#"; exit 1; }
tmp=$(mktemp -d)
for order in "$1 $2 $3" "$3 $1 $2" "$2 $3 $1"; do
rm -rf "$tmp/d"; mkdir "$tmp/d"; n=0
for f in $order; do n=$((n + 1)); cp "$f" "$tmp/d/$n.cue"; done
if ! pravo -C "$tmp/d" eval | diff before.txt - > /dev/null; then
echo "different output for the order: $order"; rm -rf "$tmp"; exit 1
fi
done
rm -rf "$tmp"
echo ok
Run the check on the start state. It fails.
estate.cue still exists
Hint 1
Each new file starts with package estate. Move the buckets field into buckets.cue and the rules field into rules.cue.
Hint 2
The files of a package can hold any fields in any order. See package.
Solution
package estate
buckets: {
logs: {
name: "logs"
versioning: true // keep old versions
encrypted: true
retention_days: 365
tags: owner: "platform"
}
uploads: {
name: "uploads"
versioning: false
encrypted: true
retention_days: 30
tags: owner: "web"
}
}
package estate
rules: {
https: {
port: 443
protocol: "tcp"
}
ssh: {
port: 22
protocol: "tcp"
}
}
Recap
- Lines and files can come in any order. Unification gives the same value, so there is no precedence list.
- The files of a directory that start with the same
packageline form one package. A directory holds one package. - Order can change the order of the printed fields. It cannot change a value.
- A file without a package line, or with another one, is an error that names the file. Start every file with the same package line.
Check yourself
You rename a file so that it loads last. Does any value change?
No. pravo reads the files in name order, and unification does not depend on the order.
What does prod.cue is package prod; the other files are package estate mean?
prod.cue starts with a different package line than the other files. Give every file the same package name.
A file has no package line and breaks a bound. What does pravo eval print?
An error that names the file: prod.cue has no package clause; the other files are package estate. The bound is not checked until the file has the package line.
The next lesson turns a rule into a value: a range, or text of a given shape.