この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
こんにちは!DA(データアナリティクス)事業本部 サービスソリューション部の大高です。
Infrastructure as Code のツール「Pulumi」はSnowflakeにも対応しており、下記の通りSnowflake用のパッケージが提供されています。
先日、こちらのパッケージを利用して下記のエントリの通りSnowflakeの「仮想ウェアハウス」と「ロール」を作成してみました。
今回は、更にこちらに加えて「ユーザー」と「データベース」も作成してみたいと思います。
前提
Snowflakeのアカウントは既にセットアップ済みで利用できるものとします。
また、Pulumiについては下記のようにHomebrew経由でインストールされていることを前提としています。
% brew install pulumi
% pulumi version
v3.34.1
加えて、PulumiのプロジェクトとSnowflake接続用の設定は下記のエントリの通り設定済みであることを前提としています。
ユーザーを作成するコードを記述する
では、ドキュメントを参考にしながらコードを記述していきましょう。まずは「ユーザー」を作成するコードです。
今回作成する「ユーザー」は「ロール」に所属させたいので、併せてRoleGrants
APIも利用します。
resources/user.ts
import * as snowflake from "@pulumi/snowflake";
import { UserArgs } from "@pulumi/snowflake";
export class User {
name: string;
role: string;
resource: snowflake.User | undefined;
constructor(name: string, role: string) {
this.name = name;
this.role = role;
}
public create(args: UserArgs) {
const user = new snowflake.User(this.name, {
name: this.name,
...args,
});
const grants = new snowflake.RoleGrants(this.name, {
roleName: this.role,
users: [user.name],
});
this.resource = user;
}
}
指定された名前で「ユーザー」を作成し、また同時にユーザーが指定されたロールに所属するようにしています。
データベースを作成するコードを記述する
次に「データベース」を作成するコードです。
ここでは別途作成する「ユーザー」が所属する「ロール」に対して、「データベース」と「データベース内のPUBLICスキーマ」の「オーナー権限」を付与したいと思います。
そこで、今回は「データベース」の作成と同時に権限の付与もDatabaseGrant
APIとSchemaGrant
APIを利用して実施します。
resources/database.ts
import * as snowflake from "@pulumi/snowflake";
import { DatabaseArgs } from "@pulumi/snowflake";
export class Database {
name: string;
role: string;
resource: snowflake.Database | undefined;
constructor(name: string, role: string) {
this.name = name;
this.role = role;
}
public create(args: DatabaseArgs) {
const database = new snowflake.Database(this.name, {
name: this.name,
...args,
});
const databaseGrants = new snowflake.DatabaseGrant(this.name, {
databaseName: database.name,
privilege: "OWNERSHIP",
roles: [this.role],
withGrantOption: false,
});
const schemaGrants = new snowflake.SchemaGrant(this.name, {
databaseName: database.name,
privilege: "OWNERSHIP",
roles: [this.role],
schemaName: "PUBLIC",
withGrantOption: false,
});
this.resource = database;
}
}
また、詳細は割愛しますがWarehouseも同様に作成した「ロール」で利用できるようにコードを修正しています。
resources/warehouse.ts
import * as snowflake from "@pulumi/snowflake";
import { WarehouseArgs } from "@pulumi/snowflake";
export class Warehouse {
name: string;
role: string;
resource: snowflake.Warehouse | undefined;
constructor(name: string, role: string) {
this.name = name;
this.role = role;
}
public create(args: WarehouseArgs) {
const warehouse = new snowflake.Warehouse(this.name, {
name: this.name,
autoSuspend: 60,
...args,
});
const grantUsage = new snowflake.WarehouseGrant(this.name + "Usage", {
privilege: "USAGE",
roles: [this.role],
warehouseName: warehouse.name,
withGrantOption: false,
});
const grantOperate = new snowflake.WarehouseGrant(this.name + "Operate", {
privilege: "OPERATE",
roles: [this.role],
warehouseName: warehouse.name,
withGrantOption: false,
});
this.resource = warehouse;
}
}
あとはこれらをindex.ts
から呼び出します。
index.ts
import { Database } from "./resources/database";
import { Role } from "./resources/role";
import { User } from "./resources/user";
import { Warehouse } from "./resources/warehouse";
// Role
const role = new Role("role-ootaka-pulumi");
role.create({});
// Warehouse
const warehouse = new Warehouse("vwh-ootaka-pulumi", role.name);
warehouse.create({ warehouseSize: "xsmall" });
// User
const user = new User("user-ootaka-pulumi", role.name);
user.create({
password: "MustBeSecret!!!",
loginName: user.name,
displayName: user.name,
firstName: "pulumi",
lastName: "ootaka",
defaultRole: role.name,
defaultWarehouse: warehouse.name,
defaultNamespace: "PUBLIC",
mustChangePassword: true,
});
// Database
const database = new Database("db-ootaka-pulumi", role.name);
database.create({});
// Export Resource Info
export const roleInfo = role.resource;
export const warehouseInfo = warehouse.resource;
export const userInfo = user.resource;
export const databaseInfo = database.resource;
こちらは作成したクラスを単純に呼んでいるだけですが、1点注意点があります。
今回は検証のためuser.create
のpassword
に対して、直接初期パスワード値を記載していますが、これは本来コードに記載すべきではありません。
やり方は色々あると思いますが、例えばランダムなパスワードを生成してAWS Secrets Managerに保存し、その値を参照するなど、セキュリティに考慮した対応が必要になってくると思います。
デプロイしてみる
では、デプロイしてみます。
デプロイにはpulumi up
コマンドを実行します。
% pulumi up
Previewing update (dev)
View Live: https://app.pulumi.com/ootaka-daisuke/pulumi-snowflake/dev/previews/2443e9d0-ccd0-44a6-9094-1a1a41d6700a
Type Name Plan
+ pulumi:pulumi:Stack pulumi-snowflake-dev create
+ ├─ snowflake:index:Warehouse vwh-ootaka-pulumi create
+ ├─ snowflake:index:Role role-ootaka-pulumi create
+ ├─ snowflake:index:Database db-ootaka-pulumi create
+ ├─ snowflake:index:User user-ootaka-pulumi create
+ ├─ snowflake:index:WarehouseGrant vwh-ootaka-pulumiUsage create
+ ├─ snowflake:index:WarehouseGrant vwh-ootaka-pulumiOperate create
+ ├─ snowflake:index:DatabaseGrant db-ootaka-pulumi create
+ ├─ snowflake:index:SchemaGrant db-ootaka-pulumi create
+ └─ snowflake:index:RoleGrants user-ootaka-pulumi create
Resources:
+ 10 to create
Do you want to perform this update? [Use arrows to move, enter to select, type to filter]
yes
> no
details
作成リソースのプレビュー後にyes
を選択し、成功すると以下のように表示されます。
Do you want to perform this update? yes
Updating (dev)
View Live: https://app.pulumi.com/ootaka-daisuke/pulumi-snowflake/dev/updates/17
Type Name Status
+ pulumi:pulumi:Stack pulumi-snowflake-dev created
+ ├─ snowflake:index:Role role-ootaka-pulumi created
+ ├─ snowflake:index:Warehouse vwh-ootaka-pulumi created
+ ├─ snowflake:index:User user-ootaka-pulumi created
+ ├─ snowflake:index:Database db-ootaka-pulumi created
+ ├─ snowflake:index:RoleGrants user-ootaka-pulumi created
+ ├─ snowflake:index:SchemaGrant db-ootaka-pulumi created
+ ├─ snowflake:index:DatabaseGrant db-ootaka-pulumi created
+ ├─ snowflake:index:WarehouseGrant vwh-ootaka-pulumiUsage created
+ └─ snowflake:index:WarehouseGrant vwh-ootaka-pulumiOperate created
Outputs:
databaseInfo : {
dataRetentionTimeInDays: 1
id : "db-ootaka-pulumi"
name : "db-ootaka-pulumi"
urn : "urn:pulumi:dev::pulumi-snowflake::snowflake:index/database:Database::db-ootaka-pulumi"
}
roleInfo : {
id : "role-ootaka-pulumi"
name : "role-ootaka-pulumi"
urn : "urn:pulumi:dev::pulumi-snowflake::snowflake:index/role:Role::role-ootaka-pulumi"
}
userInfo : {
defaultNamespace : "PUBLIC"
defaultRole : "role-ootaka-pulumi"
defaultWarehouse : "vwh-ootaka-pulumi"
disabled : false
displayName : "user-ootaka-pulumi"
firstName : "pulumi"
hasRsaPublicKey : false
id : "user-ootaka-pulumi"
lastName : "ootaka"
loginName : "USER-OOTAKA-PULUMI"
mustChangePassword: true
name : "user-ootaka-pulumi"
password : [secret]
urn : "urn:pulumi:dev::pulumi-snowflake::snowflake:index/user:User::user-ootaka-pulumi"
}
warehouseInfo: {
autoResume : true
autoSuspend : 60
id : "vwh-ootaka-pulumi"
maxClusterCount : 1
maxConcurrencyLevel : 8
minClusterCount : 1
name : "vwh-ootaka-pulumi"
resourceMonitor : "null"
scalingPolicy : "STANDARD"
statementQueuedTimeoutInSeconds: 0
statementTimeoutInSeconds : 172800
urn : "urn:pulumi:dev::pulumi-snowflake::snowflake:index/warehouse:Warehouse::vwh-ootaka-pulumi"
warehouseSize : "X-Small"
}
Resources:
+ 10 created
Duration: 8s
実際にsnowsql
を利用してSQLでユーザーとデータベースの情報を確認してみます。
USE ROLE ACCOUNTADMIN;
+--------------------+-------------------------------+--------------------+--------------------+------------+-----------+-------+----------------+----------------+---------+----------+----------------------+----------------+-------------------+-------------------+--------------------+-------------------------+---------------+---------------+--------------------+--------------+--------------------+-----------------+-------------------+--------------+--------------------+
| name | created_on | login_name | display_name | first_name | last_name | email | mins_to_unlock | days_to_expiry | comment | disabled | must_change_password | snowflake_lock | default_warehouse | default_namespace | default_role | default_secondary_roles | ext_authn_duo | ext_authn_uid | mins_to_bypass_mfa | owner | last_success_login | expires_at_time | locked_until_time | has_password | has_rsa_public_key |
|--------------------+-------------------------------+--------------------+--------------------+------------+-----------+-------+----------------+----------------+---------+----------+----------------------+----------------+-------------------+-------------------+--------------------+-------------------------+---------------+---------------+--------------------+--------------+--------------------+-----------------+-------------------+--------------+--------------------|
| user-ootaka-pulumi | 2022-06-20 00:44:13.116 -0700 | USER-OOTAKA-PULUMI | user-ootaka-pulumi | pulumi | ootaka | | | | | false | true | false | vwh-ootaka-pulumi | PUBLIC | role-ootaka-pulumi | | false | | | ACCOUNTADMIN | NULL | NULL | NULL | true | false |
+--------------------+-------------------------------+--------------------+--------------------+------------+-----------+-------+----------------+----------------+---------+----------+----------------------+----------------+-------------------+-------------------+--------------------+-------------------------+---------------+---------------+--------------------+--------------+--------------------+-----------------+-------------------+--------------+--------------------+
1 Row(s) produced. Time Elapsed: 0.496s
SHOW DATABASES LIKE 'db-ootaka-pulumi';
+-------------------------------+------------------+------------+------------+--------+--------------------+---------+---------+----------------+
| created_on | name | is_default | is_current | origin | owner | comment | options | retention_time |
|-------------------------------+------------------+------------+------------+--------+--------------------+---------+---------+----------------|
| 2022-06-20 00:44:13.255 -0700 | db-ootaka-pulumi | N | N | | role-ootaka-pulumi | | | 1 |
+-------------------------------+------------------+------------+------------+--------+--------------------+---------+---------+----------------+
1 Row(s) produced. Time Elapsed: 0.164s
想定どおり作成できていますね!
最後に、pulumi destroy
コマンドで後片付けをしておしまいです。
まとめ
以上、PulumiでSnowflakeのユーザーとデータベースを作成してみました。
今回作成したように、コードベースで処理を書いていくことで「開発で利用しているSnowflakeアカウントに新規メンバー用ユーザと、開発用データベースを追加したい」というようなケースで活用できるかなと思いました。
今回は細かいところまで実装していませんが、例えばよくある名字.名前@foobar.com
のように会社用メールアドレスが付与されている場合「メールアドレスを元にユーザー、ユーザー専用データベースを作成して、作成したSnowflakeアカウント情報をメールで通知」なんてこともできるかなと思います。
それぞれの処理についてはSQLだけでも対応できますが、他のシステムとの連携(メールでアカウント作成通知)などをやろうとする場合には、こういったInfrastructure as Code のツールが選択肢に入ってくるのかなと思います。(Snowflakeの外部関数を使う、という手もありますね)
どなたかのお役に立てば幸いです。それでは!