cdk-rds-sql 7.3.1 → 7.3.2
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/.jsii +8 -8
- package/lib/database.js +1 -1
- package/lib/handler/handler.js +28 -21
- package/lib/iam-grant.d.ts +0 -1
- package/lib/iam-grant.js +2 -12
- package/lib/provider.js +1 -1
- package/lib/role.js +1 -1
- package/lib/schema.js +1 -1
- package/lib/sql.js +1 -1
- package/package.json +1 -1
- package/lambda/engine.abstract.ts +0 -114
- package/lambda/engine.dsql.ts +0 -391
- package/lambda/engine.factory.ts +0 -23
- package/lambda/engine.mysql.test.ts +0 -228
- package/lambda/engine.mysql.ts +0 -249
- package/lambda/engine.postgresql.test.ts +0 -151
- package/lambda/engine.postgresql.ts +0 -329
- package/lambda/handler.mysql.test.ts +0 -349
- package/lambda/handler.postgresql.test.ts +0 -338
- package/lambda/handler.test.ts +0 -164
- package/lambda/handler.ts +0 -388
- package/lambda/types.ts +0 -86
- package/lambda/util.ts +0 -186
package/lambda/engine.dsql.ts
DELETED
|
@@ -1,391 +0,0 @@
|
|
|
1
|
-
import { DsqlSigner } from "@aws-sdk/dsql-signer"
|
|
2
|
-
import { format as pgFormat } from "node-pg-format"
|
|
3
|
-
import { Client, ClientConfig } from "pg"
|
|
4
|
-
import { AbstractEngine, EngineConnectionConfig } from "./engine.abstract"
|
|
5
|
-
import {
|
|
6
|
-
EngineDatabaseProperties,
|
|
7
|
-
EngineRoleProperties,
|
|
8
|
-
EngineSchemaProperties,
|
|
9
|
-
EngineSqlProperties,
|
|
10
|
-
} from "./types"
|
|
11
|
-
|
|
12
|
-
export class DsqlEngine extends AbstractEngine {
|
|
13
|
-
createDatabase(
|
|
14
|
-
_resourceId: string,
|
|
15
|
-
_props: EngineDatabaseProperties
|
|
16
|
-
): string | string[] {
|
|
17
|
-
throw new Error(
|
|
18
|
-
"DSQL does not support creating databases. Database is always 'postgres'."
|
|
19
|
-
)
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
updateDatabase(
|
|
23
|
-
_resourceId: string,
|
|
24
|
-
_oldResourceId: string,
|
|
25
|
-
_props: EngineDatabaseProperties
|
|
26
|
-
): string[] {
|
|
27
|
-
throw new Error(
|
|
28
|
-
"DSQL does not support database operations. Database is always 'postgres'."
|
|
29
|
-
)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
deleteDatabase(_resourceId: string, _masterUser: string): string | string[] {
|
|
33
|
-
throw new Error(
|
|
34
|
-
"DSQL does not support deleting databases. Database is always 'postgres'."
|
|
35
|
-
)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
async createRole(resourceId: string, props: EngineRoleProperties): Promise<string[]> {
|
|
39
|
-
return this.generateCreateRoleSql(resourceId, props?.DatabaseName)
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async updateRole(
|
|
43
|
-
resourceId: string,
|
|
44
|
-
oldResourceId: string,
|
|
45
|
-
props: EngineRoleProperties,
|
|
46
|
-
oldProps: EngineRoleProperties
|
|
47
|
-
): Promise<string[]> {
|
|
48
|
-
const sql: string[] = []
|
|
49
|
-
|
|
50
|
-
if (oldResourceId !== resourceId) {
|
|
51
|
-
// DSQL doesn't support RENAME, so we create new role and drop old one
|
|
52
|
-
// Create new role with old database permissions first
|
|
53
|
-
sql.push(...this.generateCreateRoleSql(resourceId, oldProps?.DatabaseName))
|
|
54
|
-
|
|
55
|
-
// Add special marker for IAM grant revocation
|
|
56
|
-
sql.push(`-- REVOKE_IAM_GRANTS_FOR_ROLE: ${oldResourceId}`)
|
|
57
|
-
|
|
58
|
-
// Drop old role
|
|
59
|
-
sql.push(...this.generateDropRoleSql(oldResourceId))
|
|
60
|
-
|
|
61
|
-
// If database changed as well, grant new database permissions
|
|
62
|
-
if (props?.DatabaseName && props.DatabaseName !== oldProps?.DatabaseName) {
|
|
63
|
-
sql.push("BEGIN")
|
|
64
|
-
sql.push(
|
|
65
|
-
pgFormat("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
66
|
-
)
|
|
67
|
-
sql.push("COMMIT")
|
|
68
|
-
}
|
|
69
|
-
} else {
|
|
70
|
-
// Only permissions are changing (no rename)
|
|
71
|
-
sql.push("BEGIN")
|
|
72
|
-
|
|
73
|
-
// Handle database permission changes
|
|
74
|
-
if (
|
|
75
|
-
oldProps?.DatabaseName &&
|
|
76
|
-
props?.DatabaseName &&
|
|
77
|
-
oldProps.DatabaseName !== props.DatabaseName
|
|
78
|
-
) {
|
|
79
|
-
// Revoke from old database and grant to new
|
|
80
|
-
sql.push(
|
|
81
|
-
pgFormat(
|
|
82
|
-
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
83
|
-
oldProps.DatabaseName,
|
|
84
|
-
resourceId
|
|
85
|
-
)
|
|
86
|
-
)
|
|
87
|
-
sql.push(
|
|
88
|
-
pgFormat("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
89
|
-
)
|
|
90
|
-
} else if (props?.DatabaseName && !oldProps?.DatabaseName) {
|
|
91
|
-
// Grant to new database
|
|
92
|
-
sql.push(
|
|
93
|
-
pgFormat("GRANT CONNECT ON DATABASE %I TO %I", props.DatabaseName, resourceId)
|
|
94
|
-
)
|
|
95
|
-
} else if (!props?.DatabaseName && oldProps?.DatabaseName) {
|
|
96
|
-
// Revoke from old database
|
|
97
|
-
sql.push(
|
|
98
|
-
pgFormat(
|
|
99
|
-
"REVOKE CONNECT ON DATABASE %I FROM %I",
|
|
100
|
-
oldProps.DatabaseName,
|
|
101
|
-
resourceId
|
|
102
|
-
)
|
|
103
|
-
)
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
sql.push("COMMIT")
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return sql
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async deleteRole(
|
|
113
|
-
resourceId: string,
|
|
114
|
-
props: EngineRoleProperties
|
|
115
|
-
): Promise<string | string[]> {
|
|
116
|
-
const sql: string[] = []
|
|
117
|
-
|
|
118
|
-
// Add special marker for IAM grant revocation
|
|
119
|
-
sql.push(`-- REVOKE_IAM_GRANTS_FOR_ROLE: ${resourceId}`)
|
|
120
|
-
|
|
121
|
-
// Add the regular drop role SQL
|
|
122
|
-
sql.push(...this.generateDropRoleSql(resourceId, props?.DatabaseName))
|
|
123
|
-
|
|
124
|
-
return sql
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
createSchema(resourceId: string, props: EngineSchemaProperties): string | string[] {
|
|
128
|
-
const sql: string[] = [pgFormat("CREATE SCHEMA IF NOT EXISTS %I", resourceId)]
|
|
129
|
-
if (props?.RoleName) {
|
|
130
|
-
this.grantRoleForSchema(resourceId, props.RoleName).forEach((stmt) =>
|
|
131
|
-
sql.push(stmt)
|
|
132
|
-
)
|
|
133
|
-
}
|
|
134
|
-
return sql
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
updateSchema(
|
|
138
|
-
resourceId: string,
|
|
139
|
-
oldResourceId: string,
|
|
140
|
-
props: EngineSchemaProperties
|
|
141
|
-
): string | string[] {
|
|
142
|
-
const statements: string[] = []
|
|
143
|
-
if (props?.RoleName) {
|
|
144
|
-
this.revokeRoleFromSchema(oldResourceId, props.RoleName).forEach((stmt) =>
|
|
145
|
-
statements.push(stmt)
|
|
146
|
-
)
|
|
147
|
-
}
|
|
148
|
-
if (resourceId !== oldResourceId) {
|
|
149
|
-
statements.push(pgFormat("ALTER SCHEMA %I RENAME TO %I", oldResourceId, resourceId))
|
|
150
|
-
}
|
|
151
|
-
if (props?.RoleName) {
|
|
152
|
-
this.grantRoleForSchema(resourceId, props.RoleName).forEach((stmt) =>
|
|
153
|
-
statements.push(stmt)
|
|
154
|
-
)
|
|
155
|
-
}
|
|
156
|
-
return statements
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
deleteSchema(resourceId: string, props: EngineSchemaProperties): string | string[] {
|
|
160
|
-
const statements: string[] = []
|
|
161
|
-
if (props?.RoleName) {
|
|
162
|
-
this.revokeRoleFromSchema(resourceId, props.RoleName).forEach((stmt) =>
|
|
163
|
-
statements.push(stmt)
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
statements.push(pgFormat("DROP SCHEMA IF EXISTS %I CASCADE", resourceId))
|
|
167
|
-
return statements
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
createSql(_resourceId: string, props: EngineSqlProperties): string | string[] {
|
|
171
|
-
if (!props.Statement) {
|
|
172
|
-
throw new Error("Statement is required for SQL resource")
|
|
173
|
-
}
|
|
174
|
-
return props.Statement
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
updateSql(
|
|
178
|
-
_resourceId: string,
|
|
179
|
-
_oldResourceId: string,
|
|
180
|
-
props: EngineSqlProperties
|
|
181
|
-
): string | string[] {
|
|
182
|
-
if (!props.Statement) {
|
|
183
|
-
throw new Error("Statement is required for SQL resource")
|
|
184
|
-
}
|
|
185
|
-
return props.Statement
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
deleteSql(_resourceId: string, props: EngineSqlProperties): string | string[] {
|
|
189
|
-
if (props.Rollback) {
|
|
190
|
-
return props.Rollback
|
|
191
|
-
}
|
|
192
|
-
return []
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
createIamGrant(roleName: string, iamArn: string): string | string[] {
|
|
196
|
-
if (!iamArn) {
|
|
197
|
-
throw new Error("ResourceArn is required for IAM grant")
|
|
198
|
-
}
|
|
199
|
-
return pgFormat("AWS IAM GRANT %I TO %L", roleName, iamArn)
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
updateIamGrant(
|
|
203
|
-
roleName: string,
|
|
204
|
-
oldRoleName: string,
|
|
205
|
-
iamArn: string,
|
|
206
|
-
oldIamArn: string
|
|
207
|
-
): string | string[] {
|
|
208
|
-
const statements: string[] = []
|
|
209
|
-
|
|
210
|
-
// Only update if role name or IAM role ARN changed
|
|
211
|
-
const roleNameChanged = oldRoleName !== roleName
|
|
212
|
-
const arnChanged = oldIamArn !== iamArn
|
|
213
|
-
|
|
214
|
-
if (!roleNameChanged && !arnChanged) {
|
|
215
|
-
return statements // No changes needed
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Revoke old grant if resource ARN or role name changed
|
|
219
|
-
if (oldIamArn) {
|
|
220
|
-
statements.push(pgFormat("AWS IAM REVOKE %I FROM %L", oldRoleName, oldIamArn))
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Grant new permissions
|
|
224
|
-
if (iamArn) {
|
|
225
|
-
statements.push(pgFormat("AWS IAM GRANT %I TO %L", roleName, iamArn))
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
return statements
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
deleteIamGrant(roleName: string, iamArn: string): string | string[] {
|
|
232
|
-
if (!iamArn) {
|
|
233
|
-
throw new Error("ResourceArn is required for IAM grant deletion")
|
|
234
|
-
}
|
|
235
|
-
return pgFormat("AWS IAM REVOKE %I FROM %L", roleName, iamArn)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
async executeSQL(sql: string | string[], config: EngineConnectionConfig): Promise<any> {
|
|
239
|
-
this.log("Connecting to DSQL cluster...")
|
|
240
|
-
|
|
241
|
-
// For DSQL, we need to generate an IAM auth token
|
|
242
|
-
const region = process.env.AWS_REGION
|
|
243
|
-
if (!region) {
|
|
244
|
-
throw new Error("AWS_REGION environment variable is required for DSQL")
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const signer = new DsqlSigner({
|
|
248
|
-
hostname: config.host,
|
|
249
|
-
region: region,
|
|
250
|
-
})
|
|
251
|
-
const authToken = await signer.getDbConnectAdminAuthToken()
|
|
252
|
-
|
|
253
|
-
const clientConfig: ClientConfig = {
|
|
254
|
-
host: config.host,
|
|
255
|
-
port: config.port,
|
|
256
|
-
user: "admin", // DSQL always uses 'admin' user
|
|
257
|
-
password: authToken, // Use IAM auth token as password
|
|
258
|
-
database: "postgres", // DSQL always uses 'postgres' database
|
|
259
|
-
ssl: {
|
|
260
|
-
//ca: fs.readFileSync(`${process.env.LAMBDA_TASK_ROOT}/global-bundle.pem`),
|
|
261
|
-
rejectUnauthorized: true,
|
|
262
|
-
},
|
|
263
|
-
connectionTimeoutMillis: 30000,
|
|
264
|
-
query_timeout: 30000,
|
|
265
|
-
//statement_timeout: 30000, // not supported on DSQL
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
const client = new Client(clientConfig)
|
|
269
|
-
|
|
270
|
-
try {
|
|
271
|
-
await client.connect()
|
|
272
|
-
this.log("Connected to DSQL cluster")
|
|
273
|
-
|
|
274
|
-
const statements = Array.isArray(sql) ? sql : [sql]
|
|
275
|
-
const results = []
|
|
276
|
-
|
|
277
|
-
for (const statement of statements) {
|
|
278
|
-
if (statement.trim()) {
|
|
279
|
-
// Handle special IAM grant revocation marker
|
|
280
|
-
if (statement.startsWith("-- REVOKE_IAM_GRANTS_FOR_ROLE:")) {
|
|
281
|
-
const roleName = statement
|
|
282
|
-
.replace("-- REVOKE_IAM_GRANTS_FOR_ROLE:", "")
|
|
283
|
-
.trim()
|
|
284
|
-
await this.revokeIamGrantsForRole(client, roleName)
|
|
285
|
-
} else if (statement.includes("AWS IAM REVOKE")) {
|
|
286
|
-
this.log(`Executing SQL: ${statement}`)
|
|
287
|
-
try {
|
|
288
|
-
const result = await client.query(statement)
|
|
289
|
-
results.push(result)
|
|
290
|
-
} catch (error: any) {
|
|
291
|
-
if (error.message && error.message.includes("does not exist")) {
|
|
292
|
-
this.log(
|
|
293
|
-
`Ignoring error for IAM REVOKE (role doesn't exist): ${error.message}`
|
|
294
|
-
)
|
|
295
|
-
} else {
|
|
296
|
-
throw error
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
} else {
|
|
300
|
-
this.log(`Executing SQL: ${statement}`)
|
|
301
|
-
const result = await client.query(statement)
|
|
302
|
-
results.push(result)
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
this.log("SQL execution completed")
|
|
308
|
-
return results.length === 1 ? results[0] : results
|
|
309
|
-
} catch (error) {
|
|
310
|
-
this.log(`Error executing SQL: ${error}`)
|
|
311
|
-
throw error
|
|
312
|
-
} finally {
|
|
313
|
-
try {
|
|
314
|
-
await client.end()
|
|
315
|
-
this.log("Disconnected from DSQL cluster")
|
|
316
|
-
} catch (error) {
|
|
317
|
-
this.log(`Error disconnecting: ${error}`)
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
private grantRoleForSchema(schema: string, roleName: string): string[] {
|
|
323
|
-
return [
|
|
324
|
-
pgFormat("GRANT USAGE ON SCHEMA %I TO %I", schema, roleName),
|
|
325
|
-
pgFormat("GRANT CREATE ON SCHEMA %I TO %I", schema, roleName),
|
|
326
|
-
]
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private revokeRoleFromSchema(schema: string, roleName: string): string[] {
|
|
330
|
-
return [
|
|
331
|
-
pgFormat("REVOKE CREATE ON SCHEMA %I FROM %I", schema, roleName),
|
|
332
|
-
pgFormat("REVOKE ALL ON SCHEMA %I FROM %I", schema, roleName),
|
|
333
|
-
]
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private generateCreateRoleSql(roleName: string, databaseName?: string): string[] {
|
|
337
|
-
const sql = ["BEGIN"]
|
|
338
|
-
sql.push(pgFormat("CREATE ROLE %I WITH LOGIN", roleName))
|
|
339
|
-
|
|
340
|
-
if (databaseName) {
|
|
341
|
-
sql.push(pgFormat("GRANT CONNECT ON DATABASE %I TO %I", databaseName, roleName))
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
sql.push("COMMIT")
|
|
345
|
-
return sql
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
private generateDropRoleSql(roleName: string, databaseName?: string): string[] {
|
|
349
|
-
const sql = ["BEGIN"]
|
|
350
|
-
|
|
351
|
-
if (databaseName) {
|
|
352
|
-
sql.push(
|
|
353
|
-
pgFormat("REVOKE ALL PRIVILEGES ON DATABASE %I FROM %I", databaseName, roleName)
|
|
354
|
-
)
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
sql.push(pgFormat("DROP ROLE IF EXISTS %I", roleName))
|
|
358
|
-
sql.push("COMMIT")
|
|
359
|
-
return sql
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
private async revokeIamGrantsForRole(client: Client, roleName: string): Promise<void> {
|
|
363
|
-
try {
|
|
364
|
-
this.log(`Querying IAM grants for role: ${roleName}`)
|
|
365
|
-
|
|
366
|
-
// Query the IAM role mappings for this role
|
|
367
|
-
const result = await client.query(
|
|
368
|
-
pgFormat(
|
|
369
|
-
"SELECT arn FROM sys.iam_pg_role_mappings WHERE pg_role_name = %L",
|
|
370
|
-
roleName
|
|
371
|
-
)
|
|
372
|
-
)
|
|
373
|
-
|
|
374
|
-
if (result.rows && result.rows.length > 0) {
|
|
375
|
-
this.log(`Found ${result.rows.length} IAM grants to revoke for role: ${roleName}`)
|
|
376
|
-
|
|
377
|
-
// Revoke each IAM grant
|
|
378
|
-
for (const row of result.rows) {
|
|
379
|
-
const revokeStatement = pgFormat("AWS IAM REVOKE %I FROM %L", roleName, row.arn)
|
|
380
|
-
this.log(`Executing IAM revoke: ${revokeStatement}`)
|
|
381
|
-
await client.query(revokeStatement)
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
this.log(`No IAM grants found for role: ${roleName}`)
|
|
385
|
-
}
|
|
386
|
-
} catch (error) {
|
|
387
|
-
this.log(`Error revoking IAM grants for role ${roleName}: ${error}`)
|
|
388
|
-
// Don't throw here - the role might not have any IAM grants, which is fine
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
package/lambda/engine.factory.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { AbstractEngine } from "./engine.abstract"
|
|
2
|
-
import { DsqlEngine } from "./engine.dsql"
|
|
3
|
-
import { MysqlEngine } from "./engine.mysql"
|
|
4
|
-
import { PostgresqlEngine } from "./engine.postgresql"
|
|
5
|
-
|
|
6
|
-
export class EngineFactory {
|
|
7
|
-
static createEngine(engine: string): AbstractEngine {
|
|
8
|
-
switch (engine.toLowerCase()) {
|
|
9
|
-
case "dsql":
|
|
10
|
-
return new DsqlEngine()
|
|
11
|
-
case "postgres":
|
|
12
|
-
case "postgresql":
|
|
13
|
-
case "aurora-postgresql":
|
|
14
|
-
return new PostgresqlEngine()
|
|
15
|
-
case "mysql":
|
|
16
|
-
case "mariadb":
|
|
17
|
-
case "aurora-mysql":
|
|
18
|
-
return new MysqlEngine()
|
|
19
|
-
default:
|
|
20
|
-
throw new Error(`Unsupported database engine: ${engine}`)
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
}
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import { MysqlEngine } from "./engine.mysql"
|
|
2
|
-
|
|
3
|
-
describe("MySQL Engine", () => {
|
|
4
|
-
let engine: MysqlEngine
|
|
5
|
-
|
|
6
|
-
beforeEach(() => {
|
|
7
|
-
engine = new MysqlEngine()
|
|
8
|
-
})
|
|
9
|
-
|
|
10
|
-
describe("Database", () => {
|
|
11
|
-
it("should generate correct SQL for creating a database", () => {
|
|
12
|
-
const sql = engine.createDatabase("testdb", {})
|
|
13
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
14
|
-
expect(sql[0]).toContain("CREATE DATABASE IF NOT EXISTS")
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
it("should generate correct SQL for creating a database with an owner", () => {
|
|
18
|
-
const sql = engine.createDatabase("testdb", { Owner: "testuser" })
|
|
19
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
20
|
-
expect(sql[0]).toContain("CREATE DATABASE IF NOT EXISTS")
|
|
21
|
-
expect(sql[1]).toContain("GRANT ALL PRIVILEGES")
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
it("should generate correct SQL for deleting a database", () => {
|
|
25
|
-
const sql = engine.deleteDatabase("testdb", "masteruser")
|
|
26
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
27
|
-
expect(sql[0]).toContain("DROP DATABASE IF EXISTS")
|
|
28
|
-
})
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
describe("Role", () => {
|
|
32
|
-
it("should throw error when creating a role without password ARN", async () => {
|
|
33
|
-
await expect(engine.createRole("testrole", {})).rejects.toThrow(
|
|
34
|
-
"No PasswordArn provided"
|
|
35
|
-
)
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
it("should generate correct SQL for renaming a role", async () => {
|
|
39
|
-
// Mock getPassword implementation
|
|
40
|
-
jest.spyOn(engine as any, "getPassword").mockResolvedValue("test-password")
|
|
41
|
-
|
|
42
|
-
const sql = await engine.updateRole(
|
|
43
|
-
"newrole",
|
|
44
|
-
"oldrole",
|
|
45
|
-
{
|
|
46
|
-
PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
|
|
47
|
-
},
|
|
48
|
-
{}
|
|
49
|
-
)
|
|
50
|
-
|
|
51
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
52
|
-
expect(sql[0]).toContain("CREATE USER IF NOT EXISTS")
|
|
53
|
-
expect(sql[1]).toContain("DROP USER IF EXISTS")
|
|
54
|
-
expect(sql[2]).toContain("FLUSH PRIVILEGES")
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
it("should generate correct SQL for deleting a role", async () => {
|
|
58
|
-
const sql = await engine.deleteRole("testrole", { DatabaseName: "testdb" })
|
|
59
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
60
|
-
expect(sql[0]).toContain("REVOKE ALL PRIVILEGES")
|
|
61
|
-
expect(sql[1]).toContain("DROP USER IF EXISTS")
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
it("should generate SQL to revoke privileges on old database when database is changed", async () => {
|
|
65
|
-
// Mock getPassword implementation
|
|
66
|
-
jest.spyOn(engine as any, "getPassword").mockResolvedValue("test-password")
|
|
67
|
-
|
|
68
|
-
const oldProps = {
|
|
69
|
-
DatabaseName: "olddb",
|
|
70
|
-
PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const newProps = {
|
|
74
|
-
DatabaseName: "newdb",
|
|
75
|
-
PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const sql = await engine.updateRole("testrole", "testrole", newProps, oldProps)
|
|
79
|
-
|
|
80
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
81
|
-
|
|
82
|
-
// Check for revoke statement for the old database
|
|
83
|
-
const revokeStatement = sql.find(
|
|
84
|
-
(statement) =>
|
|
85
|
-
statement.includes("REVOKE ALL PRIVILEGES") && statement.includes("`olddb`")
|
|
86
|
-
)
|
|
87
|
-
expect(revokeStatement).toBeDefined()
|
|
88
|
-
|
|
89
|
-
// Check for grant statement for the new database
|
|
90
|
-
const grantStatement = sql.find(
|
|
91
|
-
(statement) =>
|
|
92
|
-
statement.includes("GRANT ALL PRIVILEGES") && statement.includes("`newdb`")
|
|
93
|
-
)
|
|
94
|
-
expect(grantStatement).toBeDefined()
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
describe("IAM Authentication", () => {
|
|
98
|
-
it("should create user with IAM authentication", async () => {
|
|
99
|
-
const props = {
|
|
100
|
-
EnableIamAuth: true,
|
|
101
|
-
DatabaseName: "testdb",
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const sql = await engine.createRole("iamuser", props)
|
|
105
|
-
|
|
106
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
107
|
-
expect(sql[0]).toContain(
|
|
108
|
-
"CREATE USER IF NOT EXISTS 'iamuser'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS'"
|
|
109
|
-
)
|
|
110
|
-
expect(sql[1]).toContain("GRANT ALL PRIVILEGES ON `testdb`.* TO 'iamuser'@'%'")
|
|
111
|
-
expect(sql[2]).toBe("FLUSH PRIVILEGES")
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it("should create user without IAM authentication", async () => {
|
|
115
|
-
jest.spyOn(engine as any, "getPassword").mockResolvedValue("test-password")
|
|
116
|
-
|
|
117
|
-
const props = {
|
|
118
|
-
EnableIamAuth: false,
|
|
119
|
-
PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
|
|
120
|
-
DatabaseName: "testdb",
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const sql = await engine.createRole("passworduser", props)
|
|
124
|
-
|
|
125
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
126
|
-
expect(sql[0]).toContain(
|
|
127
|
-
"CREATE USER IF NOT EXISTS 'passworduser'@'%' IDENTIFIED BY 'test-password'"
|
|
128
|
-
)
|
|
129
|
-
expect(sql[1]).toContain(
|
|
130
|
-
"GRANT ALL PRIVILEGES ON `testdb`.* TO 'passworduser'@'%'"
|
|
131
|
-
)
|
|
132
|
-
expect(sql[2]).toBe("FLUSH PRIVILEGES")
|
|
133
|
-
})
|
|
134
|
-
|
|
135
|
-
it("should switch from password to IAM authentication", async () => {
|
|
136
|
-
const oldProps = {
|
|
137
|
-
EnableIamAuth: false,
|
|
138
|
-
PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const newProps = {
|
|
142
|
-
EnableIamAuth: true,
|
|
143
|
-
DatabaseName: "testdb",
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const sql = await engine.updateRole(
|
|
147
|
-
"switchuser",
|
|
148
|
-
"switchuser",
|
|
149
|
-
newProps,
|
|
150
|
-
oldProps
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
154
|
-
expect(sql[0]).toContain("DROP USER IF EXISTS 'switchuser'@'%'")
|
|
155
|
-
expect(sql[1]).toContain(
|
|
156
|
-
"CREATE USER 'switchuser'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS'"
|
|
157
|
-
)
|
|
158
|
-
expect(sql[2]).toContain("GRANT ALL PRIVILEGES ON `testdb`.* TO 'switchuser'@'%'")
|
|
159
|
-
expect(sql[3]).toBe("FLUSH PRIVILEGES")
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it("should switch from IAM to password authentication", async () => {
|
|
163
|
-
jest.spyOn(engine as any, "getPassword").mockResolvedValue("new-password")
|
|
164
|
-
|
|
165
|
-
const oldProps = {
|
|
166
|
-
EnableIamAuth: true,
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const newProps = {
|
|
170
|
-
EnableIamAuth: false,
|
|
171
|
-
PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
|
|
172
|
-
DatabaseName: "testdb",
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const sql = await engine.updateRole(
|
|
176
|
-
"switchuser",
|
|
177
|
-
"switchuser",
|
|
178
|
-
newProps,
|
|
179
|
-
oldProps
|
|
180
|
-
)
|
|
181
|
-
|
|
182
|
-
expect(Array.isArray(sql)).toBe(true)
|
|
183
|
-
expect(sql[0]).toContain("DROP USER IF EXISTS 'switchuser'@'%'")
|
|
184
|
-
expect(sql[1]).toContain(
|
|
185
|
-
"CREATE USER 'switchuser'@'%' IDENTIFIED BY 'new-password'"
|
|
186
|
-
)
|
|
187
|
-
expect(sql[2]).toContain("GRANT ALL PRIVILEGES ON `testdb`.* TO 'switchuser'@'%'")
|
|
188
|
-
expect(sql[3]).toBe("FLUSH PRIVILEGES")
|
|
189
|
-
})
|
|
190
|
-
})
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
describe("Schema", () => {
|
|
194
|
-
it("should throw an error when trying to create a schema", () => {
|
|
195
|
-
expect(() => engine.createSchema("testschema", {})).toThrow("not supported")
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
it("should throw an error when trying to update a schema", () => {
|
|
199
|
-
expect(() => engine.updateSchema("newschema", "oldschema", {})).toThrow(
|
|
200
|
-
"not supported"
|
|
201
|
-
)
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
it("should throw an error when trying to delete a schema", () => {
|
|
205
|
-
expect(() => engine.deleteSchema("testschema", {})).toThrow("not supported")
|
|
206
|
-
})
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
describe("SQL", () => {
|
|
210
|
-
it("should pass through SQL statements for create", () => {
|
|
211
|
-
const statement = "SELECT * FROM users"
|
|
212
|
-
const sql = engine.createSql("test", { Statement: statement })
|
|
213
|
-
expect(sql).toBe(statement)
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
it("should pass through SQL statements for update", () => {
|
|
217
|
-
const statement = "UPDATE users SET name = 'test'"
|
|
218
|
-
const sql = engine.updateSql("test", "old", { Statement: statement })
|
|
219
|
-
expect(sql).toBe(statement)
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
it("should pass through rollback SQL for delete", () => {
|
|
223
|
-
const rollback = "DROP TABLE users"
|
|
224
|
-
const sql = engine.deleteSql("test", { Rollback: rollback })
|
|
225
|
-
expect(sql).toBe(rollback)
|
|
226
|
-
})
|
|
227
|
-
})
|
|
228
|
-
})
|