github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/website/docs/language/expressions/type-constraints.mdx (about) 1 --- 2 page_title: Type Constraints - Configuration Language 3 description: >- 4 Learn how to use type constraints to validate user inputs to modules and 5 resources. 6 --- 7 8 # Type Constraints 9 10 Terraform module authors and provider developers can use detailed type 11 constraints to validate user-provided values for their input variables and 12 resource arguments. This requires some additional knowledge about Terraform's 13 type system, but allows you to build a more resilient user interface for your 14 modules and resources. 15 16 ## Type Keywords and Constructors 17 18 Type constraints are expressed using a mixture of _type keywords_ and 19 function-like constructs called _type constructors._ 20 21 * Type keywords are unquoted symbols that represent a static type. 22 * Type constructors are unquoted symbols followed by a pair of 23 parentheses, which contain an argument that specifies more information about 24 the type. Without its argument, a type constructor does not fully 25 represent a type; instead, it represents a _kind_ of similar types. 26 27 Type constraints look like other kinds of Terraform 28 [expressions](/terraform/language/expressions), but are a special syntax. Within the 29 Terraform language, they are only valid in the `type` argument of an 30 [input variable](/terraform/language/values/variables). 31 32 ## Primitive Types 33 34 A _primitive_ type is a simple type that isn't made from any other types. All 35 primitive types in Terraform are represented by a type keyword. The available 36 primitive types are: 37 38 * `string`: a sequence of Unicode characters representing some text, such 39 as `"hello"`. 40 * `number`: a numeric value. The `number` type can represent both whole 41 numbers like `15` and fractional values such as `6.283185`. 42 * `bool`: either `true` or `false`. `bool` values can be used in conditional 43 logic. 44 45 ### Conversion of Primitive Types 46 47 The Terraform language will automatically convert `number` and `bool` values 48 to `string` values when needed, and vice-versa as long as the string contains 49 a valid representation of a number or boolean value. 50 51 * `true` converts to `"true"`, and vice-versa 52 * `false` converts to `"false"`, and vice-versa 53 * `15` converts to `"15"`, and vice-versa 54 55 ## Complex Types 56 57 A _complex_ type is a type that groups multiple values into a single value. 58 Complex types are represented by type constructors, but several of them 59 also have shorthand keyword versions. 60 61 There are two categories of complex types: collection types (for grouping 62 similar values), and structural types (for grouping potentially dissimilar 63 values). 64 65 ### Collection Types 66 67 A _collection_ type allows multiple values of _one_ other type to be grouped 68 together as a single value. The type of value _within_ a collection is called 69 its _element type._ All collection types must have an element type, which is 70 provided as the argument to their constructor. 71 72 For example, the type `list(string)` means "list of strings", which is a 73 different type than `list(number)`, a list of numbers. All elements of a 74 collection must always be of the same type. 75 76 The three kinds of collection type in the Terraform language are: 77 78 * `list(...)`: a sequence of values identified by consecutive whole numbers 79 starting with zero. 80 81 The keyword `list` is a shorthand for `list(any)`, which accepts any 82 element type as long as every element is the same type. This is for 83 compatibility with older configurations; for new code, we recommend using 84 the full form. 85 * `map(...)`: a collection of values where each is identified by a string label. 86 87 The keyword `map` is a shorthand for `map(any)`, which accepts any 88 element type as long as every element is the same type. This is for 89 compatibility with older configurations; for new code, we recommend using 90 the full form. 91 92 Maps can be made with braces ({}) and colons (:) or equals signs (=): 93 { "foo": "bar", "bar": "baz" } OR { foo = "bar", bar = "baz" }. Quotes 94 may be omitted on keys, unless the key starts with a number, in which 95 case quotes are required. Commas are required between key/value pairs 96 for single line maps. A newline between key/value pairs is sufficient 97 in multi-line maps. 98 99 -> **Note:** Although colons are valid delimiters between keys and values, `terraform fmt` ignores them. In contrast, `terraform fmt` attempts to vertically align equals signs. 100 101 * `set(...)`: a collection of unique values that do not have any secondary 102 identifiers or ordering. 103 104 ### Structural Types 105 106 A _structural_ type allows multiple values of _several distinct types_ to be 107 grouped together as a single value. Structural types require a _schema_ as an 108 argument, to specify which types are allowed for which elements. 109 110 The two kinds of structural type in the Terraform language are: 111 112 * `object(...)`: a collection of named attributes that each have their own type. 113 114 The schema for object types is `{ <KEY> = <TYPE>, <KEY> = <TYPE>, ... }` — a 115 pair of curly braces containing a comma-separated series of `<KEY> = <TYPE>` 116 pairs. Values that match the object type must contain _all_ of the specified 117 keys, and the value for each key must match its specified type. (Values with 118 _additional_ keys can still match an object type, but the extra attributes 119 are discarded during type conversion.) 120 * `tuple(...)`: a sequence of elements identified by consecutive whole 121 numbers starting with zero, where each element has its own type. 122 123 The schema for tuple types is `[<TYPE>, <TYPE>, ...]` — a pair of square 124 brackets containing a comma-separated series of types. Values that match the 125 tuple type must have _exactly_ the same number of elements (no more and no 126 fewer), and the value in each position must match the specified type for 127 that position. 128 129 For example: an object type of `object({ name=string, age=number })` would match 130 a value like the following: 131 132 ```hcl 133 { 134 name = "John" 135 age = 52 136 } 137 ``` 138 139 Also, an object type of `object({ id=string, cidr_block=string })` would match 140 the object produced by a reference to an `aws_vpc` resource, like 141 `aws_vpc.example_vpc`; although the resource has additional attributes, they 142 would be discarded during type conversion. 143 144 Finally, a tuple type of `tuple([string, number, bool])` would match a value 145 like the following: 146 147 ```hcl 148 ["a", 15, true] 149 ``` 150 151 ### Complex Type Literals 152 153 The Terraform language has literal expressions for creating tuple and object 154 values, which are described in 155 [Expressions: Literal Expressions](/terraform/language/expressions/types#literal-expressions) as 156 "list/tuple" literals and "map/object" literals, respectively. 157 158 Terraform does _not_ provide any way to directly represent lists, maps, or sets. 159 However, due to the automatic conversion of complex types (described below), the 160 difference between similar complex types is almost never relevant to a normal 161 user, and most of the Terraform documentation conflates lists with tuples and 162 maps with objects. The distinctions are only useful when restricting input 163 values for a module or resource. 164 165 ### Conversion of Complex Types 166 167 Similar kinds of complex types (list/tuple/set and map/object) can usually be 168 used interchangeably within the Terraform language, and most of Terraform's 169 documentation glosses over the differences between the kinds of complex type. 170 This is due to two conversion behaviors: 171 172 * Whenever possible, Terraform converts values between similar kinds of complex 173 types if the provided value is not the exact type requested. "Similar kinds" 174 is defined as follows: 175 * Objects and maps are similar. 176 * A map (or a larger object) can be converted to an object if it has 177 _at least_ the keys required by the object schema. Any additional 178 attributes are discarded during conversion, which means map -> object 179 -> map conversions can be lossy. 180 * Tuples and lists are similar. 181 * A list can only be converted to a tuple if it has _exactly_ the 182 required number of elements. 183 * Sets are _almost_ similar to both tuples and lists: 184 * When a list or tuple is converted to a set, duplicate values are 185 discarded and the ordering of elements is lost. 186 * When a `set` is converted to a list or tuple, the elements will be 187 in an arbitrary order. If the set's elements were strings, they will 188 be in lexicographical order; sets of other element types do not 189 guarantee any particular order of elements. 190 * Whenever possible, Terraform converts _element values_ within a complex type, 191 either by converting complex-typed elements recursively or as described above 192 in [Conversion of Primitive Types](#conversion-of-primitive-types). 193 194 For example: if a module argument requires a value of type `list(string)` and a 195 user provides the tuple `["a", 15, true]`, Terraform will internally transform 196 the value to `["a", "15", "true"]` by converting the elements to the required 197 `string` element type. Later, if the module uses those elements to set different 198 resource arguments that require a string, a number, and a bool (respectively), 199 Terraform will automatically convert the second and third strings back to the 200 required types at that time, since they contain valid representations of a 201 number and a bool. 202 203 On the other hand, automatic conversion will fail if the provided value 204 (including any of its element values) is incompatible with the required type. If 205 an argument requires a type of `map(string)` and a user provides the object 206 `{name = ["Kristy", "Claudia", "Mary Anne", "Stacey"], age = 12}`, Terraform 207 will raise a type mismatch error, since a tuple cannot be converted to a string. 208 209 ## Dynamic Types: The "any" Constraint 210 211 ~> **Warning:** `any` is very rarely the correct type constraint to use. 212 **Do not use `any` just to avoid specifying a type constraint**. Always write an 213 exact type constraint unless you are truly handling dynamic data. 214 215 The keyword `any` is a special construct that serves as a placeholder for a 216 type yet to be decided. `any` is not _itself_ a type: when interpreting a 217 value against a type constraint containing `any`, Terraform will attempt to 218 find a single actual type that could replace the `any` keyword to produce 219 a valid result. 220 221 The only situation where it's appropriate to use `any` is if you will pass 222 the given value directly to some other system without directly accessing its 223 contents. For example, it's okay to use a variable of type `any` if you use 224 it only with `jsonencode` to pass the full value directly to a resource, as 225 shown in the following example: 226 227 ``` 228 variable "settings" { 229 type = any 230 } 231 232 resource "aws_s3_object" "example" { 233 # ... 234 235 # This is a reasonable use of "any" because this module 236 # just writes any given data to S3 as JSON, without 237 # inspecting it further or applying any constraints 238 # to its type or value. 239 content = jsonencode(var.settings) 240 } 241 ``` 242 243 If any part of your module accesses elements or attributes of the value, or 244 expects it to be a string or number, or any other non-opaque treatment, it 245 is _incorrect_ to use `any`. Write the exact type that your module is expecting 246 instead. 247 248 ### `any` with Collection Types 249 250 All of the elements of a collection must have the same type, so if you use 251 `any` as the placeholder for the element type of a collection then Terraform 252 will attempt to find a single exact element type to use for the resulting 253 collection. 254 255 For example, given the type constraint `list(any)`, Terraform will examine 256 the given value and try to choose a replacement for the `any` that would 257 make the result valid. 258 259 If the given value were `["a", "b", "c"]` -- whose physical type is 260 `tuple([string, string, string])` -- Terraform analyzes this as follows: 261 262 * Tuple types and list types are _similar_ per the previous section, so the 263 tuple-to-list conversion rule applies. 264 * All of the elements in the tuple are strings, so the type constraint 265 `string` would be valid for all of the list elements. 266 * Therefore in this case the `any` argument is replaced with `string`, 267 and the final concrete value type is `list(string)`. 268 269 If the elements of the given tuple are not all of the same type then Terraform 270 will attempt to find a single type that they can all convert to. Terraform 271 will consider various conversion rules as described in earlier sections. 272 273 * If the given value were instead `["a", 1, "b"]` then Terraform would still 274 select `list(string)`, because of the primitive type conversion rules, and 275 the resulting value would be `["a", "1", "b"]` due to the string conversion 276 implied by that type constraint. 277 * If the given value were instead `["a", [], "b"]` then the value cannot 278 conform to the type constraint: there is no single type that both a string 279 and an empty tuple can convert to. Terraform would reject this value, 280 complaining that all elements must have the same type. 281 282 Although the above examples use `list(any)`, a similar principle applies to 283 `map(any)` and `set(any)`. 284 285 ## Optional Object Type Attributes 286 287 Terraform typically returns an error when it does not receive a value for specified object attributes. When you mark an attribute as optional, Terraform instead inserts a default value for the missing attribute. This allows the receiving module to describe an appropriate fallback behavior. 288 289 To mark attributes as optional, use the `optional` modifier in the object type constraint. The following example creates optional attribute `b` and optional attribute with a default value `c`. 290 291 ```hcl 292 variable "with_optional_attribute" { 293 type = object({ 294 a = string # a required attribute 295 b = optional(string) # an optional attribute 296 c = optional(number, 127) # an optional attribute with default value 297 }) 298 } 299 ``` 300 301 The `optional` modifier takes one or two arguments. 302 - **Type:** (Required) The first argument 303 specifies the type of the attribute. 304 - **Default:** (Optional) The second argument defines the default value that Terraform should use if the attribute is not present. This must be compatible with the attribute type. If not specified, Terraform uses a `null` value of the appropriate type as the default. 305 306 An optional attribute with a non-`null` default value is guaranteed to never have the value `null` within the receiving module. Terraform will substitute the default value both when a caller omits the attribute altogether and when a caller explicitly sets it to `null`, thereby avoiding the need for additional checks to handle a possible null value. 307 308 Terraform applies object attribute defaults top-down in nested variable types. This means that Terraform applies the default value you specify in the `optional` modifier first and then later applies any nested default values to that attribute. 309 310 ### Example: Nested Structures with Optional Attributes and Defaults 311 312 The following example defines a variable for storage buckets that host a website. This variable type uses several optional attributes, including `website`, which is itself an optional `object` type that has optional attributes and defaults. 313 314 ```hcl 315 variable "buckets" { 316 type = list(object({ 317 name = string 318 enabled = optional(bool, true) 319 website = optional(object({ 320 index_document = optional(string, "index.html") 321 error_document = optional(string, "error.html") 322 routing_rules = optional(string) 323 }), {}) 324 })) 325 } 326 ``` 327 328 The following example `terraform.tfvars` file specifies three bucket configurations for `var.buckets`. 329 330 - `production` sets the routing rules to add a redirect 331 - `archived` uses default configuration, but is disabled 332 - `docs` overrides the index and error documents to use text files 333 334 The `production` bucket does not specify the index and error documents, and the `archived` bucket omits the website configuration entirely. Terraform will use the default values specified in the `bucket` type constraint. 335 336 ```hcl 337 buckets = [ 338 { 339 name = "production" 340 website = { 341 routing_rules = <<-EOT 342 [ 343 { 344 "Condition" = { "KeyPrefixEquals": "img/" }, 345 "Redirect" = { "ReplaceKeyPrefixWith": "images/" } 346 } 347 ] 348 EOT 349 } 350 }, 351 { 352 name = "archived" 353 enabled = false 354 }, 355 { 356 name = "docs" 357 website = { 358 index_document = "index.txt" 359 error_document = "error.txt" 360 } 361 }, 362 ] 363 ``` 364 365 This configuration produces the following variable values. 366 367 - For the `production` and `docs` buckets, Terraform sets `enabled` to `true`. Terraform also supplies default values for `website`, and then the values specified in `docs` override those defaults. 368 - For the `archived` and `docs` buckets, Terraform sets `routing_rules` to a `null` value. When Terraform does not receive optional attributes and there are no specified defaults, Terraform populates those attributes with a `null` value. 369 - For the `archived` bucket, Terraform populates the `website` attribute with the default values specified in the `buckets` type constraint. 370 371 ```hcl 372 tolist([ 373 { 374 "enabled" = true 375 "name" = "production" 376 "website" = { 377 "error_document" = "error.html" 378 "index_document" = "index.html" 379 "routing_rules" = <<-EOT 380 [ 381 { 382 "Condition" = { "KeyPrefixEquals": "img/" }, 383 "Redirect" = { "ReplaceKeyPrefixWith": "images/" } 384 } 385 ] 386 387 EOT 388 } 389 }, 390 { 391 "enabled" = false 392 "name" = "archived" 393 "website" = { 394 "error_document" = "error.html" 395 "index_document" = "index.html" 396 "routing_rules" = tostring(null) 397 } 398 }, 399 { 400 "enabled" = true 401 "name" = "docs" 402 "website" = { 403 "error_document" = "error.txt" 404 "index_document" = "index.txt" 405 "routing_rules" = tostring(null) 406 } 407 }, 408 ]) 409 ``` 410 411 ### Example: Conditionally setting an optional attribute 412 413 Sometimes the decision about whether or not to set a value for an optional argument needs to be made dynamically based on some other data. In that case, the calling `module` block can use a conditional expression with `null` as one of its result arms to represent dynamically leaving the argument unset. 414 415 With the `variable "buckets"` declaration shown in the previous section, the following example conditionally overrides the `index_document` and `error_document` settings in the `website` object based on a new variable `var.legacy_filenames`: 416 417 ```hcl 418 variable "legacy_filenames" { 419 type = bool 420 default = false 421 nullable = false 422 } 423 424 module "buckets" { 425 source = "./modules/buckets" 426 427 buckets = [ 428 { 429 name = "maybe_legacy" 430 website = { 431 error_document = var.legacy_filenames ? "ERROR.HTM" : null 432 index_document = var.legacy_filenames ? "INDEX.HTM" : null 433 } 434 }, 435 ] 436 } 437 ``` 438 439 When `var.legacy_filenames` is set to `true`, the call will override the document filenames. When it is `false`, the call will leave the two filenames unspecified, thereby allowing the module to use its specified default values.