TerraformのTanzu Mission Control Providerを試す

Terraformと言えば様々なベンダー・コミュニティが提供するProviderを利用することで各種インフラを簡単に操作することが出来るが、Tanzu Mission Control（以下TMC）の操作用に[Tanzu Mission Control Provider](https://registry.terraform.io/providers/vmware/tanzu-mission-control/latest/docs)が用意されている。
これを試してみる。

## 何が出来るか
どこまでTerraformで出来るかを書いているドキュメントが見つけられていないが、[Resourceの一覧](https://registry.terraform.io/providers/vmware/tanzu-mission-control/latest/docs)を見るとTerraform経由でTMCの操作のほとんどが出来るようである。
[Known Issue](https://registry.terraform.io/providers/vmware/tanzu-mission-control/latest/docs/guides/known_issues)を見るとvSphere8でClassBaseでのクラスタ構築には失敗するようであるが、中の人に聞いたら「直ってる」とのことなのでクラスタ構築も出来そうだ。

## 前提条件
今回の検証をするにあたり、以下は既に用意しているものとする。
- Terraformコマンド
- TKGクラスタがあり、TMCのAgentを導入済み
- VMware Cloud ServicesからAPIトークンを取得済み
- APIトークンの権限はTMC上でAdmin相当

なお、TMCのProviderの前提として、K8sクラスタはTKGのみとなっている点に注意。
APIトークンの取得方法が分からない人は[こちら](https://qiita.com/ipppppei/items/14d2302a89b263a97266#cli%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB%E3%81%A8%E5%88%9D%E6%9C%9F%E8%A8%AD%E5%AE%9A)を参照するとよい。

## 触ってみる
TMCの`Automation center`から`Terraform`のタブをクリックするとサンプルが表示されるので、それに従って進める。

最初にProviderの定義を作成する。なお、TMCのAutomation centerで確認できるサンプルでは1.0.3を使っていたが、'23/12/22時点では最新版が1.4.0だったので、バージョンを最新のものに変更している。
```terraform:provider.tf
cat << EOF > ./provider.tf
terraform {
  required_providers {
    tanzu-mission-control = {
      source = "vmware/tanzu-mission-control"
      version = "1.4.0"
    }
  }
}

provider "tanzu-mission-control" {
  endpoint = var.endpoint
  vmw_cloud_api_token = var.vmw_cloud_api_token
}
EOF
```
次に変数の定義を作成し、TMCのエンドポイントとAPIトークンを設定する。
```terraform:variables.tf
cat << EOF > ./variables.tf
variable "endpoint" {
  description = "The Tanzu Mission Control service endpoint hostname"
  type = string
  default = "xxxx.tmc.cloud.vmware.com"
}

variable "vmw_cloud_api_token" {
  description = "The VMware Cloud Services API Token"
  type = string
  default = "325y0....jC"
}
EOF
```
この段階で`terraform init`は通るようになる。
```sh
$ terraform init

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of vmware/tanzu-mission-control from the dependency lock file
- Using previously-installed vmware/tanzu-mission-control v1.4.0

Terraform has been successfully initialized!
：（省略）
```
サンプルにあるように、ClusterGroupを作ってみる。
ただし、サンプルには以下の問題があるため訂正して作成する。
- シングルクォートで括っているが、シングルクォートはTerraform的に使用不可
- `name`にアンダースコアを使っているが、アンダースコアはTMC的に使用不可
```terraform:create_cluster_group.tf
cat << EOF > ./create_cluster_group.tf
resource "tanzu-mission-control_cluster_group" "create_cluster_group" {
  name = "imurata-test-cluster-group"
  meta {
    description = "test_cluster_group_description"
    labels = {
      "env" : "test",
      "using" : "terraform"
    }
  }
}
EOF
```
念のためドライランを実施。
```
terraform plan
```
特にエラーが出なければ、applyする。
```
terraform apply
```
無事作成出来た。
![1703221887071.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/12771282-45c2-6e73-aaf6-9e094a0ab831.png)

削除も実施してみる。`terraform destroy`で削除する。
```
terraform destroy
```
フィルターに出てこなくなったので、無事削除できた。
![1703221963707.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/547981/53589657-eb81-8d70-5659-25765e7a017a.png)

## Harborをデプロイする
[Package Install](https://registry.terraform.io/providers/vmware/tanzu-mission-control/latest/docs/resources/package_install)Resourceを見ると、Tanzu Packageもデプロイ出来そうなので、試しにHarborをデプロイしてみる。
Harborに必要なものと言えば、cert-manager, contourなので、これらも合わせてデプロイする。
が、結論からいうと上手く行かない。
試したコードはこちら。（Contour部分で躓いたため、Harborまでは書いていない）
<details><summary>create-contour.tf</summary><div>

```terraform:create-contour.tf
variable "cluster_name" {}
variable "provisioner_name" {}
variable "management_cluster_name" {}

locals {
    install-list = {
        "cert-manager" = {
            name = "cert-manager"
            package_metadata_name = "cert-manager.tanzu.vmware.com"
            constraints = "1.12.2+vmware.1-tkg.1"
            inline_values = { "" : "" }
        }
        "contour" = {
            name = "contour"
            package_metadata_name = "contour.tanzu.vmware.com"
            constraints = "1.25.2+vmware.1-tkg.1"
            inline_values = jsonencode({
                    {
                        "certificates": {
                            "duration": "8760h",
                            "renewBefore": "360h"
                        },
                        "contour": {
                            "configFileContents": {},
                            "logLevel": "info",
                            "pspNames": "vmware-system-restricted",
                            "replicas": 2,
                            "useProxyProtocol": false
                        },
                        "envoy": {
                            "hostNetwork": false,
                            "hostPorts": {
                                "enable": true,
                                "http": 80,
                                "https": 443
                            },
                            "logLevel": "info",
                            "service": {
                                "annotations": {},
                                "disableWait": false,
                                "externalTrafficPolicy": "Cluster",
                                "type": "LoadBalancer"
                            },
                            "terminationGracePeriodSeconds": 300
                        },
                        "infrastructure_provider": "vsphere",
                        "namespace": "tanzu-system-ingress"
                    }
                    })
        }
    }
}

resource "tanzu-mission-control_package_install" "create_package_install" {
  for_each = local.install-list
  name = each.value.name
  namespace = "my-packages"

  scope {
    cluster {
      name                    = "${var.cluster_name}"
      provisioner_name        = "${var.provisioner_name}"
      management_cluster_name = "${var.management_cluster_name}"
    }
  }

  spec {
    package_ref {
      package_metadata_name = each.value.package_metadata_name
      version_selection {
        constraints = each.value.constraints
      }
    }
    inline_values = each.value.inline_values
  }
}
```

</div></details>

同じリソースでcert-manager, contourを作成するため、for_eachで回す形で各種パラメータを設定している。
また、`package_metadata_name`や`constraints`は`kind: PackageInstall`が中で持っているものと同じ値を指定する。
`inline_values`は取り敢えずJSONで渡してみたが上手く行かず、仕様を確認したところ、StringのMapとなっているようで、`{"<文字列>": "<文字列>"}`のみ受け付けている模様。
これが問題で、ContourやHarborのvalues.yamlの中身はStringで渡せるようなものではなく、無理やり文字列にしても以下のように逆に`PackageInstall`側で怒られる。

```sh
inline-values:
    |
  1 | {"certificates":"{duration:8760h, renewBefore:360h}"}
    |

    = found: string
    = expected: map (by schema.yaml:179)
```

ということで、現時点ではvalues.yamlが不要なものだったり、値がシンプルなものにしか利用できない。
Feature Requestは発行したので、そのうち実装されることを期待したい。

## まとめ
TerraformのTMC Providerを使って、TMCやTKGをTerraformで管理する手がかりを得ることが出来た。
ただ、まだ機能としては未熟な部分もあり、ドキュメントも最低限の部分のみ整備されている感じなので、利用する場合はしっかり検証してから利用するようにした方が良さそうだ。