TypeScript初心者のAWS CDKをProjenで使うメモ
はじめに
AWS CDKからTypeScriptを始めたが、npmとかごちゃごちゃしていたり、どうすれば正解なのかがわからなくなってきてProjenを試してみようとしているメモ。
ホントに初心者なので間違っている箇所はあると思う。
作業メモなので使っていくうちに更新していく。
この記事を書き始めた時点での感想としては、.projenrc.ts
だけを変更するという制約があるので迷わなくてすむ。いい感触。
Projen
参考URL
インストール
と思ったら普通に実行するだけらしい。npx初心者すぎる。
mkdir test_project && cd test_project
npx projen new awscdk-app-ts
$ ll
total 292
drwxr-xr-x 8 raiha raiha 4096 Mar 30 00:59 ./
drwxr-xr-x 3 raiha raiha 4096 Mar 30 00:57 ../
-r--r--r-- 1 raiha raiha 4303 Mar 30 00:58 .eslintrc.json
drwxr-xr-x 8 raiha raiha 4096 Mar 30 00:59 .git/
-r--r--r-- 1 raiha raiha 818 Mar 30 00:58 .gitattributes
drwxr-xr-x 3 raiha raiha 4096 Mar 30 00:58 .github/
-r--r--r-- 1 raiha raiha 817 Mar 30 00:58 .gitignore
-r--r--r-- 1 raiha raiha 629 Mar 30 00:58 .mergify.yml
-r--r--r-- 1 raiha raiha 391 Mar 30 00:58 .npmignore
drwxr-xr-x 2 raiha raiha 4096 Mar 30 00:58 .projen/
-rw-r--r-- 1 raiha raiha 533 Mar 30 00:59 .projenrc.ts
-r--r--r-- 1 raiha raiha 11358 Mar 30 00:58 LICENSE
-rw-r--r-- 1 raiha raiha 14 Mar 30 00:58 README.md
-r--r--r-- 1 raiha raiha 483 Mar 30 00:58 cdk.json
drwxr-xr-x 392 raiha raiha 16384 Mar 30 00:59 node_modules/
-rw-r--r-- 1 raiha raiha 2348 Mar 30 00:59 package.json
drwxr-xr-x 2 raiha raiha 4096 Mar 30 00:58 src/
drwxr-xr-x 2 raiha raiha 4096 Mar 30 00:58 test/
-r--r--r-- 1 raiha raiha 870 Mar 30 00:58 tsconfig.dev.json
-r--r--r-- 1 raiha raiha 844 Mar 30 00:58 tsconfig.json
-rw-r--r-- 1 raiha raiha 189043 Mar 30 00:59 yarn.lock
.projenrc.tsの編集
.gitignore
を含めて読み取り権限のみが与えられており、これらのファイルの編集は.projenrc.ts
から行う。
awscdk.AwsCdkTypeScriptAppのプロパティにgitignore
とdeps
およびdevDeps
を追加してみます。
Constructを作成するプロジェクトになりますが、以下もイメージをつかむ参考になりそうです。
import { awscdk } from 'projen';
const project = new awscdk.AwsCdkTypeScriptApp({
cdkVersion: '2.1.0',
defaultReleaseBranch: 'main',
name: 'CloudTFd',
projenrcTs: true,
// deps: [], /* Runtime dependencies of this module. */
// description: undefined, /* The description is just a string that helps people understand the purpose of the package. */
// devDeps: [], /* Build dependencies for this module. */
// packageName: undefined, /* The "name" in package.json. */
gitignore: [
'cdk.context.json',
'lib/config/config.ts',
],
deps: ['cdk-monitoring-constructs'],
devDeps: ['@types/aws-lambda'],
});
project.synth();
反映には再度以下を実施すると、追加が確認できる。
npx projen
// snip
.cache
cdk.context.json
lib/config/config.ts
/test-reports/
// snip
{ // snip
"devDependencies": {
"@types/aws-lambda": "^8.10.136",
"@types/jest": "^29.5.12",
"@types/node": "^18",
"@typescript-eslint/eslint-plugin": "^6",
"@typescript-eslint/parser": "^6",
"aws-cdk": "^2.1.0",
"esbuild": "^0.20.2",
"eslint": "^8",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
"jest": "^29.7.0",
"jest-junit": "^15",
"projen": "^0.80.15",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
"typescript": "^5.4.3"
},
"dependencies": {
"aws-cdk-lib": "^2.1.0",
"cdk-monitoring-constructs": "^7.8.0",
"constructs": "^10.0.5"
},
// snip
}
yarn/npx projen
.projen/tasks.json
配下に実行コマンドが記載されている。
yarn build
yarn eslint
npx projen build
npx projen eslint
eslint
build
yarn buildするとテストも行われる。
Successfully synthesized to /projen/CloudTFd/cdk.out
Supply a stack id (CloudTFdStack, GlobalStack) to display its template.
👾 build » test | jest --passWithNoTests --updateSnapshot
PASS test/main.test.ts (5.725 s)
✓ Snapshot (1614 ms)
----------------------------------|---------|----------|---------|---------|----------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------------------------------|---------|----------|---------|---------|----------------------------
All files | 96.52 | 62.96 | 95.74 | 96.52 |
lib | 100 | 100 | 100 | 100 |
cloudtfd-stack.ts | 100 | 100 | 100 | 100 |
global-stack.ts | 100 | 100 | 100 | 100 |
lib/config | 100 | 100 | 100 | 100 |
config.ts | 100 | 100 | 100 | 100 |
lib/constructs | 97.05 | 61.53 | 95.23 | 97.05 |
application-patterns.ts | 100 | 100 | 100 | 100 |
base.ts | 100 | 100 | 100 | 100 |
basic-auth.ts | 100 | 100 | 100 | 100 |
bucket.ts | 100 | 100 | 100 | 100 |
cdn.ts | 100 | 50 | 100 | 100 | 84
database.ts | 100 | 100 | 100 | 100 |
mail.ts | 100 | 100 | 100 | 100 |
monitor.ts | 100 | 100 | 100 | 100 |
redis.ts | 100 | 100 | 100 | 100 |
waf.ts | 89.23 | 63.63 | 90 | 89.23 | 36,107-111,163-165,183,244
lib/constructs/utils | 94.17 | 64.28 | 95.83 | 94.17 |
aws-managed-prefix-list.ts | 100 | 100 | 100 | 100 |
cloudfront-keypair-generator.ts | 100 | 100 | 100 | 100 |
default-security-group.ts | 100 | 100 | 100 | 100 |
domain.ts | 100 | 100 | 100 | 100 |
smtp-credentials-generator.ts | 100 | 100 | 100 | 100 |
waf-ipsets.ts | 83.33 | 83.33 | 100 | 83.33 | 22-28
waf-statement.ts | 87.09 | 50 | 94.44 | 87.09 | 26,62,204,221
----------------------------------|---------|----------|---------|---------|----------------------------
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 2 passed, 2 total
Time: 5.852 s, estimated 7 s
Ran all test suites.
👾 build » test » eslint | eslint --ext .ts,.tsx --fix --no-error-on-unmatched-pattern src test build-tools projenrc .projenrc.ts
deploy/destroy
cdk deploy/destroyと同じ
yarn deploy
yarn deploy --all
yarn destroy
yarn destroy --all
サブプロジェクトの追加
AWS CDKでLambdaをデプロイしたい場合に、そのLambdaを(CDKのProjenを親とした)Projenでプロジェクトを生成できる。
以下を実行すると./src/lib/lambda/typescript
にProjenで生成される設定ファイルが生成されるが、.projenrc.ts
だけは親側にしかない。
つまりサブプロジェクトであっても、親プロジェクトの.projenrc.ts
を編集したらnpx projen
という流れは変わらない。
import { awscdk, typescript } from 'projen';
const cdkProject = new awscdk.AwsCdkTypeScriptApp({
cdkVersion: '2.116.0',
defaultReleaseBranch: 'main',
name: 'CloudTFd',
projenrcTs: true,
gitignore: [
'cdk.context.json',
'config.ts',
],
deps: [
'cdk-monitoring-constructs',
'@aws-cdk/aws-redshift-alpha',
'constructs',
],
devDeps: ['@types/aws-lambda'],
});
const lambdaTS = new typescript.TypeScriptProject({
defaultReleaseBranch: 'main',
name: 'lambdaTS',
parent: cdkProject,
outdir: './src/lib/lambda/typescript',
});
cdkProject.synth();
lambdaTS.synth();
サブプロジェクトのよくわかっていないところ
-
サブプロジェクトが参照するdepsLockFilePath
親と子を両方ともTypeScriptで作ったせい(?)なのかNodejsFuntionで指定するdepsLockFilePathはプロジェクトのルート配下にある./package-lock.json
を指定したら動いた。
そもそもNodejsFunctionを使ったのも初めてなのでいろいろ勘違いしている場所かも。 -
親プロジェクトがTypeScriptでサブプロジェクトがPythonで作成したときのテスト
サブプロジェクトもTypescriptだったらJestが動いているっぽいけど、サブプロジェクトがPythonだったときにテストが動いてない気がする。親が作られたときにできるtestのタスクがjestなのでそれはそうなのだが、Projen的に自分でタスクに追加するものなのかがよくわかっていない。(未調査)
タスクの追加
.projenrc.tsにtaskを追加することで、yarn
で実行できるコマンドを増やせる。
exec
に実行したいコマンド
condition
にexecを実行する条件を書く。conditionを実行した終了コードが0であれば実行される。
spawn
で別のタスクを呼び出す
const cdkProject = new awscdk.AwsCdkTypeScriptApp({
...
});
cdkProject.addTask('initial-ctfd-repos', {
steps: [
{
name: 'add-ctfd',
exec: 'git submodule add https://github.com/CTFd/CTFd.git CTFd',
condition: '! git submodule status CTFd >/dev/null',
},
{
name: 'add-ctfd-cloudfornt-plugin',
exec: 'git submodule add https://github.com/raihalea/CTFd-CloudFront-signed-url.git CTFd-Plugins/cloudfront',
condition: '! git submodule status CTFd-Plugins/cloudfront >/dev/null',
},
{
name: 'copy-plugins',
spawn: 'copy-plugins',
},
],
});
cdkProject.addTask('copy-plugins', {
exec: 'cp -r ./CTFd-Plugins/* ./CTFd/CTFd/plugins/',
});
cdkProject.addTask('pull-submodules', {
exec: 'git submodule update --init --recursive',
});
以下のようなものが生成される。
"initial-ctfd-repos": {
"name": "initial-ctfd-repos",
"steps": [
{
"name": "add-ctfd",
"exec": "git submodule add https://github.com/CTFd/CTFd.git CTFd",
"condition": "! git submodule status CTFd >/dev/null"
},
{
"name": "add-ctfd-cloudfornt-plugin",
"exec": "git submodule add https://github.com/raihalea/CTFd-CloudFront-signed-url.git CTFd-Plugins/cloudfront",
"condition": "! git submodule status CTFd-Plugins/cloudfront >/dev/null"
},
{
"name": "copy-plugins",
"spawn": "copy-plugins"
}
]
},
Github Actionsの対応
projenはデフォルトでいくつかのWorkflows(./github/workflows/)を展開するが、何も設定しないとpersonal access token
を前提に作成される。
PATを使用する場合
PATを使用したい場合はPROJEN_GITHUB_TOKEN
をRepository secretsに入れる
- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.PROJEN_GITHUB_TOKEN }}
GitHub Appsを使いたい場合にはprojenCredentials
を追記する。
import { awscdk, github } from 'projen';
const project = new awscdk.AwsCdkTypeScriptApp({
cdkVersion: '2.1.0',
defaultReleaseBranch: 'main',
name: 'BottleNat',
projenrcTs: true,
repository: 'https://github.com/raihalea/CDK-AutoScalingNatInstance.git',
gitignore: [
'cdk.context.json',
'test/__snapshots__',
],
deps: [
'constructs',
],
devDeps: ['@types/aws-lambda'],
projenCredentials: github.GithubCredentials.fromApp({
appIdSecret: 'APP_ID',
privateKeySecret: 'PRIVATE_KEY',
}),
});
project.synth();
GitHub Appsの作成
Projenの話ではないので簡単に
Applicationsの作成と、それを該当リポジトリからアクセスできるようにする
画面が現時点(2024/04/07)と異なるけどこれが参考になる
リポジトリから利用する
上記のコードの場合はAPP_ID
とPRIVATE_KEY
をリポジトリのRepository secrets
に作成する。(Secrets and variables -> Actions)
Workflowの追加
参考になりそうなURL
git submoduleの更新を月1でPRするAction
(長くなったけど、これをいい感じに省略できるものはProjenで提供されてるのだろうか?別ファイルで管理したくなってくる)
if (project.github) {
const updateSubmoduels = project.github.addWorkflow('update-submodules');
updateSubmoduels.on({ schedule: [{ cron: '0 0 1 * *' }], workflowDispatch: {} });
updateSubmoduels.addJobs({
updateCtfd: {
runsOn: ['ubuntu-latest'],
name: 'update-submoduels',
permissions: {
contents: JobPermission.READ,
},
steps: [
{
name: 'Generate token',
id: 'generate_token',
uses: 'actions/create-github-app-token@v1',
with: {
app_id: '${{ secrets.APP_ID }}',
private_key: '${{ secrets.PRIVATE_KEY }}',
},
},
{
name: 'check out',
uses: 'actions/checkout@v4',
with: {
submodules: 'recursive',
},
},
{
name: 'update submodules',
run: 'git submodule update --remote --recursive',
},
{
name: 'git status',
id: 'status',
run: 'echo "status=$(git status -s)" >> $GITHUB_OUTPUT',
},
{
name: 'Create Pull Request',
uses: 'peter-evans/create-pull-request@v6',
if: '${{ steps.status.outputs.status }}',
with: {
token: '${{ steps.generate_token.outputs.token }}',
message: 'chore(deps): upgrade submodules',
branch: 'github-actions/upgrade-submodules',
title: 'chore(deps): upgrade submodules',
author: 'github-actions <github-actions@github.com>',
comitter: 'github-actions <github-actions@github.com>',
},
},
],
},
});
}
一応.projenrc.tsの全文
import { awscdk, github } from 'projen';
import { JobPermission } from 'projen/lib/github/workflows-model';
const project = new awscdk.AwsCdkTypeScriptApp({
cdkVersion: '2.1.0',
defaultReleaseBranch: 'main',
name: 'BottleNat',
projenrcTs: true,
repository: 'https://github.com/raihalea/CDK-AutoScalingNatInstance.git',
gitignore: [
'cdk.context.json',
'test/__snapshots__',
],
deps: [
'constructs',
],
devDeps: ['@types/aws-lambda'],
projenCredentials: github.GithubCredentials.fromApp({
appIdSecret: 'APP_ID',
privateKeySecret: 'PRIVATE_KEY',
}),
});
if (project.github) {
const updateSubmoduels = project.github.addWorkflow('update-submodules');
updateSubmoduels.on({ schedule: [{ cron: '0 0 1 * *' }], workflowDispatch: {} });
updateSubmoduels.addJobs({
updateCtfd: {
runsOn: ['ubuntu-latest'],
name: 'update-submoduels',
permissions: {
contents: JobPermission.READ,
},
steps: [
{
name: 'Generate token',
id: 'generate_token',
uses: 'actions/create-github-app-token@v1',
with: {
app_id: '${{ secrets.APP_ID }}',
private_key: '${{ secrets.PRIVATE_KEY }}',
},
},
{
name: 'check out',
uses: 'actions/checkout@v4',
with: {
submodules: 'recursive',
},
},
{
name: 'update submodules',
run: 'git submodule update --remote --recursive',
},
{
name: 'git status',
id: 'status',
run: 'echo "status=$(git status -s)" >> $GITHUB_OUTPUT',
},
{
name: 'Create Pull Request',
uses: 'peter-evans/create-pull-request@v6',
if: '${{ steps.status.outputs.status }}',
with: {
token: '${{ steps.generate_token.outputs.token }}',
message: 'chore(deps): upgrade submodules',
branch: 'github-actions/upgrade-submodules',
title: 'chore(deps): upgrade submodules',
author: 'github-actions <github-actions@github.com>',
comitter: 'github-actions <github-actions@github.com>',
},
},
],
},
});
}
project.synth();
Discussion