Skip to content

Commit

Permalink
chore(spec2cdk): Resource class implements ICfnResource (#27781)
Browse files Browse the repository at this point in the history
This PR implements the following:

- If all `primaryIdentifiers` are `attributes`, there is nothing to do;
`ICfnResource` is already implemented by `Resource`
- if all `primaryIdentifiers` are `properties`, then
- if there is 1 `primaryIdentifier` initialize it as `this.attrId =
this.ref;`
- if there are multiple `primaryIdentifiers` initialize each as
`this.attrId = cdk.Fn.select(0, cdk.Fn.split('|', this.ref));` and so on

----

*By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache-2.0 license*

---------

Co-authored-by: Momo Kornher <kornherm@amazon.co.uk>
  • Loading branch information
kaizencc and mrgrain authored Nov 13, 2023
1 parent c990c00 commit 86ab734
Show file tree
Hide file tree
Showing 4 changed files with 519 additions and 56 deletions.
2 changes: 1 addition & 1 deletion tools/@aws-cdk/spec2cdk/lib/cdk/resource-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ export class ResourceClass extends ClassType {
}),
},
extends: CDK_CORE.CfnResource,
implements: [CDK_CORE.IInspectable, ...ResourceDecider.taggabilityInterfaces(props.resource)],
implements: [CDK_CORE.IInspectable, resourceInterface.type, ...ResourceDecider.taggabilityInterfaces(props.resource)],
});

this.db = props.db;
Expand Down
68 changes: 50 additions & 18 deletions tools/@aws-cdk/spec2cdk/lib/cdk/resource-decider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export const HAS_25610 = false;
// This convenience typewriter builder is used all over the place
const $this = $E(expr.this_());

// This convenience typewriter builder is used for cloudformation intrinsics
const $Fn = $E(expr.directCode('cdk.Fn'));

/**
* Decide how properties get mapped between model types, Typescript types, and CloudFormation
*/
Expand Down Expand Up @@ -56,41 +59,70 @@ export class ResourceDecider {
// A list of possible names for the arn, in order of importance.
// This is relevant because some resources, like AWS::VpcLattice::AccessLogSubscription
// has both `Arn` and `ResourceArn`, and we want to select the `Arn` property.
const possibleArnNames = ['Arn', 'ResourceArn', `${this.resource.name}Arn`];
const possibleArnNames = ['Arn', `${this.resource.name}Arn`];
for (const arn of possibleArnNames) {
const att = this.classAttributeProperties.filter((a) => a.propertySpec.name === attributePropertyName(arn));
const prop = this.propsProperties.filter((p) => p.propertySpec.name === propertyNameFromCloudFormation(arn));
if (att.length > 0 || prop.length > 0) {
return att[0] ? att[0].propertySpec : prop[0].propertySpec;
}
const att = this.classAttributeProperties.find((a) => a.propertySpec.name === attributePropertyName(arn));
if (att) { return att.propertySpec; }
}
return;
}

private convertPropertySpecToRefAttribute(propSpec: PropertySpec): PropertySpec {
return {
...propSpec,
name: attributePropertyName(propSpec.name[0].toUpperCase() + propSpec.name.slice(1)),
docs: {
...propSpec.docs,
summary: (propSpec.docs?.summary ?? '').concat('\nThis property gets determined after the resource is created.'),
remarks: (propSpec.docs?.remarks ?? '').concat('@cloudformationAttribute Ref'),
},
immutable: true,
};
}

private convertPrimaryIdentifier() {
for (const cfnName of this.resource.primaryIdentifier ?? []) {
if (this.resource.primaryIdentifier === undefined) { return; }
for (let i = 0; i < (this.resource.primaryIdentifier).length; i++) {
const cfnName = this.resource.primaryIdentifier[i];
const att = this.findAttributeByName(attributePropertyName(cfnName));
const prop = this.findPropertyByName(propertyNameFromCloudFormation(cfnName));
if (att) {
this.primaryIdentifier.push(att);
} else if (prop) {
// rename the prop name as an attribute name, since it is gettable by ref
this.primaryIdentifier.push({
...prop,
name: attributePropertyName(prop.name[0].toUpperCase() + prop.name.slice(1)),
docs: {
...prop.docs,
remarks: prop.docs?.remarks?.concat(['\n', `@cloudformationRef ${prop.name}`].join('\n')),
},
const propSpec = prop.propertySpec;

// Build an attribute out of the property we're getting
// Create initializer for new attribute, if possible
let initializer: Expression | undefined = undefined;
if (propSpec.type === Type.STRING) { // handling only this case for now
if (this.resource.primaryIdentifier!.length === 1) {
initializer = CDK_CORE.tokenAsString($this.ref);
} else {
initializer = CDK_CORE.tokenAsString($Fn.select(expr.lit(i), $Fn.split(expr.lit('|'), $this.ref)));
}
}

// If we cannot come up with an initializer, we're dropping this property on the floor
if (!initializer) { continue; }

// Build an attribute spec out of the property spec
const attrPropertySpec = this.convertPropertySpecToRefAttribute(propSpec);

// Add the new attribute to the relevant places
this.classAttributeProperties.push({
propertySpec: attrPropertySpec,
initializer,
});

this.primaryIdentifier.push(attrPropertySpec);
}
}
}

private findPropertyByName(name: string): PropertySpec | undefined {
const props = this.propsProperties.filter((prop) => prop.propertySpec.name === name);
private findPropertyByName(name: string): ClassProperty | undefined {
const props = this.classProperties.filter((prop) => prop.propertySpec.name === name);
// there's no way we have multiple properties with the same name
if (props.length > 0) { return props[0].propertySpec; }
if (props.length > 0) { return props[0]; }
return;
}

Expand Down
Loading

0 comments on commit 86ab734

Please sign in to comment.