TerraformでSnowflake上にリソースを作成する手順

# はじめに
TerraformでSnowflakeを構築する手順としては、Snowflakeが出しているチュートリアルを参考にすると分かりやすいです。

https://quickstarts.snowflake.com/guide/terraforming_snowflake/index.html?index=..%2F..index#0

しかしチュートリアル通りにやっても上手くいかずハマったポイントがあったので、自分なりに手順をまとめました。

# 環境
- macOS Ventura 13.1
- Homebrew 4.0.11

# 概要
この記事ではチュートリアルを参考にしつつ自分なりに少し修正し、最終的にTerraformを用いて以下リソースをSnowflake上に作成する手順を紹介します。
- データベース
- スキーマ
- ステージ
- ウェアハウス
- ユーザー
- ロール
- 諸々の権限付与

# 1. Terraformインストール
ローカルで開発を進めるにはTerraformをインストールする必要があるので、まだしていない場合はインストールしておきましょう。

tfenvをインストール
```zsh:zsh
$ brew install tfenv
```

バージョン確認
```zsh:zsh
$ tfenv --version
```

インストール可能なTerraformバージョン確認
```zsh:zsh
$ tfenv list-remote
```

2023/04/09時点では最新はv1.4.4なのでそれをインストール
```zsh:zsh
$ tfenv install 1.4.4
```

インストールしたバージョンのTerraformを使用するように指定
```zsh:zsh
$ tfenv use 1.4.4
```

# 2. Snowflake準備
Snowflakeは無料で30日間のトライアルアカウントを作成できるので、まだない場合はアカウントを作成しておきます。

https://signup.snowflake.com/

画面の指示通りに項目を記入していけば簡単にアカウントが作成できます。
私はEditionは`スタンダード`、クラウドプロバイダーは`AWS`、リージョンは`Asia Pacific (Tokyo)`を選択しました。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/61e09acc-3b75-4187-bfe0-c980355727c1.png" width="60%">

アカウントを作成するとメールが届くので、アカウントを有効化してログインしておきましょう。

# 3. 接続準備
以下を実行し、接続に使うための鍵を作成してクリップボードにコピーしておきます。
```zsh:zsh
$ cd ~/.ssh
$ openssl genrsa 2048 | openssl pkcs8 -topk8 -inform PEM -out snowflake_tf_snow_key.p8 -nocrypt
$ openssl rsa -in snowflake_tf_snow_key.p8 -pubout -out snowflake_tf_snow_key.pub
$ cat snowflake_tf_snow_key.pub | pbcopy
```

次にSnowflake上で以下を実行します。
やってることはTerraformで使用するSnowflakeユーザーの作成と、そのユーザーへのロール付与です。

```sql:SQL Worksheet
CREATE USER "tf-snow"
	RSA_PUBLIC_KEY='${生成した公開鍵}'
	DEFAULT_ROLE=PUBLIC
	MUST_CHANGE_PASSWORD=FALSE;

GRANT ROLE SYSADMIN TO USER "tf-snow";
GRANT ROLE SECURITYADMIN TO USER "tf-snow";
```

※公開鍵をペーストした際に前後に`-----BEGIN PUBLIC KEY-----`, `-----END PUBLIC KEY----—`も入ると思うので、それは忘れずに消しておいてください。

作成したユーザーに2つのロールを使用できるようにしていますが、以下のように使い分けます。
- `SYSADMIN`: データベースやスキーマ、ウェアハウスなどのオブジェクト作成
- `SECURITYADMIN`: ユーザーとロールの作成、権限付与

そして環境変数を設定するためにローカルで以下を実行します。
```zsh:zsh
$ export SNOWFLAKE_USER="tf-snow"
$ export SNOWFLAKE_PRIVATE_KEY_PATH="~/.ssh/snowflake_tf_snow_key.p8"
$ export SNOWFLAKE_ACCOUNT="${アカウントLocator}"
$ export SNOWFLAKE_REGION="${リージョンID}"
```

`アカウントLocator`はURLに含まれる`xy12345`のような文字列です。
`リージョンID`は、私はアカウント作成時にAWSの`Asia Pacific (Tokyo)`を選択したので、`ap-northeast-1.aws`になります。

これらの情報はSnowfalke上で以下を実行することでも確認できます。
```sql:SQL Worksheet
SELECT
	current_account() as YOUR_ACCOUNT_LOCATOR,
	current_region() as YOUR_SNOWFLAKE_REGION_ID;
```
※`リージョンID`に関しては、[ドキュメント](https://docs.snowflake.com/ja/user-guide/admin-account-identifier)を参考に対応するクラウド地域IDに変換する必要があります（必要ならクラウドプロバイダー名も加える）。

環境変数はターミナルを立ち上げる度に入力しないといけないので、`snow.env`などのファイルを用意しそこに記載しておくと次回以降入力が楽です。
（Gitでコード管理する場合は`snow.env`を`.gitignore`へ追加するのを忘れずに）

# 4. Terraform実装
プロジェクトのベースディレクトリに`main.tf`を作り、以下を貼り付けてください。
```terraform:main.tf
terraform {
  required_providers {
    snowflake = {
      source  = "Snowflake-Labs/snowflake"
      version = "0.42.1"
    }
  }
}

provider "snowflake" {
  role  = "SYSADMIN"
}

resource "snowflake_database" "db" {
  name     = "TF_DEMO_DB"
}

resource "snowflake_warehouse" "warehouse" {
  name           = "TF_DEMO_WH"
  warehouse_size = "large"
  auto_suspend = 60
}
```

:::note warn
Providerのバージョンを0.42.1に固定しています。
:::

チュートリアルでは`0.35`以上の最新のバージョンを使用するようにしていますが、比較的新しいバージョンだと、スタンダードエディションではエラーが出てしまします。
ウェアハウス作成時にスタンダードディションでは利用できないパラメータが自動で追加されてしまうことが原因で起こるエラーで、2023/04/09時点で最新のバージョン`0.61.0`でも解決はしていないようです。
そのため問題なく使える`0.42.1`あたりを使用するのが妥当だと思います。

# 5. Terraform実行
プロジェクトのディレクトリで以下を実行します。
Terraformの実行に必要な依存関係や、状態を保持するファイルがローカルにダウンロードされます。
```zsh:zsh
$ terraform init
```

Gitでコード管理している場合、作成された状態ファイルなどはpushしてしまわないように`.gitignore`に追加しておきましょう。
```:.gitignore
*.terraform*
*.tfstate
*.tfstate.*
```

次に以下を実行し、Terraformを実行した場合の変更内容を確認します。
```zsh:zsh
$ terraform plan
```

データベースとウェアハウスを作成するという変更内容になっていると思います。
リソースの変更内容に問題がなければ、以下を実行します。
```zsh:zsh
$ terraform apply
```
再度変更内容が表示され適用して良いか聞かれると思うので、問題なければ`yes`と入力してください。

成功するとSnowfalke上でデータベースとウェアハウスが作成されます。

▼ データベース
![スクリーンショット 2023-04-09 3.00.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/d44ac9f1-c618-a018-8eb8-a223364a1543.png)
▼ ウェアハウス
![スクリーンショット 2023-04-09 3.00.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/92280bac-ffc1-e6e7-44ef-4683bdd1dc55.png)

# 6. リソースの追加
最後にその他のリソースを作っていきましょう。
`main.tf`に以下を追加してください。

```terraform:main.tf
// SECURITYADMINロールで操作できるように
provider "snowflake" {
  alias = "security_admin"
  role  = "SECURITYADMIN"
}

// ロール作成
resource "snowflake_role" "role" {
  provider = snowflake.security_admin
  name     = "TF_DEMO_ROLE"
}

// データベースへの権限付与
resource "snowflake_database_grant" "grant" {
  provider          = snowflake.security_admin
  database_name     = snowflake_database.db.name
  privilege         = "USAGE"
  roles             = [snowflake_role.role.name]
  with_grant_option = false
}

// スキーマ作成
resource "snowflake_schema" "schema" {
  database     = snowflake_database.db.name
  name         = "TF_DEMO_SCHEMA"
}

// デフォルトスキーマへの権限付与
resource "snowflake_schema_grant" "schema_default_grant" {
  provider          = snowflake.security_admin
  database_name     = snowflake_database.db.name
  schema_name       = snowflake_schema.schema.name
  privilege         = "USAGE"
  roles             = [snowflake_role.role.name]
  with_grant_option = false
}

// スキーマへの権限付与（今後作成されるものも含む）
resource "snowflake_schema_grant" "schema_grant" {
  provider          = snowflake.security_admin
  database_name     = snowflake_database.db.name
  privilege         = "USAGE"
  roles             = [snowflake_role.role.name]
  with_grant_option = false
  on_future         = true
}

// テーブルへの権限付与（今後作成されるものも含む）
resource "snowflake_table_grant" "table_grant" {
  provider      = snowflake.security_admin
  database_name = snowflake_database.db.name
  privilege     = "SELECT"
  roles         = [snowflake_role.role.name]
  on_future     = true
}

// ビューへの権限付与（今後作成されるものも含む）
resource "snowflake_view_grant" "view_grant" {
  provider      = snowflake.security_admin
  database_name = snowflake_database.db.name
  privilege     = "SELECT"
  roles         = [snowflake_role.role.name]
  on_future     = true
}

// ステージ作成
resource "snowflake_stage" "stage" {
  name     = "TF_DEMO_STAGE"
  database = snowflake_database.db.name
  schema   = snowflake_schema.schema.name
}

// ステージへの権限付与
resource "snowflake_stage_grant" "stage_grant" {
  provider      = snowflake.security_admin
  database_name = snowflake_database.db.name
  schema_name   = snowflake_schema.schema.name
  stage_name    = snowflake_stage.stage.name
  privilege     = "READ"
  roles         = [snowflake_role.role.name]
  depends_on    = [snowflake_stage.stage]
}

// ユーザー作成
resource "snowflake_user" "user" {
  provider             = snowflake.security_admin
  name                 = "TF_DEMO_USER"
  password             = "password"
  default_role         = ""
}
```
それぞれやっていることはコメントに書いてある通りです。

コード追加後、planで変更を確認し問題がなければapplyします。

```zsh:zsh
$ terraform plan
$ terraform apply
```
Snowflake上で確認すると、`TF_DEMO_ROLE`というロールが作成され、`TF_DEMO_DB.TF_DEMO_SCHEMA`の操作権限を持っていることが分かります。
![スクリーンショット 2023-04-09 15.15.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/8bee9236-0b57-76b7-e0fe-1a5b5f55f198.png)

その後`TF_DEMO_DB`に別のスキーマやテーブルを作成すると、`ON_FUTURE`が効いていることにより`TF_DEMO_ROLE`に権限が与えられることも確認できます。
![スクリーンショット 2023-04-09 15.17.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/319284/a3cea219-f49c-274b-1927-d066ddb2abc0.png)

他の作成したリソースに関しても意図した通りに作成されていると思います。

その他設定できるリソースやパラメータなどはTerraformのProviderページを参考にしてください。

https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs

# まとめ
Terraformで基本的なリソースを作成する手順を紹介しました。
今回作成したコードは以下にアップしています。

https://github.com/hiro-wa/snowflake-terrafrom/tree/feature/basic-setting

実際に運用する際には、ファイル分割や状態ファイルの保管方法、リソースが増えた場合の処理の共通化など、まだまだ考慮する観点があります。
これらに関してはまた記事を書こうと思います。