Terraform Provider 開發紀錄

2022 年終於要結束了,今年仍舊是在精神時光屋中度過…

在新的工作內容中恰好碰到了客製化 Terraform Provider 的需求,所以寫一篇文章來記錄一下開發和 Release 的流程,本篇文章假設已經對於 Terraform 這套 IaC 工具使用有基礎的了解,如果沒有聽過或使用過的人可以先拜讀 Che-Chia (David) Chang 大大的 2021 鐵人賽文章 - Terraform Workshop - Infrastructure as Code for Public Cloud 疫情警戒陪你度過 30 天,有助新手們由淺入深了解這套深度整合各大公有雲的 IaC 工具。

環境以及使用版本

  • Terraform Version: v1.3.6
  • Go Version: go1.19.3 darwin/arm64

前置準備

對於 Providers 概念不夠清晰的人可以先從 Terraform - Plugin Development 看起,如下圖所示,基本上 Providers 就是一個連接 APITerraform 的橋樑。

upload successful

接下來要準備開發,首先確認環境有安裝 Go

1
2
3
4
# 確認你的 Go 安裝路徑
go env GOPATH
# 可以用這個命令來確認 Go 相關的環境變數
go env

本篇會以 terraform-provider-stripe 來示範,可以直接 Clone 下來輸出執行檔:

1
go build -o terraform-provider-stripe

upload successful

接下來我們要打開 ~/.terraformrc 這個 Terraform 命令列設定檔設定本機測試 Provider 的路徑,範例如下,並且要注意 Provider 的命名規則

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 這邊要把 Provider 路徑指到剛剛得到的 Go 環境變數,dev_overriders 會在本機開發時直接引用這個環境變數下的 Go 執行檔

provider_installation {
dev_overrides {
"hashicorp.com/edu/hashicups-pf" = "/Users/hazelshen/go/bin"
"hashicorp.com/edu/stripe" = "/Users/hazelshen/go/bin"
}

# For all other providers, install them directly from their origin provider
# registries as normal. If you omit this, Terraform will _only_ use
# the dev_overrides block, and so no other providers will be available.
direct {

}
}

接下來把剛剛輸出的 Go 執行檔複製到 Go 的執行目錄下:

1
cp  terraform-provider-stripe ${GOPATH}

就可以開始寫我們測試用的 Terraform Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
terraform {
required_providers {
stripe = {
# version = "~> 1.0.0"
source = "hashicorp.com/edu/stripe"
# source = "hazel-shen/stripe"
}
}
}

provider "stripe" {
api_key = var.stripe_api_token
}

resource "stripe_tax_rate" "test_tax" {
percentage = "5"
display_name = "Sales tax"
inclusive = false
country = "US"
description = "Test Exclusive Tax"
state = "OH"
}

本機測試成功!直接執行就會呼叫 StripeAPI 新增一項測試的 tax rate

upload successful

⚠️ 備註 ⚠️

之前有搜尋到教學文章說要放在 ~/.terraform.d/plugins 底下,這的確是較多文章寫的標準做法,但是這個命名規則就有點冗長,就像以下範例:

1
~/.terraform.d/plugins/terraform-example.com/exampleprovider/example/1.0.0/linux_amd64/terraform-provider-example

然後 provider 要這樣寫:

1
2
3
4
5
6
7
8
terraform {
required_providers {
example = {
version = "~> 1.0.0"
source = "terraform-example.com/exampleprovider/example"
}
}
}

思來想去覺得麻煩,就走了個比較簡便的方式來開發了。

如何 Release 寫好的 Provider

開發完之後,如果想要推送到 Terraform Registry,可以參考 Terraform - Publishing Providers 的步驟來 Release

  1. 準備好 Providers 的文件,具體可以參照 Terraform - Provider Documentation

  2. 在根目錄下建立一份 terraform-registry-manifest.json 檔案,範例如下:

1
2
3
4
5
6
7
# 因為 fork 的原作者 Release 的版本是 1,這邊為了做出區別,我從 2 開始
{
"version": 2,
"metadata": {
"protocol_versions": ["5.0"]
}
}
  1. 為你的 GitHub Provider Repo 建置 CI/CD 工具

我這邊選擇使用 GitHub Action 當我的 CI/CD 工具,可以從 Terraform - Publishing Providers #github-actions-preferred 開始看。

基本上就是直接拷貝 .goreleaser.yml & .github/workflows/release.yml 這兩個檔案,並且按照目錄結構放置就好。

  1. 準備 gpg key 並且分別設定在 GitHub RepoTerraform Registry

可以參考 GitHub Doc - generating-a-new-gpg-key 產生 Private Key & Public Key

1
2
3
4
5
6
7
8
9
# 產生公鑰和私鑰,我這邊是選擇 (1) RSA and RSA
gpg --full-generate-key

# 顯示公鑰
gpg --armor --export ${Your Email}

# 顯示私鑰
gpg --armor --export-secret-keys ${Your Email}

公鑰貼在 GitHub > Settings > SSH and GPG keys > New GPG Key
upload successful

upload successful

私鑰貼在 Terraform Registry > Settings > Signing Keys > New GPG Key

upload successful

upload successful

  1. Release Tag 打上去,然後推到 remote repo 就會自動 ReleaseTerraform Registry
1
git tag v2.0.2

Actions 標籤裡可以看到有 Release Workflow 在跑:
upload successful

Release 之後可以在 Terraform Registry > Publish > Provider 列表裡面看到結果:

upload successful

  1. 使用已經 ReleasedProvider

示範 `Terraform code 如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
terraform {
required_providers {
stripe = {
source = "hazel-shen/stripe"
version = "2.0.2"
configuration_aliases = [stripe]
}
}
}

provider "stripe" {
api_key = var.STRIPE_API_KEY
}

之後只要添加新功能,就打新的 Tag Version 就可以 Release 新版了,其實蠻方便的。

Reference

🍀 Lukasaron - Terraform-provider-stripe
🍀 Terraform - Plugin Development
🍀 MY FIRST TERRAFORM PROVIDER
🍀 Terraform - CLI Configuration File
🍀 Terraform - How to Develop a Custom Provider in Terraform
🍀 StackOverflow - Terraform cannot init custom provider written in Go
🍀 GitHub Doc - generating-a-new-gpg-key