Cloudflare PagesのTerraform Moduleを作った


## Cloudflare Pagesを簡単にセットアップしたい

Cloudflare Pagesは、簡単に静的ファイルをホスティングできるので便利で、いろいろなテスト的な使い方をしているのですが、毎回コンソールでセットアップするのが手間に感じたので、Terraformの勉強も兼ねて、TerraformでCloudflare Pagesプロジェクトとその関連リソースを管理するようにしました。

## 最終的な構成

```bash
$ tree .
.
├── README.md
└── cloudflare
    ├── main.tf
    ├── modules
    │   └── pages
    │       ├── README.md
    │       ├── main.tf
    │       ├── outputs.tf
    │       └── variables.tf
    ├── terraform.tfstate
    ├── terraform.tfstate.backup
    ├── terraform.tfvars
    └── variables.tf

3 directories, 10 files
```

上記が最終的なディレクトリ構造です。

将来的にはcloudflare以外のリソースも管理することを考えて、プロジェクトルートに`cloudflare`ディレクトリを作成し、その中にCloudflare関連のmoduleを配置するようにしました。

## Pages Moduleについて

普段Cloudflare Pagesを使うときに、GitHub連携をさせないパターンが多いので、今回はとてもシンプルに空のプロジェクトと、カスタムドメインの設定とそれに伴うDNSの設定ができるようなModuleにしました。

完成形としては以下のとおりです。

```hcl:modules/pages/main.tf
terraform {
  required_providers {
    cloudflare = {
      source = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}

resource "cloudflare_pages_project" "cloudflare_pages" {
  account_id = var.cloudflare_account_id
  name = var.project_name
  production_branch = var.production_branch
}

resource "cloudflare_pages_domain" "cloudflare_pages" {
  account_id = var.cloudflare_account_id
  project_name = cloudflare_pages_project.cloudflare_pages.name
  domain = var.custom_domain
}

resource "cloudflare_record" "cloudflare_pages" {
  zone_id = var.cloudflare_zone_id
  name = var.custom_domain
  value = cloudflare_pages_project.cloudflare_pages.subdomain
  type = "CNAME"
  proxied = true
}
```

```hcl:modules/pages/variables.tf
variable "cloudflare_account_id" {
  description = "Cloudflare account ID"
  type = string
  nullable = false
}

variable "project_name" {
  description = "Cloudflare pages project"
  type = string
  nullable = false
}

variable "production_branch" {
  description = "Production branch for Cloudflare pages project"
  type = string
  default = "main"
  nullable = true
}

variable "custom_domain" {
  description = "Custom subdomain for Cloudflare pages project"
  type = string
  nullable = false
}

variable "cloudflare_zone_id" {
  description = "Cloudflare zone ID"
  type = string
  nullable = false
}
```

```hcl:modules/pages/outputs.tf
output "name" {
  description = "Cloudflare pages project name"
  value = cloudflare_pages_project.cloudflare_pages.name
}

output "default_url" {
  description = "Cloudflare pages project default URL"
  value = cloudflare_pages_project.cloudflare_pages.subdomain
}

output "custom_url" {
  description = "Cloudflare pages project custom URL"
  value = cloudflare_record.cloudflare_pages.name
}
```

## 躓いたところ

### terraform applyに失敗する

最初は、`terraform apply`を実行すると、Cloudflare Pagesにカスタムドメインを設定するためには、Cloudflare Pagesのプロジェクトが必要だというエラーが出てきてしまいました。

これは、はじめは以下のように各リソースに対して値を与えてそれぞれのリソースを作成するようにしていたため、Cloudflare Pagesのプロジェクトが作成される前に、カスタムドメインの設定が行われてしまっていたためです。

```hcl
resource "cloudflare_pages_project" "cloudflare_pages" {
  account_id = var.cloudflare_account_id
  name = var.project_name
  production_branch = var.production_branch
}

resource "cloudflare_pages_domain" "cloudflare_pages" {
  account_id = var.cloudflare_account_id
  project_name = var.project_name
  domain = var.custom_domain
}

resource "cloudflare_record" "cloudflare_pages" {
  zone_id = var.cloudflare_zone_id
  name = var.custom_domain
  value = cloudflare_pages_project.cloudflare_pages.subdomain
  type = "CNAME"
  proxied = true
}
```

これを修正して、Cloudflare Pagesのプロジェクトリソースのoutputを参照するようにしたところ依存関係が解決し、正常に適用できるようになりました。

ちなみにこれ以外にも`depends_on`を使う方法もあるようですが、今回はoutputを使う方法で解決しました。

### Inconsistent dependency lock fileエラー

最初は、`terraform plan`を実行すると、以下のエラーが出てしまいました。

```bash
Error: Inconsistent dependency lock file
│
│ The following dependency selections recorded in the lock file are inconsistent with the current configuration:
│   - provider registry.terraform.io/hashicorp/cloudflare: required by this configuration but no version is selected
│
│ To update the locked dependency selections to match a changed configuration, run:
│   terraform init -upgrade
```

`hashcorp/cloudflare`のバージョンが指定されていないというエラーですが、`terraform init -upgrade`を実行しても解決しませんでした。

実際に使用しているのは`cloudflare/cloudflare`なので、その旨をmoduleの`main.tf`に記載して解決しました。

```hcl
terraform {
  required_providers {
    cloudflare = {
      source = "cloudflare/cloudflare"
      version = "~> 4.0"
    }
  }
}
```

## まとめ

とりあえずTerraformでリソース管理デビューを果たすことができました。今後は、他のリソースも管理できるようにしていきたいと思います。

多分、Terraformのベストプラクティスとかもあると思うので、それも勉強していきたいですね。

ありがとうございました。