CI/CD - CodeBuild + CodeDeploy + CodePipeline
This commit is contained in:
@@ -8,8 +8,12 @@ import {
|
||||
DescribeOrderableDBInstanceOptionsCommand,
|
||||
ListTagsForResourceCommand,
|
||||
ModifyDBInstanceCommand,
|
||||
RebootDBInstanceCommand,
|
||||
StartDBInstanceCommand,
|
||||
StopDBInstanceCommand,
|
||||
type RDSClient,
|
||||
} from '@aws-sdk/client-rds'
|
||||
import {ExecuteStatementCommand, type RDSDataClient} from '@aws-sdk/client-rds-data'
|
||||
import {ConflictError, ValidationError} from '../cloud-spi/errors'
|
||||
import {createRdsService} from '../services/rds'
|
||||
import {mapAwsSdkError} from './awsErrors'
|
||||
@@ -24,6 +28,9 @@ type RdsCommand =
|
||||
| DescribeOrderableDBInstanceOptionsCommand
|
||||
| ListTagsForResourceCommand
|
||||
| ModifyDBInstanceCommand
|
||||
| RebootDBInstanceCommand
|
||||
| StartDBInstanceCommand
|
||||
| StopDBInstanceCommand
|
||||
|
||||
type Responder = (command: RdsCommand) => unknown | Promise<unknown>
|
||||
|
||||
@@ -47,6 +54,17 @@ function adapterWith(responder: Responder) {
|
||||
}
|
||||
}
|
||||
|
||||
function recordingRdsData(responder: (command: ExecuteStatementCommand) => unknown | Promise<unknown>) {
|
||||
const commands: ExecuteStatementCommand[] = []
|
||||
const client = {
|
||||
async send(command: ExecuteStatementCommand) {
|
||||
commands.push(command)
|
||||
return responder(command)
|
||||
},
|
||||
} as unknown as RDSDataClient
|
||||
return {client, commands}
|
||||
}
|
||||
|
||||
function instanceResponse(command: CreateDBInstanceCommand) {
|
||||
return {
|
||||
DBInstance: {
|
||||
@@ -69,6 +87,47 @@ const validCreateValues = {
|
||||
}
|
||||
|
||||
describe('AwsDatabaseAdapter', () => {
|
||||
test('explores PostgreSQL databases through the RDS Data API without console credentials', async () => {
|
||||
const {client, commands} = recordingRdsData((command) => {
|
||||
expect(command.input.resourceArn).toBe('arn:aws:rds:us-east-1:000000000000:db:orders-db')
|
||||
expect(command.input.database).toBe('orders')
|
||||
expect(command.input.sql).toContain('FROM pg_database')
|
||||
return {
|
||||
columnMetadata: [{name: 'name', typeName: 'varchar'}, {name: 'state', typeName: 'varchar'}, {name: 'isSystem', typeName: 'bool'}],
|
||||
records: [[{stringValue: 'orders'}, {stringValue: 'ONLINE'}, {booleanValue: false}]],
|
||||
}
|
||||
})
|
||||
const {client: rds, commands: rdsCommands} = recordingRds((command) => {
|
||||
expect(command).toBeInstanceOf(DescribeDBInstancesCommand)
|
||||
return {DBInstances: [{
|
||||
DBInstanceIdentifier: 'orders-db',
|
||||
DBInstanceArn: 'arn:aws:rds:us-east-1:000000000000:db:orders-db',
|
||||
Engine: 'postgres',
|
||||
DBName: 'orders',
|
||||
VpcSecurityGroups: [],
|
||||
}]}
|
||||
})
|
||||
const adapter = new AwsDatabaseAdapter(createRdsService(rds), rds, client)
|
||||
|
||||
await expect(adapter.listSqlDatabases('orders-db', {username: '', password: ''})).resolves.toEqual([{
|
||||
name: 'orders', state: 'ONLINE', createdAt: null, isSystem: false,
|
||||
}])
|
||||
expect(rdsCommands).toHaveLength(1)
|
||||
expect(commands).toHaveLength(1)
|
||||
expect(commands[0].input.secretArn).toContain('floci-ui-rds-data')
|
||||
})
|
||||
|
||||
test('rejects SQL exploration for unsupported RDS engines', async () => {
|
||||
const {client: rds} = recordingRds(() => ({DBInstances: [{
|
||||
DBInstanceIdentifier: 'maria-db', Engine: 'mariadb', VpcSecurityGroups: [],
|
||||
}]}))
|
||||
const {client} = recordingRdsData(() => ({}))
|
||||
const adapter = new AwsDatabaseAdapter(createRdsService(rds), rds, client)
|
||||
|
||||
await expect(adapter.querySql('maria-db', {username: '', password: '', database: 'app'}, 'SELECT 1'))
|
||||
.rejects.toBeInstanceOf(ValidationError)
|
||||
})
|
||||
|
||||
test('keeps list search behavior intact', async () => {
|
||||
const {adapter} = adapterWith(async (command) => {
|
||||
expect(command).toBeInstanceOf(DescribeDBInstancesCommand)
|
||||
@@ -285,6 +344,25 @@ describe('AwsDatabaseAdapter', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('sends RDS lifecycle commands for an instance', async () => {
|
||||
const {adapter, commands} = adapterWith(async () => ({}))
|
||||
|
||||
await adapter.start('orders-db')
|
||||
await adapter.stop('orders-db')
|
||||
await adapter.reboot('orders-db')
|
||||
|
||||
expect(commands.map((command) => command.constructor)).toEqual([
|
||||
StartDBInstanceCommand,
|
||||
StopDBInstanceCommand,
|
||||
RebootDBInstanceCommand,
|
||||
])
|
||||
expect(commands.map((command) => command.input)).toEqual([
|
||||
{DBInstanceIdentifier: 'orders-db'},
|
||||
{DBInstanceIdentifier: 'orders-db'},
|
||||
{DBInstanceIdentifier: 'orders-db'},
|
||||
])
|
||||
})
|
||||
|
||||
test('lists and normalizes database snapshots', async () => {
|
||||
const {adapter, commands} = adapterWith(async (command) => {
|
||||
expect(command).toBeInstanceOf(DescribeDBSnapshotsCommand)
|
||||
|
||||
Reference in New Issue
Block a user