kmuto’s blog

はてな社でMackerel CREをやっています。料理と旅行といろんなIT技術

TerraformフォークとなるOpenTofu 1.6.0を、Provider側の気持ちになりながら試してみた

3行

現時点ではOpenTofu 1.6.0はユーザー体験としてはほぼTerraform 1.6なので、コマンドがterraformtofuになった以外はユーザー側で感じる変化はなさそう(設定ファイルやstateファイルなどもTerraformそのまま)。Mackerel Providerも動く。

Provider側としては、すでに登録されているProviderであれば、当面は何もしなくてもGitHubリリースしたものがOpenTofuレジストリに最新反映される模様(GPG署名している場合は公開鍵をレジストリにサブミットする必要あり)。

今の時点では問題ないと言っても、OpenTofu/Terraformの乖離が進んで互換性が失われると、Providerは別リポジトリを用意する必要がありそう or Terraform/OpenTofuどちらかを諦めることになりそう。

背景

クラウドワークスさんの記事「Terraform職人のためのOpenTofu入門」を読み、ちょうど先日に正式GAリリースとなるOpenTofu 1.6.0が発表されていた。

OpenTofuはみんな大好きIaCツールTerraformのOSS fork版。Terraformを提供するHashiCorp社が、OSSであったTerraformをライセンスを変更して競合他社の商用利用に制限をかける形にしたので、その影響を受けることになるGruntwork社などの人たちがTerraform OSS最終バージョンをforkして独自に開発を進めている。詳細についてはクラウドワークスさんの記事を読みましょう。CNCF内定という見立ても書かれていて、そうなるとOpenTelemetryのように覇権、スタンダードということになり得る。

自分がCREとして関わっているMackerelもTerraform Providerを提供しており、OpenTofuだとどうなるのかなという興味で試してみることにした。

OpenTofu 1.6自体はforkなので動くのは問題ないじゃろ(Mackerel Terraform Providerもbrand-newな新機能は使っていないはず)という予想はあるんだけど、一応これまでどおり動くかどうかを見て、今後問い合わせがあったときに確信を持って返信できるか、レジストリ管理はどうなるのかというあたりを見たい。

ユーザー側視点

インストールは、macOSではbrew install opentofuterraformの代わりにtofuというコマンドで操作することになる。引数類もterraformコマンドと変わらない。

レジストリはTerraformのものが使えないことになっているので、新たに別のプラグインレジストリとしてhttps://registry.opentofu.org/が用意されている。

Terraformレジストリの特定時点のをコピーしているようで、Mackerel Providerもちゃんと入っていた

では、適当にmain.tfを作る。

terraform {
  required_providers {
    mackerel = {
      source = "mackerelio-labs/mackerel"
      version = "~> 0.3.0"
    }
  }
}

resource "mackerel_service" "tofu_app" {
  name = "tofu_app"
}

resource "mackerel_role" "tofu_app" {
  service = mackerel_service.tofu_service.name
  name = "tofu_role"
}

Mackerelのオーガニゼーションにtofu_appというサービスと、その中にtofu_roleというロールを作るだけのサンプル。APIキーは環境変数で指定するので、ファイル内では指定しない。

拡張子はtfのままだけど、Tofuの略なので問題ないな、ガハハ!

terraform」の設定ブロックについては変わるのかなと思ったけど、OpenTofu 1.6.0時点では「terraform」のままだった。このあたりはドキュメントにも「As a part of OpenTofu v1.x Compatibility Promises, the terraform block stays as-is. A tofu block may be introduced in the future, but it doesn't exist yet.(1.x系では互換性保証のためにterraformブロックのままにしておくよ、将来的にはtofuブロック導入するかもしれないけど今はまだないよ)」と書かれている。

実行は単にterraformコマンドの代わりにtofuコマンドを使うだけ。

$ tofu init
terraform {

Initializing the backend...

Initializing provider plugins...
- Finding mackerelio-labs/mackerel versions matching "~> 0.3.0"...
- Installing mackerelio-labs/mackerel v0.3.2...
- Installed mackerelio-labs/mackerel v0.3.2. Signature validation was skipped due to the registry not containing GPG keys for this provider

OpenTofu has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that OpenTofu can guarantee to make the same selections by default when
you run "tofu init" in the future.

OpenTofu has been successfully initialized!

You may now begin working with OpenTofu. Try running "tofu plan" to see
any changes that are required for your infrastructure. All OpenTofu commands
should now work.

If you ever set or change modules or backend configuration for OpenTofu,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

$ MACKEREL_APIKEY=… tofu plan

OpenTofu used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

OpenTofu will perform the following actions:

  # mackerel_role.tofu_role will be created
  + resource "mackerel_role" "tofu_role" {
      + id      = (known after apply)
      + name    = "tofu_role"
      + service = "tofu_app"
    }

  # mackerel_service.tofu_app will be created
  + resource "mackerel_service" "tofu_app" {
      + id   = (known after apply)
      + name = "tofu_app"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so OpenTofu can't
guarantee to take exactly these actions if you run "tofu apply" now.

$  MACKEREL_APIKEY=… tofu apply
OpenTofu used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  + create

OpenTofu will perform the following actions:

…

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  OpenTofu will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

mackerel_service.tofu_app: Creating...
mackerel_service.tofu_app: Creation complete after 0s [id=tofu_app]
mackerel_role.tofu_role: Creating...
mackerel_role.tofu_role: Creation complete after 0s [id=tofu_app:tofu_role]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

$ MACKEREL_APIKEY=… tofu destroy
mackerel_service.tofu_app: Refreshing state... [id=tofu_app]
mackerel_role.tofu_role: Refreshing state... [id=tofu_app:tofu_role]

OpenTofu used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
  - destroy

OpenTofu will perform the following actions:

  # mackerel_role.tofu_role will be destroyed
  - resource "mackerel_role" "tofu_role" {
      - id      = "tofu_app:tofu_role" -> null
      - name    = "tofu_role" -> null
      - service = "tofu_app" -> null
    }

  # mackerel_service.tofu_app will be destroyed
  - resource "mackerel_service" "tofu_app" {
      - id   = "tofu_app" -> null
      - name = "tofu_app" -> null
    }

Plan: 0 to add, 0 to change, 2 to destroy.

Do you really want to destroy all resources?
  OpenTofu will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

mackerel_role.tofu_role: Destroying... [id=tofu_app:tofu_role]
mackerel_role.tofu_role: Destruction complete after 0s
mackerel_service.tofu_app: Destroying... [id=tofu_app]
mackerel_service.tofu_app: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

うむ、完全に普通のTerraformだな。できるファイルもそのままTerraform。

$ ls -a
./                        .terraform.lock.hcl       terraform.tfstate.backup
../                       main.tf
.terraform/               terraform.tfstate

Provider側視点

Terraformレジストリにあった情報をそのまま持ってきているようで、mackerelio-labsにあるリポジトリを参照しているようだった。

GPG署名を使っている場合は、署名検証のためにレジストリリポジトリに公開鍵をサブミットする必要があるように見える。Mackerel Providerは署名はしていないので、これは考えなくてよいけど。

懸念したのは、ProviderをGitHubで更新リリースしたときに、こちらから何かせずともOpenTofu側でちゃんと更新してくれるかである。

Provider情報のクロール更新まわりのコードを探してレジストリリポジトリを掘ってみる。

bunmp-versions」というのがいかにもそれっぽいので、go buildして試してみた。トークンはread onlyでpublic accessのみの、ほぼ何もできないやつを作っている。

$ GH_TOKEN=github_pat_… ./bump-versions -module-data ../../../modules -module-namespace nothing -provider-data ../../../providers -provider-namespace mackerelio-labs
{"time":"2024-01-13T18:23:44.664167+09:00","level":"INFO","msg":"Starting version bump process for modules and providers"}
{"time":"2024-01-13T18:23:45.626797+09:00","level":"INFO","msg":"Beginning version bump process","type":"provider","provider":{"namespace":"mackerelio-labs","name":"mackerel"}}
{"time":"2024-01-13T18:23:45.627846+09:00","level":"INFO","msg":"Getting tags for repository","type":"provider","provider":{"namespace":"mackerelio-labs","name":"mackerel"},"github":{"repository":"https://github.com/mackerelio-labs/terraform-provider-mackerel"}}
{"time":"2024-01-13T18:23:47.841924+09:00","level":"INFO","msg":"Found tags for repository","type":"provider","provider":{"namespace":"mackerelio-labs","name":"mackerel"},"github":{"repository":"https://github.com/mackerelio-labs/terraform-provider-mackerel","count":14}}
{"time":"2024-01-13T18:23:47.842214+09:00","level":"INFO","msg":"Found 0 releases that do not already exist in the metadata file","type":"provider","provider":{"namespace":"mackerelio-labs","name":"mackerel"}}
{"time":"2024-01-13T18:23:47.842232+09:00","level":"INFO","msg":"No version bump required, all versions exist","type":"provider","provider":{"namespace":"mackerelio-labs","name":"mackerel"}}
{"time":"2024-01-13T18:23:47.842323+09:00","level":"INFO","msg":"Completed version bump process for modules and providers"}

とりあえず挙動的にはちゃんと見えていそうで、新しいものを上げてリリースしておけば、今のところは自然にOpenTofuのレジストリにも反映されると思われる。

キーとしては「mackerelio-labs/mackerel」しかないのに、terraform--provider-mackerelを引けるのがちょっと気になったのだが、provider.goにベタ書きされていた(なお、overrides.goもなかなか面白い)。

// RepositoryName returns the name of the repository that the provider is assumed to be living in.
func (p Provider) RepositoryName() string {
    return fmt.Sprintf("terraform-provider-%s", p.ProviderName)
}

// RepositoryURL returns the URL of the repository that the provider is assumed to be living in.
func (p Provider) RepositoryURL() string {
    return fmt.Sprintf("https://github.com/%s/%s", p.EffectiveNamespace(), p.RepositoryName())
}

いつまでも挙動が同じではいかないだろうし、terraform-provider-*という名前なのにTerraformで動かないものを我々がリポジトリで提供するのも変なので、どこかの時点で互換性が完全にダメになったらこのあたりもopentofu-provider-*を見るかあるいは何かフラグで共存させるようなアナウンスが出るのかなと予想している。ただそのアナウンスがpushでくるわけではないだろうから、こちらからウォッチしておく必要はありそうだ(逆に言えば、現時点だとopentofu-provider-*という名前で備えようとしてもダメということでもある)。

Terraform/OpenTofuの挙動が割れていったときに、両方を保守するのはしんどすぎるので、SREの利用意向や市場を見ながら、どちらかを選択することになるのだろう。

おわりに

『詳解Terraform 第3版』をようやく読み始めたところで、無駄になると悲しいかなと思ったけど、当分(というかたぶんずっと)問題なく知識適用はできそう。著者のBrikman氏はGruntwork社の共同創設者だね。

www.oreilly.co.jp

あと、OpenTofuレジストリのREADME.mdの見出しがOpen"t"ofuになっていたので、issue・PRしたらシュッとマージされた。これでボクもOpenTofu Contributorだぜ(もっとがんばりましょう)。

github.com