injecuetにTerraform stateを参照する機能を追加した

injecuetとは: injecuet: CUEに環境変数を注入する便利CLIツールを書いた - Sexually Knowing

これまでCUEに埋め込むデータソースとして環境変数だけだったのですが、新たにTerraform stateを参照する機能を追加しました。

こういうCUEがあって:

@inject(tfstate,stateURL=./terraform/ok/terraform.tfstate)

name: string @inject(tfstate,name=output.user.name)
age: int @inject(tfstate,name=output.user.age)

terraform/ok/terraform.tfstate がこういうかんじだとして:

{
  "version": 4,
  "terraform_version": "1.1.6",
  "serial": 2,
  "lineage": "3124ddff-8837-9bb1-a0d6-fe4fd14969aa",
  "outputs": {
    "user": {
      "value": {
        "age": 17,
        "name": "aereal"
      },
      "type": [
        "object",
        {
          "age": "number",
          "name": "string"
        }
      ]
    }
  },
  "resources": []
}

injecuet ./tfstate.cue を実行するとこんな風に解決されます:

{
    @inject(tfstate,stateURL=./terraform/ok/terraform.tfstate)
    name: "aereal" @inject(tfstate,name=output.user.name)
    age:  17       @inject(tfstate,name=output.user.age)
}

attributeの形式を変更

before:

@injectenv(USER)

after:

@inject(env,name=USER)

Terraform state対応を入れるにあたり、データソースを識別できるようより拡張性のあるフォーマットに変えました。

古い形式も現在サポートしていますが、そのうち消えるかもしれません。

実装の話を簡単にすると、元々は (*Attribute) Contents() string という関数でattributeの中身を得て、それを strings.Split していました。 Contents()@some_attr(ここだよ)ここだよ を返します。

が、よくよく *Attribute のメソッドを見て Arg(int) などが生えていることに気がつきます。 Arg reports the contents of the ith comma-separated argument of a. という説明の通り、attributeの括弧の中身をカンマ区切りのリストとみなす実装です。

このようにライブラリのサポートがより受けやすい形式にしようということで変えました。

tfstate-lookup最高

Terraform stateから値を取る処理はtfstate-lookupにおんぶにだっこです。とても助かっています。

fujiwara/tfstate-lookup: Lookup resource attributes in tfstate.

CUEとJSONにおける数値の扱い

ちょっとハックが必要だったのが数値の扱いです。具体的にはfloatとintで、ちょっと込み入ったややdirtyな実装になっています。

具体的にはここ: github.com

CUEはintとfloatという区別があるのですが、JSONはnumberというGoやCUEでいうintとfloatをまとめた型しかありません。 これは元になったECMAScriptの仕様に由来するものですね。

で、Goのencoding/jsonのunmarshalはデフォルトでJSONのnumberをfloat64に変換します。

Goのfloat64をCUEがintを求める式に埋めようとすると型の不一致でエラーになりますが、Terraform stateに数値が含まれているとCUEに埋められないのも不条理です。

なので以下の条件をすべて満たす際にint64へキャストするようにしています。

  • CUEの式がintを受け入れる
  • CUEの式が floatは受け入れない
  • Terraform stateから得た値 (interface{}) がfloat32かfloat64である

理想的にはTerraform stateから得た値の小数部が0であることまで見るとよいのでしょうが、だいぶ大変なのでここらへんで諦めています。

良い実装方法があればPull Requestしてもらえると嬉しいです。