やりたいこと
AWS で RDS 作ったけど、テーブル作ったりするのどうしよー
という事で、今回は Prisma を AWS Lambda に入れて、migrate を実行してみました。
- ローカルで
prisma migrate dev
を実行して migration ファイルを作成 - AWS Lambda で
prisma migrate deploy
を実行して Amazon RDS に適応
ということをやろうと思います。こうすることで、
- AWS 上にサーバーを立てることなく DB のテーブル作成が可能
- Prisma の migration ファイルを管理することで、複数人での開発や、他環境への適応も楽
になるはず。
ちなみに、私は Amplify を使って Lambda を作成しました。
Lambda のコード
RDS のユーザ名、パスワードは Secrets Manager にある前提です。
以下の環境変数の設定が必要です。
- DATABASE_SECRET_ARN: Secrets Manager の ARN
- DATABASE_ENGINE: データベースエンジン名 (postgresql とか)
- DATABASE_HOST: RDS のエンドポイント
- DATABASE_PORT: RDS のポート番号
- DATABASE_NAME: データベース名
index.ts
import { execFile } from 'child_process';
import * as path from 'path';
import {
SecretsManagerClient,
GetSecretValueCommand,
} from '@aws-sdk/client-secrets-manager';
import { type Handler, type Context } from 'aws-lambda';
type Event = {
typeName: string;
fieldName: string;
arguments?: {
command?: string; // deploy, reset or resolve
option?: string; // resolve の時に migration 名を指定
};
};
type DatabaseSecrets = {
username: string;
password: string;
};
const client = new SecretsManagerClient();
/* SecretsManager から username と password を取得 */
const getDatabaseSecrets = async (
secretName: string,
): Promise<DatabaseSecrets | undefined> => {
const response = await client.send(
new GetSecretValueCommand({
SecretId: secretName,
VersionStage: 'AWSCURRENT',
}),
);
if (response.SecretString == null) return;
const secret = JSON.parse(response.SecretString) as DatabaseSecrets;
return secret;
};
/* prisma コマンドを実行 */
const execMigrate = async (command: string, options: string[]) => {
const exitCode = await new Promise<number>((resolve, _reject) => {
execFile(
path.resolve('./node_modules/prisma/build/index.js'),
['migrate', command].concat(options),
(error, stdout, _stderr) => {
console.log(stdout);
if (error != null) {
console.error(
`prisma migrate ${command} exited with error ${error.message}`,
);
resolve(Number(error.code) ?? 1);
} else {
resolve(0);
}
},
);
});
if (exitCode !== 0)
throw Error(`prisma migrate ${command} failed with exit code ${exitCode}`);
};
export const handler: Handler = async (event: Event, _context: Context) => {
console.log(`EVENT: ${JSON.stringify(event)}`);
// 環境変数チェック
if (process.env.DATABASE_SECRET_ARN == null) {
throw new Error('DATABASE_SECRET_ARN is not defined.');
}
if (process.env.DATABASE_ENGINE == null) {
throw new Error('DATABASE_ENGINE is not defined.');
}
if (process.env.DATABASE_HOST == null) {
throw new Error('DATABASE_HOST is not defined.');
}
if (process.env.DATABASE_PORT == null) {
throw new Error('DATABASE_PORT is not defined.');
}
if (process.env.DATABASE_NAME == null) {
throw new Error('DATABASE_NAME is not defined.');
}
// SecretsManager から username, password を取得
const secretArn = process.env.DATABASE_SECRET_ARN;
const secrets = await getDatabaseSecrets(secretArn);
if (secrets == null) {
throw new Error('Cannot get database secrets.');
}
// URL セーフにする
const username = encodeURIComponent(secrets.username);
const password = encodeURIComponent(secrets.password);
// Prisma コマンドで使う環境変数を設定
const engine = process.env.DATABASE_ENGINE;
const host = process.env.DATABASE_HOST;
const port = process.env.DATABASE_PORT;
const dbName = process.env.DATABASE_NAME;
process.env.DATABASE_URL = `${engine}://${username}:${password}@${host}:${port}/${dbName}`;
// 引数なしの場合は prisma migrate deploy を実行
const command: string = event?.arguments?.command ?? 'deploy';
let options: string[] = [];
switch (command) {
case 'reset':
// prisma reset --force --skip-generate を実行
options = ['--force', '--skip-generate'];
break;
case 'resolve':
// prisma migrate resolve --rolled-back を実行
if (event?.arguments?.option == null) {
throw new Error('migration name is not defined.');
}
options = ['--rolled-back', event.arguments.option];
}
// Prisma コマンドを実行
await execMigrate(command, options);
};
Lambda 作成時の注意事項!
npm install
時に環境変数を設定する
Prisma は npm install
時、環境に合わせた BINARY を取得します。
Lambda の実行環境は Amazon Linux なので、ローカルで npm install
したものをアップロードしてしまうと BINARY が無いため動きません。
そこで、環境変数 PRISMA_CLI_BINARY_TARGETS
に rhel-openssl-3.0.x
を追加する必要があります。
Apple Silicon の Mac の場合は以下のようにして npm install
を実行します。
PRISMA_CLI_BINARY_TARGETS=darwin-arm64,rhel-openssl-3.0.x npm install prisma
使い方
事前に、ローカルで DB を立て、環境変数 DATABASE_URL
を立てた DB 向けに設定しておく必要があります。
- ローカルで
prisma/schema.prisma
を作成/編集 - ローカルで
npx prisma migrate dev
を実行-
prisma/migrations/
に migration ファイルが追加される
-
- 作成された migration ファイルを含めて AWS Lambda を作成
- AWS Lambda を実行
-
npx prisma migrate deploy
が Amazon RDS 向けに実行される
-
migration ファイルをアップロードするために、毎回 Lambda を更新するのがちょっと手間だなー