Skip to content

Commit a7c8aa6

Browse files
author
Leon Michalski
committed
Init
1 parent dbd7626 commit a7c8aa6

File tree

14 files changed

+2946
-158
lines changed

14 files changed

+2946
-158
lines changed

packages/aws-cdk-lib/aws-lambda/test/function.test.ts

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5108,6 +5108,120 @@ describe('telemetry metadata', () => {
51085108
});
51095109
});
51105110

5111+
describe('L1 Relationships', () => {
5112+
it('simple union', () => {
5113+
const stack = new cdk.Stack();
5114+
const role = new iam.Role(stack, 'SomeRole', {
5115+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
5116+
});
5117+
new lambda.CfnFunction(stack, 'MyLambda', {
5118+
code: { zipFile: 'foo' },
5119+
role: role, // Simple Union
5120+
});
5121+
Template.fromStack(stack).hasResource('AWS::Lambda::Function', {
5122+
Properties: {
5123+
Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] },
5124+
},
5125+
});
5126+
});
5127+
5128+
it('array of unions', () => {
5129+
const stack = new cdk.Stack();
5130+
const role = new iam.Role(stack, 'SomeRole', {
5131+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
5132+
});
5133+
const layer1 = new lambda.LayerVersion(stack, 'LayerVersion1', {
5134+
code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')),
5135+
compatibleRuntimes: [lambda.Runtime.PYTHON_3_13],
5136+
});
5137+
const layer2 = new lambda.LayerVersion(stack, 'LayerVersion2', {
5138+
code: lambda.Code.fromAsset(path.join(__dirname, 'my-lambda-handler')),
5139+
compatibleRuntimes: [lambda.Runtime.PYTHON_3_13],
5140+
});
5141+
new lambda.CfnFunction(stack, 'MyLambda', {
5142+
code: { zipFile: 'foo' },
5143+
role: role,
5144+
layers: [layer1, layer2], // Array of Unions
5145+
});
5146+
Template.fromStack(stack).hasResource('AWS::Lambda::Function', {
5147+
Properties: {
5148+
Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] },
5149+
Layers: [{ Ref: 'LayerVersion139D4D7A8' }, { Ref: 'LayerVersion23E5F3CEA' }],
5150+
},
5151+
});
5152+
});
5153+
5154+
it('nested union', () => {
5155+
const stack = new cdk.Stack();
5156+
const role = new iam.Role(stack, 'SomeRole', {
5157+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
5158+
});
5159+
const bucket = new s3.Bucket(stack, 'MyBucket');
5160+
5161+
new lambda.CfnFunction(stack, 'MyLambda', {
5162+
code: {
5163+
s3Bucket: bucket, // Nested union
5164+
},
5165+
role: role,
5166+
});
5167+
Template.fromStack(stack).hasResource('AWS::Lambda::Function', {
5168+
Properties: {
5169+
Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] },
5170+
Code: { S3Bucket: { Ref: 'MyBucketF68F3FF0' } },
5171+
},
5172+
});
5173+
});
5174+
5175+
it('deeply nested union', () => {
5176+
const stack = new cdk.Stack();
5177+
const topic = new sns.CfnTopic(stack, 'Topic');
5178+
5179+
new lambda.CfnEventInvokeConfig(stack, 'EventConfig', {
5180+
functionName: 'myFunction',
5181+
qualifier: '$LATEST',
5182+
destinationConfig: {
5183+
onFailure: {
5184+
destination: topic, // Deeply nested: destinationConfig -> onFailure -> destination (union)
5185+
},
5186+
},
5187+
});
5188+
Template.fromStack(stack).hasResource('AWS::Lambda::EventInvokeConfig', {
5189+
Properties: {
5190+
DestinationConfig: {
5191+
OnFailure: {
5192+
Destination: { Ref: 'Topic' },
5193+
},
5194+
},
5195+
},
5196+
});
5197+
});
5198+
5199+
it('nested array of unions', () => {
5200+
const stack = new cdk.Stack();
5201+
const role = new iam.Role(stack, 'SomeRole', {
5202+
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
5203+
});
5204+
const securityGroup = new ec2.SecurityGroup(stack, 'SG', {
5205+
vpc: new ec2.Vpc(stack, 'VPC'),
5206+
});
5207+
new lambda.CfnFunction(stack, 'MyLambda', {
5208+
code: { zipFile: 'foo' },
5209+
role: role,
5210+
vpcConfig: {
5211+
securityGroupIds: [securityGroup], // Nested array of union
5212+
},
5213+
});
5214+
Template.fromStack(stack).hasResource('AWS::Lambda::Function', {
5215+
Properties: {
5216+
Role: { 'Fn::GetAtt': ['SomeRole6DDC54DD', 'Arn'] },
5217+
VpcConfig: {
5218+
SecurityGroupIds: [{ 'Fn::GetAtt': ['SGADB53937', 'GroupId'] }],
5219+
},
5220+
},
5221+
});
5222+
});
5223+
});
5224+
51115225
function newTestLambda(scope: constructs.Construct) {
51125226
return new lambda.Function(scope, 'MyLambda', {
51135227
code: new lambda.InlineCode('foo'),

tools/@aws-cdk/spec2cdk/lib/cdk/ast.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Module } from '@cdklabs/typewriter';
33
import { AugmentationsModule } from './augmentation-generator';
44
import { CannedMetricsModule } from './canned-metrics';
55
import { CDK_CORE, CONSTRUCTS, ModuleImportLocations } from './cdk';
6+
import { SelectiveImport } from './relationship-decider';
67
import { ResourceClass } from './resource-class';
78

89
/**
@@ -59,7 +60,7 @@ export class AstBuilder<T extends Module> {
5960
for (const link of resources) {
6061
ast.addResource(link.entity);
6162
}
62-
63+
ast.renderImports();
6364
return ast;
6465
}
6566

@@ -74,6 +75,7 @@ export class AstBuilder<T extends Module> {
7475

7576
const ast = new AstBuilder(scope, props, aug, metrics);
7677
ast.addResource(resource);
78+
ast.renderImports();
7779

7880
return ast;
7981
}
@@ -85,6 +87,8 @@ export class AstBuilder<T extends Module> {
8587
public readonly resources: Record<string, string> = {};
8688
private nameSuffix?: string;
8789
private deprecated?: string;
90+
public readonly selectiveImports = new Array<SelectiveImport>();
91+
private readonly modulesRootLocation: string;
8892

8993
protected constructor(
9094
public readonly module: T,
@@ -95,6 +99,7 @@ export class AstBuilder<T extends Module> {
9599
this.db = props.db;
96100
this.nameSuffix = props.nameSuffix;
97101
this.deprecated = props.deprecated;
102+
this.modulesRootLocation = props.importLocations?.modulesRoot ?? '../..';
98103

99104
CDK_CORE.import(this.module, 'cdk', { fromLocation: props.importLocations?.core });
100105
CONSTRUCTS.import(this.module, 'constructs');
@@ -111,6 +116,35 @@ export class AstBuilder<T extends Module> {
111116

112117
resourceClass.build();
113118

119+
this.addImports(resourceClass);
114120
this.augmentations?.augmentResource(resource, resourceClass);
115121
}
122+
123+
private addImports(resourceClass: ResourceClass) {
124+
for (const selectiveImport of resourceClass.imports) {
125+
const existingModuleImport = this.selectiveImports.find(
126+
(imp) => imp.moduleName === selectiveImport.moduleName,
127+
);
128+
if (!existingModuleImport) {
129+
this.selectiveImports.push(selectiveImport);
130+
} else {
131+
// We need to avoid importing the same reference multiple times
132+
for (const type of selectiveImport.types) {
133+
if (!existingModuleImport.types.find((t) =>
134+
t.originalType === type.originalType && t.aliasedType === type.aliasedType,
135+
)) {
136+
existingModuleImport.types.push(type);
137+
}
138+
}
139+
}
140+
}
141+
}
142+
143+
public renderImports() {
144+
const sortedImports = this.selectiveImports.sort((a, b) => a.moduleName.localeCompare(b.moduleName));
145+
for (const selectiveImport of sortedImports) {
146+
const sourceModule = new Module(selectiveImport.moduleName);
147+
sourceModule.importSelective(this.module, selectiveImport.types.map((t) => `${t.originalType} as ${t.aliasedType}`), { fromLocation: `${this.modulesRootLocation}/${sourceModule.name}` });
148+
}
149+
}
116150
}

tools/@aws-cdk/spec2cdk/lib/cdk/cdk.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ export interface ModuleImportLocations {
2222
* @default 'aws-cdk-lib/aws-cloudwatch'
2323
*/
2424
readonly cloudwatch?: string;
25+
/**
26+
* The root location of all the modules
27+
* @default '../..'
28+
*/
29+
readonly modulesRoot?: string;
2530
}
2631

2732
export class CdkCore extends ExternalModule {
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { Resource } from '@aws-cdk/service-spec-types';
2+
import { $E, expr, Expression, PropertySpec, Type } from '@cdklabs/typewriter';
3+
import { attributePropertyName, referencePropertyName } from '../naming';
4+
import { CDK_CORE } from './cdk';
5+
6+
export interface ReferenceProp {
7+
readonly declaration: PropertySpec;
8+
readonly cfnValue: Expression;
9+
}
10+
11+
// Convenience typewriter builder
12+
const $this = $E(expr.this_());
13+
14+
export function getReferenceProps(resource: Resource): ReferenceProp[] {
15+
const referenceProps = [];
16+
// Primary identifier. We assume all parts are strings.
17+
const primaryIdentifier = resource.primaryIdentifier ?? [];
18+
if (primaryIdentifier.length === 1) {
19+
referenceProps.push({
20+
declaration: {
21+
name: referencePropertyName(primaryIdentifier[0], resource.name),
22+
type: Type.STRING,
23+
immutable: true,
24+
docs: {
25+
summary: `The ${primaryIdentifier[0]} of the ${resource.name} resource.`,
26+
},
27+
},
28+
cfnValue: $this.ref,
29+
});
30+
} else if (primaryIdentifier.length > 1) {
31+
for (const [i, cfnName] of enumerate(primaryIdentifier)) {
32+
referenceProps.push({
33+
declaration: {
34+
name: referencePropertyName(cfnName, resource.name),
35+
type: Type.STRING,
36+
immutable: true,
37+
docs: {
38+
summary: `The ${cfnName} of the ${resource.name} resource.`,
39+
},
40+
},
41+
cfnValue: splitSelect('|', i, $this.ref),
42+
});
43+
}
44+
}
45+
46+
const arnProp = findArnProperty(resource);
47+
if (arnProp) {
48+
referenceProps.push({
49+
declaration: {
50+
name: referencePropertyName(arnProp, resource.name),
51+
type: Type.STRING,
52+
immutable: true,
53+
docs: {
54+
summary: `The ARN of the ${resource.name} resource.`,
55+
},
56+
},
57+
cfnValue: $this[attributePropertyName(arnProp)],
58+
});
59+
}
60+
return referenceProps;
61+
}
62+
63+
/**
64+
* Find an ARN property for a given resource
65+
*
66+
* Returns `undefined` if no ARN property is found, or if the ARN property is already
67+
* included in the primary identifier.
68+
*/
69+
export function findArnProperty(resource: Resource) {
70+
const possibleArnNames = ['Arn', `${resource.name}Arn`];
71+
for (const name of possibleArnNames) {
72+
const prop = resource.attributes[name];
73+
if (prop && !resource.primaryIdentifier?.includes(name)) {
74+
return name;
75+
}
76+
}
77+
return undefined;
78+
}
79+
80+
function splitSelect(sep: string, n: number, base: Expression) {
81+
return CDK_CORE.Fn.select(expr.lit(n), CDK_CORE.Fn.split(expr.lit(sep), base));
82+
}
83+
84+
function enumerate<A>(xs: A[]): Array<[number, A]> {
85+
return xs.map((x, i) => [i, x]);
86+
}

0 commit comments

Comments
 (0)