Programming/IaC

[IaC] 기본 사용법( data source, input variables, local, output, loop)

Hayley Shim 2023. 10. 28. 18:01

안녕하세요. 최근 도서 “테라폼으로 시작하는 IaC” 의 내용을 기준으로 스터디한 내용을 정리했습니다. 지난 [IaC] IaC/테라폼 이해, 기본 명령 사용법 내용에 이어 해당 글에서는 기본 사용법(data source, input variables, local, output, loop)에 대해 알아보겠습니다.

 

2.기본 명령어

1. data source : 테라폼으로 정의되지 않은 외부 리소스 또는 저장된 정보를 테라폼 내에서 참조할 때 사용

  • 데이터 소스 블록은 data로 시작하고 이후 ‘데이터 소스 유형’을 정의 (Resource 블록 정의와 유사)

사용 예)

data "local_file" "foo" {
  filename = "${path.module}/foo.bar"
}

resource "aws_s3_object" "shared_zip" {
  bucket   = "my-bucket"
  key      = "my-key"
  content  = data.local_file.foo.content
}                                                                                                                                                                                                                                                                                                          

데이터 소스를 정의할 때 사용 가능한 메타인수

  • depends_on : 종속성을 선언하며, 선언된 구성요소와의 생성 시점에 대해 정의
  • count : 선언된 개수에 따라 여러 리소스를 생성
  • for_each : map 또는 set 타입의 데이터 배열의 값을 기준으로 여러 리소스를 생성
  • lifecycle : 리소스의 수명주기 관리

실습 확인

# 실습을 위해 3.5 디렉터리를 신규 생성
$ mkdir 3.5 && cd 3.5
# 실습 확인을 위해서 abc.txt 파일 생성
$ echo "t101 study - 2week" > abc.txt

$ terraform init && terraform plan && terraform apply -auto-approve
$ terraform state list# 테라폼 콘솔 : 데이터 소스 참조 확인
$ echo "data.local_file.abc" | terraform console

데이터 소스 속성 참조

  • 데이터 소스로 읽은 대상을 참조하는 방식은 리소스와 구별되게 data가 앞에 붙는다. 속성 값은 다음과 같이 접근할 수 있다.

# Terraform Code
data "<리소스 유형>" "<이름>" {
  <인수> = <값>
}

# 데이터 소스 참조
data.<리소스 유형>.<이름>.<속성>
  • 코드 예시 : 데이터 소스를 활용해 AWS 가용영역 인수를 정의하고 리전 내에서 사용 가능한 가용영역 목록 가져와서 사용하는 방법

# Declare the data source
data "aws_availability_zones" "available" {
  state = "available"
}
resource "aws_subnet" "primary" {
  availability_zone = data.aws_availability_zones.available.names[0]
  # e.g. ap-northeast-2a
}
resource "aws_subnet" "secondary" {
  availability_zone = data.aws_availability_zones.available.names[1]
  # e.g. ap-northeast-2b
}
  • 해당 문서에서 데이터 소스로 가져오기 위한 조건인 인수는 Argument로 표현되어 있고, 가져온 데이터 소스의 내용은 Attributes에 안내되어 있다
  • main.tf 파일 코드 수정
resource "local_file" "abc" {
  content  = "123!"
  filename = "${path.module}/abc.txt"
}

data "local_file" "abc" {
  filename = local_file.abc.filename
}

resource "local_file" "def" {
  content  = data.local_file.abc.content
  filename = "${path.module}/def.txt"
}

$ terraform apply -auto-approve
$ terraform state list

# 파일 확인
$ ls *.txt
diff abc.txt def.txt

# graph 확인
$ terraform graph > graph.dot

# 테라폼 콘솔 : 데이터 소스 참조 확인
$ terraform console
> 
data.local_file.abc.content
...
exit

ls -l

2. input variables

  • 입력 변수는 인프라를 구성하는 데 필요한 속성 값을 정의해 코드의 변경 없이 여러 인프라를 생성하는 데 목적
  • 변수는 variable로 시작되는 블록으로 구성됨. 변수 블록 뒤의 이름 값은 동일 모듈 내 모든 변수 선언에서 고유해야 하며, 이 이름으로 다른 코드 내에서 참조됨
# variable 블록 선언의 예
variable "<이름>" {
 <인수> = <값>
}

variable "image_id" {
 type = string
}
  • 테라폼 예약 변수 이름으로 사용 불가능한이름 : source, version, providers, count, for_each, lifecycle, depends_on, locals

변수 정의 시 사용 가능한 메타인수

  • default : 변수 값을 전달하는 여러 가지 방법을 지정하지 않으면 기본값이 전달됨, 기본값이 없으면 대화식으로 사용자에게 변수에 대한 정보를 물어봄
  • type : 변수에 허용되는 값 유형 정의, string number bool list map set object tuple 와 유형을 지정하지 않으면 any 유형으로 간주
  • description : 입력 변수의 설명
  • validation : 변수 선언의 제약조건을 추가해 유효성 검사 규칙을 정의 — 링크
  • sensitive : 민감한 변수 값임을 알리고 테라폼의 출력문에서 값 노출을 제한 (암호 등 민감 데이터의 경우) — 링크
  • nullable : 변수에 값이 없어도 됨을 지정

변수 유형

1)기본 유형

  • string : 글자 유형
  • number : 숫자 유형
  • bool : true 또는 false
  • any : 명시적으로 모든 유형이 허용됨을 표시

2)집합 유형

  • list (<유형>): 인덱스 기반 집합
  • map (<유형>): 값 = 속성 기반 집합이며 키값 기준 정렬
  • set (<유형>): 값 기반 집합이며 정렬 키값 기준 정렬
  • object ({<인수 이름>=<유형>, …})
  • tuple ([<유형>, …])

list와 set은 선언하는 형태가 비슷하지만 참조 방식이 인덱스와 키로 각각 차이가 있고, map와 set의 경우 선언된 값이 정렬되는 특징을 가진다.

입력 변수 사용 예시

  1. 전달할 값이 number 인지 확인하는 입력 변수의 예
variable "number_example" {
  description = "An example of a number variable in Terraform"
  type        = number
  default     = 42
}

2. 전달할 값이 list 인지 확인하는 입력 변수의 예

variable "list_example" {
  description = "An example of a list in Terraform"
  type        = list
  default     = ["a", "b", "c"]
}

3. 조건 결합 사용 가능. 다음은 리스트의 모든 항목이 number 인 list 의 예

variable "list_numeric_example" {
  description = "An example of a numeric list in Terraform"
  type        = list(number)
  default     = [1, 2, 3]
}

4.다음은 모든 값이 string 인 map 의 예

variable "map_example" {
  description = "An example of a map in Terraform"
  type        = map(string)

  default = {
    key1 = "value1"
    key2 = "value2"
    key3 = "value3"
  }
}

5. 다음은 object 또는 tuple 제약 조건을 사용하여 보다 복잡한 구조적 유형(structural type) 작성 가능

variable "object_example" {
  description = "An example of a structural type in Terraform"
  type        = object({
    name    = string
    age     = number
    tags    = list(string)
    enabled = bool
  })

  default = {
    name    = "value1"
    age     = 42
    tags    = ["a", "b", "c"]
    enabled = true
  }
}

변수 유형별 선언 방식의 예시 — main.tf 파일

# 실습을 위해 3.6 디렉터리를 신규 생성 후 열기
$ cd .. && rm -rf 3.5
$ mkdir 3.6 && cd 3.6
variable "string" {
  type        = string
  description = "var String"
  default     = "myString"
}

variable "number" {
  type    = number
  default = 123
}

variable "boolean" {
  default = true
}

variable "list" {
  default = [
    "google",
    "vmware",
    "amazon",
    "microsoft"
  ]
}

output "list_index_0" {
  value = var.list.0
}

output "list_all" {
  value = [
    for name in var.list : upper(name)
  ]
}

variable "map" { # Sorting
  default = {
    aws   = "amazon",
    azure = "microsoft",
    gcp   = "google"
  }
}

variable "set" { # Sorting
  type = set(string)
  default = [
    "google",
    "vmware",
    "amazon",
    "microsoft"
  ]
}

variable "object" {
  type = object({ name = string, age = number })
  default = {
    name = "abc"
    age  = 12
  }
}

variable "tuple" {
  type    = tuple([string, number, bool])
  default = ["abc", 123, true]
}

variable "ingress_rules" { # optional ( >= terraform 1.3.0)
  type = list(object({
    port        = number,
    description = optional(string),
    protocol    = optional(string, "tcp"),
  }))
  default = [
    { port = 80, description = "web" },
  { port = 53, protocol = "udp" }]

확인


$ terraform init && terraform plan && terraform apply -auto-approve
$ terraform state list

#
$ terraform output
list_all = [
  "GOOGLE",
  "VMWARE",
  "AMAZON",
  "MICROSOFT",
]
list_index_0 = "google"

유효성 검사 : 입력되는 변수 타입 지정 이외, 사용자 지정 유효성 검사가 가능

  • 변수 블록 내에 validation 블록에서 조건인 condition에 지정되는 규칙이 true 또는 false를 반환해야 하며, error_message는 condition 값의 결과가 false 인 경우 출력되는 메시지를 정의
  • regex 함수는 대상의 문자열에 정규식을 적용하고 일치하는 문자열을 반환하는데, 여기에 can 함수를 함께 사용하면 정규식에 일치하지 않는 경우의 오류를 검출
  • validation 블록은 중복으로 선언할 수 있음
  • variable 유효성 검사의 예 — main.tf 코드 파일 내용 수정
variable "image_id" {
  type        = string
  description = "The id of the machine image (AMI) to use for the server."

  validation {
    condition     = length(var.image_id) > 4
    error_message = "The image_id value must exceed 4."
  }

  validation {
    # regex(...) fails if it cannot find a match
    condition     = can(regex("^ami-", var.image_id))
    error_message = "The image_id value must starting with \"ami-\"."
  }
}

확인

#
terraform apply -auto-approve
var.image_id
  The id of the machine image (AMI) to use for the server.

  Enter a value: ami
...

#
terraform apply -auto-approve
var.image_id
  The id of the machine image (AMI) to use for the server.

  Enter a value: ami-
...


#
terraform apply -auto-approve
var.image_id
  The id of the machine image (AMI) to use for the server.

  Enter a value: ami-12345678
...

변수 참조 : variable은 코드 내에서 var.<이름>으로 참조된다.

main.tf 코드 파일 내용 수정

variable "my_password" {}

resource "local_file" "abc" {
  content  = var.my_password
  filename = "${path.module}/abc.txt"
}
# 실행
terraform init -upgrade
terraform apply -auto-approve
var.my_password
  Enter a value: qwe123
...

# 확인
terraform state list
terraform state show local_file.abc
cat abc.txt ; echo

# 해당 파일에 다른 내용으로 변경해보기
terraform apply -auto-approve
var.my_password
  Enter a value: t101mypss
...

# 확인
cat abc.txt ; echo

민감한 변수 취급 : 입력 변수의 민감 여부 선언 가능

main.tf 코드 파일 내용 수정

  • 기본값 추가로 입력 항목은 발생하지 않지만, 출력에서 참조되는 변수 값이(sensitive)로 감춰지는 것을 확인 할 수 있다
variable "my_password" {
  default   = "password"
  sensitive = true
}

resource "local_file" "abc" {
  content  = var.my_password
  filename = "${path.module}/abc.txt"
}

확인 : 민감한 변수로 지정해도 terraform.tfstate 파일에는 결과물이 평문으로 기록되므로 State 파일의 보안에 유의해야 한다. [참고 — Docs]

# 출력부분에 내용 안보임!
terraform apply -auto-approve
terraform state show local_file.abc

# 결과물 파일 확인
cat abc.txt ; echo

# terraform.tfstate 파일 확인
cat terraform.tfstate | grep '"content":'
            "content": "password",

변수 입력 방식과 우선순위

  • variable의 목적은 코드 내용을 수정하지 않고 테라폼의 모듈적 특성을 통해 입력되는 변수로 재사용성을 높이는 데 있다.
  • 특히 입력 변수라는 명칭에 맞게 사용자는 프로비저닝 실행 시에 원하는 값으로 변수에 정의할 수 있다.
  • 선언되는 방식에 따라 변수의 우선순위가 있으므로, 이를 적절히 사용해 로컬 환경과 빌드 서버 환경에서의 정의를 다르게 하거나, 프로비저닝 파이프라인을 구성하는 경우 외부 값을 변수에 지정할 수 있다.
  • main.tf 코드 파일 내용 수정
variable "my_var" {}

resource "local_file" "abc" {
  content  = var.my_var
  filename = "${path.module}/abc.txt"
}

Variable Loading Precedence

  • [우선순위 수준]의 숫자가 작을수록 우선순위도 낮다.

[실습1] VPC + 보안그룹 + EC2 배포

  • 실습내용:default VPC 대신 VPC를 직접 만들고 해당 VPC 내에 EC2 1대를배포

vpc.tf 배포

# 신규 디렉토리 생성
mkdir my-vpc-ec2
cd my-vpc-ec2


# vpc.tf 파일 생성
provider "aws" {
  region =  "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block        = "10.10.0.0/16"

  tags = {
    Name = "t101-study"
  }
}

# 배포
terraform init && terraform plan && terraform apply -auto-approve
terraform state list
terraform state show aws_vpc.myvpc

# VPC 확인
export AWS_PASER=""
aws ec2 describe-vpcs | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false" | jq
aws ec2 describe-vpcs --filter 'Name=isDefault,Values=false' --output yaml


# AWS 관리콘솔에서 VPC 생성 정보 확인 : DNS 옵션값확인

#VPC.tf 내용수정: VPC DNS옵션 수정
provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block        = "10.10.0.0/16"
  enable_dns_support    = true
  enable_dns_hostnames  = true

  tags= {
    Name ="t101-study"
  }
}

# 배포
terraform plan && terraformapply -auto-approve


# VPC.tf 내용수정: 서브넷 2개 생성 추가
provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "t101-study"
  }
}

resource "aws_subnet" '"mysubnet2" {
  vpc_id      = aws_vpc.myvpc.id
  cidr_block  = "10.10.2.0/24"

  availability_zone ="ap-northeast-2c"

  tags = {
    Name = "t101-subnet2"
  }
}

output "aws_vpc_id" {
  value  =  aws_vpc.myvpc.id
}

# 배포
terraform plan && terraform apply -auto-approve
terraform state list

terraform state show aws_subnet.mysubnet1

terraform output
terraform output aws_vpc_id
terraform output -raw aws_vpc_id


#VPC.tf 내용수정 : IGW 로 전달하는 디폴트 라우팅 정보 추가


provider "aws" {
  region  = "ap-northeast-2"
}

resource "aws_vpc" "myvpc" {
  cidr_block       = "10.10.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "t101-study"
  }
}

resource "aws_subnet" "mysubnet1" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.1.0/24"

  availability_zone = "ap-northeast-2a"

  tags = {
    Name = "t101-subnet1"
  }
}

resource "aws_subnet" "mysubnet2" {
  vpc_id     = aws_vpc.myvpc.id
  cidr_block = "10.10.2.0/24"

  availability_zone = "ap-northeast-2c"

  tags = {
    Name = "t101-subnet2"
  }
}


resource "aws_internet_gateway" "myigw" {
  vpc_id = aws_vpc.myvpc.id

  tags = {
    Name = "t101-igw"
  }
}

resource "aws_route_table" "myrt" {
  vpc_id = aws_vpc.myvpc.id

  tags = {
    Name = "t101-rt"
  }
}

resource "aws_route_table_association" "myrtassociation1" {
  subnet_id      = aws_subnet.mysubnet1.id
  route_table_id = aws_route_table.myrt.id
}

resource "aws_route_table_association" "myrtassociation2" {
  subnet_id      = aws_subnet.mysubnet2.id
  route_table_id = aws_route_table.myrt.id
}

resource "aws_route" "mydefaultroute" {
  route_table_id         = aws_route_table.myrt.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.myigw.id
}

output "aws_vpc_id" {
  value = aws_vpc.myvpc.id
}

# 배포
terraform plan && terraform apply -auto-approve
terraform state list
aws_internet_gateway.myigw
aws_route.mydefaultroute
aws_route_table.**myrt
aws_route_table_association.myrtassociation1
aws_route_table_association.myrtassociation2
aws_subnet.mysubnet1
aws_subnet.mysubnet2
aws_vpc.myvpc

terraform state show aws_route.mydefaultroute

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 라우팅 테이블 확인
#aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=t101-rt' --query 'RouteTables[].Associations[].SubnetId'
aws ec2 describe-route-tables --filters 'Name=tag:Name,Values=**t101-rt**' --output table

sg.tf 배포

# sg.tf 파일 생성 : 보안그룹 생성
resource "aws_security_group" "mysg" {
  vpc_id      = aws_vpc.myvpc.id
  name        = "T101 SG"
  description = "T101 Study SG"
}

resource "aws_security_group_rule" "mysginbound" {
  type              = "ingress"
  from_port         = 80
  to_port           = 80
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

resource "aws_security_group_rule" "mysgoutbound" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.mysg.id
}

# 보안그룹 배포
ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
aws_security_group.mysg
aws_security_group_rule.mysginbound
aws_security_group_rule.mysgoutbound
...

terraform state show aws_security_group.mysg
terraform state show aws_security_group_rule.mysginbound

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

ec2.tf 파일 생성 : EC2 생성

data "aws_ami" "my_amazonlinux2" {
  most_recent = true
  filter {
    name   = "owner-alias"
    values = ["amazon"]
  }

  filter {
    name   = "name"
    values = ["amzn2-ami-hvm-*-x86_64-ebs"]
  }

  owners = ["amazon"]
}

resource "aws_instance" "myec2" {

  depends_on = [
    aws_internet_gateway.myigw
  ]

  ami                         = data.aws_ami.my_amazonlinux2.id
  associate_public_ip_address = true
  instance_type               = "t2.micro"
  vpc_security_group_ids      = ["${aws_security_group.mysg.id}"]
  subnet_id                   = aws_subnet.mysubnet1.id

  user_data = <<-EOF
              #!/bin/bash
              wget https://busybox.net/downloads/binaries/1.31.0-defconfig-multiarch-musl/busybox-x86_64
              mv busybox-x86_64 busybox
              chmod +x busybox
              RZAZ=$(curl http://169.254.169.254/latest/meta-data/placement/availability-zone-id)
              IID=$(curl 169.254.169.254/latest/meta-data/instance-id)
              LIP=$(curl 169.254.169.254/latest/meta-data/local-ipv4)
              echo "<h1>RegionAz($RZAZ) : Instance ID($IID) : Private IP($LIP) : Web Server</h1>" > index.html
              nohup ./busybox httpd -f -p 80 &
              EOF

  user_data_replace_on_change = true

  tags = {
    Name = "t101-myec2"
  }
}

output "myec2_public_ip" {
  value       = aws_instance.myec2.public_ip
  description = "The public IP of the Instance"
}

배포 후 EC2 확인

ls *.tf
terraform plan && terraform apply -auto-approve
terraform state list
data.aws_ami.my_amazonlinux2
aws_instance.myec2
...

terraform state show data.aws_ami.my_amazonlinux2
terraform state show aws_instance.myec2

# 데이터소스 값 확인
terraform console
> 
data.aws_ami.my_amazonlinux2.id
"ami-01c81850a6167bb81"
data.aws_ami.my_amazonlinux2.image_id
data.aws_ami.my_amazonlinux2.name
data.aws_ami.my_amazonlinux2.owners
data.aws_ami.my_amazonlinux2.platform_details
data.aws_ami.my_amazonlinux2.hypervisor
data.aws_ami.my_amazonlinux2.architecture
exit

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# 출력된 EC2 퍼블릭IP로 cul 접속 확인
terraform output -raw myec2_public_ip
13.29.103.4

MYIP=$(terraform output -raw myec2_public_ip)
while true; do curl --connect-timeout 1  http://$MYIP/ ; echo "------------------------------"; date; sleep 1; done

삭제

terraform destroy -auto-approve

3.local

  • 코드 내에서 사용자가 지정한 값 또는 속성 값을 가공해 참조 가능한 local (지역 값)은 외부에서 입력되지 않고, 코드 내에서만 가공되어 동작하는 값을 선언
  • local은 입력 변수와 달리 선언된 모듈 내에서만 접근 가능하고, 변수처럼 실행 시에 입력받을 수 없다
  • 로컬은 사용자가 테라폼 코드를 구현할 때 값이나 표현식을 반복적으로 사용할 수 있는 편의를 제공
  • 빈번하게 여러 곳에서 사용되는 경우 실제 값에 대한 추적이 어려워져 유지 관리 측면에서 부담이 발생할 수 있으므로 주의

local 선언

  • 로컬이 선언되는 블록은 locals로 시작한다. 선언되는 인수에 표현되는 값은 상수만이 아닌 리소스의 속성, 변수의 값들도 조합해 정의할 수 있다.
  • 동일한 tf 파일 내에서 여러 번 선언하는 것도 가능하고 여러 파일에 걸쳐 만드는 것도 가능하다.
  • 다만 lcoals에 선언한 로컬 변수 이름은 전체 루트 모듈 내에서 유일해야 한다.
  • 정의되는 속성 값은 지정된 값의 형태에 따라 다양한 유형으로 정의할 수 있다.
  • main.tf 파일 : local 값 선언 방식의 예
# 실습을 위해 3.7 디렉토리를 생성하고 main.tf 파일을 생성
$ cd .. && rm -rf 3.6
$ mkdir 3.7 && cd 3.7
variable "prefix" {
  default = "hello"
}

locals {
  name    = "terraform"
  content = "${var.prefix} ${local.name}"
  my_info = {
    age    = 20
    region = "KR"
  }
  my_nums = [1, 2, 3, 4, 5]
}

locals {
  content = "content2" # 중복 선언되었으므로 오류가 발생한다.
}

실행

$ terraform init

main.tf 파일 수정

variable "prefix" {
  default = "hello"
}

locals {
  name    = "terraform"
  content = "${var.prefix} ${local.name}"
  my_info = {
    age    = 20
    region = "KR"
  }
  my_nums = [1, 2, 3, 4, 5]
}
#
$ terraform init
$ terraform apply -auto-approve
# 실제로 사용한 리소스가 없음
$ terraform state list
# 출력이 없다.

local 참조

  • 선언된 local 값은 local.<이름>으로 참조할 수 있다.
  • 테라폼 구성 파일을 여러 개 생성해 작업하는 경우 서로 다른 파일에 선언되어 있더라도 다른 파일에서 참조할 수 있다.
  • main.tf 파일 내용 수정
variable "prefix" {
  default = "hello"
}

locals {
  name    = "terraform"
}

resource "local_file" "abc" {
  content  = local.content
  filename = "${path.module}/abc.txt"
}

sub.tf 파일 생성

locals {
  content = "${var.prefix} ${local.name}"
}

실행 : main.tf의 content 내용 값으로 local.content를 참조하며, 해당 값은 다른 테라폼 구성 파일에 있지만 실행 시점에는 마치 하나의 구성 파일 처럼 올바로 표기되는 것을 확인할 수 있다.

#
ls *.tf
terraform init -upgrade
terraform apply -auto-approve
terraform state list
terraform state show local_file.abc

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

#
cat abc.txt ; echo

이 예제에서는 서로 다른 테라폼 구성 파일에서도 로컬 값을 참조할 수 있다는 가능성을 확인할 수 있지만, 관리 측면에서는 서로 참조하는 로컬값이 파편화되어 유지 보수가 어려워질 수 있으므로 주의가 필요하다.

[실습2] IAM User 생성

iam user 생성

mkdir local-test && cd local-test


# iamuser.tf 파일 생성
provider "aws" {
  region = "ap-northeast-2"
}

locals {
  name = "mytest"
  team = {
    group = "dev"
  }
}

resource "aws_iam_user" "myiamuser1" {
  name = "${local.name}1"
  tags = local.team
}

resource "aws_iam_user" "myiamuser2" {
  name = "${local.name}2"
  tags = local.team
}

# 콘솔에서 IAM User 확인

terraform init && terraform apply -auto-approve
terraform state list
terraform state show aws_iam_user.myiamuser1

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot

# iam 사용자 리스트 확인
aws iam list-users | jq

# 삭제
terraform destroy -auto-approve

4.output

출력 값은 주로 테라폼 코드의 프로비저닝 수행 후의 결과 속성 값을 확인하는 용도로 사용된다.

또한 프로그래밍 언어에서 코드 내 요소 간에 제한된 노출을 지원하듯, 테라폼 모듈 간, 워크스페이스 간 데이터 접근 요소로도 활용할 수 있다.

예를 들면 자바의 getter와 비슷한 역할이다. 출력 값의 용도는 다음과 같이 정의할 수 있다.

  • 루트 모듈에서 사용자가 확인하고자 하는 특정 속성 출력
  • 자식 모듈의 특정 값을 정의하고 루트 모듈에서 결과를 참조
  • 서로 다른 루트 모듈의 결과를 원격으로 읽기 위한 접근 요소

output 선언

  • 모듈 내에서 생성되는 속성 값들은 output 블록에 정의된다

output "instance_ip_addr" {
  value = "http://${aws_instance.server.private_ip}"
}

출력되는 값은 value의 값이며 테라폼이 제공하는 조합과 프로그래밍적인 기능들에 의해 원하는 값을 출력할 수 있다.

주의할 점은 output 결과에서 리소스 생성 후 결정되는 속성 값은 프로비저닝이 완료되어야 최종적으로 결과를 확인할 수 있고 terraform plan 단계에서는 적용될 값이 출력하지 않는다는 것이다.

변수 정의 시 사용 가능한 메타인수는 다음과 같다.

  • description : 출력 값 설명
  • sensitive : 민감한 출력 값임을 알리고 테라폼의 출력문에서 값 노출을 제한
  • depends_on : value에 담길 값이 특정 구성에 종속성이 있는 경우 생성되는 순서를 임의로 조정
  • precondition : 출력 전에 지정된 조건을 검증

output 활용

# 실습을 위해 3.8 디렉토리를 생성하고 main.tf 파일을 생성
$ cd .. && rm -rf 3.7
$ mkdir 3.8 && cd 3.8

main.tf 파일 내용

  • (참고) abspath : 파일 시스템 경로를 포함하는 문자열을 가져와 절대 경로로 변환하는 함수 — 링크
resource "local_file" "abc" {
  content  = "abc123"
  filename = "${path.module}/abc.txt"
}

output "file_id" {
  value = local_file.abc.id
}

output "file_abspath" {
  value = abspath(local_file.abc.filename)
}
  • 실행 : plan 실행 시, 이미 정해진 속성은 출력을 예측하지만 아직 생성되지 않은 file_id 값은 값의 경우는 결과 예측을 할 수 없다
# plan 실행 시, 이미 정해진 속성은 출력을 예측하지만
terraform init && terraform plan
...
Changes to Outputs:
  + file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
  + file_id      = (known after apply)


# 
terraform apply -auto-approve
...
Outputs:
file_abspath = "/Users/gasida/Downloads/workspaces/3.8/abc.txt"
file_id = "6367c48dd193d56ea7b0baad25b19455e529f5ee"

#
terraform state list
terraform output
  • 추가) abs 함수: 숫자의 절대값 반환, 음수면 양수로 — 링크, abapath 함수

5. loop

list 형태의 값 목록이나 Key-Value 형태의 문자열 집합인 데이터가 있는 경우 동일한 내용에 대해 테라폼 구성 정의를 반복적으로 하지 않고 관리할 수 있다.

  1. count : 반복문, 정수 값만큼 리소스나 모듈을 생성
  • 리소스 또는 모듈 블록에 count 값이 정수인 인수가 포함된 경우 선언된 정수 값만큼 리소스나 모듈을 생성하게 된다.
  • count에서 생성되는 참조 값은 count.index이며, 반복하는 경우 0부터 1씩 증가해 인덱스가 부여된다.
  • main.tf 파일
resource "local_file" "abc" {
  count    = 5
  content  = "abc"
  filename = "${path.module}/abc.txt"
}

output "filecontent" {
  value = local_file.abc.*.content
}

output "fileid" {
  value = local_file.abc.*.id
}

output "filename" {
  value = local_file.abc.*.filename
}
  • 실행 후 확인 : 5개의 파일이 생성되어야 하지만 파일명이 동일하여 결과적으로 하나의 파일만 존재 ← count 사용 시 주의

terraform init && terraform apply -auto-approve
terraform state list
echo "local_file.abc[0]" | terraform console
ls *.txt

terraform output
# terraform output filename
# terraform output fileid
# terraform output filecontent
  • main.tf 파일 수정 : count.index 값 추가
resource "local_file" "abc" {
  count    = 5
  content  = "abc${count.index}"
  filename = "${path.module}/abc${count.index}.txt"
}

output "fileid" {
  value = local_file.abc.*.id
}

output "filename" {
  value = local_file.abc.*.filename
}

output "filecontent" {
  value = local_file.abc.*.content
}
  • 실행 후 확인
terraform init && terraform apply -auto-approve
terraform state list
echo "local_file.abc[0]" | terraform console
ls *.txt


terraform output
# terraform output filename
# terraform output fileid
# terraform output filecontent

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
  • 때때로 여러 리소스나 모듈의 count로 지정되는 수량이 동일해야 하는 상황이 있다. 이 경우 count에 부여되는 정수 값을 외부 변수에 식별되도록 구성할 수 있다.
  • main.tf 파일 수정 : list 형태의 배열을 활용한 반복문 동작 구성
variable "names" {
  type    = list(string)
  default = ["a", "b", "c"]
}

resource "local_file" "abc" {
  count   = length(var.names)
  content = "abc"
  # 변수 인덱스에 직접 접근
  filename = "${path.module}/abc-${var.names[count.index]}.txt"
}

resource "local_file" "def" {
  count   = length(var.names)
  content = local_file.abc[count.index].content
  # element function 활용
  filename = "${path.module}/def-${element(var.names, count.index)}.txt"
}
  • 실행 후 확인 : local_file.abc와 local_file.def는 var.name에 선언되는 값에 영향을 받아 동일한 갯수만큼 생성하게 된다.
  • local_file.def의 경우 local_file.abc와 개수가 같아야 content에 선언되는 인수 값에 오류가 없을 것이므로 서로 참조되는 리소스와 모듈의 반복정의에 대한 공통의 영향을 주는 변수로 관리할 수 있다.
#
terraform apply -auto-approve
terraform state list
ls *.txt
diff abc-a.txt abc-b.txt
diff abc-a.txt def-c.txt


echo "local_file.abc[0]" | terraform console
echo "local_file.def[0]" | terraform console

terraform output
# terraform output filename
# terraform output fileid
# terraform output filecontent

# graph 확인 > graph.dot 파일 선택 후 오른쪽 상단 DOT 클릭
terraform graph > graph.dot
  • count로 생성되는 리소스의 경우 <리소스 타입>.<이름>[<인덱스 번호>], 모듈의 경우 module.<모듈 이름>[<인덱스 번호>]로 해당 리소스의 값을 참조한다.
  • 단, 모듈 내에 count 적용이 불가능한 선언이 있으므로 주의해야 한다.
  • 예를 들어 provider 블록 선언부가 포함되어 있는 경우에는 count 적용이 불가능하다 → provider 분리
  • 또한 외부 변수가 list 타입인 경우 중간에 값이 삭제되면 인덱스가 줄어들어 의도했던 중간 값에 대한 리소스만 삭제되는 것이 아니라 이후의 정의된 리소스들도 삭제되고 재생성된다. → 아래 실습으로 확인

main.tf 파일 수정 : list 형태의 값 변경

```bash
variable "names" {
  type    = list(string)
  default = **["a", "c"]** # index 1자리의 b를 삭제
}

resource "local_file" "abc" {
  count   = length(var.names)
  content = "abc"
  # 변수 인덱스에 직접 접근
  filename = "${path.module}/abc-${var.names[count.index]}.txt"
}

resource "local_file" "def" {
  count   = length(var.names)
  content = local_file.abc[count.index].content
  # element function 활용
  filename = "${path.module}/def-${element(var.names, count.index)}.txt"
}
```
  • 실행 후 확인
#
terraform plan
ls -l *.txt
terraform apply -auto-approve

#
terraform state list
ls *.txt
  • 배열의 중간에 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨짐.
  • 테라폼이 인덱스 번호를 리소스 식별자로 보기 때문에 ‘인덱스 1에서는 계정 생성, 인덱스2에서는 계정 삭제한다’라고 해석합니다.
  • 즉 count 사용 시 목록 중간 항목을 제거하면 테라폼은 해당 항목 뒤에 있는 모든 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만듬

2. for_each : 반복문, 선언된 key 값 개수만큼 리소스를 생성

  • 리소스 또는 모듈 블록에서 for_each에 입력된 데이터 형태가 map 또는 set이면, 선언된 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 apply -auto-approve
terraform state list
ls *.txt
cat a.txt ;echo
cat b.txt ;echo

echo 'local_file.abc["a"]' | terraform console
  • or_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
ls *.txt
  • 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
ls *.txt

3. for : 복합 형식 값의 형태를 변환하는 데 사용 ← 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"
  • 실행 후 확인 : jsonencode Function — 링크
terraform apply -auto-approve
terraform state list
ls *.txt
cat abc.txt ;echo

echo "local_file.abc" | terraform console

# 참고 jsonencode Function
echo 'jsonencode({"hello"="world"})' | terraform console

for 구문을 사용하는 몇 가지 규칙은 다음과 같다

  • list 유형의 경우 반환 받는 값이 하나로 되어 있으면 을, 두 개의 경우 앞의 인수가 인덱스를 반환하고 뒤의 인수가 을 반환
  • 관용적으로 인덱스는 i, 값은 v로 표현
  • map 유형의 경우 반환 받는 값이 하나로 되어 있으면 를, 두 개의 경우 앞의 인수가 를 반환하고 뒤의 인수가 을 반환
  • 관용적으로 키는 k, 값은 v로 표현
  • 결과 값은 for 문을 묶는 기호가 **[ ]**인 경우 tuple로 반환되고 **{ }**인 경우 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
[for v in var.names : upper(v)]
[for i, v in var.names : "${i} is ${v}"]
{ for v in var.names : v => upper(v) }
[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_upper_value

# 
terraform console
>
-----------------
var.members
[for k, v in var.members : "${k} is ${v.role}"]
{for name, user in var.members : name => user.role}
{for name, user in var.members : name => user.role  if user.role == "admin"}
{for name, user in var.members : user.role => name...}
exit
-----------------

4. 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에 지정한 이름에 대해 속성이 부여된다.
  • dynamic 블록 활용 예

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

[실습3] 반복문

# IAM 사용자 3명 생성

# 사용자 1명 생성 코드
provider "aws" {
  region = "us-east-2"
}

resource "aws_iam_user" "example" {
  name = "neo"
}

# 테라폼은 for 반복문 또는 언어에 내장된 절차 논리가 없다
# 테라폼에서 count 를 사용하여 3명 IAM 사용자 생성 
# → 3명의 IAM 사용자 이름 중복으로 오류 발생 
# ⇒ for 반복문의 인덱스를 사용하여 각 사용자에게 고유한 이름 지정 가능

resource "aws_iam_user" "example" {
  count = 3
  name  = "neo"
}


# iam.tf
# count.index 를 사용하여 반복문 안에 있는 각각의 반복 ieration 을 가리키는 인덱스를 얻을 수 있음

provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_iam_user" "myiam" {
  count = 3
  name  = "myuser.${count.index}"
}

# init & plan 실행 및 apply
terraform init && terraform plan
...
  # aws_iam_user.myiam[0] will be created
  # aws_iam_user.myiam[1] will be created
  # aws_iam_user.myiam[2] will be created

# apply
terraform apply -auto-approve

# 확인
terraform state list
aws_iam_user.myiam[0]
aws_iam_user.myiam[1]
aws_iam_user.myiam[2]

aws iam list-users | jq
...

# 
terraform console
>
-----------------
aws_iam_user.myiam
aws_iam_user.myiam[0]
aws_iam_user.myiam[1].name
exit
-----------------

# 삭제
terraform destroy -auto-approve
aws iam list-users | jq
# count 입력 변수를 통해 IAM 사용자 생성
# 입력 변수 코드 생성 variables.tf

variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["gasida", "akbun", "ssoon"]
}

# 테라폼에서 count 와 함께 배열 조회 구문과 length 함수를 사용해서 사용자들 생성 가능

# iam.tf : 코드 내용 수정
provider "aws" {
  region = "ap-northeast-2"
}

resource "aws_iam_user" "myiam" {
  count = length(var.user_names)
  name  = var.user_names[count.index]
}

# init & plan
# 리소스에 count 사용한 후에는 하나의 리소스가 아니라 리소스의 배열이 됨
terraform plan
...
  # aws_iam_user.myiam[0] will be created
  # aws_iam_user.myiam[1] will be created
  # aws_iam_user.myiam[2] will be created


# output.tf
# IAM 사용자 전체의 ARN을 원하면 인덱스 대신 스플랫 splat 연산자인 * 를 사용

terraform apply -auto-approve
terraform state list
terraform output
terraform output all_arns
# count 제약사항
# 전체 리소스를 반복할 수는 있지만 리소스 내에서 인라인 블록을 반복할 수는 없다.

resource "aws_autoscaling_group" "example" {
  launch_configuration = aws_launch_configuration.example.name
  vpc_zone_identifier  = data.aws_subnets.default.ids
  target_group_arns    = [aws_lb_target_group.asg.arn]
  health_check_type    = "ELB"

  min_size = var.min_size
  max_size = var.max_size

  tag {
    key                 = "Name"
    value               = var.cluster_name
    propagate_at_launch = true
  }
}

# 위 ASG 리소스 태그 설정 방법에서 각각의 tag 를 사용하려면 
# key, value, propagate_at_launch 에 대한 값으로 새 인라인 블록을 만들어야 함

# 배열 중간 값을 변경할 때 발생

#실습을 위해 다시 IAM 사용자 생성
terraform apply -auto-approve


# variables.tf : IAM 사용자 생성 목록에서 생각
variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["**gasida**", "**akbun**", "**ssoon**"]
}

# variables.tf : 중간에 있는 akbun 을 제거하고 plan & apply
variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["gasida", "ssoon"]
}


# plan : 출력 내용 확인!
terraform plan
...
  ~ update in-place
  - destroy

# 배열의 중간에 항목을 제거하면 모든 항목이 1칸씩 앞으로 당겨짐
# 즉 count 사용 시 목록 중간 항목을 제거하면 
# 테라폼은 해당 항목 뒤에 있는 모든 리소스를 삭제한 다음 해당 리소스를 처음부터 다시 만듬

# 삭제
terraform destroy -auto-approve
aws iam list-users | jq
# 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 는 리소스에 사용될 때는 집합과 맵만 지원.
# for_each 가 이 집합을 반복하면 each.value 에서 각 사용자 이름을 사용할 수 있음
# iam.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
}


# variables.tf
variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["gasida", "akbun", "ssoon"]
}

# for_each 를 사용한 후에는 하나의 리소스 또는 count 를 사용한 것과 같은 
# 리소스 배열이 되는 것이 아니라 리소스 맵 list into a set 이 됨


# outputs.tf : 이 의미를 확인하려면 원래의 outputs 을 제거하고 새로운 all_users 출력 변수를 추가
output "all_users" {
  value = aws_iam_user.myiam
}


# plan & apply

terraform plan && terraform apply -auto-approve

# 확인
terraform state list
terraform output


# for_each 를 사용해 리소스를 맵으로 처리하면 컬렉션 중간의 항목도 안전하게 제거할 수 있어서, 
# count 로 리소스를 배열 처리보다 이점이 큼

# variables.tf
variable "user_names" {
  description = "Create IAM users with these names"
  type        = list(string)
  default     = ["gasida", "ssoon"]
}

# plan & apply
terraform plan
terraform apply -auto-approve
terraform state list

# 삭제
terraform destroy -auto-approve
aws iam list-users | jq