Skip to content

Commit eb97d2d

Browse files
authored
feat(rds): new DatabaseInstance.fromLookup (#33258)
### Issue # (if applicable) Closes #31720 This replaces my previous PR #32901. I addressed the PR comments in this new PR. This depends on this PR: cdklabs/cloud-assembly-schema#124. Also depends on this CDK CLI PR: aws/aws-cdk-cli#138. That PR should be merged first and the CLI released, before this PR can be merged. ### Reason for this change Add DatabaseInstance.fromLookup() feature ### Description of changes * Add CC API Context Provider. Needs this PR: cdklabs/cloud-assembly-schema#124 * DatabaseInstance.fromLookup call CC API to get the database instance info from instanceIdentifier. * Add units tests. ### Describe any new or updated permissions being added User will need to have permission to run CloudControl API. ### Description of how you validated changes Tested with this code. I already have an RDS DB in my AWS account. I want to look it up and grant connect to a new user. Saved to packages/@aws-cdk-testing/framework-integ/test/aws-rds/test/my-test-app.ts ``` import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as rds from 'aws-cdk-lib/aws-rds'; const awsAccountId = 'XXXXXXXXXX79'; const instanceId = 'XXXXXXXXXX-instance-1'; const appWithDb = new cdk.App(); const stack = new cdk.Stack(appWithDb, 'StackWithVpc', { env: { region: 'us-east-1', account: awsAccountId, }, }); const dbFromLookup = rds.DatabaseInstance.fromLookup(stack, 'dbFromLookup', { instanceIdentifier: instanceId, }); /* eslint-disable no-console */ console.log('lookup values', dbFromLookup.dbInstanceEndpointAddress, dbFromLookup.dbInstanceEndpointPort); const consoleReadOnlyRole = new iam.Role(stack, 'TestRole', { assumedBy: new iam.ArnPrincipal('arn_for_trusted_principal'), }); dbFromLookup.grantConnect(consoleReadOnlyRole, 'dbTestUser'); ``` Ran this command: ``` ../../aws-cdk/bin/cdk -a 'npx ts-node test/aws-rds/test/my-test-app.ts' synth ``` ### Checklist - [ X ] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent d04e40f commit eb97d2d

File tree

3 files changed

+212
-1
lines changed

3 files changed

+212
-1
lines changed

Diff for: packages/aws-cdk-lib/aws-rds/README.md

+19
Original file line numberDiff line numberDiff line change
@@ -1553,6 +1553,25 @@ new rds.DatabaseCluster(this, 'Cluster', {
15531553
});
15541554
```
15551555

1556+
## Importing existing DatabaseInstance
1557+
1558+
### Lookup DatabaseInstance by instanceIdentifier
1559+
1560+
You can lookup an existing DatabaseInstance by its instanceIdentifier using `DatabaseInstance.fromLookup()`. This method returns an `IDatabaseInstance`.
1561+
1562+
Here's how `DatabaseInstance.fromLookup()` can be used:
1563+
1564+
```ts
1565+
declare const myUserRole: iam.Role;
1566+
1567+
const dbFromLookup = rds.DatabaseInstance.fromLookup(this, 'dbFromLookup', {
1568+
instanceIdentifier: 'instanceId',
1569+
});
1570+
1571+
// Grant a connection
1572+
dbFromLookup.grantConnect(myUserRole, 'my-user-id');
1573+
```
1574+
15561575
## Limitless Database Cluster
15571576

15581577
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 for: packages/aws-cdk-lib/aws-rds/lib/instance.ts

+66-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ import * as kms from '../../aws-kms';
1717
import * as logs from '../../aws-logs';
1818
import * as s3 from '../../aws-s3';
1919
import * as secretsmanager from '../../aws-secretsmanager';
20-
import { ArnComponents, ArnFormat, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '../../core';
20+
import * as cxschema from '../../cloud-assembly-schema';
21+
import { ArnComponents, ArnFormat, ContextProvider, Duration, FeatureFlags, IResource, Lazy, RemovalPolicy, Resource, Stack, Token, Tokenization } from '../../core';
2122
import { ValidationError } from '../../core/lib/errors';
2223
import { addConstructMetadata } from '../../core/lib/metadata-resource';
2324
import * as cxapi from '../../cx-api';
@@ -134,6 +135,60 @@ export interface DatabaseInstanceAttributes {
134135
* A new or imported database instance.
135136
*/
136137
export abstract class DatabaseInstanceBase extends Resource implements IDatabaseInstance {
138+
/**
139+
* Lookup an existing DatabaseInstance using instanceIdentifier.
140+
*/
141+
public static fromLookup(scope: Construct, id: string, options: DatabaseInstanceLookupOptions): IDatabaseInstance {
142+
const response: {[key: string]: any}[] = ContextProvider.getValue(scope, {
143+
provider: cxschema.ContextProvider.CC_API_PROVIDER,
144+
props: {
145+
typeName: 'AWS::RDS::DBInstance',
146+
exactIdentifier: options.instanceIdentifier,
147+
propertiesToReturn: [
148+
'DBInstanceArn',
149+
'Endpoint.Address',
150+
'Endpoint.Port',
151+
'DbiResourceId',
152+
'DBSecurityGroups',
153+
],
154+
} as cxschema.CcApiContextQuery,
155+
dummyValue: [
156+
{
157+
'Identifier': 'TEST',
158+
'DBInstanceArn': 'TESTARN',
159+
'Endpoint.Address': 'TESTADDRESS',
160+
'Endpoint.Port': '5432',
161+
'DbiResourceId': 'TESTID',
162+
'DBSecurityGroups': [],
163+
},
164+
],
165+
}).value;
166+
167+
// getValue returns a list of result objects. We are expecting 1 result or Error.
168+
const instance = response[0];
169+
170+
// Get ISecurityGroup from securityGroupId
171+
let securityGroups: ec2.ISecurityGroup[] = [];
172+
const dbsg: string[] = instance.DBSecurityGroups;
173+
if (dbsg) {
174+
securityGroups = dbsg.map(securityGroupId => {
175+
return ec2.SecurityGroup.fromSecurityGroupId(
176+
scope,
177+
`LSG-${securityGroupId}`,
178+
securityGroupId,
179+
);
180+
});
181+
}
182+
183+
return this.fromDatabaseInstanceAttributes(scope, id, {
184+
instanceEndpointAddress: instance['Endpoint.Address'],
185+
port: instance['Endpoint.Port'],
186+
instanceIdentifier: options.instanceIdentifier,
187+
securityGroups: securityGroups,
188+
instanceResourceId: instance.DbiResourceId,
189+
});
190+
}
191+
137192
/**
138193
* Import an existing database instance.
139194
*/
@@ -1142,6 +1197,16 @@ abstract class DatabaseInstanceSource extends DatabaseInstanceNew implements IDa
11421197
}
11431198
}
11441199

1200+
/**
1201+
* Properties for looking up an existing DatabaseInstance.
1202+
*/
1203+
export interface DatabaseInstanceLookupOptions {
1204+
/**
1205+
* The instance identifier of the DatabaseInstance
1206+
*/
1207+
readonly instanceIdentifier: string;
1208+
}
1209+
11451210
/**
11461211
* Construction properties for a DatabaseInstance.
11471212
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import * as cxschema from '../../cloud-assembly-schema';
2+
import { ContextProvider, Stack } from '../../core';
3+
import * as rds from '../lib';
4+
5+
/* eslint-disable */
6+
describe('DatabaseInstanceBase from lookup', () => {
7+
test('return correct instance info', () => {
8+
// GIVEN
9+
const resultObjs = [
10+
{
11+
'DBInstanceArn': 'arn:aws:rds:us-east-1:123456789012:db:instance-1',
12+
'Endpoint.Address': 'instance-1.testserver.us-east-1.rds.amazonaws.com',
13+
'Endpoint.Port': '5432',
14+
'DbiResourceId': 'db-ABCDEFGHI',
15+
'DBSecurityGroups': [],
16+
'Identifier': 'instance-1',
17+
},
18+
];
19+
const value = {
20+
value: resultObjs,
21+
};
22+
const mock = jest.spyOn(ContextProvider, 'getValue').mockReturnValue(value);
23+
24+
// WHEN
25+
const stack = new Stack(undefined, undefined, { env: { region: 'us-east-1', account: '123456789012' } });
26+
const instance = rds.DatabaseInstance.fromLookup(stack, 'MyInstance', {
27+
instanceIdentifier: 'instance-1',
28+
});
29+
30+
// THEN
31+
expect(instance.instanceIdentifier).toEqual('instance-1');
32+
expect(instance.dbInstanceEndpointAddress).toEqual('instance-1.testserver.us-east-1.rds.amazonaws.com');
33+
expect(instance.dbInstanceEndpointPort).toEqual('5432');
34+
expect(instance.instanceResourceId).toEqual('db-ABCDEFGHI');
35+
expect(instance.connections.securityGroups.length).toEqual(0);
36+
37+
expect(mock).toHaveBeenCalledWith(stack, {
38+
provider: cxschema.ContextProvider.CC_API_PROVIDER,
39+
props: {
40+
typeName: 'AWS::RDS::DBInstance',
41+
exactIdentifier: 'instance-1',
42+
propertiesToReturn: [
43+
'DBInstanceArn',
44+
'Endpoint.Address',
45+
'Endpoint.Port',
46+
'DbiResourceId',
47+
'DBSecurityGroups',
48+
],
49+
} as cxschema.CcApiContextQuery,
50+
dummyValue: [
51+
{
52+
'Identifier': 'TEST',
53+
'DBInstanceArn': 'TESTARN',
54+
'Endpoint.Address': 'TESTADDRESS',
55+
'Endpoint.Port': '5432',
56+
'DbiResourceId': 'TESTID',
57+
'DBSecurityGroups': [],
58+
},
59+
],
60+
});
61+
62+
mock.mockRestore();
63+
});
64+
});
65+
66+
describe('DatabaseInstanceBase from lookup with DBSG', () => {
67+
test('return correct instance info', () => {
68+
// GIVEN
69+
const resultObjs = [
70+
{
71+
'DBInstanceArn': 'arn:aws:rds:us-east-1:123456789012:db:instance-1',
72+
'Endpoint.Address': 'instance-1.testserver.us-east-1.rds.amazonaws.com',
73+
'Endpoint.Port': '5432',
74+
'DbiResourceId': 'db-ABCDEFGHI',
75+
'DBSecurityGroups': ['dbsg-1', 'dbsg-2'],
76+
'Identifier': 'instance-1',
77+
},
78+
];
79+
const value = {
80+
value: resultObjs,
81+
};
82+
const mock = jest.spyOn(ContextProvider, 'getValue').mockReturnValue(value);
83+
84+
// WHEN
85+
const stack = new Stack(undefined, undefined, { env: { region: 'us-east-1', account: '123456789012' } });
86+
const instance = rds.DatabaseInstance.fromLookup(stack, 'MyInstance', {
87+
instanceIdentifier: 'instance-1',
88+
});
89+
90+
// THEN
91+
expect(instance.instanceIdentifier).toEqual('instance-1');
92+
expect(instance.dbInstanceEndpointAddress).toEqual('instance-1.testserver.us-east-1.rds.amazonaws.com');
93+
expect(instance.dbInstanceEndpointPort).toEqual('5432');
94+
expect(instance.instanceResourceId).toEqual('db-ABCDEFGHI');
95+
expect(instance.connections.securityGroups.length).toEqual(2);
96+
expect(instance.connections.securityGroups[0].securityGroupId).toEqual('dbsg-1');
97+
expect(instance.connections.securityGroups[1].securityGroupId).toEqual('dbsg-2');
98+
99+
expect(mock).toHaveBeenCalledWith(stack, {
100+
provider: cxschema.ContextProvider.CC_API_PROVIDER,
101+
props: {
102+
typeName: 'AWS::RDS::DBInstance',
103+
exactIdentifier: 'instance-1',
104+
propertiesToReturn: [
105+
'DBInstanceArn',
106+
'Endpoint.Address',
107+
'Endpoint.Port',
108+
'DbiResourceId',
109+
'DBSecurityGroups',
110+
],
111+
} as cxschema.CcApiContextQuery,
112+
dummyValue: [
113+
{
114+
'Identifier': 'TEST',
115+
'DBInstanceArn': 'TESTARN',
116+
'Endpoint.Address': 'TESTADDRESS',
117+
'Endpoint.Port': '5432',
118+
'DbiResourceId': 'TESTID',
119+
'DBSecurityGroups': [],
120+
},
121+
],
122+
});
123+
124+
mock.mockRestore();
125+
});
126+
});
127+
/* eslint-enable */

0 commit comments

Comments
 (0)