構築後にSnowflake環境をTerraform化してハマったところ v0.99

この投稿は[Snowflake Advent Calendar 2024](https://qiita.com/advent-calendar/2024/snowflake)の6日目です。

https://qiita.com/myu_mx/items/1fe846d6e2a4f6ab70a3

から早2ヶ月。（切り替えからは3ヶ月）

上記の通り切り替えて3ヶ月が経つので、「活用が捗った」的なことを書けると良かったのですが、イベントとしては「問題なく使えている」「無事にTerraform化した」ので後者を取り上げます。

## 前置き

- 構築時は試行錯誤があるので、SnowSQLで設定していく
- SnowSQL自体はフォルダ単位で残していき、terraform化するときの参考にする
- 構築後にterraform importを使用するなどしてIaC管理する
- snowflake provider versionは0.99.0を使用（執筆時最新版）

SnowSQLのみで行った内容は記載されているものの、リソースが混在していることもあり、自分以外の他のメンバーがメンテナンスしにくい状況でした。

ちなみに今回のterraform化は1ヶ月弱かかりました。（一部他の作業をやりつつ）

## Terraform で Snowflake の何を管理するべきか

[Terraform で Snowflake の何を管理するべきか](https://zenn.dev/dataheroes/articles/2021-12-23-terraform?redirected=1)  などの記事を見て、最終的には以下の形になりました。

以下のリソース以外のすべて

- SSOの設定、Slackの設定
- ステージ
- テーブル（外部テーブル、view含む）

## Terraform化する上でハマったところ

基本はSnowSQLにメモしておいたものをベースに、Github Copilotに食わせて、うまく行けば取り込むし、だめだったらドキュメントを参照しながら粛々とやっていく感じでした。

- user,schemaあたりはどうしても一度applyしないと差分がなくなりませんでした
- procedure も一度delete & createが必要になりました

気になったのは上記程度で、特に致命的なことは無かったです。

### roleまわりがややこしい

唯一ハマったのはroleまわりです。

- 執筆時最新版v0.99.0（12/9にv1が出る予定）に至るまで破壊的変更がありました
    - snowflake_grant_privileges_to_database_role をメインで使います
- データベース内のオブジェクトはDatabase Roleを使った方が良い
    - [【Snowflake】データベース内のオブジェクトはDatabase Roleで権限管理しよう!](https://zenn.dev/dataheroes/articles/snowflake-database-role-20240727) より
    - Snowflakeのドキュメント [読み取り専用カスタムロールの作成](https://docs.snowflake.com/ja/user-guide/security-access-control-configure#creating-custom-read-only-roles) ではDatabase Roleを使っていない
    - そもそも roleがUSAGEを定義したり、tableやviewにSELECTを定義したり、それらもFUTUREを別でつけたりと初見でとっつきにくい
- 以前はリソース名を完全修飾名を使う必要があったが、v0.95でfully_qualified_nameが導入され楽に
    - [Snowflake Terraform Providerの0.95でfully_qualified_nameが導入されて快適に！](https://zenn.dev/jimatomo/articles/1eea4436fdc2af) より
    - うちは大文字小文字を区別しない設定なので、導入前でも影響は少なかったと思われるが、databasename.schemaname とかも煩わしい書き方になりそうだったので、fully_qualified_nameは便利そう

上記3つの観点が複雑に折り重なって、見る情報を精査しないと良くわからなくなります。

最終的に、readonlyはこのような形になりました。

-
    
    ```snowflake_role.tf & snowflake_database_role.tf
    resource "snowflake_grant_account_role" "readonly_role" {
      for_each = {
        redash   = snowflake_user.redash.name
        readonly = snowflake_user.readonly.name
      }
      role_name = snowflake_account_role.readonly_role.name
      user_name = each.value
    }
    
    resource "snowflake_grant_database_role" "readonly_role" {
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      parent_role_name   = snowflake_account_role.readonly_role.name
    }
    
    resource "snowflake_grant_privileges_to_account_role" "readonly_role" {
      for_each = {
        compute_wh = snowflake_warehouse.compute_wh.name
        speed      = snowflake_warehouse.speed.name
      }
      privileges        = ["USAGE"]
      account_role_name = snowflake_account_role.readonly_role.name
      on_account_object {
        object_type = "WAREHOUSE"
        object_name = each.value
      }
    }
    
    resource "snowflake_database_role" "readonly_role" {
      database = snowflake_database.gms_datalake.name
      name     = "READONLY_ROLE"
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_database" {
      privileges         = ["USAGE"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_database        = snowflake_database_role.readonly_role.database
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema" {
      for_each = {
        default   = snowflake_schema.default.fully_qualified_name
        stats     = snowflake_schema.stats.fully_qualified_name
        data_mart = snowflake_schema.data_mart.fully_qualified_name
        jpn       = snowflake_schema.jpn.fully_qualified_name
        # ...省略
      }
      privileges         = ["USAGE"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema {
        schema_name = each.value
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_tables" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        all {
          object_type_plural = "TABLES"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_views" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        all {
          object_type_plural = "VIEWS"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_tables_future" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        future {
          object_type_plural = "TABLES"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    
    resource "snowflake_grant_privileges_to_database_role" "readonly_role_on_schema_object_views_future" {
      privileges         = ["SELECT"]
      database_role_name = snowflake_database_role.readonly_role.fully_qualified_name
      on_schema_object {
        future {
          object_type_plural = "VIEWS"
          in_database        = snowflake_database_role.readonly_role.database
        }
      }
    }
    ```
    

ちなみに弊社の場合は、共通で使うschemaとは別に、各国のサブシステムのデータがschema毎にあり、各国のユーザーに合わせたreadonly role & userを使うことで、他国の指すシステムのデータは見られないようにしています。そのため、roleの定義は結構長くなっています…

構築時はDatabase Roleを使っていなかったため、terraform化の時点で、Database Roleを作成し、最初に作ったRoleは動作確認しつつ削除しました。

たしかにDatabase Roleにまとめると、SHOW GRANTS TO ROLE <Role Name>;がシンプルで良いです。

## 今後の課題

### テーブル等々の定義をどこで管理するか

流石に素の状態でterraform化するのは、AthenaのときにGlueの定義をterraform化していたときのように大変な気がするので、OpenMetadataなどを検討したいです。

### Task定義が可読性が低い

毎時、毎日の処理をSnowflakeの標準taskで回しています。
task数は20強あり、SnowflakeでのGUI上ではタスクグラフが確認できるのですが、tfからは確認ができず、可読性が低い状態です。

AWS Toolkit for Visual Studioのstep function表示 [参考](https://dev.classmethod.jp/articles/aws-toolkit-vscode-step-functions/) のように可視化ができるといいなと思っています。
ルール的にはシンプルなので、拡張機能を作れないことは無いと思うのですがそこまでの余力がない…

## 終わりに

課題はありつつもとりあえずこれで構築 & 切り替えが完了したので、今月からは活用していくフェーズに入ってきます！