테라폼(Terraform)은 de facto에 가까운
IaC(Infrastructure as Code) 도구다.
 
그중에서도 수많은 Provider들은 테라폼으로
마법 같은 인프라 정의와 배포를 가능하게 한다.
 
프로바이더들은 비교적 데이터 Input과 Output이
명확하게 정의되어 다음과 같은 데이터 흐름을 가진다.

HCL(HashiCorp Configuration Language) -> Expand -> Flatten -> State

 
 
아주 직관적인 구조라고 생각한다. HCL과 State의 경우에는 인프라를 정의하는 언어와 현재 상태다.
 
그중에 생소한 Expand와 Flatten이 있다.
본 글에서는 대부분 terraform-provider-aws를 기준으로 설명한다.
 
Expand는 HCL을 프로바이더에 요청 가능한 구조체로 변환해 주는 작업,
Flatten은 프로바이더에서 응답한 Response를 State로 변환해 주는 작업이다.
VPC를 생성하는 경우를 보자. HCL의 경우에는 아래와 같다.

resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

 
이것이 Expand 되면 다음과 같은 형태로 변한다.

type CreateVpcInput struct {
	AmazonProvidedIpv6CidrBlock *bool
	CidrBlock *string
	DryRun *bool
	InstanceTenancy types.Tenancy
	Ipv4IpamPoolId *string
	Ipv4NetmaskLength *int32
	Ipv6CidrBlock *string
	Ipv6CidrBlockNetworkBorderGroup *string
	Ipv6IpamPoolId *string
	Ipv6NetmaskLength *int32
	Ipv6Pool *string
	TagSpecifications []types.TagSpecification
}

 
이를 이용한 리서스 생성 요청 이후에 flatten을 통해 terraform state로 사용 가능한 형태로 만든다.
원래 상태로 돌아온다고 보면 된다.

resource "aws_vpc" "example" {
  cidr_block = "10.0.0.0/16"
}

 
여기서 원래 상태는 편의를 위해 HCL의 문법으로 표현하고 있지만 실제로는
Resource, Schema로 대표되는 테라폼 프로바이더에서 사용하는 데이터 타입이다.
 
짧은 예시를 보자

	return &schema.Resource{
		CreateWithoutTimeout: resourceRegistryPolicyPut,
		ReadWithoutTimeout:   resourceRegistryPolicyRead,
		UpdateWithoutTimeout: resourceRegistryPolicyPut,
		DeleteWithoutTimeout: resourceRegistryPolicyDelete,

		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},

		Schema: map[string]*schema.Schema{
			names.AttrPolicy: {
				Type:                  schema.TypeString,
				Required:              true,
				DiffSuppressFunc:      verify.SuppressEquivalentPolicyDiffs,
				DiffSuppressOnRefresh: true,
				ValidateFunc:          validation.StringIsJSON,
				StateFunc: func(v any) string {
					json, _ := structure.NormalizeJsonString(v)
					return json
				},
			},
			"registry_id": {
				Type:     schema.TypeString,
				Computed: true,
			},
		},
	}

 
map[string]을 기반으로 되어있고 한 스키마당 Required부터 시작해서
4~5개의 속성을 지정해 주어야 하므로 자동완성도 지원하지 않고
코드 길이가 아주 길어지기 때문에 개인적으로 불만이 많다.
 
위와 같은 구조는 HCL로는 다음과 같이 표현된다.

resource "aws_ecr_registry_policy" "example" {
  policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Sid    = "testpolicy",
        Effect = "Allow",
        Principal = {
          "AWS" : "arn:${data.aws_partition.current.partition}:iam::${data.aws_caller_identity.current.account_id}:root"
        },
        Action = [
          "ecr:ReplicateImage"
        ],
        Resource = [
          "arn:${data.aws_partition.current.partition}:ecr:${data.aws_region.current.region}:${data.aws_caller_identity.current.account_id}:repository/*"
        ]
      }
    ]
  })
}

 
직관적으로 string을 받는 policy와 registry_id가 있다.
policy에는 jsonencode된 iam policy 등이 들어가게 된다. 
 
registry_id는 computed가 True로 되어있다.
이것은 프로바이더가 값을 설정할 것이라는 의미로 리소스에서는 정의할 수 없다.
이렇게 정의된 속성은 어트리뷰트가 된다.
 
실제 registry_id에 대한 설명을 보면 더 이해가 쉬울 것이다.
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_registry_policy#registry_id-1
 
기본적인 개념은 이게 끝이다.
언제라도 없는 AWS리소스를 추가할 수 있다.
 
다음과 같이 접근하라.
1. 대상의 리소스와 스키마 정의
2. expand 함수와 API 요청 작성
3. flatten 함수 작성
 
물론 아예 새로운 리소스를 추가할 땐
이런저런 절차가 더 들어간다.
기회가 되면 다뤄보도록 하겠다.
 
참고 문서
https://hashicorp.github.io/terraform-provider-aws/data-handling-and-conversion/
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/vpc
https://developer.hashicorp.com/terraform/plugin/framework/handling-data/schemas
 

+ Recent posts