本篇介紹 Terraform syntax,為何 .tf 內容是這個格式
課程內容與代碼會放在 Github 上: https://github.com/chechiachang/terraform-30-days
賽後文章會整理放到個人的部落格上 http://chechia.net/
寫到現在,應該都可以寫出能夠工作的 terraform。
到目前為止,課程對學生的期待是『能夠 google 到社群分享的 .tf,並且會正確的複製貼上,轉化成自己的 .tf 內容』。
做到現在,想必也累積了一些問題。之前的內容,或許有些邏輯還不是很清楚,課程都簡單帶過,許多複雜的部分略過不提;目的先讓學生對語言有個基礎的了解,有實際操作的經驗跟手感,之後做深入的討論時,更能理解內容。
接下來要繼續深入,我們會花一點篇幅,回頭細講細節,把關防文件細細地走過
先從這篇出發Terraform Syntax,介紹 Terraform syntax。這裡又分為兩部分:一個是Configuration Syntax,另一個是Json Configuration Syntax。乍看之下有點疑惑,json 是從哪跑出來的?我們可以從幾個角度看這件事:
Terraform 底層 low-level syntax 是由 Hashicorp Configuration Language 定義的
這裡要講古一下:歷史來看, hashicorp 在 2014 年發表 terraform,同時也釋出 hcl 語言 spec
用範例講解一下上面一大段,回頭看 _poc/security_group/security_group.tf
使用 cli 的 global option -json 來輸出 json
terraform plan
# azurerm_network_security_group.main will be created
+ resource "azurerm_network_security_group" "main" {
+ id = (known after apply)
+ location = "southeastasia"
+ name = "poc-chechia"
+ resource_group_name = "terraform-30-days"
+ security_rule = (known after apply)
+ tags = {
+ "environment" = "poc"
}
}
# azurerm_network_security_rule.main["homeport22"] will be created
+ resource "azurerm_network_security_rule" "main" {
+ access = "Allow"
+ destination_address_prefix = "*"
+ destination_port_range = "22"
+ direction = "Inbound"
+ id = (known after apply)
+ name = "Port_22"
+ network_security_group_name = "poc-chechia"
+ priority = 100
+ protocol = "*"
+ resource_group_name = "terraform-30-days"
+ source_address_prefix = "17.110.101.57"
+ source_port_range = "*"
}
terraform plan -json
{"@level":"info","@message":"azurerm_network_security_group.main: Plan to create","@module":"terraform.ui","@timestamp":"2021-08-18T22:58:26.459366+08:00","change":{"resource":{"addr":"azurerm_network_security_group.main","module":"","resource":"azurerm_network_security_group.main","implied_provider":"azurerm","resource_type":"azurerm_network_security_group","resource_name":"main","resource_key":null},"action":"create"},"type":"planned_change"}
{"@level":"info","@message":"azurerm_network_security_rule.main[\"homeport22\"]: Plan to create","@module":"terraform.ui","@timestamp":"2021-08-18T22:58:26.461178+08:00","change":{"resource":{"addr":"azurerm_network_security_rule.main[\"homeport22\"]","module":"","resource":"azurerm_network_security_rule.main[\"homeport22\"]","implied_provider":"azurerm","resource_type":"azurerm_network_security_rule","resource_name":"main","resource_key":"homeport22"},"action":"create"},"type":"planned_change"}
{"@level":"info","@message":"Plan: 2 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","@timestamp":"2021-08-18T22:58:26.461227+08:00","changes":{"add":2,"change":0,"remove":0,"operation":"plan"},"type":"change_summary"}
# format with jq
{
"@level": "info",
"@message": "azurerm_network_security_rule.main[\"homeport22\"]: Plan to create",
"@module": "terraform.ui",
"@timestamp": "2021-08-18T22:58:26.461178+08:00",
"change": {
"resource": {
"addr": "azurerm_network_security_rule.main[\"homeport22\"]",
"module": "",
"resource": "azurerm_network_security_rule.main[\"homeport22\"]",
"implied_provider": "azurerm",
"resource_type": "azurerm_network_security_rule",
"resource_name": "main",
"resource_key": "homeport22"
},
"action": "create"
},
"type": "planned_change"
}
{
"@level": "info",
"@message": "Plan: 2 to add, 0 to change, 0 to destroy.",
"@module": "terraform.ui",
"@timestamp": "2021-08-18T22:58:26.461227+08:00",
"changes": {
"add": 2,
"change": 0,
"remove": 0,
"operation": "plan"
},
"type": "change_summary"
}
.tf 與 .tf.json 的格式轉換,我們可以用一個小工具 kvz/json2hcl來做轉換
# azure/_poc/security_group/provider.tf
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = "~> 2.65.0"
}
}
required_version = ">= 1.0.1"
# remote Backend
backend "azurerm" {
resource_group_name = "terraform-30-days-poc"
storage_account_name = "tfstate8b8bff248c5c60c0"
container_name = "tfstate"
key = "_poc/security_group/terraform.tfstate"
}
}
provider "azurerm" {
features {}
}
安裝 json2hcl
# azure/_poc/security_group/provider.tf
curl -SsL https://github.com/kvz/json2hcl/releases/download/v0.0.6/json2hcl_v0.0.6_darwin_amd64 \
| sudo tee /usr/local/bin/json2hcl > /dev/null && sudo chmod 755 /usr/local/bin/json2hcl && json2hcl -version
v0.0.6
json2hcl -reverse < azure/_poc/security_group/provider.tf
{
"provider": [
{
"azurerm": [
{
"features": [
{}
]
}
]
}
],
"terraform": [
{
"backend": [
{
"azurerm": [
{
"container_name": "tfstate",
"key": "_poc/security_group/terraform.tfstate",
"resource_group_name": "terraform-30-days-poc",
"storage_account_name": "tfstate8b8bff248c5c60c0"
}
]
}
],
"required_providers": [
{
"azurerm": [
{
"source": "hashicorp/azurerm",
"version": "~\u003e 2.65.0"
}
]
}
],
"required_version": "\u003e= 1.0.1"
}
]
}
然而輸入 _poc/security_group/security_group.tf
則會出錯
for_each
在 json 中也不存在json2hcl -reverse < azure/_poc/security_group/security_group.tf
unable to parse HCL: At 2:25: Unknown token: 2:25 IDENT local.name
我們可以依據hcl readme 補上 double quote "",讓格式符合 json 的格式,這樣就可以順利轉換
cat _poc/security_group/security_group_json.tf
resource "azurerm_network_security_rule" "main" {
for_each = "${local.rules}"
name = "${each.value.name}"
priority = "${each.value.priority}"
direction = "${each.value.direction}"
access = "${each.value.access}"
protocol = "${each.value.protocol}"
source_port_range = "${each.value.source_port_range}"
destination_port_range = "${each.value.destination_port_range}"
source_address_prefix = "${each.value.source_address_prefix}"
destination_address_prefix = "${each.value.destination_address_prefix}"
resource_group_name = "${local.resource_group_name}"
network_security_group_name = "${local.name}"
}
json2hcl -reverse < azure/_poc/security_group/security_group_json.tf
{
"resource": [
{
"azurerm_network_security_rule": [
{
"main": [
{
"access": "${each.value.access}",
"destination_address_prefix": "${each.value.destination_address_prefix}",
"destination_port_range": "${each.value.destination_port_range}",
"direction": "${each.value.direction}",
"for_each": "${local.rules}",
"name": "${each.value.name}",
"network_security_group_name": "${local.name}",
"priority": "${each.value.priority}",
"protocol": "${each.value.protocol}",
"resource_group_name": "${local.resource_group_name}",
"source_address_prefix": "${each.value.source_address_prefix}",
"source_port_range": "${each.value.source_port_range}"
}
]
}
]
}
]
}
這也是舊版 terraform 常出現 "${}" 語法的原因。新版 terraform 已經能自動 parse 沒有 double quote 的 hcl 語法,並且 validate 時會對有不必要 double quote 的 "${}" 語法跳出 warning。
了解 terraform syntax 後,我們要回頭重新檢視 hcl 中描述的 terraform resources,以及為什麼 resources 會有這些行為
resource block {}
resource.azurerm_linux_virtual_machine
的 name, size ...等是雲端物件的參數for_each
, lifecycle,...)
我們在編寫 hcl 內容時應善用 meta-argument,應注意
depends_on
與 variable + argument
的使用,會造成 module 彼此的依賴性
如果對 Terraform 原始碼有興趣,可以看與 hcl config parse
官方文件閱讀測驗
meta-argument 只在 terraform 內部有效,細節範例我們下一章節會說明