JSON 구성 문법

JSON 구성 문법 (JSON Configuration Syntax)

대부분의 Terraform 구성은 사람이 읽고 갱신하기 비교적 쉽도록 설계된 네이티브 Terraform 언어 문법으로 작성돼요.

출처: 문서

본문

Terraform은 JSON과 호환되는 대체 문법도 지원해요. 이 문법은 구성의 일부를 프로그래밍 방식으로 생성할 때 유용한데, 기존 JSON 라이브러리로 생성된 구성 파일을 준비할 수 있기 때문이에요.

JSON 문법은 네이티브 문법으로 정의돼요. 네이티브 문법으로 표현할 수 있는 모든 것은 JSON 문법으로도 표현할 수 있지만, JSON 문법의 한계 때문에 일부 구성을 JSON으로 표현하는 것은 더 복잡해요.

Terraform은 .tf 접미사를 가진 파일에는 네이티브 문법을, .tf.json 접미사를 가진 파일에는 JSON 문법을 기대해요.

네이티브 문법과 마찬가지로 저수준 JSON 문법은 HCL이라는 사양으로 정의돼요. Terraform을 사용하기 위해 HCL 문법이나 그 JSON 매핑의 모든 세부 내용을 알 필요는 없으므로, 이 페이지에서는 네이티브 문법과 JSON 문법의 가장 중요한 차이점을 요약해요. 관심이 있다면 HCL의 JSON 문법 전체 정의를 그 사양에서 볼 수 있어요.

JSON 파일 구조 (JSON File Structure)

JSON 기반 Terraform 구성의 루트에는 JSON 객체가 있어요. 이 객체의 속성은 Terraform 언어의 최상위 블록 타입에 대응해요. 예를 들어:

{
  "variable": {
    "example": {
      "default": "hello"
    }
  }
}

각 최상위 객체 속성은 기대되는 최상위 블록 타입 중 하나의 이름과 일치해야 해요. 위에 나온 variable처럼 라벨을 기대하는 블록 타입은 각 라벨 수준마다 하나의 중첩 객체 값으로 표현돼요. resource 블록은 두 개의 라벨을 기대하므로 두 수준의 중첩이 필요해요:

{
  "resource": {
    "aws_instance": {
      "example": {
        "instance_type": "t2.micro",
        "ami": "ami-abc123"
      }
    }
  }
}

라벨을 나타내는 중첩 객체 다음에는 마지막으로 블록 본문 자체를 나타내는 중첩 객체가 하나 더 와요. 위 예시에서 variable "example"default 인자와 resource "aws_instance" "example"instance_type·ami 인자가 지정돼요.

종합하면 위 두 구성 파일은 네이티브 문법의 다음 블록과 동일해요.

variable "example" {
  default = "hello"
}

resource "aws_instance" "example" {
  instance_type = "t2.micro"
  ami           = "ami-abc123"
}

각 최상위 블록 타입 안에서 JSON으로의 매핑 규칙은 조금씩 다르지만(아래 블록 타입별 예외 참고), 대부분의 경우 다음 일반 규칙이 적용돼요.

  • 블록 본문을 나타내는 JSON 객체에는 인자 이름 또는 중첩 블록 타입 이름에 대응하는 속성이 들어 있어요.
  • 네이티브 문법에서 임의의 표현식을 받아들이는 인자에 대응하는 속성이면, 속성 값은 아래 표현식 매핑에서 설명하는 대로 표현식으로 매핑돼요. 임의의 표현식을 받아들이지 않는 인자의 경우 속성 값의 해석은 인자에 따라 달라지며, 이 페이지 뒷부분의 블록 타입별 예외에서 설명해요.
  • 속성 이름이 기대되는 중첩 블록 타입 이름에 대응하면, 값은 이 페이지 뒷부분의 블록 타입별 예외에서 달리 명시하지 않는 한 아래 중첩 블록 매핑에서 설명하는 대로 해석돼요.

표현식 매핑 (Expression Mapping)

JSON 문법은 Terraform 언어의 표현식 문법을 모두 표현할 수 없으므로, 표현식으로 해석되는 JSON 값은 다음과 같이 매핑돼요.

JSON Terraform 언어 해석
Boolean 리터럴 bool
Number 리터럴 number
String 문자열 템플릿으로 파싱한 후 아래 설명대로 평가
Object 각 속성 값을 이 표에 따라 매핑해 적절한 속성 타입의 object(...) 값 생성
Array 각 요소를 이 표에 따라 매핑해 적절한 요소 타입의 tuple(...) 값 생성
Null 리터럴 null

임의의 표현식이 기대되는 위치에서 JSON 문자열을 만나면, 그 값은 먼저 문자열 템플릿으로 파싱된 후 최종 결과를 생성하기 위해 평가돼요.

주어진 템플릿이 단 하나의 보간(interpolation) 시퀀스로만 구성되어 있다면, 문자열로 먼저 변환하지 않고 그 표현식의 결과를 직접 가져와요. 이 덕분에 JSON 문법 안에서 문자열이 아닌 표현식도 사용할 수 있어요.

{
  "output": {
    "example": {
      "value": "${aws_instance.example}"
    }
  }
}

위에서 선언된 output "example"은 문자열 값이 아니라, 주어진 aws_instance 리소스 블록을 나타내는 객체 값을 값으로 가져요. 이 특수 동작은 템플릿에 리터럴이나 제어 시퀀스가 나타나면 적용되지 않으며, 그러한 상황에서는 항상 문자열 값이 생성돼요.

중첩 블록 매핑 (Nested Block Mapping)

JSON 객체 속성이 중첩 블록 타입으로 이름 지어졌으면, 이 속성의 값은 해당 타입의 블록 하나 이상을 나타내요. 속성 값은 JSON 객체 또는 JSON 배열이어야 해요.

가장 단순한 상황은 해당 타입이 라벨을 기대하지 않을 때 단일 블록만 나타내는 경우예요. resource 블록 안에 사용되는 lifecycle 중첩 블록이 그 예시예요.

{
  "resource": {
    "aws_instance": {
      "example": {
        "lifecycle": {
          "create_before_destroy": true
        }
      }
    }
  }
}

위는 다음 네이티브 문법 구성과 동일해요.

resource "aws_instance" "example" {
  lifecycle {
    create_before_destroy = true
  }
}

중첩 블록 타입이 라벨을 하나 이상 요구하거나, 같은 타입의 블록을 여러 개 줄 수 있으면 매핑은 조금 더 복잡해져요. 예를 들어 resource 블록 안에 사용되는 provisioner 중첩 블록 타입은 사용할 프로비저너를 나타내는 라벨을 기대하며, 프로비저너 블록의 순서는 작업 순서를 결정하므로 중요해요.

다음 네이티브 문법 예시는 다양한 타입의 프로비저너가 여러 개 있는 resource 블록을 보여줘요.

resource "aws_instance" "example" {
  # (resource configuration omitted for brevity)

  provisioner "local-exec" {
    command = "echo 'Hello World' >example.txt"
  }
  provisioner "file" {
    source      = "example.txt"
    destination = "/tmp/example.txt"
  }
  provisioner "remote-exec" {
    inline = [
      "sudo install-something -f /tmp/example.txt",
    ]
  }
}

이 블록들의 순서를 보존하려면 이 블록 타입을 나타내는 속성의 직접 값으로 JSON 배열을 사용해야 해요. 위의 JSON 등가물은 다음과 같아요.

{
  "resource": {
    "aws_instance": {
      "example": {
        "provisioner": [
          {
            "local-exec": {
              "command": "echo 'Hello World' >example.txt"
            }
          },
          {
            "file": {
              "source": "example.txt",
              "destination": "/tmp/example.txt"
            }
          },
          {
            "remote-exec": {
              "inline": ["sudo install-something -f /tmp/example.txt"]
            }
          }
        ]
      }
    }
  }
}

provisioner 배열의 각 요소는 단일 속성을 가진 객체이며, 그 속성 이름이 각 provisioner 블록의 라벨을 나타내요. 여러 라벨을 기대하는 블록 타입의 경우, 배열과 객체를 번갈아 중첩하는 이 패턴을 추가 수준마다 사용할 수 있어요.

중첩 블록 타입이 라벨을 요구하지만 순서가 중요하지 않은 경우에는 배열을 생략하고, 속성 이름이 고유한 블록 라벨에 대응하는 단일 객체만 제공할 수 있어요. 이는 단순한 경우를 위한 축약 형태로 허용되지만, 배열과 객체를 번갈아 사용하는 방식이 가장 일반적이에요. 네이티브 문법에서 JSON으로 체계적으로 변환할 때는 구성을 정확히 보존할 수 있도록 가장 일반적인 형태를 사용할 것을 권장해요.

주석 속성 (Comment Properties)

JSON 문법 구성 파일의 수동 편집은 권장하지 않지만(이 포맷은 주로 프로그래밍 방식 생성과 소비를 위한 것이에요), 블록 본문을 나타내는 JSON 객체 안에서는 특수한 속성 이름을 사용한 제한된 형태의 주석이 허용돼요.

{
  "resource": {
    "aws_instance": {
      "example": {
        "//": "This instance runs the scheduled tasks for backup",

        "instance_type": "t2.micro",
        "ami": "ami-abc123"
      }
    }
  }
}

블록 본문을 나타내는 객체에서 "//"라는 이름의 속성은 Terraform이 완전히 무시해요. 이 예외는 표현식으로 해석되는 객체에는 적용되지 않는데, 그 경우 "//"라는 이름의 객체 타입 속성으로 해석되기 때문이에요.

이 특수 속성 이름은 JSON 기반 구성 파일의 루트에서도 사용할 수 있어요. 이는 어떤 프로그램이 파일을 만들었는지 기록하는 데 유용할 수 있어요.

{
  "//": "This file is generated by generate-outputs.py. DO NOT HAND-EDIT!",

  "output": {
    "example": {
      "value": "${aws_instance.example}"
    }
  }
}

블록 타입별 예외 (Block-type-specific Exceptions)

특정 블록 타입 안의 일부 인자는 Terraform이 특별한 방식으로 처리하므로, 그 JSON 문법 매핑은 위의 일반 규칙을 따르지 않아요. 다음 하위 섹션들은 각 최상위 블록 타입에 적용되는 특수 매핑 규칙을 설명해요.

resourcedata 블록

resourcedata 블록 타입의 일부 메타 인자는 객체에 대한 직접 참조 또는 리터럴 키워드를 받아요. JSON으로 표현할 때 참조나 키워드는 추가로 둘러싼 공백이나 기호 없이 JSON 문자열로 주어져요.

예를 들어 provider 메타 인자는 프로바이더 구성에 대한 <PROVIDER>.<ALIAS> 참조를 받는데, 네이티브 문법에서는 따옴표 없이 나타나지만 JSON 문법에서는 문자열로 제시해야 해요.

{
  "resource": {
    "aws_instance": {
      "example": {
        "provider": "aws.foo"
      }
    }
  }
}

이 특수 처리는 다음 메타 인자에 적용돼요.

  • provider: 위에 나온 것처럼 단일 문자열
  • depends_on: ["aws_instance.example"]처럼 명명된 엔티티에 대한 참조를 담는 문자열 배열
  • lifecycle 블록 안의 ignore_changes: all로 설정되면 단일 문자열 "all"을 주어야 해요. 그렇지 않으면 ["ami"]처럼 속성 참조를 담는 JSON 문자열 배열을 사용해야 해요.

또한 resource 블록 안에 직접 있든 provisioner 블록 안에 중첩되어 있든 모든 connection 블록의 type 인자에도 특수 처리가 적용돼요. 주어진 문자열은 리터럴로 해석되며, 문자열 템플릿으로 파싱·평가되지 않아요.

variable 블록

variable 블록 안의 모든 인자는 JSON으로 비표준 매핑을 가져요.

  • type: 타입 표현식을 담은 문자열 (예: "string" 또는 "list(string)")
  • default: 주어진 타입으로 변환될 수 있는 리터럴 JSON 값. 이 값 안의 문자열은 리터럴로 간주되며 문자열 템플릿으로 해석되지 않아요.
  • description: 리터럴 JSON 문자열, 템플릿으로 해석되지 않아요.
{
  "variable": {
    "example": {
      "type": "string",
      "default": "hello"
    }
  }
}

output 블록

descriptionsensitive 인자는 리터럴 JSON 값으로 해석돼요. description 문자열은 문자열 템플릿으로 해석되지 않아요.

value 인자는 표현식으로 해석돼요.

{
  "output": {
    "example": {
      "value": "${aws_instance.example}"
    }
  }
}

locals 블록

locals 블록 타입을 나타내는 JSON 객체 속성의 값은, 속성 이름이 선언할 로컬 값 이름인 JSON 객체여야 해요.

{
  "locals": {
    "greeting": "Hello, ${var.name}"
  }
}

이 중첩 속성 각각의 값은 표현식으로 해석돼요.

module 블록

sourceversion 메타 인자는 리터럴 문자열로 주어져야 해요. 값은 문자열 템플릿으로 해석되지 않아요.

providers 메타 인자는, 속성이 자식 모듈에 노출할 컴팩트한 프로바이더 주소이고 값이 현재 모듈에서 사용할 프로바이더 주소인 JSON 객체로 주어져야 해요. 둘 다 리터럴 문자열로 주어져요.

{
  "module": {
    "example": {
      "source": "hashicorp/consul/azurerm",
      "version": "= 1.0.0",
      "providers": {
        "aws": "aws.usw1"
      }
    }
  }
}

provider 블록

aliasversion 메타 인자는 리터럴 문자열로 주어져야 해요. 값은 문자열 템플릿으로 해석되지 않아요.

{
  "provider": {
    "aws": [
      {
        "region": "us-east-1"
      },
      {
        "alias": "usw1",
        "region": "us-west-1"
      }
    ]
  }
}

terraform 블록

terraform 블록 안의 어떤 설정도 명명된 객체 참조나 함수 호출을 받아들이지 않으므로, 모든 설정 값은 리터럴로 간주돼요. 문자열 값은 문자열 템플릿으로 해석되지 않아요.

terraform 블록당 backend 블록은 하나만 허용되므로, 컴팩트 블록 매핑으로 이를 나타낼 수 있어요. 객체 안에 백엔드 타입을 나타내는 단일 속성을 가진 중첩 객체를 사용해요.

{
  "terraform": {
    "required_version": ">= 0.12.0",
    "backend": {
      "s3": {
        "region": "us-west-2",
        "bucket": "acme-terraform-states"
      }
    }
  }
}

더 알아보기 (Learn more)