kakakakakku blog

Weekly Tech Blog: Keep on Learning!

Cloudflare Terraform Provider でリソース名を変更する (GritQL / removed & import)

2024年8月にリリースされた Cloudflare Terraform Provider v4.40.0 で Cloudflare Zero Trust 関連のリソースに変更があって,多くのリソースが deprecated になっている.よって,現在最新の v5 にアップデートするためにはリソースの変更に対応する必要がある.最近 Cloudflare Terraform Provider v4 → v5 にアップデートをする機会があって,個人的に検証したことをまとめておこうと思う📝

今回は以下のバージョンを使う.

  • Cloudflare Terraform Provider v4.52.5
  • Cloudflare Terraform Provider v5.11.0

github.com

リソース例

今回は大きく2種類のリソースをサンプルとして使う.

1. cloudflare_access_tag

👾 main.tf

resource "cloudflare_access_tag" "sandbox" {
  account_id = "xxx"
  name       = "sandbox-tag"
}

Warning

cloudflare_access_tag に対して警告が出ている🚨

╷
│ Warning: Deprecated Resource
│ 
│   with cloudflare_access_tag.sandbox,
│   on main.tf line 10, in resource "cloudflare_access_tag" "sandbox":
│   10: resource "cloudflare_access_tag" "sandbox" {
│ 
│ `cloudflare_access_tag` is now deprecated and will be removed in the next major version. Use `cloudflare_zero_trust_access_tag` instead.
╵

GritQL を使って更新する

Terraform Cloudflare Provider Version 5 Upgrade GuideMigrating renamed resources では GritQL を使って Terraform コードと tfstate を更新するアプローチが紹介されていた.grit apply コマンドは今まで使ったことがなかった❗️

docs.grit.io

まずは GritQL で main.tf のリソース名を更新する.

$ grit apply cloudflare_terraform_v5_resource_renames_configuration
./main.tf
    -resource "cloudflare_access_tag" "sandbox" {
    +resource "cloudflare_zero_trust_access_tag" "sandbox" {
       account_id = "xxx"
       name       = "sandbox-tag"
     }
    

Processed 5 files and found 1 matches

cloudflare_access_tag リソースの属性はシンプルで更新対象はなかった.

$ grit apply cloudflare_terraform_v5
Processed 5 files and found 0 matches

最後は GritQL で terraform.tfstate を更新する.

$ grit apply cloudflare_terraform_v5_resource_renames_state terraform.tfstate
terraform.tfstate
       "resources": [
         {
           "mode": "managed",
    -      "type": "cloudflare_access_tag",
    +      "type": "cloudflare_zero_trust_access_tag",
           "name": "sandbox",
           "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
           "instances": [
    

Processed 1 files and found 1 matches

Cloudflare Terraform Provider v5 にアップデートする

$ terraform init -upgrade
Initializing the backend...
Initializing provider plugins...
- Finding cloudflare/cloudflare versions matching "~> 5.0"...
- Installing cloudflare/cloudflare v5.11.0...

差分を確認する

差分なしで問題なくリソース名を変更できた👌

$ terraform plan
No changes. Your infrastructure matches the configuration.

2. cloudflare_access_group(失敗)

👾 main.tf

resource "cloudflare_access_group" "sandbox" {
  account_id = "xxx"
  name       = "sandbox-group"

  include {
    email = ["y.yoshida22@gmail.com"]
  }
}

Warning

cloudflare_access_group に対して警告が出ている🚨

╷
│ Warning: Deprecated Resource
│
│   with cloudflare_access_group.sandbox,
│   on main.tf line 1, in resource "cloudflare_access_group" "sandbox":
│    1: resource "cloudflare_access_group" "sandbox" {
│
│ `cloudflare_access_group` is now deprecated and will be removed in the next major version. Use `cloudflare_zero_trust_access_group` instead.
╵

GritQL を使って更新する

まずは GritQL で main.tf のリソース名を更新する.

$ grit apply cloudflare_terraform_v5_resource_renames_configuration
./main.tf
    -resource "cloudflare_access_group" "sandbox" {
    +resource "cloudflare_zero_trust_access_group" "sandbox" {
       account_id = "xxx"
       name       = "sandbox-group"
     
    

Processed 5 files and found 1 matches

次に GritQL で main.tf の属性を更新する.

⚠️ちなみに以下の変更は間違っていて別途直すことになる.

$ grit apply cloudflare_terraform_v5
./main.tf
       account_id = "xxx"
       name       = "sandbox-group"
     
    -  include {
    +  include =[ {
         email = ["y.yoshida22@gmail.com"]
    -  }
    +  }]
     }
    

Processed 5 files and found 1 matches

最後は GritQL で terraform.tfstate を更新する.

$ grit apply cloudflare_terraform_v5_resource_renames_state terraform.tfstate
terraform.tfstate
       "resources": [
         {
           "mode": "managed",
    -      "type": "cloudflare_access_group",
    +      "type": "cloudflare_zero_trust_access_group",
           "name": "sandbox",
           "provider": "provider[\"registry.terraform.io/cloudflare/cloudflare\"]",
           "instances": [
    

Processed 1 files and found 1 matches

Cloudflare Terraform Provider v5 にアップデートする

$ terraform init -upgrade
Initializing the backend...
Initializing provider plugins...
- Finding cloudflare/cloudflare versions matching "~> 5.0"...
- Installing cloudflare/cloudflare v5.11.0...

差分を確認する

しかし terraform plan コマンドを実行するとエラーになってしまう.原因は include 属性の構文が違うことだった.

$ terraform plan
╷
│ Error: Incorrect attribute value type
│ 
│   on main.tf line 5, in resource "cloudflare_zero_trust_access_group" "sandbox":
│    5:   include =[ {
│    6:     email = ["y.yoshida22@gmail.com"]
│    7:   }]
│ 
│ Inappropriate value for attribute "include": element 0: attribute "email": object required, but have tuple.
╵

👾 main.tf

cloudflare_zero_trust_access_group を参考に修正しつつ適切なコードに修正する.

resource "cloudflare_zero_trust_access_group" "sandbox" {
  account_id = "xxx"
  name       = "sandbox-group"

  include = [
    {
      email = {
        email = "y.yoshida22@gmail.com"
      }
    }
  ]
}

差分を確認する

今度はまた別のエラーが出てしまう.cloudflare_access_groupcloudflare_zero_trust_access_group はリソース名以外に構造も変わっていて単純な移行は難しそうだった😇

$ terraform plan          

Planning failed. Terraform encountered an error while generating this plan.


│ Error: Unable to Read Previously Saved State for UpgradeResourceState
│ 
│   with cloudflare_zero_trust_access_group.sandbox,
│   on main.tf line 1, in resource "cloudflare_zero_trust_access_group" "sandbox":
│    1: resource "cloudflare_zero_trust_access_group" "sandbox" {

│ There was an error reading the saved resource state using the current resource schema.

│ If this resource state was last refreshed with Terraform CLI 0.11 and earlier, it must be refreshed or applied with an older provider version first. If you manually modified the resource
│ state, you will need to manually modify it to match the current resource schema. Otherwise, please report this to the provider developer:

│ AttributeName("include").ElementKeyInt(0).AttributeName("any_valid_service_token"): invalid JSON, expected "{", got %!q(bool=false)

3. cloudflare_access_group(成功)

GritQL 以外の選択肢として Terraform ネイティブの仕組みを使って removed ブロックと import ブロックを組み合わせるアプローチを試す.個人的には tfstate を直接書き換えることに抵抗があって,可能であればこちらを選択したいとは思う😇

Terraform 管理の対象外にする

まずは Cloudflare Terraform Provider v4 のまま removed ブロックを使ってリソースを Terraform 管理の対象外にする.今回はコードベースで操作しているけど terraform state rm コマンドを使う選択肢もある.

removed {
  from = cloudflare_access_group.sandbox

  lifecycle {
    destroy = false
  }
}

そして terraform apply コマンドを実行しておく.

$ terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

 # cloudflare_access_group.sandbox will no longer be managed by Terraform, but will not be destroyed
 # (destroy = false is set in the configuration)
 . resource "cloudflare_access_group" "sandbox" {
        id         = "bddd74d0-f73c-4e74-9e22-ef6640b86e66"
        name       = "sandbox-group"
        # (1 unchanged attribute hidden)

        # (1 unchanged block hidden)
    }

Plan: 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Some objects will no longer be managed by Terraform
│ 
│ If you apply this plan, Terraform will discard its tracking information for the following objects, but it will not delete them:
│  - cloudflare_access_group.sandbox
│ 
│ After applying this plan, Terraform will no longer manage these objects. You will need to import them into Terraform to manage them again.

Cloudflare Terraform Provider v5 にアップデートする

$ terraform init -upgrade
Initializing the backend...
Initializing provider plugins...
- Finding cloudflare/cloudflare versions matching "~> 5.0"...
- Installing cloudflare/cloudflare v5.11.0...

Terraform 管理の対象にする

最後は import ブロックを使ってリソースを Terraform 管理の対象にする.今回はコードベースで操作しているけど terraform import コマンドを使う選択肢もある.なお cloudflare_zero_trust_access_group の場合は <{accounts|zones}/{account_id|zone_id}>/<group_id> という id でインポートする.

import {
  id = "accounts/xxx/xxx"
  to  = cloudflare_zero_trust_access_group.sandbox
}

resource "cloudflare_zero_trust_access_group" "sandbox" {
  account_id = "xxx"
  name       = "sandbox-group"

  include = [
    {
      email = {
        email = "y.yoshida22@gmail.com"
      }
    }
  ]
}

そして terraform apply コマンドを実行してインポートする.

$ terraform apply
Apply complete! Resources: 1 imported, 0 added, 0 changed, 0 destroyed.

差分を確認する

インポート後に差分がなくうまく Terraform 管理の対象にできた👌

terraform state list コマンドでもリソースを確認できた.

$ terraform plan
No changes. Your infrastructure matches the configuration.

$ terraform state list
cloudflare_zero_trust_access_group.sandbox