Terraform

[ Terraform101 Study - 3w ] 반복문 (2)

su''@ 2024. 6. 23. 22:35
Terrafrom T101 4기 실습 스터디 게시글입니다.
"테라폼으로 시작하는 IaC" 도서를 참고하여 정리했습니다. 

 

for_each : 반복문, 선언된 key 값 개수만큼 리소스를 생성
  • for-each는 반복(for)을 할 때 타입 값에 대해 하나하나 each object로 접근한다는 의미입니다.
  • each object는 key, value 2개의 속성을 가지고 있습니다.
    • each.key — The map key (or set member) corresponding to this instance.
    • each.value — The map value corresponding to this instance. (If a set was provided, this is the same as each.key.)
  • 하지만 for_each는 모든 타입에 대해 each object로 접근 할 수 없고 map, set타입만 허용합니다.
  • map, set타입이 아닌 expression은 map 또는 set으로 타입변환(toset 등)을 해야합니다.

https://www.youtube.com/watch?v=fhgGcC7wJoc&list=PL1mta2YyMpPXxFGBtSJyvoKnWTqZslm5p&index=2

 

  • 3.9 디렉터리를 신규 생성 후 열기 → main.tf 파일 생성
    mkdir 3.9 && cd 3.9
  • 리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 map 또는 set이면, 선언된 key 값 개수만큼 리소스를 생성하게 된다.
    • Map
      resource "azurerm_resource_group" "rg" {
        for_each = tomap({
          a_group       = "eastus"
          another_group = "westus2"
        })
        name     = each.key
        location = each.value
      }

    • Set of strings
      resource "aws_iam_user" "the-accounts" {
        for_each = toset(["Todd", "James", "Alice", "Dottie"])
        name     = each.key
      }

  • main.tf 파일 수정 : for_each 값이 있는 반복문 동작 확인
    resource "local_file" "abc" {
      for_each = {
        a = "content a"
        b = "content b"
      }
      content  = each.value
      filename = "${path.module}/${each.key}.txt"
    }

  • 실행 후 확인
    #
    terraform init && terraform plan && terraform apply -auto-approve
    terraform state list
    terraform state show 'local_file.abc["a"]'
    terraform state show 'local_file.abc["b"]'
    ls *.txt
    cat a.txt ;echo
    cat b.txt ;echo
    
    # 
    echo "local_file.abc" | terraform console
    echo 'local_file.abc["a"]' | terraform console
    echo 'local_file.abc["a"].content' | terraform console
    echo 'local_file.abc["b"].content' | terraform console
    
    # VSCODDE 에서 terraform.tfstate 파일 확인 혹은 cat terraform.tfstate
    ...
          "instances": [
            {
              "index_key": "a",
              "schema_version": 0,
          		...
              "index_key": "b",
              "schema_version": 0,
    ...
  • for_each가 설정된 블록에서는 each 속성을 사용해 구성을 수정할 수 있다
    • each.key : 이 인스턴스에 해당하는 map 타입의 key 값
    • each.value : 이 인스턴스에 해당하는 map의 value 값
  • 생성되는 리소스의 경우 <리소스 타입>.<이름>[<key>], 모듈의 경우 module.<모듈 이름>[<key>]로 해당 리소스의 값을 참조한다.
  • 이 참조 방식을 통해 리소스 간 종속성을 정의하기도 하고 변수로 다른 리소스에서 사용하거나 출력을 위한 결과 값으로 사용한다.
  • main.tf 파일 수정
    • local_file.abc는 변수의 map 형태의 값을 참조,
    • local_file.def의 경우 local_file.abc 도한 결과가 map으로 반환되므로 다시 for_each 구문을 사용할 수 있다
      variable "names" {
        default = {
          a = "content a"
          b = "content b"
          c = "content c"
        }
      }
      
      resource "local_file" "abc" {
        for_each = var.names
        content  = each.value
        filename = "${path.module}/abc-${each.key}.txt"
      }
      
      resource "local_file" "def" {
        for_each = local_file.abc
        content  = each.value.content
        filename = "${path.module}/def-${each.key}.txt"
      }
    • 실행 후 확인
      #
      terraform apply -auto-approve
      terraform state list
      terraform state show 'local_file.abc["a"]'
      terraform state show 'local_file.def["a"]'
      ls *.txt
      
      # 
      echo "local_file.abc" | terraform console
      echo 'local_file.abc["a"]' | terraform console
      echo 'local_file.abc["a"].content' | terraform console
      
      # VSCODDE 에서 terraform.tfstate 파일 확인 혹은 cat terraform.tfstate
      ...
            "mode": "managed",
            "type": "local_file",
            "name": "def",
            "provider": "provider[\"registry.terraform.io/hashicorp/local\"]",
            "instances": [
              {
                "index_key": "a",
                "schema_version": 0,
      ...
       
    • key 값은 count의 index와는 달리 고유하므로 중간에 값을 삭제한 후 다시 적용해도 삭제한 값에 대해서만 리소스를 삭제한다.
    • main.tf 파일 수정 : count 경우와 유사하게 중간 값을 삭제 후 확인
      variable "names" {
        default = {
          a = "content a"
          c = "content c"
        }
      }
      
      resource "local_file" "abc" {
        for_each = var.names
        content  = each.value
        filename = "${path.module}/abc-${each.key}.txt"
      }
      
      resource "local_file" "def" {
        for_each = local_file.abc
        content  = each.value.content
        filename = "${path.module}/def-${each.key}.txt"
      }
    • 실행 후 확인
      #
      terraform apply -auto-approve
      terraform state list
      local_file.abc["a"]
      local_file.abc["c"]
      local_file.def["a"]
      local_file.def["c"]
      
      ls *.txt
       
for_each : 데이터 유형
  • main.tf 파일 수정
    resource "aws_iam_user" "the-accounts" {
      for_each = ["Todd", "James", "Alice", "Dottie"]
      name     = each.key
    }
  • 확인
    #
    terraform init -upgrade && terraform plan
    │ Error: Invalid for_each argument
    │ 
    │   on main.tf line 2, in resource "aws_iam_user" "the-accounts":
    │    2:   for_each = ["Todd", "James", "Alice", "Dottie"]
    │ 
    │ The given "for_each" argument value is unsuitable: the "for_each" argument must be a map, or
    │ set of strings, and you have provided a value of type tuple.
    
    # Jenkins 등에서 CD로 Terraform 수행 시, -no-color 활용
    terraform plan -no-color
  • main.tf 파일 수정
    resource "aws_iam_user" "the-accounts" {
      for_each = toset(["Todd", "James", "Alice", "Dottie"])
      name     = each.key
    }
  • 확인
    # plan 정상 확인
    terraform plan
    ...

데이터 유형
  • 기본 유형
    • string : 글자 유형
    • number : 숫자 유형
    • bool : true 또는 false
    • any : 명시적으로 모든 유형이 허용됨을 표시
  • 집합 유형
    • list [<유형>]: 인덱스 기반 집합
    • map (<유형>): 값 = 속성 기반 집합이며 키값 기준 정렬
    • set (<유형>): 값 기반 집합이며 정렬 키값 기준 정렬
    • object ({<인수 이름>=<유형>, …})
    • tuple ([<유형>, …])
  • list와 set은 선언하는 형태가 비슷하지만 참조 방식이 인덱스와 키로 각각 차이가 있고, map와 set의 경우 선언된 값이 정렬되는 특징을 가진다.
    • main.tf 수정
      variable "string_a" {
        default     = "myString"
      }
      
      variable "string_b" {
        type        = string
        default     = "myString"
      }
      
      # variable "string_c" {
      #   type        = string
      #   default     = myString
      # }
      
      
      variable "number_a" {
        default = 123
      }
      
      variable "number_b" {
        type    = number
        default = 123
      }
      
      variable "number_c" {
        default = "123"
      }
      
      
      variable "boolean" {
        default = true
      }
      
      
      # (Array) list , set , tuple - value , [ ] 사용
      variable "list_set_tuple_a" {
        default = ["aaa", "bbb", "ccc"]
      }
      
      variable "list_set_tuple_b" {
        type    = list(string)
        default = ["bbb", "ccc", "aaa"]
      }
      
      variable "list_set_tuple_c" {
        type    = set(string)
        default = ["bbb", "ccc", "aaa"]
      }
      
      variable "list_set_tuple_d" {
        default = ["aaa", 1, false]
      }
      
      variable "list_set_tuple_e" {
        type    = tuple([string, number, bool])
        default = ["aaa", 1, false]
      }
      
      
      # (Object) map , object - key : value , { } 사용
      variable "map_object_a" {
        default = {"a" : "aaa", "b" : "bbb" , "c" : "ccc"}
      }
      
      variable "map_object_b" {
        type    = map(string)
        default = {"b" : "bbb" , "c" : "ccc", "a" : "aaa"}
      }
      
      variable "map_object_c" {
        default = {"name" : "gasida", "age" : 27 }
      }
      
      variable "map_object_d" {
        type = object({ name = string, age = number })
        default = {"name" : "gasida", "age" : 27 }
      }

    • 확인
      #
      terraform plan
      
      #
      terraform console
      -----------------
      # 기본
      type(12)
      type("12")
      
      type(a)
      type("a")
      
      type(true)
      type("true")
      
      # string
      var.string_a
      var.string_b
      
      type(var.string_a)
      type(var.string_b)
      
      # number
      var.number_a
      var.number_b
      var.number_c
      type(var.number_a)
      type(var.number_b)
      type(var.number_c)
      
      # boolean
      var.boolean
      type(var.boolean)
      
      
      # list , set , tuple - 'value'
      var.list_set_tuple_a
      var.list_set_tuple_b
      var.list_set_tuple_c
      var.list_set_tuple_d
      var.list_set_tuple_e
      type(var.list_set_tuple_a)
      type(var.list_set_tuple_b)
      type(var.list_set_tuple_c)
      type(var.list_set_tuple_d)
      type(var.list_set_tuple_e)
      
      var.list_set_tuple_a[0]
      type(var.list_set_tuple_a[0])
      
      var.list_set_tuple_b[0]
      type(var.list_set_tuple_b[0])
      
      var.list_set_tuple_d[0]
      var.list_set_tuple_d[1]
      var.list_set_tuple_d[2]
      type(var.list_set_tuple_d[0])
      type(var.list_set_tuple_d[1])
      type(var.list_set_tuple_d[2])
      
      var.list_set_tuple_e[0]
      type(var.list_set_tuple_e[0])
      
      
      # map , object - 'key : value'
      var.map_object_a
      var.map_object_b
      var.map_object_c
      var.map_object_d
      type(var.map_object_a)
      type(var.map_object_b)
      type(var.map_object_c)
      type(var.map_object_d)
      
      var.map_object_a["a"]
      type(var.map_object_a["a"])
      
      var.map_object_b["a"]
      type(var.map_object_b["a"])
      
      var.map_object_c["name"]
      type(var.map_object_c["name"])
      
      var.map_object_d["age"]
      type(var.map_object_d["age"])
      
      
      # tuple > list > set
      type(["a","b"])
      type(tolist(["a","b"]))
      type(toset(["a","b"]))
      
      
      # object > map
      type({a="a", b="b"})
      type({a="a", b=1})
      type(tomap({a="a", b="b"}))
      type(tomap({a="a", b=1}))
      
      
      exit
      -----------------
for_each vs count 와 비교
  • main.tf 파일 수정
    resource "local_file" "abc" {
      count    = 3
      content  = "This is filename abc${count.index}.txt"
      filename = "${path.module}/abc${count.index}.txt"
    }
  • 확인 : count 는 index 로 몇번에 어떤 내용이 있는지를 알 수 없다, 중간 리소스 삭제나 추가 시 문제 발생 가능
    #
    terraform apply -auto-approve
    terraform state list
    local_file.abc[0]
    local_file.abc[1]
    local_file.abc[2]
    
    #
    terraform state show 'local_file.abc[0]'
    echo "local_file.abc" | terraform console
    echo 'local_file.abc[0]' | terraform console
    echo 'local_file.abc[0].content' | terraform console
    
    # VSCODDE 에서 terraform.tfstate 파일 확인 혹은 cat terraform.tfstate
    ...
      "resources": [
        {
          "mode": "managed",
          "type": "local_file",
          "name": "abc",
          "provider": "provider[\"registry.terraform.io/hashicorp/local\"]",
          "instances": [
            {
              "index_key": 0,
              "attributes": {
                "content": "This is filename abc0.txt",
                ...
                "filename": "./abc0.txt",
            ...
            },
            {
              "index_key": 1,
              "attributes": {
                "content": "This is filename abc1.txt",
                ...
                "filename": "./abc1.txt",
            ...
            },
            {
              "index_key": 2,
              "attributes": {
                "content": "This is filename abc2.txt",
                ...
                "filename": "./abc02.txt",
            ...
            }

 

 for_each vs count 와 비교
  ? 배열 ARRAY 와 리스트 LIST 의 차이점은 무엇인가요?
  • for_each 표현식을 사용하면 리스트 lists, 집합 sets, maps 를 사용하여 전체 리소스의 여러 복사본 또는 리소스 내 인라인 블록의 여러 복사본, 모듈의 복사본을 생성 할 수 있음
  • 먼저 for_each 를 사용하여 리소스의 여러 복사본을 만드는 구문
    resource "<PROVIDER>_<TYPE>" "<NAME>" {
      for_each = <COLLECTION>
    
      [CONFIG ...]
    }
    • COLLECTION 은 루프를 처리할 집합 sets 또는 맵 maps
    • 리소스에 for_each 를 사용할 때에는 리스트는 지원하지 않는다.
    • 그리고 CONFIG 는 해당 리소스와 관련된 하나 이상의 인수로 구성되는데 CONFIG 내에서 each.key 또는 each.value 를 사용하여 COLLECTION 에서 현재 항목의 키와 값에 접근할 수 있다.
  • 예를 들어 다음은 for_each 를 사용하여 3명의 IAM 사용자를 생성하는 방법
    • var.user_names 리스트를 집합(set)으로 변환하기 위해 toset 사용. for_each 는 리소스에 사용될 때는 setmap만 지원.
    • for_each 가 이 집합을 반복하면 each.value 에서 각 사용자 이름을 사용할 수 있습니다.
    • main.tf : 일반적으로는 each.key 는 키/값 쌍 맵에서만 사용가능하지만, 사용자 이름은 each.key 에서도 사용할 수 있습니다.
      provider "aws" {
        region = "ap-northeast-2"
      }
      
      resource "aws_iam_user" "myiam" {
        for_each = toset(var.user_names)
        name     = each.value
      }
      
      variable "user_names" {
        description = "Create IAM users with these names"
        type        = list(string)
        default     = ["gasida", "akbun", "ssoon"]
      }
      
      output "all_users" {
        value = aws_iam_user.myiam
      }
    • for_each 를 사용한 후에는 하나의 리소스 또는 count 를 사용한 것과 같은 리소스 배열이 되는 것이 아니리 리소스 맵 list into a set 이 됩니다.
    • init & plan & apply
      #
      terraform init -upgrade
      terraform plan && terraform apply -auto-approve
      
      # 확인
      terraform state list
      terraform output
      aws iam list-users | jq
      
      # 배포 결과 content만 필터링 확인
      cat terraform.tfstate | grep -e key -e name -e "content:"
      • all_users 출력 변수가 for_each 의 키, 즉 사용자 이름을 키로 가지며 값이 해당 리소스의 전체 출력인 맵을 포함합니다.
    • for_each 를 사용해 리소스 으로 처리하면 컬렉션 중간의 항목도 안전하게 제거할 수 있어서, count  리소스를 배열 처리보다 이점이 큽니다.
    • var.user_names 리스트 중간에 값을 제거하고 plan 으로 확인
      **terraform plan
      terraform apply -auto-approve
      terraform state list**
      aws iam list-users | jq
      
      ****# 배포 결과 content만 필터링 확인
      cat terraform.tfstate | grep -e key -e name -e "content:"
      cat terraform.tfstate.backup | grep -e key -e name -e "content:"
      
      terraform plan
      terraform apply -auto-approve
      terraform state list
      aws iam list-users | jq
      
      # 배포 결과 content만 필터링 확인
      cat terraform.tfstate | grep -e key -e name -e "content:"
      cat terraform.tfstate.backup | grep -e key -e name -e "content:"

      • 이제 주변 모든 리소스를 옮기지 않고 정확히 목표한 리소스만 삭제가 됩니다.
      • 따라서 리소스의 구별되는 여러 복사본을 만들 때는 count 대신 for_each 를 사용하는 것이 바람직합니다.

  • list 타입과 each object 접근 비교
    • list 타입 
      • sample[0] = aaa / sample[1] = bbb / sample[2] = ccc 와 같이 index 형식으로 접근
    • variable "sample" { type = list(string) default = [ "aaa", "bbb", "ccc" ] }
    • map 타입
      variable "sample" {
      	type		= map(string)
          default 		= { "aaa" : "aaa", "bbb" : "bbb", "ccc" :"ccc" }
      }
      • sample["aaa"] = aaa / sample["bbb"] = bbb / sample["ccc"] = ccc 와 같이 각각의 고유한 키로 접근
    • key, value 속성은 json 접근으로 비유하면 쉽게 이해 가능
      a = {
      	"hello" : "world"
      }
      # hell0 = key , "world" = value가 된다.
      # world를 얻기 위해 a["hello"]로 접근한다.
       

* map 과 each object 타입의 경우 값을 얻기 위해 해당하는 키로 접근을 하게 된다.

map 타입의 sample_b에서 index타입으로 접근하게 될 경우 error 발생 -> sample_b[0]으로 조회하면 error

 

* for_each 구문의 가장 중요한 핵심은 each object ! 
* for_each는 map과 set타입만 사용가능하기 때문에 다른 타입으로 사용할 경우 타입 변환을 해줘야 한다.

count는 index로 접근

? username 'su'와 'ho'를 바꾸게 되면 어떻게 될까?

사람의 입장에서는 변한게 없지만, Terraform 입장에서는 index 변경이 있기 때문에 update를 하려한다.

 → IAM user 'su'/'ho'를 삭제 한 뒤 'ho'를 만들어야 하는데 'su'/'ho'가 이미 존재하기 때문에 error가 발생하게 된다.

 

count를 주석처리하고 for_each를 사용
키로 접근되는 걸 확인할 수 있다.
count와 동일하게 'ho'와 'su'를 비교해서 test

 

for_each에서는 count와 달리 각각의 인자에 고유하게 접근하기 때문에 변화가 없는것으로 인식하고 성공적으로 apply 할 수 있었다.

 


 

for Expressions : 복합 형식 값의 형태를 변환하는 데 사용 (cf. for_each와 다름)
  • 예를 들어 list 값의 포맷을 변경하거나 특정 접두사 prefix를 추가할 수도 있고, output에 원하는 형태로 반복적인 결과를 표현할 수 도 있다.
    • list 타입의 경우 또는 인덱스와 값을 반환
    • map 타입의 경우 또는 키와 값에 대해 반환
    • set 타입의 경우 키 값에 대해 반환
  • main.tf 파일 수정 : list의 내용을 담는 리소스를 생성, var.name의 내용이 결과 파일에 content로 기록됨
    variable "names" {
      default = ["a", "b", "c"]
    }
    
    resource "local_file" "abc" {
      content  = jsonencode(var.names) # 결과 : ["a", "b", "c"]
      filename = "${path.module}/abc.txt"
    }
    
    output "file_content" {
      value = local_file.abc.content
    }
  • 실행 후 확인 : jsonencode Function - 링크
    • jsonencode encodes a given value to a string using JSON syntax.
#
terraform apply -auto-approve
terraform state list
terraform output --raw file_content | jq

ls *.txt
cat abc.txt ;echo

# 참고 jsonencode Function
terraform console
-----------------
var.names
type(var.names)
jsonencode(var.names)
jsonencode({"hello"="world"})

exit
-----------------

 

 

목표 : var.name 값을 일괄적으로 대문자로 변환!

  • main.tf 파일 수정 : content의 값 정의에 for 구문을 사용하여 내부 값을 일괄적으로 변경
variable "names" {
  default = ["a", "b", "c"]
}

resource "local_file" "abc" {
  content  = jsonencode([for s in var.names : upper(s)]) # 결과 : ["A", "B", "C"]
  filename = "${path.module}/abc.txt"
}

output "file_content" {
  value = local_file.abc.content
}

 

  • 실행 후 확인 - title for
    • title converts the first letter of each word in the given string to uppercase.
#
terraform apply -auto-approve
terraform state list
terraform output --raw file_content | jq
ls *.txt
cat abc.txt ;echo

# 참고 jsonencode Function
terraform console
-----------------
jsonencode([for s in var.names : upper(s)])
[for s in var.names : upper(s)]
[for txt in var.names : upper(txt)]

title("hello world")

exit
-----------------

 

  • for 구문을 사용하는 몇 가지 규칙은 다음과 같다
    • list 유형의 경우 반환 받는 값이 하나로 되어 있으면 을, 두 개의 경우 앞의 인수가 인덱스를 반환하고 뒤의 인수가 을 반환
      • 관용적으로 인덱스는 i, 값은 v로 표현
    • map 유형의 경우 반환 받는 값이 하나로 되어 있으면 를, 두 개의 경우 앞의 인수가 를 반환하고 뒤의 인수가 을 반환
      • 관용적으로 키는 k, 값은 v로 표현
    • 결과 값은 for 문을 묶는 기호가 [ ]인 경우 tuple로 반환되고 { }인 경우 object 형태로 반환
[for item in 조회할 expression : 변환작업 ] -- 리턴 타입이 tuple
{for item in 조회할 expression : 변환작업 } -- 리턴 타입이 object
    • object 형태의 경우 에 대한 쌍은 ⇒ 기호로 구분
    • { } 형식을 사용해 object 형태로 결과를 반환하는 경우 키 값은 고유해야 하므로 값 뒤에 그룹화 모드 심볼(…)를 붙여서 키의 중복을 방지(SQL의 group by 문 또는 Java의 MultiValueMap과 같은 개념)
    • if 구문을 추가해 조건 부여 가능
  • main.tf 파일 수정 : list 유형에 대한 for 구문 처리의 몇 가지 예를 확인
    variable "names" {
      type    = list(string)
      default = ["a", "b"]
    }
    
    output "A_upper_value" {
      value = [for v in var.names : upper(v)]
    }
    
    output "B_index_and_value" {
      value = [for i, v in var.names : "${i} is ${v}"]
    }
    
    output "C_make_object" {
      value = { for v in var.names : v => upper(v) }
    }
    
    output "D_with_filter" {
      value = [for v in var.names : upper(v) if v != "a"]
    }
     
  •  실행 후 확인
    #
    terraform apply -auto-approve
    terraform state list
    
    # 
    terraform output
    terraform output A_upper_value
    terraform output D_with_filter
    
    # 
    terraform console
    -----------------
    var.names
    type(var.names)
    
    # 리턴 타입 tuple : 값 v
    [for v in var.names : upper(v)]
    type([for v in var.names : upper(v)])
    
    # 리턴 타입 tuple : 인덱스 i, 값 v
    [for i, v in var.names : "${i} is ${v}"]
    type([for i, v in var.names : "${i} is ${v}"])
    
    # 리턴 타입 object : object 형태의 경우 키와 값에 대한 쌍은 ⇒ 기호로 구분
    { for v in var.names : v => upper(v) }
    type({ for v in var.names : v => upper(v) })
    
    # 조건문 활용 : f 구문을 추가해 조건 부여 가능
    [for v in var.names : upper(v) if v != "a"]
    type([for v in var.names : upper(v) if v != "a"])
    
    exit
    -----------------
  • main.tf 파일 수정 : map 유형에 대한 for 구문 처리의 몇 가지 예를 확인
    variable "members" {
      type = map(object({
        role = string
      }))
      default = {
        ab = { role = "member", group = "dev" }
        cd = { role = "admin", group = "dev" }
        ef = { role = "member", group = "ops" }
      }
    }
    
    output "A_to_tupple" {
      value = [for k, v in var.members : "${k} is ${v.role}"]
    }
    
    output "B_get_only_role" {
      value = {
        for name, user in var.members : name => user.role
        if user.role == "admin"
      }
    }
    
    output "C_group" {
      value = {
        for name, user in var.members : user.role => name...
      }
    }
  • 실행 후 확인
    #
    terraform apply -auto-approve
    terraform state list
    
    # 
    terraform output
    terraform output A_to_tupple
    terraform output B_get_only_role
    terraform output C_group
    
    # 
    terraform console
    -----------------
    var.members
    type(var.members)
    
    [for k, v in var.members : "${k} is ${v.role}"]
    type([for k, v in var.members : "${k} is ${v.role}"])
    
    {for name, user in var.members : name => user.role}
    type({for name, user in var.members : name => user.role})
    
    {for name, user in var.members : name => user.role  if user.role == "admin"}
    
    # { } 형식을 사용해 object 형태로 결과를 반환하는 경우 키 값은 고유해야 하므로 값 뒤에 그룹화 모드 심볼(…)를 붙여서 키의 중복을 방지
    {for name, user in var.members : user.role => name...}
    
    exit
    -----------------

 

  • for expression 실습 추가
    • main.tf 파일 수정 : map 유형에 대한 for 구문 처리의 몇 가지 예를 확인
      variable "fruits_set" {
        type        = set(string)
        default     = ["apple", "banana"]
        description = "fruit example"
      }
      
      variable "fruits_list" {
        type        = list(string)
        default     = ["apple", "banana"]
        description = "fruit example"
      }
      
      variable "fruits_map" {
        type        = map(string)
        default     = {"first": "apple", "second": "banana"}
        description = "fruit example"
      }
    • 실행 후 확인
      #
      terraform console
      -----------------
      var.fruits_set
      var.fruits_list
      var.fruits_map
      
      type(var.fruits_set)
      type(var.fruits_list)
      type(var.fruits_map)
      
      #
      for item in var.fruits_set: item
      [for item in var.fruits_set: item]
      type([for item in var.fruits_set: item])
      
      {for item in var.fruits_set: item}
      {for key,value in var.fruits_set: key => value}
      type({for key,value in var.fruits_set: key => value})
      
      #
      for item in var.fruits_list: item
      [for item in var.fruits_list: item]
      type([for item in var.fruits_list: item])
      
      {for item in var.fruits_list: item}
      {for key,value in var.fruits_list: key => value}
      {for i, v in var.fruits_list: i => v}
      type({for i, v in var.fruits_list: i => v})
      
      #
      for item in var.fruits_map: item
      [for item in var.fruits_map: item]
      type([for item in var.fruits_map: item])
      
      {for item in var.fruits_map: item}
      {for key,value in var.fruits_map: key => value}
      {for k, v in var.fruits_map: k => v}
      type({for k, v in var.fruits_map: k => v})
      
      
      exit
      -----------------

리턴타입 없이 조회를 했을 경우 error 발생

 

object type은 key와 value가 필요한데 현재 value(='item') 만을 지정해주었기 때문에 에러가 발생하는 것.
object type으로 변환하기 위해 key와 value를 만들어 설정함.
type 함수를 사용해 리턴타입이 어떤 타입인지를 확인할 수 있다.

 

list와 map도 동일하게 실습

 

 


 

dynamic : 리소스 내부 속성 블록을 동적인 블록으로 생성 - Docs Blog
  • count 나 for_each 구문을 사용한 리소스 전체를 여러 개 생성하는 것 이외도 리소스 내에 선언되는 구성 블록을 다중으로 작성해야 하는 경우가 있다.
  • 예를 들면 AWS Security Group 리소스 구성에 ingress, egress 요소가 리소스 선언 내부에서 블록 형태로 여러 번 정의되는 경우다.
resource "aws_security_group" "example" {
  name        = "example-security-group"
  description = "Example security group"
  vpc_id.     = aws_vpc.main.id

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    ipv6_cidr_blocks = ["::/0"]
  }
}
  • 리소스 내의 블록 속성(Attributes as Blocks)은 리소스 자체의 반복 선언이 아닌 내부 속성 요소 중 블록으로 표현되는 부분에 대해서만 반복 구문을 사용해야 하므로, 이때 dynamic 블록을 사용해 동적인 블록을 생성 할 수 있다.
  • dynamic 블록을 작성하려면, 기존 블록의 속성 이름을 dynamic 블록의 이름으로 선언하고 기존 블록 속성에 정의되는 내용을 content 블록에 작성한다.
  • 반복 선언에 사용되는 반복문 구문은 for_each를 사용한다. 기존 for_each 적용 시 each 속성에 key, value가 적용되었다면 dynamic에서는 dynamic에 지정한 이름에 대해 속성이 부여된다.
  • dynamic 블록 활용 예
일반적인 블록 속성 반복 적용 시 dynamic 블록 적용 시
resource "provider_resource" "name" {
  name = "some_resource"

  some_setting {
    key = a_value
  }
 
  some_setting {
    key = b_value
  }

  some_setting {
    key = c_value
  }

  some_setting {
    key = d_value
  }
}
resource "provider_resource" "name" {
  name = "some_resource"

  dynamic "some_setting" {
    for_each = {
       a_key = a_value
       b_key = b_value
       c_key = c_value d_key =
       d_value
     }

    content {
      key = some_setting.value
    }
  }
}
  • main.tf 파일 수정 : archive 프로바이더(링크)의 archive_file에 source 블록 선언을 반복 - 링크
    data "archive_file" "dotfiles" {
      type        = "zip"
      output_path = "${path.module}/dotfiles.zip"
    
      source {
        content  = "hello a"
        filename = "${path.module}/a.txt"
      }
    
      source {
        content  = "hello b"
        filename = "${path.module}/b.txt"
      }
    
      source {
        content  = "hello c"
        filename = "${path.module}/c.txt"
      }
    }
  • 실행 후 확인
    #
    terraform init -upgrade && terraform apply -auto-approve
    terraform state list
    terraform state show data.archive_file.dotfiles
    ls *.zip
    unzip dotfiles.zip
    ls *.txt
    cat a.txt ; echo
  • main.tf 파일 수정 : 리소스 내에 반복 선언 구성을 dynamic 블록으로 재구성
    variable "names" {
      default = {
        a = "hello a"
        b = "hello b"
        c = "hello c"
      }
    }
    
    data "archive_file" "dotfiles" {
      type        = "zip"
      output_path = "${path.module}/dotfiles.zip"
    
      dynamic "source" {
        for_each = var.names
        content {
          content  = source.value
          filename = "${path.module}/${source.key}.txt"
        }
      }
    }
  • 실행 후 확인 : 동일한 결과가 기대되어 변경 사항이 없다!
    #
    terraform apply -auto-approve
    terraform state list
    terraform state show data.archive_file.dotfiles
    ls *.zip