GitLab CI/CDパイプラインでOpenTofu CI/CDコンポーネントを使ってみよう

## はじめに
業務でTerraform (OpenTofu)を用いたAWSリソースの管理を行ってい(きたいと思っていま)す。

元々Terraformを用いたインフラ構築、GitLab CI/CDを用いたバックエンドロジックの更新などは行っていたのですが、Terraformの実行はパイプライン上ではなくローカルで行なっていました。
チームでIaCを運用していく以上、ローカルで作業を続けるわけにもいかないので今回調べてパイプラインを構築してみました。


※記事はGitLab.com v17.6時点の内容になります。

## OpenTofuって？
TerraformをフォークしたIaCツールです。
立ち上がった経緯としては「HashiCorpのTerraformライセンス変更により、OSSコミュニティがTerraformをフォークしたOpenTofuを立ち上げた」と言う感じです。
詳しくは [@minamijoyo](https://qiita.com/minamijoyo)さんの以下の記事がわかりやすかったです。ありがとうございました。

https://qiita.com/minamijoyo/items/16d1b5b15a60d17e350a


ライセンス変更により競合他社によるTerraformの商用利用制限が設けられました。
その影響を受けて元々GitLabから提供されていたTerraform CI/CDコンポーネントがDeprecateされる予定です。

https://docs.gitlab.com/ee/update/deprecations.html#deprecate-terraform-cicd-templates

代わりにOpenTofuコンポーネントが提供されているようです。

## GitLab CI/CDコンポーネントって？
その名の通り、CI/CDパイプライン上で利用できるコンポーネント(再利用可能な部品)です。
自分で作成することもできますし、GitLab公式から提供されているものもあります。.gitlab-ci.yml中でinclude句を使用して設定します。今回はGitLabから公式から提供されているものを使用します。


https://gitlab-docs.creationline.com/ee/ci/components/

https://gitlab.com/components/opentofu

## 実装
以下が実際の.gitlab-ci.ymlです。
業務では OIDCを利用した一時的な認証情報の取得を行なっていますが、主題とは関係ないので割愛しています。リソースを作成・管理するための適切な認証情報が設定されていれば問題ありません。
またAWSの環境ごとに分ける記述もしていますが割愛しています。

※なぜかグローバル変数としてGITLAB_TOFU_ROOT_DIRを設定しても参照されなかったため苦肉の策として全ジョブに記述しています。
```yml:.gitlab-ci.yml
stages:
  - opentofu_validate
  - opentofu_build
  - opentofu_deploy
  - opentofu_cleanup

include:
  - component: gitlab.com/components/opentofu/full-pipeline@~latest
    inputs:
      version: latest
      opentofu_version: 1.8.0

fmt:
  variables:
    GITLAB_TOFU_ROOT_DIR: /builds/path/to/repo/and/tffile
  stage: opentofu_validate

validate:
  variables:
    GITLAB_TOFU_ROOT_DIR: /builds/path/to/repo/and/tffile
  stage: opentofu_validate

plan:
  variables:
    GITLAB_TOFU_ROOT_DIR: /builds/path/to/repo/and/tffile
  stage: opentofu_build

apply:
  variables:
    GITLAB_TOFU_ROOT_DIR: /builds/path/to/repo/and/tffile
  stage: opentofu_deploy

destroy:
  variables:
    GITLAB_TOFU_ROOT_DIR: /builds/path/to/repo/and/tffile
  stage: opentofu_cleanup

delete-state:
  variables:
    GITLAB_TOFU_ROOT_DIR: /builds/path/to/repo/and/tffile
  stage: opentofu_cleanup

```

今回はS3バケットを作成するために以下のようなTFファイルを用意しました。
providerでGitLab上のHTTPバックエンドを指定しています。
またinitはパイプライン上では行わずローカル環境で行い、バックエンドを指定しました。

```tf:main.tf
resource "random_string" "s3_unique_key" {
  length  = 16
  upper   = false
  lower   = true
  numeric = true
  special = false
}

resource "aws_s3_bucket" "images" {
  bucket        = "s3-test-${random_string.s3_unique_key.result}"
  force_destroy = true
}

```

```tf:provider.tf
terraform {
  required_version = ">= 1.8.0"

  backend "http" {
    address        = "https://gitlab.com/api/v4/projects/12345678/terraform/state/default"
    lock_address   = "https://gitlab.com/api/v4/projects/12345678/terraform/state/default/lock"
    unlock_address = "https://gitlab.com/api/v4/projects/12345678/terraform/state/default/lock"
    lock_method    = "POST"
    unlock_method  = "DELETE"
    retry_wait_min = 5
  }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    http = {
      source  = "hashicorp/http"
      version = "~>2.0"
    }
  }
}

provider "aws" {
  region = "ap-northeast-1"
}

```

## 結果

```zsh:planログ抜粋
$ gitlab-tofu plan $args
Initializing the backend...
Successfully configured the backend "http"! OpenTofu will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding hashicorp/http versions matching "~> 2.0"...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Finding latest version of hashicorp/random...
- Installing hashicorp/random v3.6.3...
- Installed hashicorp/random v3.6.3 (signed, key ID 0A0B0C0D0E0F0G0H)
- Installing hashicorp/http v2.2.0...
- Installed hashicorp/http v2.2.0 (signed, key ID 0A0B0C0D0E0F0G0H)
- Installing hashicorp/aws v5.80.0...
- Installed hashicorp/aws v5.80.0 (signed, key ID 0A0B0C0D0E0F0G0H)
Providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://opentofu.org/docs/cli/plugins/signing/
OpenTofu has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.
OpenTofu has been successfully initialized!
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:
  # aws_s3_bucket.images will be created
  + resource "aws_s3_bucket" "images" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = (known after apply)
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = true
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)
    }
  # random_string.s3_unique_key will be created
  + resource "random_string" "s3_unique_key" {
      + id          = (known after apply)
      + length      = 16
      + lower       = true
      + min_lower   = 0
      + min_numeric = 0
      + min_special = 0
      + min_upper   = 0
      + number      = true
      + numeric     = true
      + result      = (known after apply)
      + special     = false
      + upper       = false
    }
Plan: 2 to add, 0 to change, 0 to destroy.
```

![パイプライン図](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2627031/7f8b19aa-cafa-b842-42a8-753eda7b7f17.png)

opentofu_deployステージはwhen:manualで設定されているため、planジョブが完了したタイミングでブロックされます。そのまま変更が適用されることはありません。

applyジョブの再生ボタンを押下すると、AWS環境への適用が行われます。

```zsh:applyログ抜粋
$ gitlab-tofu apply
Initializing the backend...
Successfully configured the backend "http"! OpenTofu will automatically
use this backend unless the backend configuration changes.
Initializing provider plugins...
- Finding hashicorp/http versions matching "~> 2.0"...
- Finding latest version of hashicorp/random...
- Finding hashicorp/aws versions matching "~> 5.0"...
- Installing hashicorp/random v3.6.3...
- Installed hashicorp/random v3.6.3 (signed, key ID 0A0B0C0D0E0F0G0H)
- Installing hashicorp/aws v5.80.0...
- Installed hashicorp/aws v5.80.0 (signed, key ID 0A0B0C0D0E0F0G0H)
- Installing hashicorp/http v2.2.0...
- Installed hashicorp/http v2.2.0 (signed, key ID 0A0B0C0D0E0F0G0H)
Providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://opentofu.org/docs/cli/plugins/signing/
OpenTofu has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.
OpenTofu has been successfully initialized!
random_string.s3_unique_key: Creating...
random_string.s3_unique_key: Creation complete after 0s [id=s3bucketid]
aws_s3_bucket.images: Creating...
aws_s3_bucket.images: Creation complete after 5s [id=s3-test-s3bucketid]
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.
```

無事にGitLabパイプライン上でS3バケットが作成されたことを確認できました。

## おわりに
今回はopentofu/full-pipelineを利用しましたが、実際の運用でdestroyまでパイプラインに乗せる必要はないと思うので、必要なテンプレートを適切に選択する必要があると感じました。