Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
48 changes: 32 additions & 16 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,38 @@
name: Test
name: CI
on: push

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10.12.4
- uses: actions/setup-node@v4
with:
node-version-file: '.nvmrc'
cache: 'pnpm'

- run: pnpm install --frozen-lockfile
- run: pnpm run build

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.1.0
- uses: actions/setup-node@v1
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10.12.4
- uses: actions/setup-node@v4
with:
node-version: '16.0.0'
- name: Install
working-directory: ./cdk-postgresql
run: |
yarn
- name: Build
working-directory: ./cdk-postgresql
run: |
yarn build
- name: Test
working-directory: ./cdk-postgresql
run: |
yarn test
node-version-file: '.nvmrc'
cache: 'pnpm'

- run: pnpm install --frozen-lockfile
- run: pnpm run build
- run: pnpm --filter @botpress/cdk-postgresql run test
env:
AWS_REGION: us-east-1
AWS_ACCESS_KEY_ID: test
AWS_SECRET_ACCESS_KEY: test
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,13 @@
node_modules
*.js
*.d.ts
*.js.map

.cdk.staging
cdk.out

.DS_Store
.turbo

# Build artifacts
lib/handler-bundle
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
16
18
1 change: 0 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@ pnpm-lock.yaml
**/cdk.out/**/*
**/*.gomplate.yaml
**/README.md

3 changes: 0 additions & 3 deletions .vscode/settings.json

This file was deleted.

13 changes: 2 additions & 11 deletions cdk-postgresql/.gitignore
Original file line number Diff line number Diff line change
@@ -1,11 +1,2 @@
*.js
!jest.config.js
!lib/lambda/**/*.ts
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out

.DS_Store
# Build artifacts
lib/handler-bundle
1 change: 0 additions & 1 deletion cdk-postgresql/.nvmrc

This file was deleted.

7 changes: 0 additions & 7 deletions cdk-postgresql/jest.config.js

This file was deleted.

39 changes: 22 additions & 17 deletions cdk-postgresql/lib/database.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,31 +96,36 @@ export const createDatabase = async (props: { connection: Connection; name: stri
const { connection, name, owner } = props
console.log('Creating database', name)
const client = await getConnectedClient(connection)

await postgres.createDatabase({ client, name, owner })
await client.end()
console.log('Created database')
try {
await postgres.createDatabase({ client, name, owner })
console.log('Created database')
} finally {
await client.end()
}
}

export const deleteDatabase = async (connection: Connection, name: string, owner: string) => {
console.log('Deleting database', name)
const client = await getConnectedClient(connection)

// First, drop all remaining DB connections
// Sometimes, DB connections are still alive even though the ECS service has been deleted
await client.query(
format('SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE datname=%L', name)
)
// Then, drop the DB
await client.query(format('DROP DATABASE %I', name))
// await client.query(format("REVOKE %I FROM %I", owner, connection.Username));
await client.end()
try {
// First, drop all remaining DB connections
// Sometimes, DB connections are still alive even though the ECS service has been deleted
await client.query(
format('SELECT pg_terminate_backend(pg_stat_activity.pid) FROM pg_stat_activity WHERE datname=%L', name)
)
// Then, drop the DB
await client.query(format('DROP DATABASE %I', name))
} finally {
await client.end()
}
}

export const updateDbOwner = async (connection: Connection, name: string, owner: string) => {
console.log(`Updating DB ${name} owner to ${owner}`)
const client = await getConnectedClient(connection)

await client.query(format('ALTER DATABASE %I OWNER TO %I', name, owner))
await client.end()
try {
await client.query(format('ALTER DATABASE %I OWNER TO %I', name, owner))
} finally {
await client.end()
}
}
2 changes: 2 additions & 0 deletions cdk-postgresql/lib/lambda.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export interface UpdateDatabaseEvent extends CloudFormationCustomResourceUpdateE
Owner: string
}
OldResourceProperties: {
ServiceToken: string
Connection: Connection
Name: string
Owner: string
Expand Down Expand Up @@ -74,6 +75,7 @@ export interface UpdateRoleEvent extends CloudFormationCustomResourceUpdateEvent
PasswordArn: string
}
OldResourceProperties: {
ServiceToken: string
Connection: Connection
Name: string
PasswordArn: string
Expand Down
13 changes: 6 additions & 7 deletions cdk-postgresql/lib/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as cdk from 'aws-cdk-lib'
import { Construct } from 'constructs'
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'
import * as lambda from 'aws-cdk-lib/aws-lambda-nodejs'
import * as lambda from 'aws-cdk-lib/aws-lambda'
import * as logs from 'aws-cdk-lib/aws-logs'
import * as iam from 'aws-cdk-lib/aws-iam'
import * as cr from 'aws-cdk-lib/custom-resources'
Expand Down Expand Up @@ -94,13 +94,12 @@ export class Provider extends Construct implements iam.IGrantable {

const handlerSecurityGroup = vpc ? new ec2.SecurityGroup(this, 'HandlerSecurityGroup', { vpc }) : undefined
const handlerSecurityGroups = handlerSecurityGroup ? [handlerSecurityGroup] : undefined
const handler = new lambda.NodejsFunction(scope, 'handler', {
entry: path.join(__dirname, 'handler.js'),
bundling: {
nodeModules: ['pg', 'pg-format']
},
const handler = new lambda.Function(scope, 'handler', {
code: lambda.Code.fromAsset(path.join(__dirname, 'handler-bundle')),
handler: 'index.handler',
runtime: lambda.Runtime.NODEJS_18_X,
logRetention: logs.RetentionDays.ONE_MONTH,
timeout: cdk.Duration.minutes(15),
timeout: cdk.Duration.minutes(2),
vpc,
securityGroups: handlerSecurityGroups
})
Expand Down
49 changes: 28 additions & 21 deletions cdk-postgresql/lib/role.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,46 +105,53 @@ const generatePhysicalId = (props: Props): string => {
export const deleteRole = async (connection: Connection, name: string) => {
console.log('Deleting user', name)
const client = await getConnectedClient(connection)

await client.query(format('DROP USER %I', name))
await client.end()
try {
await client.query(format('DROP USER %I', name))
} finally {
await client.end()
}
}

export const updateRoleName = async (connection: Connection, oldName: string, newName: string) => {
console.log(`Updating role name from ${oldName} to ${newName}`)
const client = await getConnectedClient(connection)

await client.query(format('ALTER ROLE %I RENAME TO %I', oldName, newName))
await client.end()
try {
await client.query(format('ALTER ROLE %I RENAME TO %I', oldName, newName))
} finally {
await client.end()
}
}

export const updateRolePassword = async (props: { connection: Connection; name: string; passwordArn: string }) => {
const { connection, name, passwordArn } = props
console.log('Updating user password', name)

const client = await getConnectedClient(connection)
try {
const { SecretString: password } = await secretsmanager.getSecretValue({
SecretId: passwordArn
})

const { SecretString: password } = await secretsmanager.getSecretValue({
SecretId: passwordArn
})

await client.query(format('ALTER USER %I WITH PASSWORD %L', name, password))
await client.end()
await client.query(format('ALTER USER %I WITH PASSWORD %L', name, password))
} finally {
await client.end()
}
}

export const createRole = async (props: { connection: Connection; name: string; passwordArn: string }) => {
const { connection, name, passwordArn } = props
console.log('Creating user', name)
const client = await getConnectedClient(connection)
try {
const { SecretString: password } = await secretsmanager.getSecretValue({
SecretId: passwordArn
})
if (!password) {
throw new Error('could not decrypt password')
}

const { SecretString: password } = await secretsmanager.getSecretValue({
SecretId: passwordArn
})
if (!password) {
throw new Error('could not decrypt password')
await postgres.createRole({ client, name, password })
} finally {
await client.end()
}

await postgres.createRole({ client, name, password })

await client.end()
}
30 changes: 19 additions & 11 deletions cdk-postgresql/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ export const isObject = (obj: any): obj is { [key: string]: any } => {
return typeof obj === 'object' && !Array.isArray(obj) && obj !== null
}

const MAX_RETRIES = 12
const RETRY_DELAY_MS = 5000

export const getConnectedClient = async (connection: Connection) => {
console.debug(`creating PG client with connection: ${JSON.stringify(connection)}`)

Expand All @@ -28,22 +31,27 @@ export const getConnectedClient = async (connection: Connection) => {
}

let client
let tries = 0
let connected = false
do {
tries++
let lastError: unknown
for (let tries = 1; tries <= MAX_RETRIES; tries++) {
client = new Client(clientProps)
try {
await client.connect()
console.debug('connected')
return client
} catch (err) {
console.debug({ err, tries })
await new Promise((resolve) => setTimeout(resolve, 5000))
continue
lastError = err
console.debug({ err, tries, maxRetries: MAX_RETRIES })
try {
await client.end()
} catch {
/* ignore cleanup errors */
}
if (tries < MAX_RETRIES) {
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS))
}
}
connected = true
} while (!connected)
console.debug('connected')
return client
}
throw new Error(`Failed to connect to PostgreSQL after ${MAX_RETRIES} attempts: ${lastError}`)
}

const getPassword = async (connection: Connection) => {
Expand Down
45 changes: 21 additions & 24 deletions cdk-postgresql/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@botpress/cdk-postgresql",
"version": "2.0.1",
"version": "3.0.0",
"description": "Postgresql constructs for AWS CDK",
"main": "lib/index.js",
"types": "lib/index.d.ts",
Expand All @@ -14,36 +14,33 @@
},
"license": "MIT",
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=something AWS_SECRET_ACCESS_KEY=something jest"
"build": "tsc && node scripts/bundle-handler.mjs",
"test": "vitest run",
"test:integration": "vitest run --config vitest.integration.config.ts"
},
"peerDependencies": {
"aws-cdk-lib": "^2.2.0",
"aws-cdk-lib": "^2.99.0",
"constructs": "^10.0.0"
},
"dependencies": {
"@aws-sdk/client-secrets-manager": "3.47.2",
"@types/aws-lambda": "^8.10.92",
"@types/node": "17.0.8",
"esbuild": "^0.14.12",
"pg": "8.7.1",
"@aws-sdk/client-secrets-manager": "3.750.0",
"@types/aws-lambda": "catalog:",
"@types/node": "catalog:",
"esbuild": "catalog:",
"pg": "8.13.1",
"pg-format": "1.0.4",
"verror": "^1.10.1"
"verror": "1.10.1"
},
"devDependencies": {
"@types/jest": "^27.4.0",
"@types/ms": "^0.7.31",
"@types/pg": "8.6.4",
"@types/pg-format": "1.0.2",
"@types/verror": "^1.10.5",
"aws-cdk-lib": "^2.2.0",
"constructs": "^10.0.0",
"eslint": "6.2.0",
"jest": "^27.4.7",
"ms": "^2.1.3",
"testcontainers": "^8.2.0",
"ts-jest": "^27.1.3",
"typescript": "4.5.4"
"@types/ms": "catalog:",
"@types/pg": "catalog:",
"@types/pg-format": "catalog:",
"@types/verror": "catalog:",
"aws-cdk-lib": "catalog:",
"constructs": "catalog:",
"ms": "2.1.3",
"testcontainers": "10.16.0",
"typescript": "catalog:",
"vitest": "catalog:"
}
}
Loading