JSON 문법 참조
JSON 문법 참조
이 주제에서는 HCL Packer 템플릿을 JSON으로 표현하는 데 사용할 수 있는 JSON 문법에 대한 참조 정보를 다뤄요. HCL 문법에 대한 정보는 HCL 문법 참조를 참고하세요.
출처: Packer 공식 문서
본문
도입 (Introduction)
Packer는 JSON으로 작성된 템플릿을 지원해요. 이는 구성의 일부를 프로그래밍 방식으로 생성할 때 유용한데, 기존 JSON 라이브러리를 사용해 생성된 구성 파일을 준비할 수 있기 때문이에요.
이 문법은 1.5 버전 이전의 "레거시" Packer 템플릿과 혼동하면 안 돼요. 레거시 템플릿은 오로지 JSON으로만 작성되고 다른 형식을 따랐어요.
JSON 문법은 네이티브 문법을 기준으로 정의돼요. 네이티브 문법으로 표현할 수 있는 모든 것은 JSON 문법으로도 표현할 수 있지만, JSON 문법의 한계 때문에 몇몇 구문은 JSON에서 표현하기가 더 복잡해요.
Packer는 .pkr.hcl 접미사로 이름 지어진 파일에는 네이티브 문법을, .pkr.json 접미사로 이름 지어진 파일에는 JSON 문법을 기대해요. 접미사에서 .pkr 부분을 빼면 Packer는 json 파일을 레거시 Packer 템플릿으로 읽으려고 해요.
저수준 JSON 문법은 네이티브 문법과 마찬가지로 HCL이라는 사양을 기준으로 정의돼요. Packer를 사용하는 데 HCL 문법이나 그 JSON 매핑의 모든 세부 사항을 알 필요는 없어요. 그래서 이 페이지는 네이티브 문법과 JSON 문법 사이의 가장 중요한 차이점을 요약해요. 관심이 있다면 HCL의 JSON 문법에 대한 전체 정의를 그 사양에서 찾을 수 있어요.
JSON 파일 구조 (JSON File Structure)
모든 JSON 기반 Packer 구성의 루트에는 JSON 객체가 있어요. 이 객체의 속성들은 Packer 언어의 최상위 블록 타입에 대응해요. 예를 들어:
{
"variables": {
"example": "value"
}
}
각 최상위 객체 속성은 기대되는 최상위 블록 타입 중 하나의 이름과 일치해야 해요. 위에 보이는 variable처럼 라벨을 기대하는 블록 타입은 라벨 수준마다 하나의 중첩 객체 값으로 표현돼요. source 블록은 라벨 두 개를 기대하므로 중첩 두 수준이 필요해요:
{
"source": {
"amazon-ebs": {
"example": {
"instance_type": "t2.micro",
"ami_name": "ami-abc123"
}
}
}
}
라벨을 나타내는 중첩 객체 뒤에는 마지막으로 블록 자체의 본문을 나타내는 중첩 객체가 하나 더 와요. 위 예시에서는 source "amazon-ebs" "example"의 instance_type과 ami_name 인자가 지정됐어요.
종합하면, 위 두 구성 파일은 네이티브 문법의 다음 블록들과 동일해요:
variables {
example = "value"
}
source "amazon-ebs" "example" {
instance_type = "t2.micro"
ami_name = "ami-abc123"
}
각 최상위 블록 타입 안에서 JSON으로 매핑하는 규칙은 조금씩 달라요(아래 블록 타입별 예외 참조). 하지만 대부분의 경우 다음 일반 규칙이 적용돼요:
- 블록 본문을 나타내는 JSON 객체에는 인자 이름이나 중첩 블록 타입 이름에 대응하는 속성들이 포함돼요.
- 속성이 네이티브 문법에서 임의 표현식을 받아들이는 인자에 대응하는 경우, 속성 값은 아래의 표현식 매핑(Expression Mapping)에 설명된 대로 표현식으로 매핑돼요. 임의 표현식을 받아들이지 않는 인자의 경우, 속성 값의 해석은 인자에 따라 다르며 이 페이지 뒷부분의 블록 타입별 예외에 설명돼 있어요.
- 속성 이름이 기대되는 중첩 블록 타입 이름에 대응하는 경우, 그 값은 아래의 중첩 블록 매핑(Nested Block Mapping)에 설명된 대로 해석돼요. 단, 이 페이지 뒷부분의 블록 타입별 예외에 달리 명시된 경우는 제외예요.
표현식 매핑 (Expression Mapping)
JSON 문법이 Packer 언어 표현식 문법의 모든 것을 표현할 수는 없기 때문에, 표현식으로 해석되는 JSON 값은 다음과 같이 매핑돼요:
| JSON | Packer 언어 해석 |
|---|---|
| Boolean | 리터럴 bool 값. |
| Number | 리터럴 number 값. |
| String | 문자열 템플릿으로 파싱된 뒤 아래에 설명된 대로 평가돼요. |
| Object | 각 속성 값이 이 표에 따라 매핑되어 적절한 속성 타입을 가진 object(...) 값을 만들어요. |
| Array | 각 요소가 이 표에 따라 매핑되어 적절한 요소 타입을 가진 tuple(...) 값을 만들어요. |
| Null | 리터럴 null. |
임의 표현식이 기대되는 위치에서 JSON 문자열을 만나면, 그 값은 먼저 문자열 템플릿으로 파싱된 다음 최종 결과를 만들기 위해 평가돼요.
주어진 템플릿이 단일 보간 시퀀스로만 구성되어 있다면, 그 표현식의 결과를 먼저 문자열로 변환하지 않고 직접 가져와요. 이 덕분에 JSON 문법 안에서 비문자열 표현식도 사용할 수 있어요.
중첩 블록 매핑 (Nested Block Mapping)
JSON 객체 속성이 중첩 블록 타입의 이름을 따를 때, 이 속성의 값은 해당 타입의 하나 이상의 블록을 나타내요. 속성의 값은 JSON 객체이거나 JSON 배열이어야 해요.
가장 단순한 상황은 해당 타입이 라벨을 기대하지 않을 때 단일 블록만을 나타내는 경우예요. source 블록 안에 사용되는 tags 중첩 블록이 그 예시예요:
{
"source": {
"amazon-ebs": {
"example": {
"tags": {
"key": "value"
}
}
}
}
}
위는 다음 네이티브 문법 구성과 동일해요:
source "amazon-ebs" "example" {
tags = {
key = "value"
}
}
중첩 블록 타입이 하나 이상의 라벨을 요구하거나, 같은 타입의 블록이 여러 개 주어질 수 있다면 매핑이 조금 더 복잡해져요. 예를 들어 build 블록 안에 사용되는 provisioner 중첩 블록 타입은 사용할 프로비저너를 주는 라벨을 기대하고, 프로비저너 블록들의 순서는 작업 순서를 정하는 데 중요해요.
다음 네이티브 문법 예시는 서로 다른 타입의 여러 프로비저너를 가진 build 블록을 보여줘요:
build {
# (source configuration omitted for brevity)
provisioner "shell-local" {
inline = ["echo 'Hello World' >example.txt"]
}
provisioner "file" {
source = "example.txt"
destination = "/tmp/example.txt"
}
provisioner "shell" {
inline = [
"sudo install-something -f /tmp/example.txt",
]
}
}
이 블록들의 순서를 보존하려면 이 블록 타입을 나타내는 속성의 직접 값으로 JSON 배열을 사용해야 해요. 위의 JSON 대응은 다음과 같아요:
{
"build": {
"//": "(source configuration omitted for brevity)",
"provisioner": [
{
"shell-local": {
"inline": ["echo 'Hello World' >example.txt"]
}
},
{
"file": {
"source": "example.txt",
"destination": "/tmp/example.txt"
}
},
{
"shell": {
"inline": ["sudo install-something -f /tmp/example.txt"]
}
}
]
}
}
provisioner 배열의 각 요소는 단일 속성을 가진 객체인데, 그 속성 이름이 각 provisioner 블록의 라벨을 나타내요. 여러 라벨을 기대하는 블록 타입의 경우, 배열과 객체 번갈아 중첩하는 이 패턴을 추가 수준마다 사용할 수 있어요.
중첩 블록 타입이 라벨을 요구하지만 순서는 중요하지 않다면, 배열을 생략하고 속성 이름이 고유한 블록 라벨에 대응하는 단일 객체만 제공할 수 있어요. 단순한 경우에 위 방식의 축약형으로 허용되지만, 배열과 객체를 번갈아 쓰는 방식이 가장 일반적이에요. 네이티브 문법에서 JSON으로 체계적으로 변환할 때는 구성을 정확히 보존하기 위해 가장 일반적인 형태를 사용하는 것을 권장해요.
주석 속성 (Comment Properties)
JSON 문법 구성 파일을 수작업으로 편집하는 것은 권장하지 않지만 - 이 형식은 주로 프로그래밍 방식의 생성과 소비를 위한 것이에요 - 블록 본문을 나타내는 JSON 객체 안에는 특수 속성 이름을 사용한 제한된 형태의 주석이 허용돼요:
{
"source": {
"amazon-ebs": {
"example": {
"//": "This instance runs the scheduled tasks for backup",
"instance_type": "t2.micro",
"ami_name": "ami-abc123"
}
}
}
}
블록 본문을 나타내는 어떤 객체든, "//"라는 이름의 속성은 Packer가 완전히 무시해요. 이 예외는 표현식으로 해석되는 객체에는 적용되지 않아요. 그 경우 "//"라는 이름의 객체 타입 속성으로 해석되기 때문이에요.
이 특수 속성 이름은 JSON 기반 구성 파일의 루트에서도 사용할 수 있어요. 이는 어떤 프로그램이 파일을 만들었는지 표시하는 데 유용할 수 있어요.
{
"//": "This file is generated by generate-outputs.py. DO NOT HAND-EDIT!"
}
블록 타입별 예외 (Block-type-specific Exceptions)
특정 블록 타입 안의 일부 인자들은 특별한 방식으로 처리되므로, JSON 문법으로의 매핑이 위에서 설명한 일반 규칙을 따르지 않아요. 다음 하위 섹션은 각 최상위 블록 타입에 적용되는 특별한 매핑 규칙을 설명해요.
variable 블록 (variable blocks)
variable 블록 안의 모든 인자는 JSON에 대한 비표준 매핑을 가져요:
type-"string"이나"list(string)"같은 타입 표현식을 담은 문자열.default- 주어진 타입으로 변환될 수 있는 리터럴 JSON 값. 이 값 안의 문자열은 문자 그대로 받아들이고 문자열 템플릿으로 해석하지 않아요.description- 템플릿으로 해석되지 않는 리터럴 JSON 문자열.
{
"variable": {
"example": {
"type": "string",
"default": "hello"
}
}
}