Skip to content

Commit dfad6e0

Browse files
authored
Merge pull request #3189 from rrjjvv/var-typing-docs
Add variable typing to reference docs
2 parents 776dbd4 + cfeca91 commit dfad6e0

File tree

1 file changed

+202
-0
lines changed

1 file changed

+202
-0
lines changed

docs/bake-reference.md

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,6 +1081,7 @@ or interpolate them in attribute values in your Bake file.
10811081

10821082
```hcl
10831083
variable "TAG" {
1084+
type = string
10841085
default = "latest"
10851086
}
10861087
@@ -1102,6 +1103,206 @@ overriding the default `latest` value shown in the previous example.
11021103
$ TAG=dev docker buildx bake webapp-dev
11031104
```
11041105

1106+
Variables can also be assigned an explicit type.
1107+
If provided, it will be used to validate the default value (if set), as well as any overrides.
1108+
This is particularly useful when using complex types which are intended to be overridden.
1109+
The previous example could be expanded to apply an arbitrary series of tags.
1110+
```hcl
1111+
variable "TAGS" {
1112+
default = ["latest"]
1113+
type = list(string)
1114+
}
1115+
1116+
target "webapp-dev" {
1117+
dockerfile = "Dockerfile.webapp"
1118+
tags = [for tag in TAGS: "docker.io/username/webapp:${tag}"]
1119+
}
1120+
```
1121+
1122+
This example shows how to generate three tags without changing the file
1123+
or using custom functions/parsing:
1124+
```console
1125+
$ TAGS=dev,latest,2 docker buildx bake webapp-dev
1126+
```
1127+
1128+
### Variable typing
1129+
1130+
The following primitive types are available:
1131+
* `string`
1132+
* `number`
1133+
* `bool`
1134+
1135+
The type is expressed like a keyword; it must be expressed as a literal:
1136+
```hcl
1137+
variable "OK" {
1138+
type = string
1139+
}
1140+
1141+
# cannot be an actual string
1142+
variable "BAD" {
1143+
type = "string"
1144+
}
1145+
1146+
# cannot be the result of an expression
1147+
variable "ALSO_BAD" {
1148+
type = lower("string")
1149+
}
1150+
```
1151+
Specifying primitive types can be valuable to show intent (especially when a default is not provided),
1152+
but bake will generally behave as expected without explicit typing.
1153+
1154+
Complex types are expressed with "type constructors"; they are:
1155+
* `tuple([<type>,...])`
1156+
* `list(<type>)`
1157+
* `set(<type>)`
1158+
* `map(<type>)`
1159+
* `object({<attr>=<type>},...})`
1160+
1161+
The following are examples of each of those, as well as how the (optional) default value would be expressed:
1162+
```hcl
1163+
# structured way to express "1.2.3-alpha"
1164+
variable "MY_VERSION" {
1165+
type = tuple([number, number, number, string])
1166+
default = [1, 2, 3, "alpha"]
1167+
}
1168+
1169+
# JDK versions used in a matrix build
1170+
variable "JDK_VERSIONS" {
1171+
type = list(number)
1172+
default = [11, 17, 21]
1173+
}
1174+
1175+
# better way to express the previous example; this will also
1176+
# enforce set semantics and allow use of set-based functions
1177+
variable "JDK_VERSIONS" {
1178+
type = set(number)
1179+
default = [11, 17, 21]
1180+
}
1181+
1182+
# with the help of lookup(), translate a 'feature' to a tag
1183+
variable "FEATURE_TO_NAME" {
1184+
type = map(string)
1185+
default = {featureA = "slim", featureB = "tiny"}
1186+
}
1187+
1188+
# map a branch name to a registry location
1189+
variable "PUSH_DESTINATION" {
1190+
type = object({branch = string, registry = string})
1191+
default = {branch = "main", registry = "prod-registry.invalid.com"}
1192+
}
1193+
1194+
# make the previous example more useful with composition
1195+
variable "PUSH_DESTINATIONS" {
1196+
type = list(object({branch = string, registry = string}))
1197+
default = [
1198+
{branch = "develop", registry = "test-registry.invalid.com"},
1199+
{branch = "main", registry = "prod-registry.invalid.com"},
1200+
]
1201+
}
1202+
```
1203+
Note that in each example, the default value would be valid even if typing was not present.
1204+
If typing was omitted, the first three would all be considered `tuple`;
1205+
you would be restricted to functions that operate on `tuple` and, for example, not be able to add elements.
1206+
Similarly, the third and fourth would both be considered `object`, with the limits and semantics of that type.
1207+
In short, in the absence of a type, any value delimited with `[]` is a `tuple`
1208+
and value delimited with `{}` is an `object`.
1209+
Explicit typing for complex types not only opens up the ability to use functions applicable to that specialized type,
1210+
but is also a precondition for providing overrides.
1211+
1212+
> [!NOTE]
1213+
> See [HCL Type Expressions][typeexpr] page for more details.
1214+
1215+
### Overriding variables
1216+
1217+
As mentioned in the [intro to variables](#variable), primitive types (`string`, `number`, and `bool`)
1218+
can be overridden without typing and will generally behave as expected.
1219+
(When explicit typing is not provided, a variable is assumed to be primitive when the default value lacks `{}` or `[]` delimiters;
1220+
a variable with neither typing nor a default value is treated as `string`.)
1221+
Naturally, these same overrides can be used alongside explicit typing too;
1222+
they may help in edge cases where you want `VAR=true` to be a `string`, where without typing,
1223+
it may be a `string` or a `bool` depending on how/where it's used.
1224+
Overriding a variable with a complex type can only be done when the type is provided.
1225+
This is still done via environment variables, but the values can be provided via CSV or JSON.
1226+
1227+
#### CSV overrides
1228+
1229+
This is considered the canonical method and is well suited to interactive usage.
1230+
It is assumed that `list` and `set` will be the most common complex type,
1231+
as well as the most common complex type designed to be overridden.
1232+
Thus, there is full CSV support for `list` and `set`
1233+
(and `tuple`; despite being considered a structural type, it is more like a collection type in this regard).
1234+
1235+
1236+
There is limited support for `map` and `object` and no support for composite types;
1237+
for these advanced cases, an alternative mechanism [using JSON](#json-overrides) is available.
1238+
1239+
#### JSON overrides
1240+
1241+
Overrides can also be provided via JSON.
1242+
This is the only method available for providing some complex types and may be convenient if overrides are already JSON
1243+
(for example, if they come from a JSON API).
1244+
It can also be used when dealing with values are difficult or impossible to specify using CSV (e.g., values containing quotes or commas).
1245+
To use JSON, simply append `_JSON` to the variable name.
1246+
In this contrived example, CSV cannot handle the second value; despite being a supported CSV type, JSON must be used:
1247+
```hcl
1248+
variable "VALS" {
1249+
type = list(string)
1250+
default = ["some", "list"]
1251+
}
1252+
```
1253+
```console
1254+
$ cat data.json
1255+
["hello","with,comma","with\"quote"]
1256+
$ VALS_JSON=$(< data.json) docker buildx bake
1257+
1258+
# CSV equivalent, though the second value cannot be expressed at all
1259+
$ VALS='hello,"with""quote"' docker buildx bake
1260+
```
1261+
1262+
This example illustrates some precedence and usage rules:
1263+
```hcl
1264+
variable "FOO" {
1265+
type = string
1266+
default = "foo"
1267+
}
1268+
1269+
variable "FOO_JSON" {
1270+
type = string
1271+
default = "foo"
1272+
}
1273+
```
1274+
1275+
The variable `FOO` can *only* be overridden using CSV because `FOO_JSON`, which would typically used for a JSON override,
1276+
is already a defined variable.
1277+
Since `FOO_JSON` is an actual variable, setting that environment variable would be expected to a CSV value.
1278+
A JSON override *is* possible for this variable, using environment variable `FOO_JSON_JSON`.
1279+
1280+
```Console
1281+
# These three are all equivalent, setting variable FOO=bar
1282+
$ FOO=bar docker buildx bake <...>
1283+
$ FOO='bar' docker buildx bake <...>
1284+
$ FOO="bar" docker buildx bake <...>
1285+
1286+
# Sets *only* variable FOO_JSON; FOO is untouched
1287+
$ FOO_JSON=bar docker buildx bake <...>
1288+
1289+
# This also sets FOO_JSON, but will fail due to not being valid JSON
1290+
$ FOO_JSON_JSON=bar docker buildx bake <...>
1291+
1292+
# These are all equivalent
1293+
$ cat data.json
1294+
"bar"
1295+
$ FOO_JSON_JSON=$(< data.json) docker buildx bake <...>
1296+
$ FOO_JSON_JSON='"bar"' docker buildx bake <...>
1297+
$ FOO_JSON=bar docker buildx bake <...>
1298+
1299+
# This results in setting two different variables, both specified as CSV (FOO=bar and FOO_JSON="baz")
1300+
$ FOO=bar FOO_JSON='"baz"' docker buildx bake <...>
1301+
1302+
# These refer to the same variable with FOO_JSON_JSON having precedence and read as JSON (FOO_JSON=baz)
1303+
$ FOO_JSON=bar FOO_JSON_JSON='"baz"' docker buildx bake <...>
1304+
```
1305+
11051306
### Built-in variables
11061307

11071308
The following variables are built-ins that you can use with Bake without having
@@ -1239,4 +1440,5 @@ target "webapp-dev" {
12391440
[ssh]: https://docs.docker.com/reference/cli/docker/buildx/build/#ssh
12401441
[tag]: https://docs.docker.com/reference/cli/docker/image/build/#tag
12411442
[target]: https://docs.docker.com/reference/cli/docker/image/build/#target
1443+
[typeexpr]: https://github.com/hashicorp/hcl/tree/main/ext/typeexpr
12421444
[userfunc]: https://github.com/hashicorp/hcl/tree/main/ext/userfunc

0 commit comments

Comments
 (0)