From 24d6e8cdba3c16b648b501f9af814c340c1acb33 Mon Sep 17 00:00:00 2001 From: Emmanuel N Kyeyune Date: Sun, 23 Jun 2024 09:02:18 -0400 Subject: [PATCH] feat: adds support for IDatabaseInstance type fixes #27 --- .projen/deps.json | 2 +- .projenrc.ts | 2 +- README.md | 19 +- package-lock.json | 449 ++++-------------------------------- package.json | 4 +- src/handler.ts | 2 +- src/provider.ts | 11 +- src/role.ts | 20 +- test/instance-stack.test.ts | 180 +++++++++++++++ test/instance1-stack.ts | 60 +++++ test/instance2-stack.ts | 126 ++++++++++ 11 files changed, 459 insertions(+), 416 deletions(-) create mode 100644 test/instance-stack.test.ts create mode 100644 test/instance1-stack.ts create mode 100644 test/instance2-stack.ts diff --git a/.projen/deps.json b/.projen/deps.json index 2e6a21a..5137501 100644 --- a/.projen/deps.json +++ b/.projen/deps.json @@ -151,7 +151,7 @@ }, { "name": "aws-cdk-lib", - "version": "^2.124.0", + "version": "^2.144.0", "type": "peer" }, { diff --git a/.projenrc.ts b/.projenrc.ts index cae25ef..46407aa 100644 --- a/.projenrc.ts +++ b/.projenrc.ts @@ -19,7 +19,7 @@ const project = new awscdk.AwsCdkConstructLibrary({ workflow: false, }, constructsVersion: "10.3.0", - cdkVersion: "2.124.0", + cdkVersion: "2.144.0", disableTsconfig: true, tsconfigDev: { compilerOptions: { diff --git a/README.md b/README.md index e6bfddb..3a650a5 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # About This CDK construct library makes it possible to create databases, -schemas, and roles in an Aurora Serverless or database cluster created -in that stack. Both Aurora Serverless v1 and v2 are supported. +schemas, and roles in an Aurora Serverless (v1 and v2 are supported), RDS Database Cluster or Database Instance created +in that stack. This construct library is intended to be used in enterprise environments, and works in isolated subnets. @@ -55,7 +55,7 @@ const cluster = new rds.ServerlessCluster(this, "Cluster", { }) ``` -Then create a provider which will connect to your database: +Then create a provider which will connect to your database. For a cluster: ```ts import { Provider } from "cdk-rds-sql" @@ -67,6 +67,17 @@ const provider = new Provider(this, "Provider", { }) ``` +For an instance: +```ts +import { Provider } from "cdk-rds-sql" + +const provider = new Provider(this, "Provider", { + vpc: vpc, + instance: instance, + secret: cluster.secret!, +}) +``` + The provider will setup a lambda, which normally lives in the same VPC as the database. You can give a different VPC, as long as that VPC has access to the VPC of the database. Only the provider lambda will talk @@ -98,7 +109,7 @@ const provider = new Provider(this, "Provider", { ## Roles -Create a postgres role (user) as follows: +Create a postgres role (user) for a cluster as follows: ```ts import { Role } from "cdk-rds-sql" diff --git a/package-lock.json b/package-lock.json index f2aab09..18b372a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,7 +33,7 @@ "@types/pg": "^8.11.6", "@typescript-eslint/eslint-plugin": "^6", "@typescript-eslint/parser": "^6", - "aws-cdk-lib": "2.124.0", + "aws-cdk-lib": "2.144.0", "constructs": "10.3.0", "esbuild": "^0.21.3", "eslint": "^8", @@ -56,7 +56,7 @@ "typescript": "^4.9.5" }, "peerDependencies": { - "aws-cdk-lib": "^2.124.0", + "aws-cdk-lib": "^2.144.0", "constructs": "^10.3.0" } }, @@ -1345,74 +1345,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.3.tgz", - "integrity": "sha512-yTgnwQpFVYfvvo4SvRFB0SwrW8YjOxEoT7wfMT7Ol5v7v5LDNvSGo67aExmxOb87nQNeWPVvaGBNfQ7BXcrZ9w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.3.tgz", - "integrity": "sha512-bviJOLMgurLJtF1/mAoJLxDZDL6oU5/ztMHnJQRejbJrSc9FFu0QoUoFhvi6qSKJEw9y5oGyvr9fuDtzJ30rNQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.3.tgz", - "integrity": "sha512-c+ty9necz3zB1Y+d/N+mC6KVVkGUUOcm4ZmT5i/Fk5arOaY3i6CA3P5wo/7+XzV8cb4GrI/Zjp8NuOQ9Lfsosw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.3.tgz", - "integrity": "sha512-JReHfYCRK3FVX4Ra+y5EBH1b9e16TV2OxrPAvzMsGeES0X2Ndm9ImQRI4Ket757vhc5XBOuGperw63upesclRw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@esbuild/darwin-arm64": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.3.tgz", @@ -1430,312 +1362,6 @@ "node": ">=12" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.3.tgz", - "integrity": "sha512-3m1CEB7F07s19wmaMNI2KANLcnaqryJxO1fXHUV5j1rWn+wMxdUYoPyO2TnAbfRZdi7ADRwJClmOwgT13qlP3Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.3.tgz", - "integrity": "sha512-fsNAAl5pU6wmKHq91cHWQT0Fz0vtyE1JauMzKotrwqIKAswwP5cpHUCxZNSTuA/JlqtScq20/5KZ+TxQdovU/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.3.tgz", - "integrity": "sha512-tci+UJ4zP5EGF4rp8XlZIdq1q1a/1h9XuronfxTMCNBslpCtmk97Q/5qqy1Mu4zIc0yswN/yP/BLX+NTUC1bXA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.3.tgz", - "integrity": "sha512-f6kz2QpSuyHHg01cDawj0vkyMwuIvN62UAguQfnNVzbge2uWLhA7TCXOn83DT0ZvyJmBI943MItgTovUob36SQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.3.tgz", - "integrity": "sha512-vvG6R5g5ieB4eCJBQevyDMb31LMHthLpXTc2IGkFnPWS/GzIFDnaYFp558O+XybTmYrVjxnryru7QRleJvmZ6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.3.tgz", - "integrity": "sha512-HjCWhH7K96Na+66TacDLJmOI9R8iDWDDiqe17C7znGvvE4sW1ECt9ly0AJ3dJH62jHyVqW9xpxZEU1jKdt+29A==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.3.tgz", - "integrity": "sha512-BGpimEccmHBZRcAhdlRIxMp7x9PyJxUtj7apL2IuoG9VxvU/l/v1z015nFs7Si7tXUwEsvjc1rOJdZCn4QTU+Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.3.tgz", - "integrity": "sha512-5rMOWkp7FQGtAH3QJddP4w3s47iT20hwftqdm7b+loe95o8JU8ro3qZbhgMRy0VuFU0DizymF1pBKkn3YHWtsw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.3.tgz", - "integrity": "sha512-h0zj1ldel89V5sjPLo5H1SyMzp4VrgN1tPkN29TmjvO1/r0MuMRwJxL8QY05SmfsZRs6TF0c/IDH3u7XYYmbAg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.3.tgz", - "integrity": "sha512-dkAKcTsTJ+CRX6bnO17qDJbLoW37npd5gSNtSzjYQr0svghLJYGYB0NF1SNcU1vDcjXLYS5pO4qOW4YbFama4A==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.3.tgz", - "integrity": "sha512-vnD1YUkovEdnZWEuMmy2X2JmzsHQqPpZElXx6dxENcIwTu+Cu5ERax6+Ke1QsE814Zf3c6rxCfwQdCTQ7tPuXA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.3.tgz", - "integrity": "sha512-IOXOIm9WaK7plL2gMhsWJd+l2bfrhfilv0uPTptoRoSb2p09RghhQQp9YY6ZJhk/kqmeRt6siRdMSLLwzuT0KQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.3.tgz", - "integrity": "sha512-uTgCwsvQ5+vCQnqM//EfDSuomo2LhdWhFPS8VL8xKf+PKTCrcT/2kPPoWMTs22aB63MLdGMJiE3f1PHvCDmUOw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.3.tgz", - "integrity": "sha512-vNAkR17Ub2MgEud2Wag/OE4HTSI6zlb291UYzHez/psiKarp0J8PKGDnAhMBcHFoOHMXHfExzmjMojJNbAStrQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.3.tgz", - "integrity": "sha512-W8H9jlGiSBomkgmouaRoTXo49j4w4Kfbl6I1bIdO/vT0+0u4f20ko3ELzV3hPI6XV6JNBVX+8BC+ajHkvffIJA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.3.tgz", - "integrity": "sha512-EjEomwyLSCg8Ag3LDILIqYCZAq/y3diJ04PnqGRgq8/4O3VNlXyMd54j/saShaN4h5o5mivOjAzmU6C3X4v0xw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.3.tgz", - "integrity": "sha512-WGiE/GgbsEwR33++5rzjiYsKyHywE8QSZPF7Rfx9EBfK3Qn3xyR6IjyCr5Uk38Kg8fG4/2phN7sXp4NPWd3fcw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.3.tgz", - "integrity": "sha512-xRxC0jaJWDLYvcUvjQmHCJSfMrgmUuvsoXgDeU/wTorQ1ngDdUBuFtgY3W1Pc5sprGAvZBtWdJX7RPg/iZZUqA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -3830,9 +3456,9 @@ } }, "node_modules/aws-cdk-lib": { - "version": "2.124.0", - "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.124.0.tgz", - "integrity": "sha512-K/Tey8TMw30GO6UD0qb19CPhBMZhleGshz520ZnbDUJwNfFtejwZOnpmRMOdUP9f4tHc5BrXl1VGsZtXtUaGhg==", + "version": "2.144.0", + "resolved": "https://registry.npmjs.org/aws-cdk-lib/-/aws-cdk-lib-2.144.0.tgz", + "integrity": "sha512-DpyIyTs8NHX6WgAyYM2mGorirIk+eTjWzXGQRfzAe40qkwcqsb5Ax4JEl5gz1OEo9QIJIgWDtmImgWN0tUbILA==", "bundleDependencies": [ "@balena/dockerignore", "case", @@ -3843,22 +3469,24 @@ "punycode", "semver", "table", - "yaml" + "yaml", + "mime-types" ], "dev": true, "dependencies": { "@aws-cdk/asset-awscli-v1": "^2.2.202", "@aws-cdk/asset-kubectl-v20": "^2.1.2", - "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.1", + "@aws-cdk/asset-node-proxy-agent-v6": "^2.0.3", "@balena/dockerignore": "^1.0.2", "case": "1.6.3", "fs-extra": "^11.2.0", - "ignore": "^5.3.0", + "ignore": "^5.3.1", "jsonschema": "^1.4.1", + "mime-types": "^2.1.35", "minimatch": "^3.1.2", "punycode": "^2.3.1", - "semver": "^7.5.4", - "table": "^6.8.1", + "semver": "^7.6.0", + "table": "^6.8.2", "yaml": "1.10.2" }, "engines": { @@ -3875,15 +3503,15 @@ "license": "Apache-2.0" }, "node_modules/aws-cdk-lib/node_modules/ajv": { - "version": "8.12.0", + "version": "8.13.0", "dev": true, "inBundle": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", + "fast-deep-equal": "^3.1.3", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", - "uri-js": "^4.2.2" + "uri-js": "^4.4.1" }, "funding": { "type": "github", @@ -4005,7 +3633,7 @@ "license": "ISC" }, "node_modules/aws-cdk-lib/node_modules/ignore": { - "version": "5.3.0", + "version": "5.3.1", "dev": true, "inBundle": true, "license": "MIT", @@ -4067,6 +3695,27 @@ "node": ">=10" } }, + "node_modules/aws-cdk-lib/node_modules/mime-db": { + "version": "1.52.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/aws-cdk-lib/node_modules/mime-types": { + "version": "2.1.35", + "dev": true, + "inBundle": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/aws-cdk-lib/node_modules/minimatch": { "version": "3.1.2", "dev": true, @@ -4098,7 +3747,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/semver": { - "version": "7.5.4", + "version": "7.6.0", "dev": true, "inBundle": true, "license": "ISC", @@ -4156,7 +3805,7 @@ } }, "node_modules/aws-cdk-lib/node_modules/table": { - "version": "6.8.1", + "version": "6.8.2", "dev": true, "inBundle": true, "license": "BSD-3-Clause", @@ -4384,12 +4033,12 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -6491,9 +6140,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -12739,9 +12388,9 @@ } }, "node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "engines": { "node": ">=8.3.0" diff --git a/package.json b/package.json index 3ff77df..3734551 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "@types/pg": "^8.11.6", "@typescript-eslint/eslint-plugin": "^6", "@typescript-eslint/parser": "^6", - "aws-cdk-lib": "2.124.0", + "aws-cdk-lib": "2.144.0", "constructs": "10.3.0", "esbuild": "^0.21.3", "eslint": "^8", @@ -63,7 +63,7 @@ "typescript": "^4.9.5" }, "peerDependencies": { - "aws-cdk-lib": "^2.124.0", + "aws-cdk-lib": "^2.144.0", "constructs": "^10.3.0" }, "dependencies": { diff --git a/src/handler.ts b/src/handler.ts index 9db796b..e5b350d 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -68,7 +68,7 @@ const jumpTable: JumpTable = { Update: (_: string, __: string, props?: any) => { return props.Statement }, - Delete: (_: string, __: string, props?: any) => { + Delete: (_: string, props?: any) => { return props.Rollback }, }, diff --git a/src/provider.ts b/src/provider.ts index ef81ef4..b9acc85 100644 --- a/src/provider.ts +++ b/src/provider.ts @@ -5,7 +5,11 @@ import { IVpc, SubnetType, SubnetSelection } from "aws-cdk-lib/aws-ec2" import { IFunction, Runtime } from "aws-cdk-lib/aws-lambda" import * as lambda from "aws-cdk-lib/aws-lambda-nodejs" import { NodejsFunctionProps } from "aws-cdk-lib/aws-lambda-nodejs" -import { IDatabaseCluster, IServerlessCluster } from "aws-cdk-lib/aws-rds" +import { + IDatabaseCluster, + IServerlessCluster, + IDatabaseInstance, +} from "aws-cdk-lib/aws-rds" import { ISecret } from "aws-cdk-lib/aws-secretsmanager" import * as customResources from "aws-cdk-lib/custom-resources" import { Construct } from "constructs" @@ -30,7 +34,7 @@ export interface RdsSqlProps { /** * Your database. */ - readonly cluster: IServerlessCluster | IDatabaseCluster + readonly cluster: IServerlessCluster | IDatabaseCluster | IDatabaseInstance /** * Secret that grants access to your database. @@ -69,7 +73,7 @@ export class Provider extends Construct { public readonly serviceToken: string public readonly secret: ISecret public readonly handler: IFunction - public readonly cluster: IServerlessCluster | IDatabaseCluster + public readonly cluster: IServerlessCluster | IDatabaseCluster | IDatabaseInstance constructor(scope: Construct, id: string, props: RdsSqlProps) { super(scope, id) @@ -122,7 +126,6 @@ export class Provider extends Construct { } const logger = props.logger ?? false const fn = new lambda.NodejsFunction(scope, id, { - ...props.functionProps, vpc: props.vpc, vpcSubnets: props.vpcSubnets ?? { subnetType: SubnetType.PRIVATE_ISOLATED, diff --git a/src/role.ts b/src/role.ts index e7e1f83..bfa1d47 100644 --- a/src/role.ts +++ b/src/role.ts @@ -1,5 +1,6 @@ import { RemovalPolicy } from "aws-cdk-lib" import * as kms from "aws-cdk-lib/aws-kms" +import { IDatabaseCluster, IDatabaseInstance } from "aws-cdk-lib/aws-rds" import { ISecret, Secret } from "aws-cdk-lib/aws-secretsmanager" import { Construct } from "constructs" import { IDatabase } from "./database" @@ -72,6 +73,19 @@ export class Role extends Construct { ) throw "Specify either database or databaseName" super(scope, id) + + const host = (props.provider.cluster as IDatabaseCluster).clusterEndpoint + ? (props.provider.cluster as IDatabaseCluster).clusterEndpoint.hostname + : (props.provider.cluster as IDatabaseInstance).instanceEndpoint.hostname + + const port = (props.provider.cluster as IDatabaseCluster).clusterEndpoint + ? (props.provider.cluster as IDatabaseCluster).clusterEndpoint.port + : (props.provider.cluster as IDatabaseInstance).instanceEndpoint.port + + const identifier = (props.provider.cluster as IDatabaseCluster).clusterIdentifier + ? (props.provider.cluster as IDatabaseCluster).clusterIdentifier + : (props.provider.cluster as IDatabaseInstance).instanceIdentifier + this.secret = new Secret(this, "Secret", { secretName: props.secretName, encryptionKey: props.encryptionKey, @@ -79,10 +93,10 @@ export class Role extends Construct { generateSecretString: { passwordLength: 30, // Oracle password cannot have more than 30 characters secretStringTemplate: JSON.stringify({ - dbClusterIdentifier: props.provider.cluster.clusterIdentifier, + dbClusterIdentifier: identifier, engine: "postgres", - host: props.provider.cluster.clusterEndpoint.hostname, - port: props.provider.cluster.clusterEndpoint.port, + host: host, + port: port, username: props.roleName, dbname: props.database ? props.database.databaseName : props.databaseName, }), diff --git a/test/instance-stack.test.ts b/test/instance-stack.test.ts new file mode 100644 index 0000000..813fd19 --- /dev/null +++ b/test/instance-stack.test.ts @@ -0,0 +1,180 @@ +import * as cdk from "aws-cdk-lib" +import { Template } from "aws-cdk-lib/assertions" +import * as ec2 from "aws-cdk-lib/aws-ec2" +import * as rds from "aws-cdk-lib/aws-rds" +import * as serverlessInstancev1 from "./instance1-stack" +import * as serverlessInstancev2 from "./instance2-stack" +import { Provider } from "../src/provider" +import { Role } from "../src/role" + +test("serverless instance v1", () => { + const app = new cdk.App() + const stack = new serverlessInstancev1.TestInstanceStack(app, "TestInstanceStack", { + env: { + account: "123456789", + region: "us-east-1", + }, + }) + let template = Template.fromStack(stack) + //console.debug("TEMPLATE", template.toJSON()) + template.hasResourceProperties("AWS::CloudFormation::CustomResource", { + Resource: "role", + }) + /* + template.hasResourceProperties("AWS::SecretsManager::Secret", { + GenerateSecretString: { + SecretStringTemplate: "{\"username\":\"myrole\"}", + }, + }) + */ +}) + +test("instance role without database", () => { + const app = new cdk.App() + const stack = new cdk.Stack(app, "TestInstanceStack", { + env: { + account: "123456789", + region: "us-east-1", + }, + }) + const vpc = new ec2.Vpc(stack, "Vpc", { + subnetConfiguration: [ + { + cidrMask: 28, + name: "rds", + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, + }, + ], + }) + + const instance = new rds.DatabaseInstance(stack, "Instance", { + vpc: vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, + }, + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_15, + }), + }) + + const provider = new Provider(stack, "Provider", { + vpc: vpc, + cluster: instance, + secret: instance.secret!, + }) + + expect(() => { + new Role(stack, "Role", { + provider: provider, + roleName: "role", + }) + }).toThrowError() +}) + +test("serverless instance v2", () => { + const app = new cdk.App() + const stack = new serverlessInstancev2.TestInstanceStack(app, "TestInstanceStack", { + env: { + account: "123456789", + region: "us-east-1", + }, + }) + let template = Template.fromStack(stack) + //console.debug("TEMPLATE", template.toJSON()) + template.hasResourceProperties("AWS::CloudFormation::CustomResource", { + Resource: "role", + }) + /* + template.hasResourceProperties("AWS::SecretsManager::Secret", { + GenerateSecretString: { + SecretStringTemplate: "{\"username\":\"myrole\"}", + }, + }) + */ + template.hasResourceProperties("AWS::EC2::SecurityGroupIngress", { + FromPort: { + "Fn::GetAtt": ["InstanceC1063A87", "Endpoint.Port"], + }, + IpProtocol: "tcp", + SourceSecurityGroupId: { + "Fn::GetAtt": [ + "RdsSql28b9e791af604a33bca8ffb6f30ef8c5SecurityGroup60F64508", + "GroupId", + ], + }, + }) +}) + +test("absence of security group is detected", () => { + const app = new cdk.App() + const stack = new serverlessInstancev2.ImportedInstanceStack(app, "TestInstanceStack", { + env: { + account: "123456789", + region: "us-east-1", + }, + }) + let template = Template.fromStack(stack) + template.hasResourceProperties("AWS::CloudFormation::CustomResource", { + Resource: "role", + }) + template.hasResourceProperties("AWS::EC2::SecurityGroupIngress", { + FromPort: 5432, + IpProtocol: "tcp", + SourceSecurityGroupId: { + "Fn::GetAtt": [ + "RdsSql28b9e791af604a33bca8ffb6f30ef8c5SecurityGroup60F64508", + "GroupId", + ], + }, + }) +}) + +test("vpcSubnet selection can be specified", () => { + const app = new cdk.App() + const stack = new cdk.Stack(app, "TestInstanceStack", { + env: { + account: "123456789", + region: "us-east-1", + }, + }) + const vpc = new ec2.Vpc(stack, "Vpc", { + subnetConfiguration: [ + { + cidrMask: 28, + name: "rds", + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + { + cidrMask: 28, + name: "nat", + subnetType: ec2.SubnetType.PUBLIC, + }, + ], + }) + + const instance = new rds.DatabaseInstance(stack, "Instance", { + vpc: vpc, + // vpcSubnets: { + // subnetType: ec2.SubnetType.PRIVATE_ISOLATED, + // }, + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_15, + }), + }) + + const provider = new Provider(stack, "Provider", { + vpc: vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + cluster: instance, + secret: instance.secret!, + }) + + expect(() => { + new Role(stack, "Role", { + provider: provider, + roleName: "role", + }) + }).toThrowError() +}) diff --git a/test/instance1-stack.ts b/test/instance1-stack.ts new file mode 100644 index 0000000..7a17087 --- /dev/null +++ b/test/instance1-stack.ts @@ -0,0 +1,60 @@ +import { Duration, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib" +import * as ec2 from "aws-cdk-lib/aws-ec2" +import * as rds from "aws-cdk-lib/aws-rds" +import { Construct } from "constructs" +import { Vpc } from "./vpc" +import { Provider, Database, Role, Schema, Sql } from "../src/index" + +export class TestInstanceStack extends Stack { + constructor(scope: Construct, id: string, props: StackProps) { + super(scope, id, props) + + const vpc = new Vpc(this, "Vpc") + + const instance = new rds.DatabaseInstance(this, "Instance", { + vpc: vpc.vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, + }, + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_13_4, + }), + databaseName: "example", + credentials: rds.Credentials.fromGeneratedSecret("pgroot"), + instanceType: ec2.InstanceType.of( + ec2.InstanceClass.BURSTABLE3, + ec2.InstanceSize.MICRO + ), + removalPolicy: RemovalPolicy.DESTROY, + }) + + const provider = new Provider(this, "Provider", { + vpc: vpc.vpc, + cluster: instance, + secret: instance.secret!, + timeout: Duration.seconds(10), + }) + Database.fromDatabaseName(this, "DefaultDatabase", "example") + + new Schema(this, "Schema", { + provider: provider, + schemaName: "myschema", + }) + const role = new Role(this, "Role", { + provider: provider, + roleName: "myrole", + databaseName: "mydb", + }) + const database = new Database(this, "Database", { + provider: provider, + databaseName: "mydb", + owner: role, + }) + new Sql(this, "Sql", { + provider: provider, + database: database, + statement: "create table t (i int)", + rollback: "drop table t", + }) + } +} diff --git a/test/instance2-stack.ts b/test/instance2-stack.ts new file mode 100644 index 0000000..0594f77 --- /dev/null +++ b/test/instance2-stack.ts @@ -0,0 +1,126 @@ +import { Aspects, Fn, RemovalPolicy, Stack, StackProps } from "aws-cdk-lib" +import * as ec2 from "aws-cdk-lib/aws-ec2" +import { LogGroup, RetentionDays } from "aws-cdk-lib/aws-logs" +import * as rds from "aws-cdk-lib/aws-rds" +import * as secrets from "aws-cdk-lib/aws-secretsmanager" +import { Construct } from "constructs" +import { Vpc } from "./vpc" +import { Provider, Database, Role, Schema, Sql } from "../src/index" + +export class TestInstanceStack extends Stack { + constructor(scope: Construct, id: string, props: StackProps) { + super(scope, id, props) + + const vpc = new Vpc(this, "Vpc") + + const instance = new rds.DatabaseInstance(this, "Instance", { + vpc: vpc.vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_ISOLATED, + }, + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_13_4, + }), + databaseName: "example", + credentials: rds.Credentials.fromGeneratedSecret("pgroot"), + instanceType: ec2.InstanceType.of( + ec2.InstanceClass.BURSTABLE3, + ec2.InstanceSize.MICRO + ), + removalPolicy: RemovalPolicy.DESTROY, + }) + + const provider = new Provider(this, "Provider", { + vpc: vpc.vpc, + cluster: instance, + secret: instance.secret!, + functionProps: { + logGroup: new LogGroup(this, "loggroup", { + retention: RetentionDays.ONE_WEEK, + logGroupName: "/aws/lambda/provider", + }), + }, + }) + Database.fromDatabaseName(this, "DefaultDatabase", "example") + + new Schema(this, "Schema", { + provider: provider, + schemaName: "myschema", + }) + const role = new Role(this, "Role", { + provider: provider, + roleName: "myrole", + databaseName: "mydb", + }) + const database = new Database(this, "Database", { + provider: provider, + databaseName: "mydb", + owner: role, + }) + new Sql(this, "Sql", { + provider: provider, + database: database, + statement: ` +create table if not exists t (i int); +grant select on t to myrole; +`, + rollback: ` +DO $$BEGIN + IF EXISTS (select from pg_database WHERE datname = 't') THEN + IF EXISTS (select from pg_catalog.pg_roles WHERE rolname = 'myrole') THEN + revoke select database t from myrole; + END IF; + drop table t; + END IF; +END$$;, +`, + }) + } +} + +export class ImportedInstanceStack extends Stack { + constructor(scope: Construct, id: string, props: StackProps) { + super(scope, id, props) + + const vpc = new Vpc(this, "Vpc") + + const secret = secrets.Secret.fromSecretCompleteArn( + this, + "Secret", + Fn.importValue("secret-arn") + ) + + const instance = rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, + "DatabaseInstance", + { + instanceIdentifier: Fn.importValue("instance-identifier"), + instanceEndpointAddress: Fn.importValue("instance-endpoint"), + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_13_4, + }), + port: 5432, // absence of port in import causes an exception + securityGroups: [ + ec2.SecurityGroup.fromSecurityGroupId( + this, + "RdsSecurityGroup", + "sg-00bbd66b014133c45" + ), + ], + } + ) + + const provider = new Provider(this, "Provider", { + vpc: vpc.vpc, + cluster: instance, + secret: secret, + }) + Database.fromDatabaseName(this, "DefaultDatabase", "example") + + new Role(this, "Role", { + provider: provider, + roleName: "myrole", + databaseName: "mydb", + }) + } +}