cdk-rds-sql 7.3.1 → 8.0.0

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