본문 바로가기
DevOps

[T1014-이론] 3장 기본 사용법 (5)

by 서어켜엉 2024. 6. 30.
해당 내용은 cloudNet@ 팀의 가시다 님이 진행하는 테라폼 스터디 T101 4기에서 다룬 내용과 "테라폼으로 시작하는 IaC" (한빛미디어) 저서 내용을 정리한 것입니다.

 

9. 반복문

for_each

  • for-each는 반복(for)을 할 때 타입 값에 대해 하나하나 each object로 접근한다는 의미입니다.
  • each object는 key, value 2개의 속성을 가지고 있습니다.
    아래 속성은 for_each가 선언된 블록에서만 사용할 수 있다.
    • each.key
    • each.value
  • map, set 타입에 대해서만 each object로 접근할 수 있다.
    map, set 타입이 아닌 expression은 toset, tomap 등을 사용해서 타입 변환을 해야한다.
  • 예제코드
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"]'

 

for_each : count 와 비교

  • 예제 코드
variable "users" {
  default = [
    "smlim",
    "yjna",
    "jwlee",
    "nykim"
  ]
}

resource "local_file" "t1014" {
  count = length(var.users)
  content = "This is file name is ${count.index}-${var.users[count.index]}.txt"
  filename = "${path.module}/${count.index}-${var.users[count.index]}.txt"
}
  • count 값을 var.users의 길이로 참조하였고, 인덱스로 변수에 접근을 하면, 0-smlim.txt / 1-yjna.txt / 2-jwlee.txt / 3-nykim.txt 파일이 생성된다.
  • 이 때 1-yjna.txt 파일만 삭제하고 싶어서 variable 에서 yjna를 삭제하게 되면 jwlee, nykim 의 인덱스도 변경이 되어서 파일명이 유지가 되지 않는다.
  • 이 때 variable 을 object 형태로 선언한 후 for_each 를 사용한다.
variable "users" {
  default = {
    "1": "smlim",
    "2": "yjna",
    "3": "jwlee",
    "4": "nykim"
  }
}

resource "local_file" "t1014" {
  for_each = tomap(var.users)
  content = "This file name is ${each.value}.txt"
  filename = "${path.module}/${each.key}-${each.value}.txt"
}

  • 이 때 variable 에서 2 : "yjna" 를 삭제하면 인덱스 값을 유지한 상태에서 특정 파일만 삭제할 수 있다. 

 

for Expression

  • 예를 들어 list 값의 포맷을 변경하거나 특정 접두사 prefix를 추가할 수도 있고, output에 원하는 형태로 반복적인 결과를 표현할 수 도 있다.
    • list 타입의 경우 또는 인덱스와 값을 반환
    • map 타입의 경우 또는 키와 값에 대해 반환
    • set 타입의 경우 키 값에 대해 반환
  • 예제 코드
variable "names" {
  default = ["a", "b", "c"]
}

# variable 에 있는 값들을 모두 대문자로 파일에 쓰는 것이 목표
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
}

 

  • for 구문을 사용하는 몇 가지 규칙은 다음과 같다
    • list 유형의 경우 반환 받는 값이 하나로 되어 있으면 을, 두 개의 경우 앞의 인수가 인덱스를 반환하고 뒤의 인수가 을 반환
      • 관용적으로 인덱스는 i, 값은 v로 표현
    • map 유형의 경우 반환 받는 값이 하나로 되어 있으면 를, 두 개의 경우 앞의 인수가 를 반환하고 뒤의 인수가 을 반환
      • 관용적으로 키는 k, 값은 v로 표현
    • 결과 값은 for 문을 묶는 기호가 **[ ]**인 경우 tuple로 반환되고 **{ }**인 경우 object 형태로 반환
    • object 형태의 경우 에 대한 쌍은 ⇒ 기호로 구분
    • { } 형식을 사용해 object 형태로 결과를 반환하는 경우 키 값은 고유해야 하므로 값 뒤에 그룹화 모드 심볼(…)를 붙여서 키의 중복을 방지(SQL의 group by 문 또는 Java의 MultiValueMap과 같은 개념)
    • if 구문을 추가해 조건 부여 가능
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"]
}

 

  • 악분님 for expression 실습
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"
}

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})

 

dynamic

리소스 내부 속성 블록 동적인 블록으로 생성

  • 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에 지정한 이름에 대해 속성이 부여된다.
  • 예제 코드
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"
  }
}

반복되는 source 선언을 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"
    }
  }
}