日曜日, 10月 12, 2025
日曜日, 10月 12, 2025
- Advertisment -
ホームニューステックニュースSnowflake Quickstart実践:Terraforming Snowflake

Snowflake Quickstart実践:Terraforming Snowflake


Terraform良いですよねTerraform。

と言いながら自分自身ではそこまでがっつり使う機会が無かったのでこれを機に入門してみる事にしました。入門してみるならやっぱりチュートリアル実践でしょう!という事で毎度お馴染みSnowflakeのQuickstart。勿論「Terraform x Snowflake」という理想(?)の組み合わせのコンテンツも用意されています。当エントリではこのチュートリアルを実践した記録を紹介していこうと思います。

https://quickstarts.snowflake.com/guide/terraforming_snowflake/index.html#0

1. 概要

このステップでは以下の内容が記載されていました。

  • Terraformの朝ざっくりな概要説明(Terraformとは何か?的な)
  • Terraformには「Snowflake Provider」が用意されており、これによってTerraformからSnowflakeが操作連携できるようになっている

そして、当エントリで進めるチュートリアルを実践していくうえで必要な環境・アカウントは以下となります。

  • GitHubアカウント(Terraformの設定ファイルを管理するリポジトリとして利用)
  • Gitコマンド操作が出来る環境
  • Snowflakeアカウント(ACCOUNTADMINアカウントが利用出来る環境であれば有償/無償(トライアル)どちらでもOK)
  • Terraform実行環境
    • 当エントリではMacOSXにインストールを行い、MacOS環境下(ローカル)で実行する形を取ります。

そして次ステップ以降では以下のような作業を行い、TerrformでSnowflakeの各種リソースを作成・管理していきます。

2. Gitリポジトリの新規作成

GitHub上で空リポジトリを用意(プライベート)。Terraformの作業を行う環境として利用します。ドキュメントの内容に従い、sfguide-terraform-sampleというプライベートリポジトリを作成しました。

MacOS環境上で必要最低限のファイルを作成し反映。MacOSXにGitをインストールしたうえで作業を進めていきます。

% git --version
git version 2.51.0

## 部分は対応するユーザーアカウント名に置き換えて実行.
### チュートリアル専用フォルダを設け、Gitリポジトリはこの配下で作成.
% pwd
/Users/xxxxxxxxx/xxxxxx/shinyaa31-tutorials
### プロジェクト作成&Git初期設定実施.
% mkdir sfguide-terraform-sample && cd sfguide-terraform-sample
% echo "# sfguide-terraform-sample" >> README.md
% git init
% git add README.md
% git commit -m "first commit"
% git branch -M main
% git remote add origin [email protected]:/sfguide-terraform-sample.git
% git push -u origin main
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Writing objects: 100% (3/3), 236 bytes | 236.00 KiB/s, done.
Total 3 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
To github.com:/sfguide-terraform-sample.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.

作成したリポジトリに内容が反映(README.mdが作成、追加)されていることを確認出来ればOKです。

3. Snowflakeサービスユーザーを作成(Terraform実行用)

こちらタイトルには「Create a Service User for Terraform」とありますが、より正確には”Snowflake環境におけるTerraform用のサービスユーザーを作成する”となります。

今回の環境ではSnowflakeにおいてサービスユーザーの形式でユーザーを作成します。この形式を用いるとユーザーは、ログイン名とパスワードを必要とせず、キーペア認証のみでSnowflakeに認証します。パスワードやMFAは必要ありません。これは、多くのCI/CDパイプラインがTerraformを実行する方法でもあります。

認証用のRSAキーを作成。

% 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
writing RSA key

生成されたファイル:snowflake_tf_snow_key.pubの内容をヘッダー&フッター部分を含めてコピーしておきます。

% cat ~/.ssh/snowflake_tf_snow_key.pub
-----BEGIN PUBLIC KEY-----
(中略)
-----END PUBLIC KEY-----

そして対象となるSnowflake環境にログイン、以下のSQLを実行。サービスユーザー(TYPE = SERVICE)としてユーザー:TERRAFORM_SVCを作成し、アカウント管理に必要なSYSADMINおよびSECURITYADMINの権限をユーザーに付与しています。

USE ROLE ACCOUNTADMIN;



CREATE USER TERRAFORM_SVC
    TYPE = SERVICE
    COMMENT = "Service user for Terraforming Snowflake"
    RSA_PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----
(中略)
-----END PUBLIC KEY-----";

GRANT ROLE SYSADMIN TO USER TERRAFORM_SVC;
GRANT ROLE SECURITYADMIN TO USER TERRAFORM_SVC;

4. TerraformのインストールとTerraform認証の設定

このチュートリアルでは「Terraformがインストールされていること」を前提に手順を実行していますが、Terraform実行環境がなければこのタイミングでインストールしておきます。その後、チュートリアルで紹介されている認証の設定を行っていきましょう。

Terraformのインストール

という事でまずはTerraformのインストールから。公式ドキュメントの案内に倣い、terraformをインストール。なお今回はMacOS環境に導入する形を取っています。


% sw_vers
ProductName:            macOS
ProductVersion:         15.6.1
BuildVersion:           24G90


% brew tap hashicorp/tap
% brew install hashicorp/tap/terraform


% terraform --version
Terraform v1.13.2
on darwin_arm64
+ provider registry.terraform.io/hashicorp/local v2.5.3


% terraform -help
% terraform plan -help



% echo $SHELL
/bin/zsh

% touch ~/.zshrc

% terraform -install-autocomplete

% terraform -install-autocomplete
Error executing CLI: 1 error occurred:
        * already installed in /Users/xxxxxxxx/.zshrc

% cat ~/.zshrc
autoload -U +X bashcompinit && bashcompinit
complete -o nospace -C /opt/homebrew/bin/terraform terraform

上記設定でインストールは完了です。そもそもまだTerraform自体使ったことが無いよ!という場合であれば下記のエントリを参考に(超)基本操作を試してみるのも良いかもしれません。

📙(チュートリアル実践内容はこちら)

% mkdir terraform
% cd terraform
% mkdir hello-world
% cd hello-world
% pwd
/Users/xxxxxxxxxxxxxx/Desktop/terraform/hello-world


% vi hello-world.tf
% cat hello-world.tf
resource "local_file" "helloworld" {
    content  = "hello world!"
    filename = "hello.txt"
}


% terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/local...
- Installing hashicorp/local v2.5.3...
- Installed hashicorp/local v2.5.3 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.


% ls -lta
total 16
drwxr-xr-x@ 5 xxxxxxxxx  staff   160 Sep 14 11:07 .
-rw-r--r--@ 1 xxxxxxxxx  staff  1153 Sep 14 11:07 .terraform.lock.hcl
drwxr-xr-x@ 3 xxxxxxxxx  staff    96 Sep 14 11:07 .terraform
-rw-r--r--@ 1 xxxxxxxxx  staff    96 Sep 14 11:06 hello-world.tf
drwxr-xr-x@ 3 xxxxxxxxx  staff    96 Sep 14 11:06 ..


% terraform plan

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

Terraform will perform the following actions:

  
  + resource "local_file" "helloworld" {
      + content              = "hello world!"
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "hello.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.


% terraform apply

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

Terraform will perform the following actions:

  
  + resource "local_file" "helloworld" {
      + content              = "hello world!"
      + content_base64sha256 = (known after apply)
      + content_base64sha512 = (known after apply)
      + content_md5          = (known after apply)
      + content_sha1         = (known after apply)
      + content_sha256       = (known after apply)
      + content_sha512       = (known after apply)
      + directory_permission = "0777"
      + file_permission      = "0777"
      + filename             = "hello.txt"
      + id                   = (known after apply)
    }

Plan: 1 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

local_file.helloworld: Creating...
local_file.helloworld: Creation complete after 0s [id=430ce34d020724ed75a196dfc2ad67c77772d169]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.



% ll
total 32
drwxr-xr-x@ 7 xxxxxxxxx  staff   224 Sep 14 11:09 .
-rw-r--r--@ 1 xxxxxxxxx  staff  1628 Sep 14 11:09 terraform.tfstate
-rwxr-xr-x@ 1 xxxxxxxxx  staff    12 Sep 14 11:09 hello.txt
-rw-r--r--@ 1 xxxxxxxxx  staff  1153 Sep 14 11:07 .terraform.lock.hcl
drwxr-xr-x@ 3 xxxxxxxxx  staff    96 Sep 14 11:07 .terraform
-rw-r--r--@ 1 xxxxxxxxx  staff    96 Sep 14 11:06 hello-world.tf
drwxr-xr-x@ 3 xxxxxxxxx  staff    96 Sep 14 11:06 ..


% cat hello.txt
hello world!%

Terraform認証の設定

次いで、TerraformがSnowflakeアカウントのユーザーを認証するための情報の収集と設定を行います。

まずはSnowflake環境にて以下SQLを実行、アカウントが所属する組織名とアカウント名を取得。

SELECT
  LOWER(current_organization_name()) as your_org_name,
  LOWER(current_account_name()) as your_account_name;

取得した情報はそれぞれ控えておいてください。

手順「#2. Gitリポジトリの新規作成」で作成したフォルダに移動し、

% pwd
/Users/xxxxxxxxx/xxxxxx/shinyaa31-tutorials/sfguide-terraform-sample

上記情報を利用する形でTerraformの設定ファイル、main.tfを作成します。

  • は上記SQLで取得した内容を設定。
  • private_key_pathについては「認証用のRSAキーを作成」の項で作成したものとは別のパス・フォルダであればその値で置き換えておいてくだだい。
📙(main.tfへのプロバイダーの宣言内容はこちら)
% vi main.tf
% cat main.tf
terraform {
  required_providers {
    snowflake = {
      source = "snowflakedb/snowflake"
    }
  }
}

locals {
  organization_name = ""
  account_name      = ""
  private_key_path  = "~/.ssh/snowflake_tf_snow_key.p8"
}

provider "snowflake" {
    organization_name = local.organization_name
    account_name      = local.account_name
    user              = "TERRAFORM_SVC"
    role              = "SYSADMIN"
    authenticator     = "SNOWFLAKE_JWT"
    private_key       = file(local.private_key_path)
}

この設定により、Snowflake Providerはサービスユーザーとして Snowflakeアカウントを安全に認証します。

5. Terraformリソースの宣言

Terraform で作成するデータベースとウェアハウスの2つの構成を追加します。main.tfファイル、前述宣言の後(ファイル末尾)に以下の設定を追加。

📙(main.tfへのデータベースとウェアハウスの宣言内容はこちら)
resource "snowflake_database" "tf_db" {
  name         = "TF_DEMO_DB"
  is_transient = false
}

resource "snowflake_warehouse" "tf_warehouse" {
  name                      = "TF_DEMO_WH"
  warehouse_type            = "STANDARD"
  warehouse_size            = "XSMALL"
  max_cluster_count         = 1
  min_cluster_count         = 1
  auto_suspend              = 60
  auto_resume               = true
  enable_query_acceleration = false
  initially_suspended       = true
}

また、チュートリアル解説で以下のポイントを把握しました。

  • resource "リソースの種類" "リソース名" { }でオブジェクトを宣言
  • 配下のnameプロパティでオブジェクトに付ける名前(Snowflake表示名)を宣言
  • その他固有の設定項目はリソース毎に異なる
  • 新しいリソースを作成する際は要ドキュメント参照

6. プロジェクト実行の準備

上記作業で作成したmain.tfファイルの場所に居ることを確認し、

% pwd
/Users/xxxxxxxxx/xxxxxx/shinyaa31-tutorials/sfguide-terraform-sample
%  % ls
main.tf         README.md

プロジェクト初期化コマンド:terraform initを実行。Terraform を実行するために必要な依存関係がダウンロードされ、初期化が完了しました。

📙(terraform initコマンド実行結果はこちら)
% terraform init
Initializing the backend...
Initializing provider plugins...
- Finding latest version of snowflakedb/snowflake...
- Installing snowflakedb/snowflake v2.7.0...
- Installed snowflakedb/snowflake v2.7.0 (signed by a HashiCorp partner, key ID 6BD48CEEC004050A)
Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://developer.hashicorp.com/terraform/cli/plugins/signing
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

ダウンロードされた内容(フォルダ構成含め)を確認してみます。

% tree -a
.
├── .git
│   ├── (.git配下は省略)
├── .terraform
│   └── providers
│       └── registry.terraform.io
│           └── snowflakedb
│               └── snowflake
│                   └── 2.7.0
│                       └── darwin_arm64
│                           ├── CHANGELOG.md
│                           ├── LICENSE
│                           ├── README.md
│                           └── terraform-provider-snowflake_v2.7.0
├── .terraform.lock.hcl
├── main.tf
└── README.md

gitリポジトリとの連携においてTerraformの状態ファイル(機密情報が記載されいる内容)をバージョン管理システムにチェックインしたくないため、.gitignoreファイルを作成し対象に関連ファイルを含めておきます。

% vi .gitignore
% cat .gitignore
*.terraform*
*.tfstate
*.tfstate.*

% ls -lta
total 24
drwxr-xr-x@ 6 xxxxxxxxx  staff   192 Sep 14 xx:xx .
-rw-r--r--@ 1 xxxxxxxxx  staff    35 Sep 14 xx:xx .gitignore
-rw-r--r--@ 1 xxxxxxxxx  staff  1159 Sep 14 xx:xx .terraform.lock.hcl
drwxr-xr-x@ 3 xxxxxxxxx  staff    96 Sep 14 xx:xx .terraform
-rw-r--r--@ 1 xxxxxxxxx  staff   995 Sep 14 xx:xx main.tf
drwxr-xr-x@ 5 xxxxxxxxx  staff   160 Sep 14 xx:xx ..

7. ソース管理への変更をコミット

ここまでの変更内容をGitHubリポジトリに反映。上記手順に続けて以下のコマンドを実行します。

% git checkout -b dbwh
% git add .
% git commit -m "Add Config, Database and Warehouse"
% git push origin HEAD

GitHub対象リポジトリにてPull Requestを作成し、変更内容をリポジトリに取り込みます。

そしてterraform planコマンドを実行。ここでは『おーん、そういうことをやろうとしているのね』と眺める形で次に進みます。

📙(terraform planコマンド実行結果はこちら)
% git checkout main
% git pull origin main
% terraform plan

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

Terraform will perform the following actions:

  # snowflake_database.tf_db will be created
  + resource "snowflake_database" "tf_db" {
      + catalog                                       = (known after apply)
      + data_retention_time_in_days                   = (known after apply)
      + default_ddl_collation                         = (known after apply)
      + enable_console_output                         = (known after apply)
      + external_volume                               = (known after apply)
      + fully_qualified_name                          = (known after apply)
      + id                                            = (known after apply)
      + is_transient                                  = false
      + log_level                                     = (known after apply)
      + max_data_extension_time_in_days               = (known after apply)
      + name                                          = "TF_DEMO_DB"
      + quoted_identifiers_ignore_case                = (known after apply)
      + replace_invalid_characters                    = (known after apply)
      + storage_serialization_policy                  = (known after apply)
      + suspend_task_after_num_failures               = (known after apply)
      + task_auto_retry_attempts                      = (known after apply)
      + trace_level                                   = (known after apply)
      + user_task_managed_initial_warehouse_size      = (known after apply)
      + user_task_minimum_trigger_interval_in_seconds = (known after apply)
      + user_task_timeout_ms                          = (known after apply)
    }

  # snowflake_warehouse.tf_warehouse will be created
  + resource "snowflake_warehouse" "tf_warehouse" {
      + auto_resume                         = "true"
      + auto_suspend                        = 60
      + enable_query_acceleration           = "false"
      + fully_qualified_name                = (known after apply)
      + id                                  = (known after apply)
      + initially_suspended                 = true
      + max_cluster_count                   = 1
      + max_concurrency_level               = (known after apply)
      + min_cluster_count                   = 1
      + name                                = "TF_DEMO_WH"
      + parameters                          = (known after apply)
      + query_acceleration_max_scale_factor = -1
      + show_output                         = (known after apply)
      + statement_queued_timeout_in_seconds = (known after apply)
      + statement_timeout_in_seconds        = (known after apply)
      + warehouse_size                      = "XSMALL"
      + warehouse_type                      = "STANDARD"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

8. Terraformの実行

前項までの手順でTerraformによるSnowflakeリソース作成の手順が整いました。いざ作成に進みましょう!ということでterraform applyコマンド実行。

📙(terraform applyコマンド実行結果はこちら)
% terraform apply

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

Terraform will perform the following actions:

  
  + resource "snowflake_database" "tf_db" {
      + catalog                                       = (known after apply)
      + data_retention_time_in_days                   = (known after apply)
      + default_ddl_collation                         = (known after apply)
      + enable_console_output                         = (known after apply)
      + external_volume                               = (known after apply)
      + fully_qualified_name                          = (known after apply)
      + id                                            = (known after apply)
      + is_transient                                  = false
      + log_level                                     = (known after apply)
      + max_data_extension_time_in_days               = (known after apply)
      + name                                          = "TF_DEMO_DB"
      + quoted_identifiers_ignore_case                = (known after apply)
      + replace_invalid_characters                    = (known after apply)
      + storage_serialization_policy                  = (known after apply)
      + suspend_task_after_num_failures               = (known after apply)
      + task_auto_retry_attempts                      = (known after apply)
      + trace_level                                   = (known after apply)
      + user_task_managed_initial_warehouse_size      = (known after apply)
      + user_task_minimum_trigger_interval_in_seconds = (known after apply)
      + user_task_timeout_ms                          = (known after apply)
    }

  
  + resource "snowflake_warehouse" "tf_warehouse" {
      + auto_resume                         = "true"
      + auto_suspend                        = 60
      + enable_query_acceleration           = "false"
      + fully_qualified_name                = (known after apply)
      + id                                  = (known after apply)
      + initially_suspended                 = true
      + max_cluster_count                   = 1
      + max_concurrency_level               = (known after apply)
      + min_cluster_count                   = 1
      + name                                = "TF_DEMO_WH"
      + parameters                          = (known after apply)
      + query_acceleration_max_scale_factor = -1
      + show_output                         = (known after apply)
      + statement_queued_timeout_in_seconds = (known after apply)
      + statement_timeout_in_seconds        = (known after apply)
      + warehouse_size                      = "XSMALL"
      + warehouse_type                      = "STANDARD"
    }

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

snowflake_database.tf_db: Creating...
snowflake_warehouse.tf_warehouse: Creating...
snowflake_warehouse.tf_warehouse: Creation complete after 1s [id=TF_DEMO_WH]
snowflake_database.tf_db: Creation complete after 1s [id=TF_DEMO_DB]

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Snowflake環境にアクセスし、結果を確認。Terraformで指定した内容が作成されていることが確認出来ました。

9. リソースの追加と変更

ここでは前述手順までに実行・作成した環境に対して以下の操作を行っています。1〜4までがmain.tfへの追記、5がoutputs.tfという設定ファイルの新規作成&追記となります。

  • ウェアサイズの変更(x-smallからsmall)
  • スキーマの追加
  • ロールとユーザーの追加
  • 新規追加ロールに幾つかの権限を付与
  • アプリケーションの公開鍵と秘密鍵の情報を取得

まずはウェアハウスサイズの変更。main.tfの該当箇所(warehouse_size)の値をSMALLに変更します。

📙(main.tfファイルへの内容追記・変更 1つめの内容はこちら)
resource "snowflake_warehouse" "tf_warehouse" {
  name                      = "TF_DEMO_WH"
  warehouse_type            = "STANDARD"
  warehouse_size            = "SMALL"
  max_cluster_count         = 1
  min_cluster_count         = 1
  auto_suspend              = 60
  auto_resume               = true
  enable_query_acceleration = false
  initially_suspended       = true
}

次いでファイルmain.tf末尾に新しいスキーマの設定を追加します。

📙(main.tfファイルへの内容追記・変更 2つめの内容はこちら)

resource "snowflake_schema" "tf_db_tf_schema" {
  name                = "TF_DEMO_SC"
  database            = snowflake_database.tf_db.name
  with_managed_access = false
}

ロールとユーザーを新規作成し、ユーザーにロール付与する設定もmain.tfに追加。

📙(main.tfファイルへの内容追記・変更 3つめの内容はこちら)

provider "snowflake" {
    organization_name = local.organization_name
    account_name      = local.account_name
    user              = "TERRAFORM_SVC"
    role              = "USERADMIN"
    alias             = "useradmin"
    authenticator     = "SNOWFLAKE_JWT"
    private_key       = file(local.private_key_path)
}


resource "snowflake_account_role" "tf_role" {
    provider          = snowflake.useradmin
    name              = "TF_DEMO_ROLE"
    comment           = "My Terraform role"
}


resource "snowflake_grant_account_role" "grant_tf_role_to_sysadmin" {
    provider         = snowflake.useradmin
    role_name        = snowflake_account_role.tf_role.name
    parent_role_name = "SYSADMIN"
}


resource "tls_private_key" "svc_key" {
    algorithm = "RSA"
    rsa_bits  = 2048
}


resource "snowflake_user" "tf_user" {
    provider          = snowflake.useradmin
    name              = "TF_DEMO_USER"
    default_warehouse = snowflake_warehouse.tf_warehouse.name
    default_role      = snowflake_account_role.tf_role.name
    default_namespace = "${snowflake_database.tf_db.name}.${snowflake_schema.tf_db_tf_schema.fully_qualified_name}"
    rsa_public_key    = substr(tls_private_key.svc_key.public_key_pem, 27, 398)
}


resource "snowflake_grant_account_role" "grants" {
    provider          = snowflake.useradmin
    role_name         = snowflake_account_role.tf_role.name
    user_name         = snowflake_user.tf_user.name
}

そしてmain.tf最後の追加、新しいロールに幾つかの権限を付与する設定も行います。

📙(main.tfファイルへの内容追記・変更 4つめの内容はこちら)

resource "snowflake_grant_privileges_to_account_role" "grant_usage_tf_db_to_tf_role" {
    provider          = snowflake.useradmin
    privileges        = ["USAGE"]
    account_role_name = snowflake_account_role.tf_role.name
    on_account_object {
        object_type = "DATABASE"
        object_name = snowflake_database.tf_db.name
  }
}


resource "snowflake_grant_privileges_to_account_role" "grant_usage_tf_db_tf_schema_to_tf_role" {
    provider          = snowflake.useradmin
    privileges        = ["USAGE"]
    account_role_name = snowflake_account_role.tf_role.name
    on_schema {
        schema_name = snowflake_schema.tf_db_tf_schema.fully_qualified_name
  }
}


resource "snowflake_grant_privileges_to_account_role" "grant_all_tables" {
    provider          = snowflake.useradmin
    privileges        = ["SELECT"]
    account_role_name = snowflake_account_role.tf_role.name
    on_schema_object {
        all {
            object_type_plural = "TABLES"
            in_schema          = snowflake_schema.tf_db_tf_schema.fully_qualified_name
    }
  }
}


resource "snowflake_grant_privileges_to_account_role" "grant_future_tables" {
    provider          = snowflake.useradmin
    privileges        = ["SELECT"]
    account_role_name = snowflake_account_role.tf_role.name
    on_schema_object {
        future {
            object_type_plural = "TABLES"
            in_schema          = snowflake_schema.tf_db_tf_schema.fully_qualified_name
    }
  }
}
📙(output.tfファイルへの内容追記・変更内容はこちら)
% vi outputs.tf
% cat outputs.tf
output "snowflake_svc_public_key" {
    value     = tls_private_key.svc_key.public_key_pem
}

output "snowflake_svc_private_key" {
    value     = tls_private_key.svc_key.private_key_pem
    sensitive = true
}

上記内容を確認するためにterraform plan実行。するとエラーが出ました。

📙(terraform plan実行(エラー発生)の実行結果はこちら)
% terraform plan
╷
│ 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/tls: required by this configuration but no version is selected
│
│ To update the locked dependency selections to match a changed configuration, run:
│   terraform init -upgrade

tls_private_keyを利用する際にはterraform init -upgradeコマンドで解決する必要があるようです。(このコマンドを打つとhashicorp/tlsがインストールされ先に進めることができる)

ということでterraform init -upgradeコマンド実行。

📙(terraform init -upgradeの実行結果はこちら)
% terraform init -upgrade
Initializing the backend...
Initializing provider plugins...
- Finding latest version of snowflakedb/snowflake...
- Finding latest version of hashicorp/tls...
- Using previously-installed snowflakedb/snowflake v2.7.0
- Installing hashicorp/tls v4.1.0...
- Installed hashicorp/tls v4.1.0 (signed by HashiCorp)
Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

そしてterraform plan再実行。今度はうまくいきました。変更追記したファイルの内容に対して検証が行われ、『こういう追加変更が入ります』の内容が出力されています。

📙(terraform plan実行結果はこちら)
% terraform plan
snowflake_database.tf_db: Refreshing state... [id=TF_DEMO_DB]
snowflake_warehouse.tf_warehouse: Refreshing state... [id=TF_DEMO_WH]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  
  + resource "snowflake_account_role" "tf_role" {
      + comment              = "My Terraform role"
      + fully_qualified_name = (known after apply)
      + id                   = (known after apply)
      + name                 = "TF_DEMO_ROLE"
      + show_output          = (known after apply)
    }

  
  + resource "snowflake_grant_account_role" "grant_tf_role_to_sysadmin" {
      + id               = (known after apply)
      + parent_role_name = "SYSADMIN"
      + role_name        = "TF_DEMO_ROLE"
    }

  
  + resource "snowflake_grant_account_role" "grants" {
      + id        = (known after apply)
      + role_name = "TF_DEMO_ROLE"
      + user_name = "TF_DEMO_USER"
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_all_tables" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "SELECT",
        ]
      + with_grant_option = false

      + on_schema_object {
          + all {
              + in_schema          = (known after apply)
              + object_type_plural = "TABLES"
            }
        }
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_future_tables" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "SELECT",
        ]
      + with_grant_option = false

      + on_schema_object {
          + future {
              + in_schema          = (known after apply)
              + object_type_plural = "TABLES"
            }
        }
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_usage_tf_db_tf_schema_to_tf_role" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "USAGE",
        ]
      + with_grant_option = false

      + on_schema {
          + schema_name = (known after apply)
        }
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_usage_tf_db_to_tf_role" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "USAGE",
        ]
      + with_grant_option = false

      + on_account_object {
          + object_name = "TF_DEMO_DB"
          + object_type = "DATABASE"
        }
    }

  
  + resource "snowflake_schema" "tf_db_tf_schema" {
      + catalog                                       = (known after apply)
      + data_retention_time_in_days                   = (known after apply)
      + database                                      = "TF_DEMO_DB"
      + default_ddl_collation                         = (known after apply)
      + describe_output                               = (known after apply)
      + enable_console_output                         = (known after apply)
      + external_volume                               = (known after apply)
      + fully_qualified_name                          = (known after apply)
      + id                                            = (known after apply)
      + is_transient                                  = "default"
      + log_level                                     = (known after apply)
      + max_data_extension_time_in_days               = (known after apply)
      + name                                          = "TF_DEMO_SC"
      + parameters                                    = (known after apply)
      + pipe_execution_paused                         = (known after apply)
      + quoted_identifiers_ignore_case                = (known after apply)
      + replace_invalid_characters                    = (known after apply)
      + show_output                                   = (known after apply)
      + storage_serialization_policy                  = (known after apply)
      + suspend_task_after_num_failures               = (known after apply)
      + task_auto_retry_attempts                      = (known after apply)
      + trace_level                                   = (known after apply)
      + user_task_managed_initial_warehouse_size      = (known after apply)
      + user_task_minimum_trigger_interval_in_seconds = (known after apply)
      + user_task_timeout_ms                          = (known after apply)
      + with_managed_access                           = "false"
    }

  
  + resource "snowflake_user" "tf_user" {
      + abort_detached_query                          = (known after apply)
      + autocommit                                    = (known after apply)
      + binary_input_format                           = (known after apply)
      + binary_output_format                          = (known after apply)
      + client_memory_limit                           = (known after apply)
      + client_metadata_request_use_connection_ctx    = (known after apply)
      + client_prefetch_threads                       = (known after apply)
      + client_result_chunk_size                      = (known after apply)
      + client_result_column_case_insensitive         = (known after apply)
      + client_session_keep_alive                     = (known after apply)
      + client_session_keep_alive_heartbeat_frequency = (known after apply)
      + client_timestamp_type_mapping                 = (known after apply)
      + date_input_format                             = (known after apply)
      + date_output_format                            = (known after apply)
      + default_namespace                             = (known after apply)
      + default_role                                  = "TF_DEMO_ROLE"
      + default_secondary_roles_option                = "DEFAULT"
      + default_warehouse                             = "TF_DEMO_WH"
      + disable_mfa                                   = "default"
      + disabled                                      = "default"
      + enable_unload_physical_type_optimization      = (known after apply)
      + enable_unredacted_query_syntax_error          = (known after apply)
      + error_on_nondeterministic_merge               = (known after apply)
      + error_on_nondeterministic_update              = (known after apply)
      + fully_qualified_name                          = (known after apply)
      + geography_output_format                       = (known after apply)
      + geometry_output_format                        = (known after apply)
      + id                                            = (known after apply)
      + jdbc_treat_decimal_as_int                     = (known after apply)
      + jdbc_treat_timestamp_ntz_as_utc               = (known after apply)
      + jdbc_use_session_timezone                     = (known after apply)
      + json_indent                                   = (known after apply)
      + lock_timeout                                  = (known after apply)
      + log_level                                     = (known after apply)
      + mins_to_bypass_mfa                            = -1
      + mins_to_unlock                                = -1
      + multi_statement_count                         = (known after apply)
      + must_change_password                          = "default"
      + name                                          = "TF_DEMO_USER"
      + network_policy                                = (known after apply)
      + noorder_sequence_as_default                   = (known after apply)
      + odbc_treat_decimal_as_int                     = (known after apply)
      + parameters                                    = (known after apply)
      + prevent_unload_to_internal_stages             = (known after apply)
      + query_tag                                     = (known after apply)
      + quoted_identifiers_ignore_case                = (known after apply)
      + rows_per_resultset                            = (known after apply)
      + rsa_public_key                                = (known after apply)
      + s3_stage_vpce_dns_name                        = (known after apply)
      + search_path                                   = (known after apply)
      + show_output                                   = (known after apply)
      + simulated_data_sharing_consumer               = (known after apply)
      + statement_queued_timeout_in_seconds           = (known after apply)
      + statement_timeout_in_seconds                  = (known after apply)
      + strict_json_output                            = (known after apply)
      + time_input_format                             = (known after apply)
      + time_output_format                            = (known after apply)
      + timestamp_day_is_always_24h                   = (known after apply)
      + timestamp_input_format                        = (known after apply)
      + timestamp_ltz_output_format                   = (known after apply)
      + timestamp_ntz_output_format                   = (known after apply)
      + timestamp_output_format                       = (known after apply)
      + timestamp_type_mapping                        = (known after apply)
      + timestamp_tz_output_format                    = (known after apply)
      + timezone                                      = (known after apply)
      + trace_level                                   = (known after apply)
      + transaction_abort_on_error                    = (known after apply)
      + transaction_default_isolation_level           = (known after apply)
      + two_digit_century_start                       = (known after apply)
      + unsupported_ddl_action                        = (known after apply)
      + use_cached_result                             = (known after apply)
      + user_type                                     = (known after apply)
      + week_of_year_policy                           = (known after apply)
      + week_start                                    = (known after apply)
    }

  
  ~ resource "snowflake_warehouse" "tf_warehouse" {
        id                                  = "TF_DEMO_WH"
        name                                = "TF_DEMO_WH"
      ~ show_output                         = [
          - {
              - auto_resume                         = true
              - auto_suspend                        = 60
              - available                           = 0
              - created_on                          = "2025-09-13 23:05:51.793 -0700 PDT"
              - enable_query_acceleration           = false
              - generation                          = "1"
              - is_current                          = false
              - is_default                          = false
              - max_cluster_count                   = 1
              - min_cluster_count                   = 1
              - name                                = "TF_DEMO_WH"
              - other                               = 0
              - owner                               = "SYSADMIN"
              - owner_role_type                     = "ROLE"
              - provisioning                        = 0
              - query_acceleration_max_scale_factor = 8
              - queued                              = 0
              - quiescing                           = 0
              - resumed_on                          = "2025-09-13 23:05:51.802 -0700 PDT"
              - running                             = 0
              - scaling_policy                      = "STANDARD"
              - size                                = "XSMALL"
              - started_clusters                    = 0
              - state                               = "SUSPENDED"
              - type                                = "STANDARD"
              - updated_on                          = "2025-09-13 23:05:51.841 -0700 PDT"
                
            },
        ] -> (known after apply)
      ~ warehouse_size                      = "XSMALL" -> "SMALL"
        
    }

  
  + resource "tls_private_key" "svc_key" {
      + algorithm                     = "RSA"
      + ecdsa_curve                   = "P224"
      + id                            = (known after apply)
      + private_key_openssh           = (sensitive value)
      + private_key_pem               = (sensitive value)
      + private_key_pem_pkcs8         = (sensitive value)
      + public_key_fingerprint_md5    = (known after apply)
      + public_key_fingerprint_sha256 = (known after apply)
      + public_key_openssh            = (known after apply)
      + public_key_pem                = (known after apply)
      + rsa_bits                      = 2048
    }

Plan: 10 to add, 1 to change, 0 to destroy.

Changes to Outputs:
  + snowflake_svc_private_key = (sensitive value)
  + snowflake_svc_public_key  = (known after apply)

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.

10. ソース管理への変更をコミット

ここまでの変更内容を改めてリポジトリにコミット。プルリクエストの作成及び取り込みについては前述手順#7と同様の流れで進めてください。

% git add main.tf
% git add outputs.tf
% git commit -m "Create Service User, Role, Schema, Grants"
% git push origin HEAD

11. 変更を適用&検証

変更を加えた諸々の内容に対して、SQLコマンド各種を実行して実際に内容が更新されていることを確認します。以下その際に利用したSQLコマンド群とキャプチャ内容です。

📙(terraform applyコマンドの実行結果はこちら)
% terraform apply
snowflake_database.tf_db: Refreshing state... [id=TF_DEMO_DB]
snowflake_warehouse.tf_warehouse: Refreshing state... [id=TF_DEMO_WH]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  
  + resource "snowflake_account_role" "tf_role" {
      + comment              = "My Terraform role"
      + fully_qualified_name = (known after apply)
      + id                   = (known after apply)
      + name                 = "TF_DEMO_ROLE"
      + show_output          = (known after apply)
    }

  
  + resource "snowflake_grant_account_role" "grant_tf_role_to_sysadmin" {
      + id               = (known after apply)
      + parent_role_name = "SYSADMIN"
      + role_name        = "TF_DEMO_ROLE"
    }

  
  + resource "snowflake_grant_account_role" "grants" {
      + id        = (known after apply)
      + role_name = "TF_DEMO_ROLE"
      + user_name = "TF_DEMO_USER"
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_all_tables" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "SELECT",
        ]
      + with_grant_option = false

      + on_schema_object {
          + all {
              + in_schema          = (known after apply)
              + object_type_plural = "TABLES"
            }
        }
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_future_tables" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "SELECT",
        ]
      + with_grant_option = false

      + on_schema_object {
          + future {
              + in_schema          = (known after apply)
              + object_type_plural = "TABLES"
            }
        }
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_usage_tf_db_tf_schema_to_tf_role" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "USAGE",
        ]
      + with_grant_option = false

      + on_schema {
          + schema_name = (known after apply)
        }
    }

  
  + resource "snowflake_grant_privileges_to_account_role" "grant_usage_tf_db_to_tf_role" {
      + account_role_name = "TF_DEMO_ROLE"
      + all_privileges    = false
      + always_apply      = false
      + id                = (known after apply)
      + on_account        = false
      + privileges        = [
          + "USAGE",
        ]
      + with_grant_option = false

      + on_account_object {
          + object_name = "TF_DEMO_DB"
          + object_type = "DATABASE"
        }
    }

  
  + resource "snowflake_schema" "tf_db_tf_schema" {
      + catalog                                       = (known after apply)
      + data_retention_time_in_days                   = (known after apply)
      + database                                      = "TF_DEMO_DB"
      + default_ddl_collation                         = (known after apply)
      + describe_output                               = (known after apply)
      + enable_console_output                         = (known after apply)
      + external_volume                               = (known after apply)
      + fully_qualified_name                          = (known after apply)
      + id                                            = (known after apply)
      + is_transient                                  = "default"
      + log_level                                     = (known after apply)
      + max_data_extension_time_in_days               = (known after apply)
      + name                                          = "TF_DEMO_SC"
      + parameters                                    = (known after apply)
      + pipe_execution_paused                         = (known after apply)
      + quoted_identifiers_ignore_case                = (known after apply)
      + replace_invalid_characters                    = (known after apply)
      + show_output                                   = (known after apply)
      + storage_serialization_policy                  = (known after apply)
      + suspend_task_after_num_failures               = (known after apply)
      + task_auto_retry_attempts                      = (known after apply)
      + trace_level                                   = (known after apply)
      + user_task_managed_initial_warehouse_size      = (known after apply)
      + user_task_minimum_trigger_interval_in_seconds = (known after apply)
      + user_task_timeout_ms                          = (known after apply)
      + with_managed_access                           = "false"
    }

  
  + resource "snowflake_user" "tf_user" {
      + abort_detached_query                          = (known after apply)
      + autocommit                                    = (known after apply)
      + binary_input_format                           = (known after apply)
      + binary_output_format                          = (known after apply)
      + client_memory_limit                           = (known after apply)
      + client_metadata_request_use_connection_ctx    = (known after apply)
      + client_prefetch_threads                       = (known after apply)
      + client_result_chunk_size                      = (known after apply)
      + client_result_column_case_insensitive         = (known after apply)
      + client_session_keep_alive                     = (known after apply)
      + client_session_keep_alive_heartbeat_frequency = (known after apply)
      + client_timestamp_type_mapping                 = (known after apply)
      + date_input_format                             = (known after apply)
      + date_output_format                            = (known after apply)
      + default_namespace                             = (known after apply)
      + default_role                                  = "TF_DEMO_ROLE"
      + default_secondary_roles_option                = "DEFAULT"
      + default_warehouse                             = "TF_DEMO_WH"
      + disable_mfa                                   = "default"
      + disabled                                      = "default"
      + enable_unload_physical_type_optimization      = (known after apply)
      + enable_unredacted_query_syntax_error          = (known after apply)
      + error_on_nondeterministic_merge               = (known after apply)
      + error_on_nondeterministic_update              = (known after apply)
      + fully_qualified_name                          = (known after apply)
      + geography_output_format                       = (known after apply)
      + geometry_output_format                        = (known after apply)
      + id                                            = (known after apply)
      + jdbc_treat_decimal_as_int                     = (known after apply)
      + jdbc_treat_timestamp_ntz_as_utc               = (known after apply)
      + jdbc_use_session_timezone                     = (known after apply)
      + json_indent                                   = (known after apply)
      + lock_timeout                                  = (known after apply)
      + log_level                                     = (known after apply)
      + mins_to_bypass_mfa                            = -1
      + mins_to_unlock                                = -1
      + multi_statement_count                         = (known after apply)
      + must_change_password                          = "default"
      + name                                          = "TF_DEMO_USER"
      + network_policy                                = (known after apply)
      + noorder_sequence_as_default                   = (known after apply)
      + odbc_treat_decimal_as_int                     = (known after apply)
      + parameters                                    = (known after apply)
      + prevent_unload_to_internal_stages             = (known after apply)
      + query_tag                                     = (known after apply)
      + quoted_identifiers_ignore_case                = (known after apply)
      + rows_per_resultset                            = (known after apply)
      + rsa_public_key                                = (known after apply)
      + s3_stage_vpce_dns_name                        = (known after apply)
      + search_path                                   = (known after apply)
      + show_output                                   = (known after apply)
      + simulated_data_sharing_consumer               = (known after apply)
      + statement_queued_timeout_in_seconds           = (known after apply)
      + statement_timeout_in_seconds                  = (known after apply)
      + strict_json_output                            = (known after apply)
      + time_input_format                             = (known after apply)
      + time_output_format                            = (known after apply)
      + timestamp_day_is_always_24h                   = (known after apply)
      + timestamp_input_format                        = (known after apply)
      + timestamp_ltz_output_format                   = (known after apply)
      + timestamp_ntz_output_format                   = (known after apply)
      + timestamp_output_format                       = (known after apply)
      + timestamp_type_mapping                        = (known after apply)
      + timestamp_tz_output_format                    = (known after apply)
      + timezone                                      = (known after apply)
      + trace_level                                   = (known after apply)
      + transaction_abort_on_error                    = (known after apply)
      + transaction_default_isolation_level           = (known after apply)
      + two_digit_century_start                       = (known after apply)
      + unsupported_ddl_action                        = (known after apply)
      + use_cached_result                             = (known after apply)
      + user_type                                     = (known after apply)
      + week_of_year_policy                           = (known after apply)
      + week_start                                    = (known after apply)
    }

  
  ~ resource "snowflake_warehouse" "tf_warehouse" {
        id                                  = "TF_DEMO_WH"
        name                                = "TF_DEMO_WH"
      ~ show_output                         = [
          - {
              - auto_resume                         = true
              - auto_suspend                        = 60
              - available                           = 0
              - created_on                          = "2025-09-13 23:05:51.793 -0700 PDT"
              - enable_query_acceleration           = false
              - generation                          = "1"
              - is_current                          = false
              - is_default                          = false
              - max_cluster_count                   = 1
              - min_cluster_count                   = 1
              - name                                = "TF_DEMO_WH"
              - other                               = 0
              - owner                               = "SYSADMIN"
              - owner_role_type                     = "ROLE"
              - provisioning                        = 0
              - query_acceleration_max_scale_factor = 8
              - queued                              = 0
              - quiescing                           = 0
              - resumed_on                          = "2025-09-13 23:05:51.802 -0700 PDT"
              - running                             = 0
              - scaling_policy                      = "STANDARD"
              - size                                = "XSMALL"
              - started_clusters                    = 0
              - state                               = "SUSPENDED"
              - type                                = "STANDARD"
              - updated_on                          = "2025-09-13 23:05:51.841 -0700 PDT"
                
            },
        ] -> (known after apply)
      ~ warehouse_size                      = "XSMALL" -> "SMALL"
        
    }

  
  + resource "tls_private_key" "svc_key" {
      + algorithm                     = "RSA"
      + ecdsa_curve                   = "P224"
      + id                            = (known after apply)
      + private_key_openssh           = (sensitive value)
      + private_key_pem               = (sensitive value)
      + private_key_pem_pkcs8         = (sensitive value)
      + public_key_fingerprint_md5    = (known after apply)
      + public_key_fingerprint_sha256 = (known after apply)
      + public_key_openssh            = (known after apply)
      + public_key_pem                = (known after apply)
      + rsa_bits                      = 2048
    }

Plan: 10 to add, 1 to change, 0 to destroy.

Changes to Outputs:
  + snowflake_svc_private_key = (sensitive value)
  + snowflake_svc_public_key  = (known after apply)

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

tls_private_key.svc_key: Creating...
tls_private_key.svc_key: Creation complete after 0s [id=716aeef9a2f95fdfb31dfcc498ebf6240b8eb4cb]
snowflake_account_role.tf_role: Creating...
snowflake_schema.tf_db_tf_schema: Creating...
snowflake_warehouse.tf_warehouse: Modifying... [id=TF_DEMO_WH]
snowflake_account_role.tf_role: Creation complete after 0s [id=TF_DEMO_ROLE]
snowflake_grant_account_role.grant_tf_role_to_sysadmin: Creating...
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_to_tf_role: Creating...
snowflake_grant_account_role.grant_tf_role_to_sysadmin: Creation complete after 0s [id="TF_DEMO_ROLE"|ROLE|"SYSADMIN"]
snowflake_warehouse.tf_warehouse: Modifications complete after 0s [id=TF_DEMO_WH]
snowflake_schema.tf_db_tf_schema: Creation complete after 1s [id="TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_tf_schema_to_tf_role: Creating...
snowflake_grant_privileges_to_account_role.grant_all_tables: Creating...
snowflake_grant_privileges_to_account_role.grant_future_tables: Creating...
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_to_tf_role: Creation complete after 1s [id="TF_DEMO_ROLE"|false|false|USAGE|OnAccountObject|DATABASE|"TF_DEMO_DB"]
snowflake_user.tf_user: Creating...
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_tf_schema_to_tf_role: Creation complete after 0s [id="TF_DEMO_ROLE"|false|false|USAGE|OnSchema|OnSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_privileges_to_account_role.grant_all_tables: Creation complete after 0s [id="TF_DEMO_ROLE"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_user.tf_user: Creation complete after 1s [id=TF_DEMO_USER]
snowflake_grant_account_role.grants: Creating...
snowflake_grant_account_role.grants: Creation complete after 0s [id="TF_DEMO_ROLE"|USER|"TF_DEMO_USER"]
snowflake_grant_privileges_to_account_role.grant_future_tables: Creation complete after 1s [id="TF_DEMO_ROLE"|false|false|SELECT|OnSchemaObject|OnFuture|TABLES|InSchema|"TF_DEMO_DB"."TF_DEMO_SC"]

Apply complete! Resources: 10 added, 1 changed, 0 destroyed.

Outputs:

snowflake_svc_private_key = sensitive>
snowflake_svc_public_key = EOT
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApmGI4yH6KxgNn5bfVUws
O/Z0BGAoldztprVcWDtlEFwD4wwbhE4E8gerpWYES7yDEOcBq9cak5ajx9E54T3V
vA8OlYvORud+eCGAkrvyhtJU70EWbwCORt5LLy0m8YvGZR9MB6hpqGkz02sE3ZaV
0GAVwyxfJFqOEnzkHvtO19glsQR0U7NnJbDisyjYNWyngWd6sRR94nftmN9QBjIF
+8cMbElEQxXgUZ+b7AmvJR+hsnpSYW1A4ESLp5uhyG3C7qTL1Fk5dgQRNhxCf5+p
O5gcQuAsAnj0WrKebhtYfs6XRZZyse31vPY3l0wM4R/WteWoZOy+PseGfGorziuW
CwIDAQAB
-----END PUBLIC KEY-----

EOT
  • 変更内容確認(ウェアハウス)
SHOW WAREHOUSES;
SELECT "name", "created_on", "state", "type", "size", "auto_suspend", "owner_role_type"
FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())) ORDER BY 1 DESC;

  • 変更内容確認(データベース内のスキーマ)
SHOW SCHEMAS IN DATABASE TF_DEMO_DB;

  • 変更内容確認(ロール)

  • 変更内容確認(ユーザー)
SHOW USERS;
SELECT "name", "created_on", "login_name", "display_name", "default_namespace", "default_role", "owner", "type" FROM TABLE(RESULT_SCAN(LAST_QUERY_ID())) ORDER BY 1 DESC;

  • 変更内容確認(ユーザーに付与した権限)
SHOW GRANTS TO USER TF_DEMO_USER;

  • 変更内容確認(ロールに付与した権限)
SHOW GRANTS TO ROLE TF_DEMO_ROLE;

12. Terraformを使ってSnowflakeの各種リソースを掃除

当エントリ最後の実践手順、リソースのお掃除です。terraform destroyコマンドを実行してください。

📙(terraform destroyコマンド実行結果はこちら)
% terraform destroy
tls_private_key.svc_key: Refreshing state... [id=716aeef9a2f95fdfb31dfcc498ebf6240b8eb4cb]
snowflake_database.tf_db: Refreshing state... [id=TF_DEMO_DB]
snowflake_warehouse.tf_warehouse: Refreshing state... [id=TF_DEMO_WH]
snowflake_account_role.tf_role: Refreshing state... [id=TF_DEMO_ROLE]
snowflake_grant_account_role.grant_tf_role_to_sysadmin: Refreshing state... [id="TF_DEMO_ROLE"|ROLE|"SYSADMIN"]
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_to_tf_role: Refreshing state... [id="TF_DEMO_ROLE"|false|false|USAGE|OnAccountObject|DATABASE|"TF_DEMO_DB"]
snowflake_schema.tf_db_tf_schema: Refreshing state... [id="TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_tf_schema_to_tf_role: Refreshing state... [id="TF_DEMO_ROLE"|false|false|USAGE|OnSchema|OnSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_privileges_to_account_role.grant_all_tables: Refreshing state... [id="TF_DEMO_ROLE"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_privileges_to_account_role.grant_future_tables: Refreshing state... [id="TF_DEMO_ROLE"|false|false|SELECT|OnSchemaObject|OnFuture|TABLES|InSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_user.tf_user: Refreshing state... [id=TF_DEMO_USER]
snowflake_grant_account_role.grants: Refreshing state... [id="TF_DEMO_ROLE"|USER|"TF_DEMO_USER"]

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

Terraform will perform the following actions:

  
  - resource "snowflake_account_role" "tf_role" {
      - comment              = "My Terraform role" -> null
      - fully_qualified_name = "\"TF_DEMO_ROLE\"" -> null
      - id                   = "TF_DEMO_ROLE" -> null
      - name                 = "TF_DEMO_ROLE" -> null
      - show_output          = [
          - {
              - assigned_to_users = 1
              - comment           = "My Terraform role"
              - created_on        = "2025-09-14 00:50:30.693 -0700 PDT"
              - granted_roles     = 0
              - granted_to_roles  = 1
              - is_current        = false
              - is_default        = false
              - is_inherited      = false
              - name              = "TF_DEMO_ROLE"
              - owner             = "USERADMIN"
            },
        ] -> null
    }
:
:
(以降、削除対象のリソースやオブジェクトの検証内容が表示される。長過ぎたので表示割愛)
:
:
Do you really want to destroy all resources?
  Terraform will destroy all your managed infrastructure, as shown above.
  There is no undo. Only 'yes' will be accepted to confirm.

  Enter a value: yes

snowflake_grant_account_role.grant_tf_role_to_sysadmin: Destroying... [id="TF_DEMO_ROLE"|ROLE|"SYSADMIN"]
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_to_tf_role: Destroying... [id="TF_DEMO_ROLE"|false|false|USAGE|OnAccountObject|DATABASE|"TF_DEMO_DB"]
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_tf_schema_to_tf_role: Destroying... [id="TF_DEMO_ROLE"|false|false|USAGE|OnSchema|OnSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_account_role.grants: Destroying... [id="TF_DEMO_ROLE"|USER|"TF_DEMO_USER"]
snowflake_grant_privileges_to_account_role.grant_future_tables: Destroying... [id="TF_DEMO_ROLE"|false|false|SELECT|OnSchemaObject|OnFuture|TABLES|InSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_privileges_to_account_role.grant_all_tables: Destroying... [id="TF_DEMO_ROLE"|false|false|SELECT|OnSchemaObject|OnAll|TABLES|InSchema|"TF_DEMO_DB"."TF_DEMO_SC"]
snowflake_grant_account_role.grant_tf_role_to_sysadmin: Destruction complete after 0s
snowflake_grant_account_role.grants: Destruction complete after 1s
snowflake_user.tf_user: Destroying... [id=TF_DEMO_USER]
snowflake_grant_privileges_to_account_role.grant_future_tables: Destruction complete after 1s
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_tf_schema_to_tf_role: Destruction complete after 1s
snowflake_grant_privileges_to_account_role.grant_all_tables: Destruction complete after 1s
snowflake_grant_privileges_to_account_role.grant_usage_tf_db_to_tf_role: Destruction complete after 1s
snowflake_user.tf_user: Destruction complete after 0s
snowflake_account_role.tf_role: Destroying... [id=TF_DEMO_ROLE]
tls_private_key.svc_key: Destroying... [id=716aeef9a2f95fdfb31dfcc498ebf6240b8eb4cb]
snowflake_warehouse.tf_warehouse: Destroying... [id=TF_DEMO_WH]
snowflake_schema.tf_db_tf_schema: Destroying... [id="TF_DEMO_DB"."TF_DEMO_SC"]
tls_private_key.svc_key: Destruction complete after 0s
snowflake_warehouse.tf_warehouse: Destruction complete after 0s
snowflake_account_role.tf_role: Destruction complete after 0s
snowflake_schema.tf_db_tf_schema: Destruction complete after 1s
snowflake_database.tf_db: Destroying... [id=TF_DEMO_DB]
snowflake_database.tf_db: Destruction complete after 0s

Destroy complete! Resources: 12 destroyed.

Snowflakeコンソールにてオブジェクト・リソース情報を確認してみます。これまでの手順で作成していたTF_DEMO_DB及び配下の要素が綺麗サッパリ無くなっていることが確認出来ました。

上記Terraformの削除処理では削除しきれていない情報としてユーザーが残っていました。これはSQL文で削除しておきます。

以上で、当エントリにおけるチュートリアル実践は終わりです。お疲れ様でした&ありがとうございました!

13. 結論&次ステップのための参考リソース

  • 次に学んでいくのにおすすめなテーマ
  • 今後の検討事案
    • Terraformの変更をどのように実行するか
      • 今回はローカルで実行したが本番環境では滅多に行われない。
      • ベストプラクティスとしては、共有環境の変更に関わるすべてのワークフローを自動化するCI/CDパイプラインを構築すること
      • CI/CD パイプラインは、環境を変更するためのより優れたゲートを提供し、ソース管理で監査証跡を生成するため、環境の履歴を確認することが出来る
    • 環境とプロジェクトをどのように分離するか。後から変更するのは困難なので熟考が必要。代替アプローチを採用する場合、ほとんどのモジュールと CI/CD インフラストラクチャに大幅な変更が必要になる
      • 名前空間/RBAC を使用するか
      • 複数アカウントを使用するか
  • データ移行やテーブルスキーマの変更などを管理するツールの組み合わせを要検討

まとめ

という訳で、TerraformでSnowflakeの環境を作成・管理していく手順を実践することが出来るSnowflake Quickstarsコンテンツ『Terraforming Snowflake』の実践内容の紹介でした。

初期の初期、Terraformのインストール手順から一連のチュートリアル実践を段階を踏んで行う事で、Terraformの便利さを十分に堪能出来る内容だったと思います。一方で途中でも言及されていましたが 『Terraformでどこまで管理するか、Terraformとそれ以外(例えばSQL、例えばdbt)の使い分け棲み分けはどうするか』 という部分については非常に頭を悩ませるトピックでもあるな、と即座に思いました。(関連するトピックで検索してみてもそういった部分、問題にぶつかり試行錯誤されている方の何と多いことか…)



Source link

Views: 0

RELATED ARTICLES

返事を書く

あなたのコメントを入力してください。
ここにあなたの名前を入力してください

- Advertisment -