はじめに
ちょうど一年前の2024年4月に OpenTofu から、HCL中の式で簡単にGoやLuaで自作したカスタム関数を呼び出せる機能およびプロバイダがリリースされていたので、本記事でそれを試していきたいと思います。
元々 OpenTofu / Terraform 双方でカスタム関数自体の機能は提供されていますが、ちょっとした関数を定義するだけでも自作プロバイダを用意する必要があり面倒です。
Provider-defined functions are supported in Terraform 1.8 and later.
https://developer.hashicorp.com/terraform/plugin/framework/v1.13.x/functions/concepts
As of OpenTofu 1.7.0, providers may define their own functions to be available during execution.
https://opentofu.org/docs/language/functions/#provider-defined-functions
今回紹介する機能はそのままTerraformコード中にカスタム関数のコードを書けばいいだけなので、とても簡単に使うことができます。
実際に試してみる
(この記事のソースコードは https://gitlab.com/ksaitou/2025-03-15_opentofu-immediate-func にあります)
環境準備
.opentofu-version
ファイルを用意した上で、 tenv でOpenTofuをインストールして試します。
1.9.0
# OpenTofu のインストール
$ tenv tofu install
今回試用するものをプロバイダとして定義します。残念ながら去年(2024-04)の発表以降開発はアクティブではないようです。
terraform {
required_providers {
lua = {
source = "opentofu/lua"
version = "0.0.2"
}
go = {
source = "opentofu/go"
version = "0.0.3"
}
}
}
# プロジェクトの初期化
$ tofu init
Lua (opentofu/lua
)
まずLua関数の実行から行っていきます。
locals {
lua_add = <<EOT
function add( input )
return input.foo + input.bar
end
return add
EOT
}
output "example" {
value = provider::lua::exec(local.lua_add, {"foo": 100, "bar": 200})
}
Luaのソースコードからは関数を返せばそれを lua::exec
で呼べるようです。
上記を実行すると、きちんと計算結果が表示されます。
$ tofu plan
Changes to Outputs:
+ example = 300
Go (opentofu/go
)
つづいて、Go関数の実行も試してみます。
package lib
type FooBar struct {
Former int `tf:"foo"`
Latter int `tf:"bar"`
}
func Add(foobar FooBar) int {
return foobar.Former + foobar.Latter
}
provider "go" {
go = file("./lib.go")
}
output "test" {
value = provider::go::add({"foo": 300, "bar": 400})
}
package lib
上で定義したエクスポートされた関数(大文字から始まる関数)をその関数名を用いて呼び出し可能なようです。
上記を実行すると、きちんと計算結果が表示されます。
$ tofu plan
Changes to Outputs:
+ test = 700
なぜGoのソースコードをいきなり実行できるのか?
opentofu/go
では ./lib.go
として外部のGoファイルをスクリプト言語のように指定していますが、本来Go言語はビルドしないと動かないはずです。どうなっているのでしょう?
プロバイダのソースコードを見てみましょう。
package main
import (
// ...
"github.com/traefik/yaegi/interp"
"github.com/traefik/yaegi/stdlib"
)
// ...
&tfprotov6.SchemaAttribute{
Name: "go",
Type: tftypes.String,
Required: true,
},
// ...
codeVal := cfg["go"]
var code string
err = codeVal.As(&code)
// ...
interpreter := interp.New(interp.Options{})
if err := interpreter.Use(stdlib.Symbols); err != nil {
// ...
_, err = interpreter.Eval(code)
// ...
exports := interpreter.Symbols("lib")
libExports := exports["lib"]
ざっと読む限り、go
プロパティに指定したコードをGoのインタプリタ実装である yaegi に渡しているようです。
つまり、ここで書いているGoはビルドされて動く通常のGoではありません。通常のGoプロバイダのように何でも自由に実装できると考えないほうがよさそうです。そういう用途であれば真っ当にGoでプロバイダを実装することになるでしょう。
プロバイダの実装については、以下の私のエントリも参考にしてください。
この機能(プロバイダ)は Terraform でも使えるのか?
これらのプロバイダがTerraformでも同じように使えるのか試してみます。
プロバイダがTerraformレジストリ側には存在していないので、プロバイダの参照について registry.opentofu.org/
を source
の先頭につけてあげれば参照可能です。
terraform {
required_providers {
lua = {
source = "registry.opentofu.org/opentofu/lua"
version = "0.0.2"
}
go = {
source = "registry.opentofu.org/opentofu/go"
version = "0.0.3"
}
}
}
Terraformもインストールしましょう。
1.11.2
# Terraform のインストール
$ tenv terraform install
これで参照自体はできるのですが、以下のようにそれぞれ実行可能かどうかは異なってしまいました。
-
opentofu/lua
: OpenTofu上と変わらず実行可能 -
opentofu/go
:There is no function named "provider::go::add".
と言われエラーで終了- そもそもプロバイダが任意の関数名を定義できるのがOpenTofuだけの機能の模様……
OpenTofuもTerraformと同じプロトコル(tfprotov6
)を話すのですが、動的な関数部分についてはOpenTofu側のみの拡張のようです。
両方で実行したい場合はLuaに限定したほうがよさそうです。
まとめ
OpenTofuで使えるインスタントな自作関数定義機能を使ってみました。
リポジトリを見る限り、去年の発表時点から開発が止まってしまっているので実際の利用はためらうところですが、今後に期待です。