근묵자흑
Terraform Mocks 본문
이 블로그는 https://developer.hashicorp.com/terraform/language/tests/mocking 페이지를 정리해 둔 블로그 입니다.
참고: Test mocking은 Terraform v1.7.0 이상에서 사용 가능합니다.
Terraform은 테스트를 위해 provider, resource, data source를 mock할 수 있게 해줍니다. 이를 통해 실제 인프라를 생성하거나 자격 증명이 필요하지 않고도 모듈의 일부를 테스트할 수 있습니다. Terraform 테스트에서 mock된 provider나 resource는 일반적으로 기본 provider API에서 제공하는 모든 computed 속성에 대해 가짜 데이터를 생성합니다.
Mocking 기능은 terraform test 언어에서만 사용할 수 있습니다.
더 고급 mocking 프레임워크 기능을 사용하기 위해서는 다음과 같은 Terraform provider 기능에 대한 이해가 필요합니다:
- 속성(attributes), 중첩 속성(nested attributes), 블록(blocks)의 차이
- optional, required, computed 속성의 정의
Mock Providers
Terraform 테스트에서는 mock_provider
블록을 사용하여 provider를 mock할 수 있습니다. Mock provider는 원래 provider와 동일한 스키마를 반환하며, 매칭되는 provider 대신 테스트에서 사용할 수 있습니다. Mock provider로 검색한 모든 resource와 data source는 구성에서 관련 값을 설정하고, computed 속성에 대해서는 가짜 데이터를 생성합니다.
Mock provider는 전통적인 provider 블록 대신 직접 사용할 수 있으며 동일한 글로벌 네임스페이스를 공유합니다. terraform test
명령 실행 중에 Terraform은 실제 provider와 mock provider를 구분하지 않습니다.
다음 예제는 AWS S3 버킷을 생성한 다음 mock provider를 사용하여 AWS 계정 없이 구성을 테스트합니다:
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
variable "bucket_name" {
type = string
}
resource "aws_s3_bucket" "my_bucket" {
bucket = var.bucket_name
}
# bucket_name.tftest.hcl
mock_provider "aws" {}
run "sets_correct_name" {
variables {
bucket_name = "my-bucket-name"
}
assert {
condition = aws_s3_bucket.my_bucket.bucket == "my-bucket-name"
error_message = "incorrect bucket name"
}
}
Terraform 테스트 파일에서 실행되는 plan이나 apply 작업의 관점에서 보면, mock provider는 구성과 일치하는 값을 가진 실제 리소스를 생성합니다. 이러한 리소스는 terraform test
가 테스트 실행 중에 메모리에 생성하고 보관하는 Terraform 상태 파일에 저장됩니다.
Terraform 테스트에서 mock provider와 실제 provider를 함께 시뮬레이션할 수 있습니다. 다음 예제는 하나는 실제이고 하나는 mock인 두 개의 AWS provider를 정의합니다. 동일한 글로벌 aws provider 네임스페이스를 공유하므로 둘 중 하나에 alias를 제공해야 합니다. 그런 다음 테스트 run 블록의 providers 속성을 사용하여 각 run 블록에 사용할 AWS provider를 customizing할 수 있습니다.
# mocked_providers.tftest.hcl
provider "aws" {}
mock_provider "aws" {
alias = "fake"
}
run "use_real_provider" {
providers = {
aws = aws
}
}
run "use_mocked_provider" {
providers = {
aws = aws.fake
}
}
Generated data
Mock provider는 참조된 data source나 속성의 computed 속성에 대한 데이터를 생성합니다. 예를 들어 arn 속성은 AWS가 대부분의 리소스에 대해 생성하는 고유 식별자입니다. Mock aws provider는 생성하는 모든 리소스에서 이 속성에 대한 값을 제공합니다.
참고: Mock provider는 computed 속성의 예상 형식에 대한 정보가 없으므로 생성된 데이터는 실제 provider가 해당 속성에 대해 반환할 구문과 거의 일치하지 않습니다.
Mock provider는 computed 속성에 대해서만 데이터를 생성합니다. Mock provider를 사용할 때는 모든 required 리소스 속성을 설정해야 합니다. Optional computed 속성에 값을 제공하지 않으면 Terraform이 자동으로 하나를 생성합니다. Terraform이 생성하는 값은 데이터 유형에 따라 다릅니다:
- 숫자는 0이 됩니다.
- Boolean은 false가 됩니다.
- 문자열은 8자의 랜덤 영숫자 문자열이 됩니다.
- 컬렉션(set, list, map 포함)은 빈 컬렉션이 됩니다.
- 객체는 이와 동일한 규칙 세트를 재귀적으로 사용하여 모든 required 하위 속성을 포함합니다.
이에 대한 예시는 aws_s3_bucket 리소스의 bucket 속성입니다. 실제 AWS provider는 지정되지 않은 경우 버킷 이름을 생성합니다. Mock AWS provider도 구성에서 이미 지정되지 않은 경우에만 값을 생성합니다.
Mock Provider data
mock_provider
블록에서 특정 리소스와 data source에 대한 특정 값을 지정할 수 있습니다. mock_resource와 mock_data 블록을 원하는 만큼 작성할 수 있습니다. mock_resource와 mock_data 블록은 모두 값을 제공하려는 리소스나 data source와 일치하는 type 인수를 받습니다. 또한 특정 속성에 대해 반환해야 하는 값을 지정하는 데 사용할 수 있는 defaults 객체 속성도 허용합니다.
다음 예제는 모든 AWS S3 버킷 리소스와 data source에 대해 설정된 arn 값을 제공하는 방법을 보여줍니다:
mock_provider "aws" {
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
}
위 예제에서 Terraform은 S3 버킷의 arn 속성에 대해 랜덤 문자열을 생성하는 대신 제공된 값을 사용합니다. 명시적인 기본값이 제공되지 않은 computed 속성은 일반적인 데이터 생성 규칙으로 돌아갑니다.
또한 전용 mock 데이터 파일을 작성하고 mock_provider 블록에서 source 속성을 사용하여 테스트 간에 mock provider 데이터를 공유할 수 있습니다. Mock 데이터 파일은 .tfmock.hcl 또는 .tfmock.json 확장자를 가지며, mock_provider 블록에 직접 정의된 것처럼 mock_resource와 mock_data 블록을 포함할 수 있습니다.
# ./testing/aws/data.tfmock.hcl
mock_resource "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_data "aws_s3_bucket" {
defaults = {
arn = "arn:aws:s3:::name"
}
}
mock_provider "aws" {
source = "./testing/aws"
}
위 예제는 ./testing/aws에 있는 mock 데이터 파일에 mock_resource와 mock_data 블록을 정의합니다. 여러 테스트 파일에서 동일한 mock provider 데이터를 로드하고 공유할 수 있어 동일한 정의를 여러 파일에 복사할 필요가 없습니다.
source 속성과 직접 중첩된 mock_resource와 mock_data 블록을 결합할 수 있습니다. source 위치와 직접 중첩된 블록이 동일한 리소스나 data source를 설명하는 경우 직접 중첩된 블록이 우선합니다.
Overrides
Provider를 mock하는 것 외에도 다음과 같은 블록 유형을 사용하여 특정 리소스, data source 및 모듈을 override할 수 있습니다:
override_resource
: 리소스의 값을 override합니다. Terraform은 기본 provider를 호출하지 않습니다.override_data
: data source의 값을 override합니다. Terraform은 기본 provider를 호출하지 않습니다.override_module
: 모듈의 출력을 override합니다. Terraform은 모듈에서 리소스를 생성하지 않습니다.
세 블록 모두 Terraform 테스트 파일과 Terraform 테스트 파일 run 블록의 root 레벨에 배치할 수 있습니다. 또한 override_resource와 override_data 블록은 mock_provider 블록과 Terraform mock 데이터 파일에 중첩될 수 있습니다.
Override는 실제 provider와 mock provider 모두에서 사용할 수 있으며 기본 provider 대신 computed 값을 제공합니다.
Overrides 구문
모든 override 블록에는 override할 리소스, data source 또는 모듈을 지정해야 하는 target 속성이 포함됩니다. override_module 블록에는 outputs 속성이 포함되어 있고, override_resource와 override_data 블록에는 values 속성이 포함되어 있습니다.
outputs과 values 속성은 선택사항이며 지정하지 않으면 Terraform이 자동으로 값을 생성합니다.
다음 예제는 다양한 범위와 레벨에서 override 블록을 보여줍니다. 메인 구성은 ./modules/s3_data 모듈을 호출하여 S3 버킷에서 파일을 읽은 다음 모듈에서 반환된 데이터로 local_file을 생성합니다.
# main.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
module "credentials" {
source = "./modules/s3_data"
data_bucket_name = "my_company_bucket_name"
}
resource "local_file" "credentials_json" {
filename = "credentials.json"
content = jsonencode(module.credentials.data)
}
# ./modules/s3_data/main.tf
variable "data_bucket_name" {
type = string
}
data "aws_s3_object" "data_bucket" {
bucket = var.data_bucket_name
key = "credentials.json"
}
output "data" {
value = jsondecode(data.aws_s3_object.data_bucket.body)
}
먼저 mock provider에서 직접 모듈의 aws_s3_bucket_object를 override할 수 있습니다. 다음 예제는 mock_provider 블록에서 override_data 블록을 정의합니다. 이 경우 Terraform은 mock provider가 생성하는 경우에만 target data source를 override합니다.
# main.tftest.hcl
mock_provider "aws" {
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
또는 테스트 파일 자체나 run 블록에서 동일한 aws_s3_bucket_object를 override할 수도 있습니다. 이 경우 Terraform이 provider에 관계없이 target 주소의 data source를 override하므로 기본 provider는 중요하지 않습니다. 다음 예제에서는 AWS 자격 증명 없이 테스트를 실행할 수 있도록 provider를 mock합니다. Override 블록은 실제 provider가 제공하는 리소스를 override할 수 있습니다.
# main.tftest.hcl
mock_provider "aws" {}
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"username\",\"password\":\"password\"}"
}
}
run "test_file_override" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
run "test_run_override" {
# 파일에 정의된 대체 값보다 이 로컬 override 블록의 값이 우선합니다
override_data {
target = module.credentials.data.aws_s3_object.data_bucket
values = {
body = "{\"username\":\"a_different_username\",\"password\":\"password\"}"
}
}
assert {
condition = jsondecode(local_file.credentials_json.content).username == "a_different_username"
error_message = "incorrect username"
}
}
위 예제에서 파일 레벨에 정의된 override 블록은 첫 번째 run 블록에서 사용됩니다. 그런 다음 두 번째 run 블록에서는 대체 로컬 override가 파일 레벨 override보다 우선합니다.
마지막으로 override_module 블록을 사용하여 전체 모듈을 override할 수 있습니다. 다음 예제에서 Terraform은 모듈의 특정 리소스 대신 전체 모듈을 override합니다.
# main.tftest.hcl
mock_provider "aws" {}
override_module {
target = module.credentials
outputs = {
data = { username = "username", password = "password" }
}
}
run "test" {
assert {
condition = jsondecode(local_file.credentials_json.content).username == "username"
error_message = "incorrect username"
}
}
이 경우 override 블록은 values 대신 outputs를 지정하고 실제 모듈이 jsondecode 함수를 통해 데이터를 전달하므로 출력 값이 문자열이 아닌 HCL로 지정됩니다. override_data 블록과 마찬가지로 override_module 블록도 run 블록에서 지정할 수 있으며 동일한 우선순위 규칙이 적용됩니다.
반복 블록과 중첩된 속성
일부 리소스와 data source는 반복되는 중첩 속성을 직접 computed로 지정하고, 리소스의 반복 블록에도 computed 속성이 포함될 수 있습니다. 반복 블록과 중첩된 속성의 경우 컬렉션의 특정 인스턴스에 대한 값을 지정할 수 없습니다. 대신 컬렉션의 모든 인스턴스에 적용되는 단일 값 세트를 제공해야 합니다.
예를 들어 aws_dynamodb_table 리소스에는 루트 레벨의 computed arn 속성과 computed arn 속성도 포함하는 set-repeated 블록인 replica가 포함되어 있습니다. 다음은 aws_dynamodb_table의 간단한 인스턴스 예제입니다:
# main.tf
resource "aws_dynamodb_table" "my_table" {
name = "my_table"
hash_key = "key"
attribute {
name = "key"
type = "S"
}
replica {
region_name = "eu-west-2"
}
replica {
region_name = "us-east-1"
}
}
# Mock 리소스 설정
mock_resource "aws_dynamodb_table" {
defaults = {
arn = "aws:dynamodb:::my_table"
replica = {
arn = "aws:dynamodb:::my_replica"
}
}
}
일반적으로 AWS provider는 DynamoDB 테이블과 지정된 두 복제본 모두에 대한 ARN 값을 반환합니다. mock_resource 블록이 이 동작을 모방할 수 있지만 리소스의 여러 replica 블록을 구분할 수는 없습니다. 이 경우 mock_resource는 DynamoDB 테이블의 ARN에 대해 특정 값을 제공하지만, replica 테이블의 ARN 값은 모든 인스턴스 간에 공유됩니다.
'IaC > terraform' 카테고리의 다른 글
자동화된 테스트부터 정적 분석까지 (2) | 2025.02.09 |
---|---|
Terraform 코드 테스트 (4) | 2025.02.02 |
프로덕션 수준의 테라폼 코드 (0) | 2025.01.19 |
테라폼 팁과 요령: 반복문, if문, 배포 및 주의사항 (1) | 2025.01.12 |
Terraform Module (5) | 2025.01.05 |