cdk-rds-sql 7.3.0 → 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.
@@ -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
- })
@@ -1,256 +0,0 @@
1
- import * as fs from "fs"
2
- import { AbstractEngine, EngineConnectionConfig } from "./engine.abstract"
3
- import {
4
- EngineDatabaseProperties,
5
- EngineRoleProperties,
6
- EngineSchemaProperties,
7
- EngineSqlProperties,
8
- EngineIamGrantProperties,
9
- } from "./types"
10
-
11
- export class MysqlEngine extends AbstractEngine {
12
- createDatabase(resourceId: string, props: EngineDatabaseProperties): string[] {
13
- const sql = [`CREATE DATABASE IF NOT EXISTS \`${resourceId}\``]
14
-
15
- if (props.Owner) {
16
- sql.push(`GRANT ALL PRIVILEGES ON \`${resourceId}\`.* TO '${props.Owner}'@'%'`)
17
- sql.push("FLUSH PRIVILEGES")
18
- }
19
-
20
- return sql
21
- }
22
-
23
- updateDatabase(): string[] {
24
- throw new Error("Renaming database is not supported in MySQL.")
25
- }
26
-
27
- deleteDatabase(resourceId: string, _masterUser: string): string[] {
28
- return [`DROP DATABASE IF EXISTS \`${resourceId}\``]
29
- }
30
-
31
- async createRole(resourceId: string, props: EngineRoleProperties): Promise<string[]> {
32
- const sql: string[] = []
33
-
34
- if (props.EnableIamAuth) {
35
- // Create user for IAM authentication
36
- sql.push(
37
- `CREATE USER IF NOT EXISTS '${resourceId}'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS'`
38
- )
39
- } else {
40
- // Create user with password authentication
41
- if (!props.PasswordArn) throw new Error("No PasswordArn provided")
42
- const password = await this.getPassword(props.PasswordArn)
43
- if (!password) throw `Cannot parse password from ${props.PasswordArn}`
44
-
45
- sql.push(
46
- `CREATE USER IF NOT EXISTS '${resourceId}'@'%' IDENTIFIED BY '${password}'`
47
- )
48
- }
49
-
50
- if (props.DatabaseName) {
51
- sql.push(
52
- `GRANT ALL PRIVILEGES ON \`${props.DatabaseName}\`.* TO '${resourceId}'@'%'`
53
- )
54
- }
55
-
56
- sql.push("FLUSH PRIVILEGES")
57
- return sql
58
- }
59
-
60
- async updateRole(
61
- resourceId: string,
62
- oldResourceId: string,
63
- props: EngineRoleProperties,
64
- oldProps: EngineRoleProperties
65
- ): Promise<string[]> {
66
- const sql: string[] = []
67
-
68
- if (oldResourceId !== resourceId) {
69
- // MySQL doesn't allow renaming users directly, we need to create a new one and drop the old one
70
- if (props.EnableIamAuth) {
71
- // Create new user with IAM auth
72
- sql.push(
73
- `CREATE USER IF NOT EXISTS '${resourceId}'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS'`
74
- )
75
- } else if (props?.PasswordArn) {
76
- // Create new user with password auth
77
- const password = await this.getPassword(props.PasswordArn)
78
- if (!password) throw `Cannot parse password from ${props.PasswordArn}`
79
-
80
- sql.push(
81
- `CREATE USER IF NOT EXISTS '${resourceId}'@'%' IDENTIFIED BY '${password}'`
82
- )
83
- } else {
84
- // If no password is provided, create user with a random password then expire it
85
- sql.push(`CREATE USER IF NOT EXISTS '${resourceId}'@'%' IDENTIFIED BY UUID()`)
86
- sql.push(`ALTER USER '${resourceId}'@'%' PASSWORD EXPIRE`)
87
- }
88
-
89
- // Drop the old user
90
- sql.push(`DROP USER IF EXISTS '${oldResourceId}'@'%'`)
91
- } else {
92
- // Handle authentication method changes for existing user
93
- if (props?.EnableIamAuth && !oldProps?.EnableIamAuth) {
94
- // Switching from password to IAM auth - need to recreate user
95
- sql.push(`DROP USER IF EXISTS '${resourceId}'@'%'`)
96
- sql.push(
97
- `CREATE USER '${resourceId}'@'%' IDENTIFIED WITH AWSAuthenticationPlugin as 'RDS'`
98
- )
99
- } else if (!props?.EnableIamAuth && oldProps?.EnableIamAuth) {
100
- // Switching from IAM to password auth - need to recreate user
101
- sql.push(`DROP USER IF EXISTS '${resourceId}'@'%'`)
102
- if (props?.PasswordArn) {
103
- const password = await this.getPassword(props.PasswordArn)
104
- if (!password) throw `Cannot parse password from ${props.PasswordArn}`
105
- sql.push(`CREATE USER '${resourceId}'@'%' IDENTIFIED BY '${password}'`)
106
- }
107
- } else if (!props?.EnableIamAuth && props?.PasswordArn) {
108
- // Just update the password for password auth
109
- const password = await this.getPassword(props.PasswordArn)
110
- if (!password) throw `Cannot parse password from ${props.PasswordArn}`
111
- sql.push(`ALTER USER '${resourceId}'@'%' IDENTIFIED BY '${password}'`)
112
- }
113
- }
114
-
115
- // Check if database name has changed
116
- if (
117
- oldProps?.DatabaseName &&
118
- props?.DatabaseName &&
119
- oldProps.DatabaseName !== props.DatabaseName
120
- ) {
121
- // Revoke from old database
122
- sql.push(
123
- `REVOKE ALL PRIVILEGES ON \`${oldProps.DatabaseName}\`.* FROM '${resourceId}'@'%'`
124
- )
125
- }
126
-
127
- if (props?.DatabaseName) {
128
- sql.push(
129
- `GRANT ALL PRIVILEGES ON \`${props.DatabaseName}\`.* TO '${resourceId}'@'%'`
130
- )
131
- }
132
-
133
- if (sql.length > 0) {
134
- sql.push("FLUSH PRIVILEGES")
135
- }
136
-
137
- return sql
138
- }
139
-
140
- async deleteRole(resourceId: string, props: EngineRoleProperties): Promise<string[]> {
141
- const sql: string[] = []
142
-
143
- if (props?.DatabaseName) {
144
- sql.push(
145
- `REVOKE ALL PRIVILEGES ON \`${props.DatabaseName}\`.* FROM '${resourceId}'@'%'`
146
- )
147
- }
148
-
149
- sql.push(`DROP USER IF EXISTS '${resourceId}'@'%'`)
150
- sql.push("FLUSH PRIVILEGES")
151
-
152
- return sql
153
- }
154
-
155
- createSchema(_resourceId: string, _props: EngineSchemaProperties): string[] {
156
- throw new Error("Schemas are not supported in MySQL/MariaDB")
157
- }
158
-
159
- updateSchema(
160
- _resourceId: string,
161
- _oldResourceId: string,
162
- _props: EngineSchemaProperties
163
- ): string[] {
164
- throw new Error("Schemas are not supported in MySQL/MariaDB")
165
- }
166
-
167
- deleteSchema(_resourceId: string, _props: EngineSchemaProperties): string[] {
168
- throw new Error("Schemas are not supported in MySQL/MariaDB")
169
- }
170
-
171
- createSql(_resourceId: string, props: EngineSqlProperties): string {
172
- return props?.Statement || ""
173
- }
174
-
175
- updateSql(
176
- _resourceId: string,
177
- _oldResourceId: string,
178
- props: EngineSqlProperties
179
- ): string {
180
- return props?.Statement || ""
181
- }
182
-
183
- deleteSql(_resourceId: string, props: EngineSqlProperties): string {
184
- return props?.Rollback || ""
185
- }
186
-
187
- createIamGrant(
188
- _resourceId: string,
189
- _props: EngineIamGrantProperties
190
- ): string | string[] {
191
- throw new Error("IAM grants are only supported with DSQL clusters")
192
- }
193
-
194
- updateIamGrant(
195
- _resourceId: string,
196
- _oldResourceId: string,
197
- _props: EngineIamGrantProperties,
198
- _oldProps: EngineIamGrantProperties
199
- ): string | string[] {
200
- throw new Error("IAM grants are only supported with DSQL clusters")
201
- }
202
-
203
- deleteIamGrant(
204
- _resourceId: string,
205
- _props: EngineIamGrantProperties
206
- ): string | string[] {
207
- throw new Error("IAM grants are only supported with DSQL clusters")
208
- }
209
-
210
- async executeSQL(sql: string | string[], config: EngineConnectionConfig): Promise<any> {
211
- // Dynamic import to avoid bundling issues
212
- const { createConnection } = await import("mysql2/promise")
213
-
214
- const isSslEnabled = process.env.SSL ? JSON.parse(process.env.SSL) : true
215
-
216
- const sslOptions = isSslEnabled
217
- ? {
218
- ssl: {
219
- ca: fs.readFileSync(`${process.env.LAMBDA_TASK_ROOT}/global-bundle.pem`),
220
- rejectUnauthorized: true,
221
- },
222
- }
223
- : {}
224
-
225
- const connectionConfig = {
226
- host: config.host,
227
- port: config.port,
228
- user: config.user,
229
- password: config.password,
230
- database: config.database,
231
- connectTimeout: 30000,
232
- multipleStatements: true,
233
- ...sslOptions,
234
- }
235
-
236
- this.log(
237
- `Connecting to MySQL/MariaDB host ${connectionConfig.host}:${
238
- connectionConfig.port
239
- }${isSslEnabled ? " using a secure connection" : ""}, database ${
240
- connectionConfig.database
241
- } as ${connectionConfig.user}`
242
- )
243
- this.log("Executing SQL", sql)
244
-
245
- const connection = await createConnection(connectionConfig)
246
- try {
247
- if (typeof sql === "string") {
248
- return await connection.query(sql)
249
- } else if (sql) {
250
- return await Promise.all(sql.map((statement) => connection.query(statement)))
251
- }
252
- } finally {
253
- await connection.end()
254
- }
255
- }
256
- }
@@ -1,151 +0,0 @@
1
- import { PostgresqlEngine } from "./engine.postgresql"
2
-
3
- describe("PostgreSQL Engine", () => {
4
- let engine: PostgresqlEngine
5
-
6
- beforeEach(() => {
7
- engine = new PostgresqlEngine()
8
- })
9
-
10
- describe("Role", () => {
11
- it("should generate SQL to revoke privileges on old database when database is changed", async () => {
12
- // Mock getPassword implementation
13
- jest.spyOn(engine as any, "getPassword").mockResolvedValue("test-password")
14
-
15
- const oldProps = {
16
- DatabaseName: "olddb",
17
- PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
18
- }
19
-
20
- const newProps = {
21
- DatabaseName: "newdb",
22
- PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
23
- }
24
-
25
- const sql = await engine.updateRole("testrole", "testrole", newProps, oldProps)
26
-
27
- expect(Array.isArray(sql)).toBe(true)
28
-
29
- // Check for transaction start
30
- expect(sql[0]).toBe("start transaction")
31
-
32
- // Find the revoke statement for the old database
33
- const revokeStatement = sql.find(
34
- (statement) =>
35
- statement.includes("revoke connect on database") && statement.includes("olddb")
36
- )
37
- expect(revokeStatement).toBeDefined()
38
-
39
- // Check for grant statement for the new database
40
- const grantStatement = sql.find(
41
- (statement) =>
42
- statement.includes("grant connect on database") && statement.includes("newdb")
43
- )
44
- expect(grantStatement).toBeDefined()
45
-
46
- // Check for transaction commit
47
- expect(sql[sql.length - 1]).toBe("commit")
48
- })
49
-
50
- describe("IAM Authentication", () => {
51
- it("should create role with IAM authentication", async () => {
52
- const props = {
53
- EnableIamAuth: true,
54
- DatabaseName: "testdb",
55
- }
56
-
57
- const sql = await engine.createRole("iamrole", props)
58
-
59
- expect(Array.isArray(sql)).toBe(true)
60
- expect(sql[0]).toBe("start transaction")
61
- expect(sql).toContain("create role iamrole with login")
62
- expect(sql).toContain("grant rds_iam to iamrole")
63
- expect(sql[sql.length - 1]).toBe("commit")
64
- })
65
-
66
- it("should create role without IAM authentication", async () => {
67
- jest.spyOn(engine as any, "getPassword").mockResolvedValue("test-password")
68
-
69
- const props = {
70
- EnableIamAuth: false,
71
- PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
72
- DatabaseName: "testdb",
73
- }
74
-
75
- const sql = await engine.createRole("passwordrole", props)
76
-
77
- expect(Array.isArray(sql)).toBe(true)
78
- expect(sql[0]).toBe("start transaction")
79
- expect(sql).toContain(
80
- "create role passwordrole with login password 'test-password'"
81
- )
82
- expect(sql[sql.length - 1]).toBe("commit")
83
- })
84
-
85
- it("should switch from password to IAM authentication", async () => {
86
- const oldProps = {
87
- EnableIamAuth: false,
88
- PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
89
- }
90
-
91
- const newProps = {
92
- EnableIamAuth: true,
93
- }
94
-
95
- const sql = await engine.updateRole(
96
- "switchrole",
97
- "switchrole",
98
- newProps,
99
- oldProps
100
- )
101
-
102
- expect(Array.isArray(sql)).toBe(true)
103
- expect(sql[0]).toBe("start transaction")
104
- expect(sql).toContain("grant rds_iam to switchrole")
105
- expect(sql[sql.length - 1]).toBe("commit")
106
- })
107
-
108
- it("should switch from IAM to password authentication", async () => {
109
- jest.spyOn(engine as any, "getPassword").mockResolvedValue("new-password")
110
-
111
- const oldProps = {
112
- EnableIamAuth: true,
113
- }
114
-
115
- const newProps = {
116
- EnableIamAuth: false,
117
- PasswordArn: "arn:aws:secretsmanager:region:account:secret:name",
118
- }
119
-
120
- const sql = await engine.updateRole(
121
- "switchrole",
122
- "switchrole",
123
- newProps,
124
- oldProps
125
- )
126
-
127
- expect(Array.isArray(sql)).toBe(true)
128
- expect(sql[0]).toBe("start transaction")
129
- expect(sql).toContain("revoke rds_iam from switchrole")
130
- expect(sql).toContain("alter role switchrole with password 'new-password'")
131
- expect(sql[sql.length - 1]).toBe("commit")
132
- })
133
- })
134
- })
135
-
136
- // Add basic tests for other PostgreSQL functionality
137
- describe("Database", () => {
138
- it("should generate correct SQL for creating a database", () => {
139
- const sql = engine.createDatabase("testdb", {})
140
- expect(sql).toContain("create database testdb")
141
- })
142
- })
143
-
144
- describe("SQL", () => {
145
- it("should pass through SQL statements", () => {
146
- const statement = "SELECT * FROM users"
147
- const sql = engine.createSql("test", { Statement: statement })
148
- expect(sql).toBe(statement)
149
- })
150
- })
151
- })