diff --git a/packages/@aws-cdk/aws-ec2/lib/vpc.ts b/packages/@aws-cdk/aws-ec2/lib/vpc.ts index 41bc9b97bcfdc..34abeef934530 100644 --- a/packages/@aws-cdk/aws-ec2/lib/vpc.ts +++ b/packages/@aws-cdk/aws-ec2/lib/vpc.ts @@ -309,9 +309,7 @@ export class VpcNetwork extends VpcNetworkRef implements cdk.ITaggable { // Create an Internet Gateway and attach it if necessary if (allowOutbound) { - const igw = new cloudformation.InternetGatewayResource(this, 'IGW', { - tags: new cdk.TagManager(this), - }); + const igw = new cloudformation.InternetGatewayResource(this, 'IGW'); const att = new cloudformation.VPCGatewayAttachmentResource(this, 'VPCGW', { internetGatewayId: igw.ref, vpcId: this.resource.ref @@ -499,7 +497,6 @@ export class VpcSubnet extends VpcSubnetRef implements cdk.ITaggable { this.subnetId = subnet.subnetId; const table = new cloudformation.RouteTableResource(this, 'RouteTable', { vpcId: props.vpcId, - tags: new cdk.TagManager(this), }); this.routeTableId = table.ref; @@ -556,7 +553,6 @@ export class VpcPublicSubnet extends VpcSubnet { allocationId: new cloudformation.EIPResource(this, `EIP`, { domain: 'vpc' }).eipAllocationId, - tags: new cdk.TagManager(this), }); return ngw.natGatewayId; } diff --git a/tools/cfn2ts/lib/codegen.ts b/tools/cfn2ts/lib/codegen.ts index e47762e7a4e90..eb57323614e16 100644 --- a/tools/cfn2ts/lib/codegen.ts +++ b/tools/cfn2ts/lib/codegen.ts @@ -8,6 +8,7 @@ import { itemTypeNames, PropertyAttributeName, scalarTypeNames, SpecName } from const CORE = genspec.CORE_NAMESPACE; const RESOURCE_BASE_CLASS = `${CORE}.Resource`; // base class for all resources const CONSTRUCT_CLASS = `${CORE}.Construct`; +const TAGGABLE_INTERFACE = `${CORE}.ITaggable`; interface Dictionary { [key: string]: T; } @@ -106,10 +107,11 @@ export default class CodeGenerator { } } - private openClass(name: genspec.CodeName, docLink?: string, superClasses?: string) { + private openClass(name: genspec.CodeName, docLink?: string, interfaces?: string[], superClasses?: string) { const extendsPostfix = superClasses ? ` extends ${superClasses}` : ''; + const implementPostfix = interfaces ? ` implements ${interfaces.join(', ')}` : ''; this.docLink(docLink); - this.code.openBlock(`export class ${name.className}${extendsPostfix}`); + this.code.openBlock(`export class ${name.className}${extendsPostfix}${implementPostfix}`); return name.className; } @@ -183,7 +185,9 @@ export default class CodeGenerator { if (propsType) { this.code.line(); } - this.openClass(resourceName, spec.Documentation, RESOURCE_BASE_CLASS); + + const implInterfaces: string[] | undefined = isTaggable(spec) ? [TAGGABLE_INTERFACE] : undefined; + this.openClass(resourceName, spec.Documentation, implInterfaces, RESOURCE_BASE_CLASS); // // Static inspectors. @@ -238,6 +242,16 @@ export default class CodeGenerator { } } + // + // ITaggable required properties + // + if (isTaggable(spec)) { + this.code.line('/**'); + this.code.line(' * Tag Manager for resoure'); + this.code.line(' */'); + this.code.line('public readonly tags: cdk.TagManager;'); + } + // // Constructor // @@ -286,11 +300,16 @@ export default class CodeGenerator { } } + // initialize tag manager + if (isTaggable(spec)) { + this.code.line(`this.tags = new ${CORE}.TagManager(parent);`); + } + this.code.closeBlock(); if (propsType) { this.code.line(); - this.emitCloudFormationPropertiesOverride(propsType); + this.emitCloudFormationPropertiesOverride(propsType, isTaggable(spec)); } this.closeClass(resourceName); @@ -305,13 +324,23 @@ export default class CodeGenerator { * * Since resolve() deep-resolves, we only need to do this once. */ - private emitCloudFormationPropertiesOverride(propsType: genspec.CodeName) { + private emitCloudFormationPropertiesOverride(propsType: genspec.CodeName, taggable: boolean) { this.code.openBlock(`public get propertyOverrides(): ${propsType.className}`); this.code.line(`return this.untypedPropertyOverrides;`); this.code.closeBlock(); + this.code.line(); this.code.openBlock('protected renderProperties(properties: any): { [key: string]: any } '); - this.code.line(`return ${genspec.cfnMapperName(propsType).fqn}(${CORE}.resolve(properties));`); + this.code.line(`const resolvedProps = ${CORE}.resolve(properties);`); + if (taggable) { + this.code.line(`const rawTags: ${CORE}.Tag[] = resolvedProps.tags || [];`); + this.code.line(`const managedTags: ${CORE}.Tag[] = ${CORE}.resolve(this.tags) || [];`); + this.code.line(`const rawTagKeys: string[] = rawTags.map(t => t.key);`); + this.code.line(`// order is important L1 (raw) tags override managed tags`); + this.code.line(`const uniqueTags = rawTags.concat(managedTags.filter(t => rawTagKeys.indexOf(t.key) < 0));`); + this.code.line(`resolvedProps.tags = uniqueTags.length !== 0 ? uniqueTags : undefined;`); + } + this.code.line(`return ${genspec.cfnMapperName(propsType).fqn}(resolvedProps);`); this.code.closeBlock(); } @@ -474,7 +503,8 @@ export default class CodeGenerator { } this.code.line(); - this.openClass(attr.typeName, attr.docLink, attr.baseClassName.fqn); + this.openClass(attr.typeName, attr.docLink, undefined, attr.baseClassName.fqn); + // Add a private member that will make the class structurally // different in TypeScript, which prevents assigning returning // incorrectly-typed Tokens. Those will cause ClassCastExceptions @@ -628,3 +658,7 @@ function mapperNames(types: genspec.CodeName[]): string { function quoteCode(code: string): string { return '``' + code + '``'; } + +function isTaggable(resource: schema.ResourceType): boolean { + return !!resource.Properties && !!resource.Properties.Tags; +}