🍣

Snowflake Terraform ProviderのVersion Upをなるべく安全にやる

2024/12/03に公開

Snowflake Providerについて

https://github.com/Snowflake-Labs/terraform-provider-snowflake

先日、Snowflake Providerのv1リリースが12/9に予定されていることが以下のドキュメントで発表されました。
https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/ROADMAP.md

現在の最新versionはv0.99.0で、Snowflake Providerはv1の正式リリースに向けて再設計を進めてきました。
頻繁にリリースされていて、さらに多くのversionでbreaking changeが含まれています。そのため何かしらの対応が必要なことがほとんどです。
安易にversionを上げてそのままapplyをしてしまうと、データ基盤に予期せぬ影響を与えてしまうので、なるべくSnowflakeに対して変更が走らないようにversion upを行うことが理想です。
この記事では私がどのようにversion upを行なっているかを紹介します。

Version upの手順

基本的にversionは一つずつ上げていくことをおすすめします。公式もそのようなやり方を推奨しています。

  • renovateがproviderの最新versionがリリースされたことを検知してPRをあげてくれます
    • providerの使用箇所が多い場合はrenovateを使って管理するのが楽です
    • renovateの設定
    • { 
        packageRules:[
          {
            groupName: "terraform snowflake dependencies",
            groupSlug: "terraform-snowflake-dependencies",
            matchManagers: ["terraform"],
            matchPackageNames: ["snowflake-labs/snowflake"],
          },
        ]
      }
      
    • snowflake providerはCIが通ったらマージすればオッケーというわけではなく、対応が必要なことが多いので、snowflakeだけで一個のPRになるようにしています
  • release noteを読んで内容をざっくり把握します
  • migration guideを読んでどのリソースが対象でどのようなbreaking changeがあり、どういう対応が必要かを確認します
  • 実際にplanしてみてplan結果にどのようなerror、warningが出ているのか確認します
    • ここで問題なければversion upは完了です
  • breaking changeやdeprecated resourceの移行に対応します

必要な対応

breaking change

  • できる限りplan結果に出るdiffを減らすことを目指します
  • プロパティが追加されている場合、default値が変わった場合は今設定されている値を確認して、指定するようにします
  • どうしてもdiffが生じてしまう場合は、変更が走っても良い一部のリソースでお試しapplyしてみます
    • apply時にtargetを指定するとやりやすいです
  • 走るクエリを確認して問題なさそうなのかを確認してから全体でapplyします

0.94.0 -> 0.95.0の例

before

~ resource "snowflake_user" "user" {
      + disable_mfa                                   = "default"
      ~ disabled                                      = "false" -> "default"
      - display_name                                  = (sensitive value) -> null
        id                                            = "id"
      + mins_to_bypass_mfa                            = -1
      + mins_to_unlock                                = -1
      + must_change_password                          = "default"
      # Warning: this attribute value will no longer be marked as sensitive
      # after applying this change. The value is unchanged.
      ~ name                                          = (sensitive value)
      ~ show_output                                   = [
          - {
              ...
            },
        ] -> (known after apply)
        # (70 unchanged attributes hidden)
    }

今回変更があったプロパティに値を設定した


resource "snowflake_user" "user" {
  name                 = upper(var.name)
  login_name           = local.login_name
  display_name         = var.display_name
  email                = var.email
  default_role         = local.default_role
  default_warehouse    = local.default_warehouse
  default_namespace    = local.default_namespace
  disabled             = false
  must_change_password = false
}

after

~ resource "snowflake_user" "user" {
      + disable_mfa                                   = "default"
      # Warning: this attribute value will no longer be marked as sensitive
      # after applying this change.
      ~ display_name                                  = (sensitive value)
        id                                            = "id"
      + mins_to_bypass_mfa                            = -1
      + mins_to_unlock                                = -1
      # Warning: this attribute value will no longer be marked as sensitive
      # after applying this change. The value is unchanged.
      ~ name                                          = (sensitive value)
      ~ show_output                                   = [
          - {
              ...
            },
        ] -> (known after apply)
        # (72 unchanged attributes hidden)
    }

この状態であまり影響が出ても問題ない一つのユーザーに対してapplyしたら以下のクエリが走りました。

ALTER USER "USER_NAME" UNSET MUST_CHANGE_PASSWORD, MINS_TO_UNLOCK, MINS_TO_BYPASS_MFA, DISABLE_MFA

このSQLがユーザーに対して走ることは問題なさそうだったら全部まとめてapplyします。

deprecated resource

removed block, import blockを使ってresourceを移行していきます

snowflake_role -> snowflake_account_roleへの移行例
terraform側での状態の持ち方が変わっただけなので、古いresourceでの状態を削除して、新しいresourceで状態を持つようにimportするイメージです。

before

resource "snowflake_account_role" "this" {
  name = "ROLE_NAME"
}

after

resource "snowflake_account_role" "this" {
  name = "ROLE_NAME"
}

removed {
  from = snowflake_role.this
  lifecycle {
    destroy = false
  }
}

import {
  to = snowflake_account_role.this
  id = "ROLE_NAME"
}

plan結果のdiffは以下のようになります

 . resource "snowflake_role" "this" {
        id                   = "ROLE_NAME"
        name                 = "ROLE_NAME"
        # (3 unchanged attributes hidden)
    }

Plan: 103 to import, 0 to add, 0 to change, 0 to destroy.

実際には多くのresourceが定義されていて、一つ一つこのようなblockを書いていくのはとても大変です。生成AIを使って作業を簡略化することができます。

自分の環境では多くのresourceはmodule内で定義されています。
そのためremoved blockはmodule内部に記述すれば良いのですが、import blockはmoduleを呼び出している箇所に記述しなければならないのでimport blockをresourceの数だけ記述する必要があります。

import対象のresourceが定義されているコードと一緒に以下のプロンプトを使って生成してもらっています。

このコードはsnowflakeのroleをterraformで定義しています。上で定義されているresourceをimport blockを使ってimportしたいです。resourceの数だけ以下のフォーマットに従ってimport blockを生成してください
```tf
import {
 to = snowflake_account_role.{name}
 id = "{ROLE_NAME}"
}
```

なんかうまくいかないなと思ったら

v1リリースに向けて開発をガンガン進めていた状態なので、バグに遭遇することは少なくありません。
そのような時はissueを立てましょう
細かく情報を書いたらすぐに反応してくれることが多いです。

Discussion