diff --git a/packages/aws-cdk-lib/aws-rds/README.md b/packages/aws-cdk-lib/aws-rds/README.md index e2addd17bd47d..f20d721d33d97 100644 --- a/packages/aws-cdk-lib/aws-rds/README.md +++ b/packages/aws-cdk-lib/aws-rds/README.md @@ -1553,6 +1553,25 @@ new rds.DatabaseCluster(this, 'Cluster', { }); ``` +## Importing existing DatabaseInstance + +### Lookup DatabaseInstance by instanceIdentifier + +You can lookup an existing DatabaseInstance by its instanceIdentifier using `DatabaseInstance.fromLookup()`. This method returns an `IDatabaseInstance`. + +Here's how `DatabaseInstance.fromLookup()` can be used: + +```ts +declare const myUserRole: iam.Role; + +const dbFromLookup = rds.DatabaseInstance.fromLookup(this, 'dbFromLookup', { + instanceIdentifier: 'instanceId', +}); + +// Grant a connection +dbFromLookup.grantConnect(myUserRole, 'my-user-id'); +``` + ## Limitless Database Cluster Amazon Aurora [PostgreSQL Limitless Database](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/limitless.html) provides automated horizontal scaling to process millions of write transactions per second and manages petabytes of data while maintaining the simplicity of operating inside a single database. diff --git a/packages/aws-cdk-lib/aws-rds/lib/instance.ts b/packages/aws-cdk-lib/aws-rds/lib/instance.ts index 423f06bbf3d2b..5c141b40143cf 100644 --- a/packages/aws-cdk-lib/aws-rds/lib/instance.ts +++ b/packages/aws-cdk-lib/aws-rds/lib/instance.ts @@ -17,7 +17,8 @@ import * as kms from '../../aws-kms'; import * as logs from '../../aws-logs'; import * as s3 from '../../aws-s3'; import * as secretsmanager from '../../aws-secretsmanager'; -import { ArnComponents, ArnFormat, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '../../core'; +import * as cxschema from '../../cloud-assembly-schema'; +import { ArnComponents, ArnFormat, ContextProvider, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '../../core'; import { ValidationError } from '../../core/lib/errors'; import { addConstructMetadata } from '../../core/lib/metadata-resource'; import * as cxapi from '../../cx-api'; @@ -134,6 +135,60 @@ export interface DatabaseInstanceAttributes { * A new or imported database instance. */ export abstract class DatabaseInstanceBase extends Resource implements IDatabaseInstance { + /** + * Lookup an existing DatabaseInstance using instanceIdentifier. + */ + public static fromLookup(scope: Construct, id: string, options: DatabaseInstanceLookupOptions): IDatabaseInstance { + const response: {[key: string]: any}[] = ContextProvider.getValue(scope, { + provider: cxschema.ContextProvider.CC_API_PROVIDER, + props: { + typeName: 'AWS::RDS::DBInstance', + exactIdentifier: options.instanceIdentifier, + propertiesToReturn: [ + 'DBInstanceArn', + 'Endpoint.Address', + 'Endpoint.Port', + 'DbiResourceId', + 'DBSecurityGroups', + ], + } as cxschema.CcApiContextQuery, + dummyValue: [ + { + 'Identifier': 'TEST', + 'DBInstanceArn': 'TESTARN', + 'Endpoint.Address': 'TESTADDRESS', + 'Endpoint.Port': '5432', + 'DbiResourceId': 'TESTID', + 'DBSecurityGroups': [], + }, + ], + }).value; + + // getValue returns a list of result objects. We are expecting 1 result or Error. + const instance = response[0]; + + // Get ISecurityGroup from securityGroupId + let securityGroups: ec2.ISecurityGroup[] = []; + const dbsg: string[] = instance.DBSecurityGroups; + if (dbsg) { + securityGroups = dbsg.map(securityGroupId => { + return ec2.SecurityGroup.fromSecurityGroupId( + scope, + `LSG-${securityGroupId}`, + securityGroupId, + ); + }); + } + + return this.fromDatabaseInstanceAttributes(scope, id, { + instanceEndpointAddress: instance['Endpoint.Address'], + port: instance['Endpoint.Port'], + instanceIdentifier: options.instanceIdentifier, + securityGroups: securityGroups, + instanceResourceId: instance.DbiResourceId, + }); + } + /** * Import an existing database instance. */ @@ -1142,6 +1197,16 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa } } +/** + * Properties for looking up an existing DatabaseInstance. + */ +export interface DatabaseInstanceLookupOptions { + /** + * The instance identifier of the DatabaseInstance + */ + readonly instanceIdentifier: string; +} + /** * Construction properties for a DatabaseInstance. */ diff --git a/packages/aws-cdk-lib/aws-rds/test/instance.from-lookup.test.ts b/packages/aws-cdk-lib/aws-rds/test/instance.from-lookup.test.ts new file mode 100644 index 0000000000000..d4fb7f8725d2d --- /dev/null +++ b/packages/aws-cdk-lib/aws-rds/test/instance.from-lookup.test.ts @@ -0,0 +1,127 @@ +import * as cxschema from '../../cloud-assembly-schema'; +import { ContextProvider, Stack } from '../../core'; +import * as rds from '../lib'; + +/* eslint-disable */ +describe('DatabaseInstanceBase from lookup', () => { + test('return correct instance info', () => { + // GIVEN + const resultObjs = [ + { + 'DBInstanceArn': 'arn:aws:rds:us-east-1:123456789012:db:instance-1', + 'Endpoint.Address': 'instance-1.testserver.us-east-1.rds.amazonaws.com', + 'Endpoint.Port': '5432', + 'DbiResourceId': 'db-ABCDEFGHI', + 'DBSecurityGroups': [], + 'Identifier': 'instance-1', + }, + ]; + const value = { + value: resultObjs, + }; + const mock = jest.spyOn(ContextProvider, 'getValue').mockReturnValue(value); + + // WHEN + const stack = new Stack(undefined, undefined, { env: { region: 'us-east-1', account: '123456789012' } }); + const instance = rds.DatabaseInstance.fromLookup(stack, 'MyInstance', { + instanceIdentifier: 'instance-1', + }); + + // THEN + expect(instance.instanceIdentifier).toEqual('instance-1'); + expect(instance.dbInstanceEndpointAddress).toEqual('instance-1.testserver.us-east-1.rds.amazonaws.com'); + expect(instance.dbInstanceEndpointPort).toEqual('5432'); + expect(instance.instanceResourceId).toEqual('db-ABCDEFGHI'); + expect(instance.connections.securityGroups.length).toEqual(0); + + expect(mock).toHaveBeenCalledWith(stack, { + provider: cxschema.ContextProvider.CC_API_PROVIDER, + props: { + typeName: 'AWS::RDS::DBInstance', + exactIdentifier: 'instance-1', + propertiesToReturn: [ + 'DBInstanceArn', + 'Endpoint.Address', + 'Endpoint.Port', + 'DbiResourceId', + 'DBSecurityGroups', + ], + } as cxschema.CcApiContextQuery, + dummyValue: [ + { + 'Identifier': 'TEST', + 'DBInstanceArn': 'TESTARN', + 'Endpoint.Address': 'TESTADDRESS', + 'Endpoint.Port': '5432', + 'DbiResourceId': 'TESTID', + 'DBSecurityGroups': [], + }, + ], + }); + + mock.mockRestore(); + }); +}); + +describe('DatabaseInstanceBase from lookup with DBSG', () => { + test('return correct instance info', () => { + // GIVEN + const resultObjs = [ + { + 'DBInstanceArn': 'arn:aws:rds:us-east-1:123456789012:db:instance-1', + 'Endpoint.Address': 'instance-1.testserver.us-east-1.rds.amazonaws.com', + 'Endpoint.Port': '5432', + 'DbiResourceId': 'db-ABCDEFGHI', + 'DBSecurityGroups': ['dbsg-1', 'dbsg-2'], + 'Identifier': 'instance-1', + }, + ]; + const value = { + value: resultObjs, + }; + const mock = jest.spyOn(ContextProvider, 'getValue').mockReturnValue(value); + + // WHEN + const stack = new Stack(undefined, undefined, { env: { region: 'us-east-1', account: '123456789012' } }); + const instance = rds.DatabaseInstance.fromLookup(stack, 'MyInstance', { + instanceIdentifier: 'instance-1', + }); + + // THEN + expect(instance.instanceIdentifier).toEqual('instance-1'); + expect(instance.dbInstanceEndpointAddress).toEqual('instance-1.testserver.us-east-1.rds.amazonaws.com'); + expect(instance.dbInstanceEndpointPort).toEqual('5432'); + expect(instance.instanceResourceId).toEqual('db-ABCDEFGHI'); + expect(instance.connections.securityGroups.length).toEqual(2); + expect(instance.connections.securityGroups[0].securityGroupId).toEqual('dbsg-1'); + expect(instance.connections.securityGroups[1].securityGroupId).toEqual('dbsg-2'); + + expect(mock).toHaveBeenCalledWith(stack, { + provider: cxschema.ContextProvider.CC_API_PROVIDER, + props: { + typeName: 'AWS::RDS::DBInstance', + exactIdentifier: 'instance-1', + propertiesToReturn: [ + 'DBInstanceArn', + 'Endpoint.Address', + 'Endpoint.Port', + 'DbiResourceId', + 'DBSecurityGroups', + ], + } as cxschema.CcApiContextQuery, + dummyValue: [ + { + 'Identifier': 'TEST', + 'DBInstanceArn': 'TESTARN', + 'Endpoint.Address': 'TESTADDRESS', + 'Endpoint.Port': '5432', + 'DbiResourceId': 'TESTID', + 'DBSecurityGroups': [], + }, + ], + }); + + mock.mockRestore(); + }); +}); +/* eslint-enable */