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.