Terraform数据类型

概括地说,Terraform的数据类型分为两种:原始类型,复杂类型。

原始类型

原始类型包含3个:stringnumberbool

  • string:表示一组Unicode字符,例如:”hello”
  • number:表示数字,可以为整数,也可以为小数
  • bool:表示布尔值,可选值为true或false,bool类型可以做逻辑判断

numberbool都可以和string进行隐式转换,当把numberbool赋值给string时,或者反过来,Terraform都会自动做类型转换,如下示例:

  • true会被转换为”true”,反之”true”会被转换为true
  • false会被转换为”false”,反之”false”会被转换为false
  • 15会被转换为”15”,反之”15”会被转换为15

复杂类型

复杂类型是一组值所组成的复合类型,有2类复杂类型。

集合类型

Terraform支持三种集合类型:

  1. 列表:list(…),列表是一组值的连续集合,可以用下标访问内部元素(从0开始)。list类型的声明可以是list(number),list(string),list(bool)等,括号中的类型即为列表元素类型。
  2. 字典:map(…),表示一组键值对的集合,键类型必须是string,值的类型任意。map(number)表示键为string类型而值为number类型的字典。map类型的声明方式有2种风格:一种是以类似{"foo":"bar", "bar":"baz"}(键和值都以双引号包含);另一种是类似{foo="bar",bar="baz"}(键不以双引号包含,但如果键是以数字开头的例外)。多个键值对以逗号分隔,也可以使用换行符分隔。推荐使用第二种风格(Terraform规范规定按等号对齐,使用等号会使得代码在格式化后更美观)。
  3. 集合:set(…),代表一组不重复的值的集合。

以上集合类型都支持通配类型缩写,如:list等价于list(any)map等价于map(any)set等价于set(any)
any代表支持任意元素类型,前提是所有元素都必须是同一个类型。例如:将list(string)赋值给list(any),将list(number)赋值给list(any)都是合法的。

结构化类型

一个结构化类型允许将多个不同类型的值组合成一个类型,结构化类型必须提供一个schema结构信息作为参数来指明元素的结构。

Terraform支持两种结构化类型:

  1. 对象:object(…),对象是指一组具有名称和类型的属性所构成的复合类型,它的schema信息为:{key=type,key=type,...}。例如:object({name=string, age=number})代表由名称
    为”name”类型为string,以及名称为”age”类型为number的两个属性组成的对象。赋值给object类型的合法值必须包含所有属性值,但是可以拥有多余的属性(多余的属性在赋值时会被抛弃)。例如:对于object({name=string,age=number})来说,{age=18}是一个非法值,而{name="zhangsan", age=18, gender="male"}是一个合法值,但赋值时gender属性会被丢弃。
  2. 元组:tuple(…),元组类似于list,也是一组值的连续集合,但是每个元素都可以有独立的类型。元组同列表一样,也可以通过下标访问元素(下标从0开始),元组的schema信息为:[type, type, ...]。元组的元素数量必须与其schema声明的类型数量相等,并且每个元素的类型都必须与其schema相应位置的类型相同。例如:tuple([string, number, bool])类型的一个合法值可以是["a", 15, true]

复杂类型也支持隐式类型转换。Terraform会尝试转换相似的类型,转换规则如下:

  • objectmap:如果一个map的键集合含有object规定的所有属性,那么map可以转换被转换为object,map里多余的键值会被抛弃,由map -> object -> map的转换可能存在数据丢失。
  • tuplelist:当一个list元素的数量正好等于一个tuple声明的长度,list可以被转换为tuple。例如:值为["18", "true", "john"]的list转换为type([number, bool, string])的tuple结果为[18, true, “john”]。
  • settuple:当一个list或者tuple被转换为一个set,重复的值将被丢弃,并且原值的顺序也将丢失。如果一个set被转换为list或者tuple,那么元素将按照以下顺序排列:如果set元素类型是string,那么将按照字母顺序排列,其他类型的元素不承诺任何特定的排列顺序。

复杂类型转换时,元素类型将在可能的情况下发生隐式转换,类似上述list到tuple的转换。

如果类型不匹配,Terraform将报错。例如:试图将object({name=["zhangsan", "lisit"],age=12})转换为map类型,这是不合法的,因为name的值为list,无法转换为string。

any类型

any类型是一种特殊的类型约束,它本身并非一个类型,而只是一个类型占位符。每当一个值被赋予由any约束的复杂类型时,Terraform会尝试计算出一个最精确的类型来替代any。
例如:把[“a”, “b”, “c”]赋值给list(any),它在Terraform中实际的物理类型首先被编译成tuple([string, string, string]),然后Terraform认为tuple和list相似,所以会尝试将它转换为list(string)。Terraform发现list(string)符合list(any)的约束,所以会用string取代any,于是赋值的最终类型为list(string)。

由于即使是list(any),所有元素类型也必须是一样的,所以某些类型转换到list(any)时会对元素进行隐式类型转换。例如:["a", 1, "b"]赋值给list(any),Terraform发现1可以转换为"1",所以最终的值为:["a", "1", "b"]

声明类型时如果不想有任何的约束,可以使用any

variable "no_type_constraint" {
    type = any # 这样Terraform可以将任何类型的数据赋值给参数
}

null类型

存在一种值是无类型的,那就是null。null代表数据缺失,如果把一个参数设置为null,Terraform会认为你忘记为它赋值。如果该参数有默认值,Terraform会使用默认值。如果没有默认值该参数又是必填字段,Terraform会报错。null在条件表达式中非常有用,可以在某项条件不满足时跳过对某参数的赋值。

object的optional成员

自Terraform 1.3开始,可以在object类型定义中使用optional修饰属性。

在1.3之前,如果一个variable类型为object,那么在赋值时必须传入一个完全相符的对象,如下所示:

variable "any_object" {
    type = object({
        a = string,
        b = string,
        c = number
    })
}

如果想传入一个对象给var.any_object,但是不准备给属性bc赋值,比如这样做:

{
    a = "1",
    b = null,
    c = null
}

传入的对象必须必须完全符合object类型定义的结构,哪怕是不想对某些属性赋值。

Terraform 1.3允许为一个属性添加optional声明:

variable "with_optional_attribute" {
    a = string,               # 必须属性
    b = optional(string),     # 可选属性
    c = optional(number, 12)  # 带默认值的可选属性,如果不传值则使用默认值
}

optional修饰符有两个参数:

  • 类型:(必填)第一个参数标明了属性的类型
  • 默认值:(选填)第二个参数定义了Terraform在没有定义该属性值时使用的默认值,默认值必须与参数类型兼容。如果没有指定默认值,Terraform会使用null作为默认值。

示例1:带有optional修饰符和默认值的内嵌结构

如下例子演示了一个输入变量,用来描述一个存储了静态网站的存储桶。
该变量的类型包含了一系列的optional属性,包括website,不但其自身是optional的,其内部还包含了数个optional的属性和默认值。

variable "buckets" {
    type = list(object({ # 列表元素类型为对象,对象中包含了3个属性
        name = string,
        enabled = optional(bool, true),
        website = optional(object({
            index_document = optional(string, "index.html"),
            error_document = optional(string, "error.html"),
            routing_rules = optional(string)
        }))
    }))
}

以下给出一个样例terraform.tfvars文件,为var.buckets定义了三个存储桶:

  • production:enabled属性使用了默认值
  • archived:website属性使用了默认值
  • docs:enabled属性使用了默认值,website属性中的”routing_rules”也使用了默认值
buckets = [
    {
        name = "production",
        website = {
            routing_rules = <<-EOF
            [
                {
                    "Condition" = { "KeyPrefixEquals": "img/" },
                    "Redirect"  = { "ReplaceKeyPrefixWith": "images/" }
                }
            ]
            EOF
        }
    },
    {
        name = "archived",
        enabled = false
    },
    {
        name = "docs",
        website = {
            index_document = "index.txt",
            error_document = "error.txt"
        }
    }
]

上述配置会产生如下的varible值:

tolist([
  {
    "enabled" = true
    "name" = "production"
    "website" = {
      "error_document" = "error.html"
      "index_document" = "index.html"
      "routing_rules" = <<-EOT
      [
        {
          "Condition" = { "KeyPrefixEquals": "img/" },
          "Redirect"  = { "ReplaceKeyPrefixWith": "images/" }
        }
      ]

      EOT
    }
  },
  {
    "enabled" = false
    "name" = "archived"
    "website" = {
      "error_document" = "error.html"
      "index_document" = "index.html"
      "routing_rules" = tostring(null)
    }
  },
  {
    "enabled" = true
    "name" = "docs"
    "website" = {
      "error_document" = "error.txt"
      "index_document" = "index.txt"
      "routing_rules" = tostring(null)
    }
  },
])

示例2:有条件地设置一个默认属性

有时需要根据其他数据的值来动态决定是否要为一个optional参数设置值:这在引入Terraform的module之后会比较常见,发起调用的module块可以使用条件表达式搭配null来动态决定是否应该设置该参数。

还是上面variable buckets例子,使用下面的演示可以根据新输入的参数var.legacy_filenames的值来有条件地覆盖website对象中的index_documenterror_document设置。

variable "legacy_filenames" {
    type = bool
    default = false
    nullable = false
}

module "buckets" {
    source = "./modules/buckets"
    buckets = [
        {
            name = "maybe_legacy",
            website = {
                index_document = var.legacy_filenames ? "INDEX.html": null,
                error_document = var.legacy_filenames ? "ERROR.html": null
            }
        }
    ]
}

var.legacy_filenames设置为true时,才会覆盖websiet属性的index_documenterror_document值;当它为false时,调用不会指定website属性的index_documenterror_document值,这样website属性的index_documenterror_document会使用定义的默认值。

【参考】
Types and Values


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,在下面评论区告诉我^_^^_^