cdk-rds-sql 7.1.0 → 7.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.json +25 -0
- package/.conform.yaml +4 -0
- package/.jsii +325 -43
- package/AGENTS.md +117 -20
- package/lambda/engine.dsql.ts +51 -28
- package/lambda/handler.ts +7 -5
- package/lib/database.d.ts +2 -2
- package/lib/database.js +4 -10
- package/lib/handler/handler.js +43 -26
- package/lib/iam-grant.d.ts +2 -2
- package/lib/iam-grant.js +4 -10
- package/lib/provider.d.ts +43 -2
- package/lib/provider.js +54 -7
- package/lib/role.custom-resource.d.ts +2 -2
- package/lib/role.custom-resource.js +1 -1
- package/lib/role.d.ts +2 -2
- package/lib/role.js +13 -6
- package/lib/schema.d.ts +2 -2
- package/lib/schema.js +2 -2
- package/lib/sql.d.ts +2 -2
- package/lib/sql.js +2 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -1,20 +1,117 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
This is a projen managed project. It contains a CDK construct library for creating databases, schemas, and roles in Aurora Serverless v2, RDS Database Cluster, or Database Instance. It supports both PostgreSQL and MySQL databases and is designed for enterprise environments with isolated subnets.
|
|
8
|
+
|
|
9
|
+
## Key Architecture
|
|
10
|
+
|
|
11
|
+
### Core Components
|
|
12
|
+
|
|
13
|
+
- **Provider**: Main construct that creates a Lambda function handler to execute database operations
|
|
14
|
+
- **Role**: Creates database users/roles with optional IAM authentication
|
|
15
|
+
- **Database**: Creates databases with optional ownership assignment
|
|
16
|
+
- **Schema**: Creates database schemas with role permissions
|
|
17
|
+
- **Sql**: Executes arbitrary SQL statements
|
|
18
|
+
|
|
19
|
+
### Lambda Handler Architecture
|
|
20
|
+
|
|
21
|
+
- **Engine Factory Pattern**: `lambda/engine.factory.ts` creates database-specific engines
|
|
22
|
+
- **Abstract Engine**: `lambda/engine.abstract.ts` defines common interface
|
|
23
|
+
- **Concrete Engines**: `lambda/engine.postgresql.ts` and `lambda/engine.mysql.ts`
|
|
24
|
+
- **Main Handler**: `lambda/handler.ts` orchestrates CloudFormation custom resource operations
|
|
25
|
+
|
|
26
|
+
The Lambda handler supports both PostgreSQL and MySQL databases, auto-detecting the engine type from the cluster/instance configuration.
|
|
27
|
+
|
|
28
|
+
## Development Commands
|
|
29
|
+
|
|
30
|
+
### Build and Test
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# After changing .projenrc.ts
|
|
34
|
+
npx projen
|
|
35
|
+
|
|
36
|
+
# Build the project
|
|
37
|
+
npx projen build
|
|
38
|
+
|
|
39
|
+
# Run all tests
|
|
40
|
+
npx projen test
|
|
41
|
+
|
|
42
|
+
# Run tests in watch mode
|
|
43
|
+
npx projen test:watch
|
|
44
|
+
|
|
45
|
+
# Run linting
|
|
46
|
+
npx projen eslint
|
|
47
|
+
|
|
48
|
+
# Build only the Lambda handler
|
|
49
|
+
npx projen build:handler
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Integration Testing
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Deploy PostgreSQL serverless test stack
|
|
56
|
+
npx projen integ:deploy:postgresql:serverless
|
|
57
|
+
|
|
58
|
+
# Deploy MySQL serverless test stack
|
|
59
|
+
npx projen integ:deploy:mysql:serverless
|
|
60
|
+
|
|
61
|
+
# Destroy test stack
|
|
62
|
+
npx projen integ:destroy:serverless
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Testing Setup
|
|
66
|
+
|
|
67
|
+
- **Framework**: Jest with TypeScript support
|
|
68
|
+
- **Test Files**: Located in `test/` and `lambda/` directories
|
|
69
|
+
- **Configuration**: See `jestOptions` in `.projenrc.ts`.
|
|
70
|
+
- **Coverage**: Enabled with multiple reporters (json, lcov, clover, cobertura, text)
|
|
71
|
+
|
|
72
|
+
## Database Engine Support
|
|
73
|
+
|
|
74
|
+
### PostgreSQL
|
|
75
|
+
|
|
76
|
+
- Uses `pg` library for connections
|
|
77
|
+
- Supports schemas, roles, and databases
|
|
78
|
+
- IAM authentication supported
|
|
79
|
+
|
|
80
|
+
### MySQL
|
|
81
|
+
|
|
82
|
+
- Uses `mysql2` library for connections
|
|
83
|
+
- Creates users with '%' host for VPC compatibility
|
|
84
|
+
- IAM authentication supported
|
|
85
|
+
|
|
86
|
+
## Build Process
|
|
87
|
+
|
|
88
|
+
The project uses projen for build management. Do not change files
|
|
89
|
+
managed by projen, but instead change `.projenrc.ts`. After changing
|
|
90
|
+
`.projenrc.ts` run `npx projen`.
|
|
91
|
+
|
|
92
|
+
In particular:
|
|
93
|
+
|
|
94
|
+
1. Do not change `package.json`.
|
|
95
|
+
2. Do not change any of the github workflow files.
|
|
96
|
+
|
|
97
|
+
Projen tasks:
|
|
98
|
+
|
|
99
|
+
1. `pre-compile`: Transpiles Lambda handler using esbuild
|
|
100
|
+
2. `compile`: TypeScript compilation
|
|
101
|
+
3. `post-compile`: Copies handler to lib directory
|
|
102
|
+
4. `eslint`: Runs linter
|
|
103
|
+
5. `typecheck`: Runs typecheck
|
|
104
|
+
|
|
105
|
+
## SSL/TLS Configuration
|
|
106
|
+
|
|
107
|
+
- SSL is enabled by default
|
|
108
|
+
- Global RDS certificate bundle is automatically downloaded during build
|
|
109
|
+
- Can be disabled via `ssl: false` in Provider props
|
|
110
|
+
|
|
111
|
+
## Key Dependencies
|
|
112
|
+
|
|
113
|
+
- **AWS CDK**: v2.171.1
|
|
114
|
+
- **Node.js**: >= 22.14
|
|
115
|
+
- **TypeScript**: ~5.8
|
|
116
|
+
- **Database Drivers**: pg (PostgreSQL), mysql2 (MySQL)
|
|
117
|
+
- **AWS SDKs**: secrets-manager, ssm clients
|
package/lambda/engine.dsql.ts
CHANGED
|
@@ -61,40 +61,63 @@ export class DsqlEngine extends AbstractEngine {
|
|
|
61
61
|
const sql = ["BEGIN"]
|
|
62
62
|
|
|
63
63
|
if (oldResourceId !== resourceId) {
|
|
64
|
-
|
|
64
|
+
// DSQL doesn't support RENAME, so we need to create new role and drop old one
|
|
65
|
+
// First create the new role
|
|
66
|
+
sql.push(pgFormat("CREATE ROLE %I WITH LOGIN", resourceId))
|
|
67
|
+
|
|
68
|
+
// Transfer database permissions from old to new role
|
|
69
|
+
if (oldProps?.DatabaseName) {
|
|
70
|
+
sql.push(
|
|
71
|
+
pgFormat(
|
|
72
|
+
"GRANT CONNECT ON DATABASE %I TO %I",
|
|
73
|
+
oldProps.DatabaseName,
|
|
74
|
+
resourceId
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Drop the old role
|
|
80
|
+
sql.push(pgFormat("DROP ROLE IF EXISTS %I", oldResourceId))
|
|
65
81
|
}
|
|
66
82
|
|
|
67
|
-
// Handle database permission changes
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
83
|
+
// Handle database permission changes (only if role wasn't renamed, otherwise already handled above)
|
|
84
|
+
if (oldResourceId === resourceId) {
|
|
85
|
+
if (
|
|
86
|
+
oldProps?.DatabaseName &&
|
|
87
|
+
props?.DatabaseName &&
|
|
88
|
+
oldProps.DatabaseName !== props.DatabaseName
|
|
89
|
+
) {
|
|
90
|
+
// Revoke from old database and grant to new
|
|
91
|
+
sql.push(
|
|
92
|
+
pgFormat(
|
|
93
|
+
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
94
|
+
oldProps.DatabaseName,
|
|
95
|
+
resourceId
|
|
96
|
+
)
|
|
79
97
|
)
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
98
|
+
sql.push(
|
|
99
|
+
pgFormat("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
100
|
+
)
|
|
101
|
+
} else if (props?.DatabaseName && !oldProps?.DatabaseName) {
|
|
102
|
+
// Grant to new database
|
|
103
|
+
sql.push(
|
|
104
|
+
pgFormat("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
105
|
+
)
|
|
106
|
+
} else if (!props?.DatabaseName && oldProps?.DatabaseName) {
|
|
107
|
+
// Revoke from old database
|
|
108
|
+
sql.push(
|
|
109
|
+
pgFormat(
|
|
110
|
+
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
111
|
+
oldProps.DatabaseName,
|
|
112
|
+
resourceId
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
} else if (props?.DatabaseName && props.DatabaseName !== oldProps?.DatabaseName) {
|
|
117
|
+
// If role was renamed AND database changed, grant to new database
|
|
86
118
|
sql.push(
|
|
87
119
|
pgFormat("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
88
120
|
)
|
|
89
|
-
} else if (!props?.DatabaseName && oldProps?.DatabaseName) {
|
|
90
|
-
// Revoke from old database
|
|
91
|
-
sql.push(
|
|
92
|
-
pgFormat(
|
|
93
|
-
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
94
|
-
oldProps.DatabaseName,
|
|
95
|
-
resourceId
|
|
96
|
-
)
|
|
97
|
-
)
|
|
98
121
|
}
|
|
99
122
|
|
|
100
123
|
sql.push("COMMIT")
|
package/lambda/handler.ts
CHANGED
|
@@ -113,15 +113,17 @@ export const handler = async (
|
|
|
113
113
|
}
|
|
114
114
|
} else {
|
|
115
115
|
// Traditional RDS configuration - use secrets
|
|
116
|
-
|
|
117
|
-
|
|
116
|
+
// Use SecretArn from event properties, fallback to environment variable
|
|
117
|
+
const secretArn = event.ResourceProperties.SecretArn || process.env.SECRET_ARN
|
|
118
|
+
if (!secretArn) {
|
|
119
|
+
throw "SecretArn is required (not in event properties or environment)"
|
|
118
120
|
}
|
|
119
121
|
|
|
120
122
|
const command = new GetSecretValueCommand({
|
|
121
|
-
SecretId:
|
|
123
|
+
SecretId: secretArn,
|
|
122
124
|
})
|
|
123
125
|
|
|
124
|
-
log(`Fetching secret ${
|
|
126
|
+
log(`Fetching secret ${secretArn}`)
|
|
125
127
|
const secret: GetSecretValueCommandOutput = await backOff(
|
|
126
128
|
async () => {
|
|
127
129
|
try {
|
|
@@ -138,7 +140,7 @@ export const handler = async (
|
|
|
138
140
|
}
|
|
139
141
|
)
|
|
140
142
|
if (!secret.SecretString) {
|
|
141
|
-
throw `No secret string in ${
|
|
143
|
+
throw `No secret string in ${secretArn}`
|
|
142
144
|
}
|
|
143
145
|
secretValues = JSON.parse(secret.SecretString)
|
|
144
146
|
|
package/lib/database.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { CustomResource } from "aws-cdk-lib";
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
|
-
import {
|
|
3
|
+
import { IProvider } from "./provider";
|
|
4
4
|
import { Role } from "./role";
|
|
5
5
|
interface DatabaseAttributes {
|
|
6
6
|
/**
|
|
@@ -16,7 +16,7 @@ export interface DatabaseProps extends DatabaseAttributes {
|
|
|
16
16
|
/**
|
|
17
17
|
* Provider.
|
|
18
18
|
*/
|
|
19
|
-
readonly provider:
|
|
19
|
+
readonly provider: IProvider;
|
|
20
20
|
}
|
|
21
21
|
export interface IDatabase {
|
|
22
22
|
readonly databaseName: string;
|
package/lib/database.js
CHANGED
|
@@ -4,15 +4,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.Database = void 0;
|
|
5
5
|
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
|
|
6
6
|
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
7
|
-
const dsql = require("aws-cdk-lib/aws-dsql");
|
|
8
7
|
const constructs_1 = require("constructs");
|
|
9
8
|
const enum_1 = require("./enum");
|
|
10
|
-
|
|
11
|
-
* Helper function to determine if a cluster is a DSQL cluster
|
|
12
|
-
*/
|
|
13
|
-
function isDsqlCluster(cluster) {
|
|
14
|
-
return cluster instanceof dsql.CfnCluster;
|
|
15
|
-
}
|
|
9
|
+
const provider_1 = require("./provider");
|
|
16
10
|
class ImportedDatabase extends constructs_1.Construct {
|
|
17
11
|
constructor(scope, id, props) {
|
|
18
12
|
super(scope, id);
|
|
@@ -30,7 +24,7 @@ class Database extends aws_cdk_lib_1.CustomResource {
|
|
|
30
24
|
}
|
|
31
25
|
constructor(scope, id, props) {
|
|
32
26
|
// Check if using DSQL provider and forbid database creation
|
|
33
|
-
if (
|
|
27
|
+
if (props.provider.engine === provider_1.DatabaseEngine.DSQL) {
|
|
34
28
|
throw new Error("Database creation is not supported with DSQL. DSQL always uses 'postgres' database.");
|
|
35
29
|
}
|
|
36
30
|
super(scope, id, {
|
|
@@ -51,5 +45,5 @@ class Database extends aws_cdk_lib_1.CustomResource {
|
|
|
51
45
|
}
|
|
52
46
|
exports.Database = Database;
|
|
53
47
|
_a = JSII_RTTI_SYMBOL_1;
|
|
54
|
-
Database[_a] = { fqn: "cdk-rds-sql.Database", version: "7.1
|
|
55
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
48
|
+
Database[_a] = { fqn: "cdk-rds-sql.Database", version: "7.2.1" };
|
|
49
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZGF0YWJhc2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvZGF0YWJhc2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7QUFBQSw2Q0FBNEM7QUFDNUMsMkNBQXNDO0FBQ3RDLGlDQUF1QztBQUN2Qyx5Q0FBc0Q7QUEwQnRELE1BQU0sZ0JBQWlCLFNBQVEsc0JBQVM7SUFHdEMsWUFBWSxLQUFnQixFQUFFLEVBQVUsRUFBRSxLQUF5QjtRQUNqRSxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxDQUFBO1FBQ2hCLElBQUksQ0FBQyxZQUFZLEdBQUcsS0FBSyxDQUFDLFlBQVksQ0FBQTtJQUN4QyxDQUFDO0NBQ0Y7QUFFRCxNQUFhLFFBQVMsU0FBUSw0QkFBYztJQUMxQzs7T0FFRztJQUNILE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxLQUFnQixFQUFFLEVBQVUsRUFBRSxZQUFvQjtRQUN4RSxPQUFPLElBQUksZ0JBQWdCLENBQUMsS0FBSyxFQUFFLEVBQUUsRUFBRTtZQUNyQyxZQUFZLEVBQUUsWUFBWTtTQUMzQixDQUFDLENBQUE7SUFDSixDQUFDO0lBSUQsWUFBWSxLQUFnQixFQUFFLEVBQVUsRUFBRSxLQUFvQjtRQUM1RCw0REFBNEQ7UUFDNUQsSUFBSSxLQUFLLENBQUMsUUFBUSxDQUFDLE1BQU0sS0FBSyx5QkFBYyxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ2xELE1BQU0sSUFBSSxLQUFLLENBQ2IscUZBQXFGLENBQ3RGLENBQUE7UUFDSCxDQUFDO1FBRUQsS0FBSyxDQUFDLEtBQUssRUFBRSxFQUFFLEVBQUU7WUFDZixZQUFZLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxZQUFZO1lBQ3pDLFVBQVUsRUFBRTtnQkFDVixRQUFRLEVBQUUscUJBQWMsQ0FBQyxRQUFRO2dCQUNqQyxVQUFVLEVBQUUsS0FBSyxDQUFDLFlBQVk7Z0JBQzlCLEdBQUcsQ0FBQyxLQUFLLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxTQUFTLEVBQUUsS0FBSyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsU0FBUyxFQUFFLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQztnQkFDaEYsS0FBSyxFQUFFLEtBQUssQ0FBQyxLQUFLLEVBQUUsUUFBUTthQUM3QjtTQUNGLENBQUMsQ0FBQTtRQUNGLElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLEtBQUssQ0FBQyxRQUFRLENBQUMsQ0FBQTtRQUN2QyxJQUFJLENBQUMsWUFBWSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUE7UUFDdEMsSUFBSSxLQUFLLENBQUMsS0FBSyxFQUFFLENBQUM7WUFDaEIsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQ3RDLENBQUM7SUFDSCxDQUFDOztBQWxDSCw0QkFtQ0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBDdXN0b21SZXNvdXJjZSB9IGZyb20gXCJhd3MtY2RrLWxpYlwiXG5pbXBvcnQgeyBDb25zdHJ1Y3QgfSBmcm9tIFwiY29uc3RydWN0c1wiXG5pbXBvcnQgeyBSZHNTcWxSZXNvdXJjZSB9IGZyb20gXCIuL2VudW1cIlxuaW1wb3J0IHsgSVByb3ZpZGVyLCBEYXRhYmFzZUVuZ2luZSB9IGZyb20gXCIuL3Byb3ZpZGVyXCJcbmltcG9ydCB7IFJvbGUgfSBmcm9tIFwiLi9yb2xlXCJcblxuaW50ZXJmYWNlIERhdGFiYXNlQXR0cmlidXRlcyB7XG4gIC8qKlxuICAgKiBOYW1lIG9mIGRhdGFiYXNlIHRvIGNyZWF0ZS5cbiAgICovXG4gIHJlYWRvbmx5IGRhdGFiYXNlTmFtZTogc3RyaW5nXG5cbiAgLyoqXG4gICAqIE9wdGlvbmFsIGRhdGFiYXNlIG93bmVyLlxuICAgKi9cbiAgcmVhZG9ubHkgb3duZXI/OiBSb2xlXG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgRGF0YWJhc2VQcm9wcyBleHRlbmRzIERhdGFiYXNlQXR0cmlidXRlcyB7XG4gIC8qKlxuICAgKiBQcm92aWRlci5cbiAgICovXG4gIHJlYWRvbmx5IHByb3ZpZGVyOiBJUHJvdmlkZXJcbn1cblxuZXhwb3J0IGludGVyZmFjZSBJRGF0YWJhc2Uge1xuICByZWFkb25seSBkYXRhYmFzZU5hbWU6IHN0cmluZ1xufVxuXG5jbGFzcyBJbXBvcnRlZERhdGFiYXNlIGV4dGVuZHMgQ29uc3RydWN0IGltcGxlbWVudHMgSURhdGFiYXNlIHtcbiAgcHVibGljIHJlYWRvbmx5IGRhdGFiYXNlTmFtZTogc3RyaW5nXG5cbiAgY29uc3RydWN0b3Ioc2NvcGU6IENvbnN0cnVjdCwgaWQ6IHN0cmluZywgcHJvcHM6IERhdGFiYXNlQXR0cmlidXRlcykge1xuICAgIHN1cGVyKHNjb3BlLCBpZClcbiAgICB0aGlzLmRhdGFiYXNlTmFtZSA9IHByb3BzLmRhdGFiYXNlTmFtZVxuICB9XG59XG5cbmV4cG9ydCBjbGFzcyBEYXRhYmFzZSBleHRlbmRzIEN1c3RvbVJlc291cmNlIGltcGxlbWVudHMgSURhdGFiYXNlIHtcbiAgLyoqXG4gICAqIFJldHVybiBhIERhdGFiYXNlIGJhc2VkIHVwb24gbmFtZSBvbmx5LiBVc2UgZm9yIGltcG9ydGluZyBleGlzdGluZyBkYXRhYmFzZXMuXG4gICAqL1xuICBzdGF0aWMgZnJvbURhdGFiYXNlTmFtZShzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBkYXRhYmFzZU5hbWU6IHN0cmluZyk6IElEYXRhYmFzZSB7XG4gICAgcmV0dXJuIG5ldyBJbXBvcnRlZERhdGFiYXNlKHNjb3BlLCBpZCwge1xuICAgICAgZGF0YWJhc2VOYW1lOiBkYXRhYmFzZU5hbWUsXG4gICAgfSlcbiAgfVxuXG4gIHB1YmxpYyByZWFkb25seSBkYXRhYmFzZU5hbWU6IHN0cmluZ1xuXG4gIGNvbnN0cnVjdG9yKHNjb3BlOiBDb25zdHJ1Y3QsIGlkOiBzdHJpbmcsIHByb3BzOiBEYXRhYmFzZVByb3BzKSB7XG4gICAgLy8gQ2hlY2sgaWYgdXNpbmcgRFNRTCBwcm92aWRlciBhbmQgZm9yYmlkIGRhdGFiYXNlIGNyZWF0aW9uXG4gICAgaWYgKHByb3BzLnByb3ZpZGVyLmVuZ2luZSA9PT0gRGF0YWJhc2VFbmdpbmUuRFNRTCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICBcIkRhdGFiYXNlIGNyZWF0aW9uIGlzIG5vdCBzdXBwb3J0ZWQgd2l0aCBEU1FMLiBEU1FMIGFsd2F5cyB1c2VzICdwb3N0Z3JlcycgZGF0YWJhc2UuXCJcbiAgICAgIClcbiAgICB9XG5cbiAgICBzdXBlcihzY29wZSwgaWQsIHtcbiAgICAgIHNlcnZpY2VUb2tlbjogcHJvcHMucHJvdmlkZXIuc2VydmljZVRva2VuLFxuICAgICAgcHJvcGVydGllczoge1xuICAgICAgICBSZXNvdXJjZTogUmRzU3FsUmVzb3VyY2UuREFUQUJBU0UsXG4gICAgICAgIFJlc291cmNlSWQ6IHByb3BzLmRhdGFiYXNlTmFtZSxcbiAgICAgICAgLi4uKHByb3BzLnByb3ZpZGVyLnNlY3JldCA/IHsgU2VjcmV0QXJuOiBwcm9wcy5wcm92aWRlci5zZWNyZXQuc2VjcmV0QXJuIH0gOiB7fSksXG4gICAgICAgIE93bmVyOiBwcm9wcy5vd25lcj8ucm9sZU5hbWUsXG4gICAgICB9LFxuICAgIH0pXG4gICAgdGhpcy5ub2RlLmFkZERlcGVuZGVuY3kocHJvcHMucHJvdmlkZXIpXG4gICAgdGhpcy5kYXRhYmFzZU5hbWUgPSBwcm9wcy5kYXRhYmFzZU5hbWVcbiAgICBpZiAocHJvcHMub3duZXIpIHtcbiAgICAgIHRoaXMubm9kZS5hZGREZXBlbmRlbmN5KHByb3BzLm93bmVyKVxuICAgIH1cbiAgfVxufVxuIl19
|
package/lib/handler/handler.js
CHANGED
|
@@ -25465,31 +25465,47 @@ var DsqlEngine = class extends AbstractEngine {
|
|
|
25465
25465
|
async updateRole(resourceId, oldResourceId, props, oldProps) {
|
|
25466
25466
|
const sql = ["BEGIN"];
|
|
25467
25467
|
if (oldResourceId !== resourceId) {
|
|
25468
|
-
sql.push((0, import_node_pg_format.format)("
|
|
25468
|
+
sql.push((0, import_node_pg_format.format)("CREATE ROLE %I WITH LOGIN", resourceId));
|
|
25469
|
+
if (oldProps?.DatabaseName) {
|
|
25470
|
+
sql.push(
|
|
25471
|
+
(0, import_node_pg_format.format)(
|
|
25472
|
+
"GRANT CONNECT ON DATABASE %I TO %I",
|
|
25473
|
+
oldProps.DatabaseName,
|
|
25474
|
+
resourceId
|
|
25475
|
+
)
|
|
25476
|
+
);
|
|
25477
|
+
}
|
|
25478
|
+
sql.push((0, import_node_pg_format.format)("DROP ROLE IF EXISTS %I", oldResourceId));
|
|
25469
25479
|
}
|
|
25470
|
-
if (
|
|
25471
|
-
|
|
25472
|
-
|
|
25473
|
-
|
|
25474
|
-
|
|
25475
|
-
|
|
25476
|
-
|
|
25477
|
-
|
|
25478
|
-
|
|
25479
|
-
|
|
25480
|
-
|
|
25481
|
-
|
|
25480
|
+
if (oldResourceId === resourceId) {
|
|
25481
|
+
if (oldProps?.DatabaseName && props?.DatabaseName && oldProps.DatabaseName !== props.DatabaseName) {
|
|
25482
|
+
sql.push(
|
|
25483
|
+
(0, import_node_pg_format.format)(
|
|
25484
|
+
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
25485
|
+
oldProps.DatabaseName,
|
|
25486
|
+
resourceId
|
|
25487
|
+
)
|
|
25488
|
+
);
|
|
25489
|
+
sql.push(
|
|
25490
|
+
(0, import_node_pg_format.format)("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
25491
|
+
);
|
|
25492
|
+
} else if (props?.DatabaseName && !oldProps?.DatabaseName) {
|
|
25493
|
+
sql.push(
|
|
25494
|
+
(0, import_node_pg_format.format)("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
25495
|
+
);
|
|
25496
|
+
} else if (!props?.DatabaseName && oldProps?.DatabaseName) {
|
|
25497
|
+
sql.push(
|
|
25498
|
+
(0, import_node_pg_format.format)(
|
|
25499
|
+
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
25500
|
+
oldProps.DatabaseName,
|
|
25501
|
+
resourceId
|
|
25502
|
+
)
|
|
25503
|
+
);
|
|
25504
|
+
}
|
|
25505
|
+
} else if (props?.DatabaseName && props.DatabaseName !== oldProps?.DatabaseName) {
|
|
25482
25506
|
sql.push(
|
|
25483
25507
|
(0, import_node_pg_format.format)("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
25484
25508
|
);
|
|
25485
|
-
} else if (!props?.DatabaseName && oldProps?.DatabaseName) {
|
|
25486
|
-
sql.push(
|
|
25487
|
-
(0, import_node_pg_format.format)(
|
|
25488
|
-
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
25489
|
-
oldProps.DatabaseName,
|
|
25490
|
-
resourceId
|
|
25491
|
-
)
|
|
25492
|
-
);
|
|
25493
25509
|
}
|
|
25494
25510
|
sql.push("COMMIT");
|
|
25495
25511
|
return sql;
|
|
@@ -26169,13 +26185,14 @@ var handler = async (event) => {
|
|
|
26169
26185
|
engine: "dsql"
|
|
26170
26186
|
};
|
|
26171
26187
|
} else {
|
|
26172
|
-
|
|
26173
|
-
|
|
26188
|
+
const secretArn = event.ResourceProperties.SecretArn || process.env.SECRET_ARN;
|
|
26189
|
+
if (!secretArn) {
|
|
26190
|
+
throw "SecretArn is required (not in event properties or environment)";
|
|
26174
26191
|
}
|
|
26175
26192
|
const command = new import_client_secrets_manager2.GetSecretValueCommand({
|
|
26176
|
-
SecretId:
|
|
26193
|
+
SecretId: secretArn
|
|
26177
26194
|
});
|
|
26178
|
-
log(`Fetching secret ${
|
|
26195
|
+
log(`Fetching secret ${secretArn}`);
|
|
26179
26196
|
const secret = await (0, import_exponential_backoff.backOff)(
|
|
26180
26197
|
async () => {
|
|
26181
26198
|
try {
|
|
@@ -26192,7 +26209,7 @@ var handler = async (event) => {
|
|
|
26192
26209
|
}
|
|
26193
26210
|
);
|
|
26194
26211
|
if (!secret.SecretString) {
|
|
26195
|
-
throw `No secret string in ${
|
|
26212
|
+
throw `No secret string in ${secretArn}`;
|
|
26196
26213
|
}
|
|
26197
26214
|
secretValues = JSON.parse(secret.SecretString);
|
|
26198
26215
|
engine = secretValues.engine || "postgresql";
|
package/lib/iam-grant.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { CustomResource } from "aws-cdk-lib";
|
|
2
2
|
import { Construct } from "constructs";
|
|
3
|
-
import {
|
|
3
|
+
import { IProvider } from "./provider";
|
|
4
4
|
export interface IamGrantProps {
|
|
5
5
|
/**
|
|
6
6
|
* Provider.
|
|
7
7
|
*/
|
|
8
|
-
readonly provider:
|
|
8
|
+
readonly provider: IProvider;
|
|
9
9
|
/**
|
|
10
10
|
* Database role name to grant IAM access to.
|
|
11
11
|
*/
|
package/lib/iam-grant.js
CHANGED
|
@@ -4,18 +4,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
4
4
|
exports.IamGrant = void 0;
|
|
5
5
|
const JSII_RTTI_SYMBOL_1 = Symbol.for("jsii.rtti");
|
|
6
6
|
const aws_cdk_lib_1 = require("aws-cdk-lib");
|
|
7
|
-
const dsql = require("aws-cdk-lib/aws-dsql");
|
|
8
7
|
const enum_1 = require("./enum");
|
|
9
|
-
|
|
10
|
-
* Helper function to determine if a cluster is a DSQL cluster
|
|
11
|
-
*/
|
|
12
|
-
function isDsqlCluster(cluster) {
|
|
13
|
-
return cluster instanceof dsql.CfnCluster;
|
|
14
|
-
}
|
|
8
|
+
const provider_1 = require("./provider");
|
|
15
9
|
class IamGrant extends aws_cdk_lib_1.CustomResource {
|
|
16
10
|
constructor(scope, id, props) {
|
|
17
11
|
// IAM grants are only supported on DSQL
|
|
18
|
-
if (
|
|
12
|
+
if (props.provider.engine !== provider_1.DatabaseEngine.DSQL) {
|
|
19
13
|
throw new Error("IAM grants are only supported with DSQL clusters. Use regular database authentication for RDS/Aurora clusters.");
|
|
20
14
|
}
|
|
21
15
|
super(scope, id, {
|
|
@@ -31,5 +25,5 @@ class IamGrant extends aws_cdk_lib_1.CustomResource {
|
|
|
31
25
|
}
|
|
32
26
|
exports.IamGrant = IamGrant;
|
|
33
27
|
_a = JSII_RTTI_SYMBOL_1;
|
|
34
|
-
IamGrant[_a] = { fqn: "cdk-rds-sql.IamGrant", version: "7.1
|
|
35
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
28
|
+
IamGrant[_a] = { fqn: "cdk-rds-sql.IamGrant", version: "7.2.1" };
|
|
29
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaWFtLWdyYW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL2lhbS1ncmFudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDZDQUE0QztBQUU1QyxpQ0FBdUM7QUFDdkMseUNBQXNEO0FBbUJ0RCxNQUFhLFFBQVMsU0FBUSw0QkFBYztJQUMxQyxZQUFZLEtBQWdCLEVBQUUsRUFBVSxFQUFFLEtBQW9CO1FBQzVELHdDQUF3QztRQUN4QyxJQUFJLEtBQUssQ0FBQyxRQUFRLENBQUMsTUFBTSxLQUFLLHlCQUFjLENBQUMsSUFBSSxFQUFFLENBQUM7WUFDbEQsTUFBTSxJQUFJLEtBQUssQ0FDYixnSEFBZ0gsQ0FDakgsQ0FBQTtRQUNILENBQUM7UUFFRCxLQUFLLENBQUMsS0FBSyxFQUFFLEVBQUUsRUFBRTtZQUNmLFlBQVksRUFBRSxLQUFLLENBQUMsUUFBUSxDQUFDLFlBQVk7WUFDekMsVUFBVSxFQUFFO2dCQUNWLFFBQVEsRUFBRSxxQkFBYyxDQUFDLFNBQVM7Z0JBQ2xDLFVBQVUsRUFBRSxLQUFLLENBQUMsUUFBUTtnQkFDMUIsV0FBVyxFQUFFLEtBQUssQ0FBQyxXQUFXO2FBQy9CO1NBQ0YsQ0FBQyxDQUFBO1FBQ0YsSUFBSSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDLFFBQVEsQ0FBQyxDQUFBO0lBQ3pDLENBQUM7O0FBbEJILDRCQW1CQyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEN1c3RvbVJlc291cmNlIH0gZnJvbSBcImF3cy1jZGstbGliXCJcbmltcG9ydCB7IENvbnN0cnVjdCB9IGZyb20gXCJjb25zdHJ1Y3RzXCJcbmltcG9ydCB7IFJkc1NxbFJlc291cmNlIH0gZnJvbSBcIi4vZW51bVwiXG5pbXBvcnQgeyBJUHJvdmlkZXIsIERhdGFiYXNlRW5naW5lIH0gZnJvbSBcIi4vcHJvdmlkZXJcIlxuXG5leHBvcnQgaW50ZXJmYWNlIElhbUdyYW50UHJvcHMge1xuICAvKipcbiAgICogUHJvdmlkZXIuXG4gICAqL1xuICByZWFkb25seSBwcm92aWRlcjogSVByb3ZpZGVyXG5cbiAgLyoqXG4gICAqIERhdGFiYXNlIHJvbGUgbmFtZSB0byBncmFudCBJQU0gYWNjZXNzIHRvLlxuICAgKi9cbiAgcmVhZG9ubHkgcm9sZU5hbWU6IHN0cmluZ1xuXG4gIC8qKlxuICAgKiBJQU0gcmVzb3VyY2UgQVJOIChyb2xlLCB1c2VyLCBvciBvdGhlciBJQU0gcHJpbmNpcGFsKS5cbiAgICovXG4gIHJlYWRvbmx5IHJlc291cmNlQXJuOiBzdHJpbmdcbn1cblxuZXhwb3J0IGNsYXNzIElhbUdyYW50IGV4dGVuZHMgQ3VzdG9tUmVzb3VyY2Uge1xuICBjb25zdHJ1Y3RvcihzY29wZTogQ29uc3RydWN0LCBpZDogc3RyaW5nLCBwcm9wczogSWFtR3JhbnRQcm9wcykge1xuICAgIC8vIElBTSBncmFudHMgYXJlIG9ubHkgc3VwcG9ydGVkIG9uIERTUUxcbiAgICBpZiAocHJvcHMucHJvdmlkZXIuZW5naW5lICE9PSBEYXRhYmFzZUVuZ2luZS5EU1FMKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgIFwiSUFNIGdyYW50cyBhcmUgb25seSBzdXBwb3J0ZWQgd2l0aCBEU1FMIGNsdXN0ZXJzLiBVc2UgcmVndWxhciBkYXRhYmFzZSBhdXRoZW50aWNhdGlvbiBmb3IgUkRTL0F1cm9yYSBjbHVzdGVycy5cIlxuICAgICAgKVxuICAgIH1cblxuICAgIHN1cGVyKHNjb3BlLCBpZCwge1xuICAgICAgc2VydmljZVRva2VuOiBwcm9wcy5wcm92aWRlci5zZXJ2aWNlVG9rZW4sXG4gICAgICBwcm9wZXJ0aWVzOiB7XG4gICAgICAgIFJlc291cmNlOiBSZHNTcWxSZXNvdXJjZS5JQU1fR1JBTlQsXG4gICAgICAgIFJlc291cmNlSWQ6IHByb3BzLnJvbGVOYW1lLFxuICAgICAgICBSZXNvdXJjZUFybjogcHJvcHMucmVzb3VyY2VBcm4sXG4gICAgICB9LFxuICAgIH0pXG4gICAgdGhpcy5ub2RlLmFkZERlcGVuZGVuY3kocHJvcHMucHJvdmlkZXIpXG4gIH1cbn1cbiJdfQ==
|
package/lib/provider.d.ts
CHANGED
|
@@ -69,11 +69,52 @@ export interface RdsSqlProps {
|
|
|
69
69
|
*/
|
|
70
70
|
readonly ssl?: boolean;
|
|
71
71
|
}
|
|
72
|
-
|
|
72
|
+
/**
|
|
73
|
+
* Supported database engines
|
|
74
|
+
*/
|
|
75
|
+
export declare enum DatabaseEngine {
|
|
76
|
+
POSTGRES = "postgres",
|
|
77
|
+
MYSQL = "mysql",
|
|
78
|
+
DSQL = "dsql"
|
|
79
|
+
}
|
|
80
|
+
export interface IProvider {
|
|
81
|
+
readonly serviceToken: string;
|
|
82
|
+
readonly handler: IFunction;
|
|
83
|
+
readonly secret?: ISecret;
|
|
84
|
+
readonly engine: string;
|
|
85
|
+
readonly cluster?: IDatabaseCluster | IDatabaseInstance | dsql.CfnCluster;
|
|
86
|
+
}
|
|
87
|
+
export interface ProviderAttributes {
|
|
88
|
+
/**
|
|
89
|
+
* Either the ARN or name of the Lambda function.
|
|
90
|
+
* Use functionArn for cross-account or cross-region scenarios.
|
|
91
|
+
* Use functionName for same-account, same-region scenarios.
|
|
92
|
+
*/
|
|
93
|
+
readonly functionArn?: string;
|
|
94
|
+
readonly functionName?: string;
|
|
95
|
+
readonly engine: DatabaseEngine;
|
|
96
|
+
/**
|
|
97
|
+
* Optional cluster information for role creation.
|
|
98
|
+
*
|
|
99
|
+
* When importing a provider, cluster details are often not available.
|
|
100
|
+
* However, some operations like role creation require cluster endpoint
|
|
101
|
+
* information to build connection secrets.
|
|
102
|
+
*
|
|
103
|
+
* If you plan to create roles with the imported provider, you must
|
|
104
|
+
* provide the cluster reference. If you only plan to use existing
|
|
105
|
+
* roles, databases, schemas, or SQL operations, this can be omitted.
|
|
106
|
+
*/
|
|
107
|
+
readonly cluster?: IDatabaseCluster | IDatabaseInstance | dsql.CfnCluster;
|
|
108
|
+
}
|
|
109
|
+
export declare class Provider extends Construct implements IProvider {
|
|
110
|
+
/**
|
|
111
|
+
* Import an existing provider Lambda function
|
|
112
|
+
*/
|
|
113
|
+
static fromProviderAttributes(scope: Construct, id: string, attrs: ProviderAttributes): IProvider;
|
|
73
114
|
readonly serviceToken: string;
|
|
74
115
|
readonly secret?: ISecret;
|
|
75
116
|
readonly handler: IFunction;
|
|
76
|
-
readonly cluster
|
|
117
|
+
readonly cluster?: IDatabaseCluster | IDatabaseInstance | dsql.CfnCluster;
|
|
77
118
|
/**
|
|
78
119
|
* The engine like "postgres" or "mysql"
|
|
79
120
|
*
|