diff --git a/packages/@aws-cdk/aws-logs/lib/log-retention.ts b/packages/@aws-cdk/aws-logs/lib/log-retention.ts index 0c733f178e605..962f675915b3d 100644 --- a/packages/@aws-cdk/aws-logs/lib/log-retention.ts +++ b/packages/@aws-cdk/aws-logs/lib/log-retention.ts @@ -130,7 +130,6 @@ export class LogRetention extends CoreConstruct { */ class LogRetentionFunction extends CoreConstruct implements cdk.ITaggable { public readonly functionArn: cdk.Reference; - readonly cfnFunction: cdk.CfnResource; tags: cdk.TagManager = new cdk.TagManager(cdk.TagType.KEY_VALUE, 'AWS::Lambda::Function'); @@ -157,7 +156,7 @@ class LogRetentionFunction extends CoreConstruct implements cdk.ITaggable { })); // Lambda function - this.cfnFunction = new cdk.CfnResource(this, 'Resource', { + const resource = new cdk.CfnResource(this, 'Resource', { type: 'AWS::Lambda::Function', properties: { Handler: 'index.handler', @@ -167,27 +166,19 @@ class LogRetentionFunction extends CoreConstruct implements cdk.ITaggable { S3Key: asset.s3ObjectKey, }, Role: role.roleArn, + Tags: this.tags.renderedTags, }, }); - this.functionArn = this.cfnFunction.getAtt('Arn'); + this.functionArn = resource.getAtt('Arn'); // Function dependencies role.node.children.forEach((child) => { if (cdk.CfnResource.isCfnResource(child)) { - this.cfnFunction.addDependsOn(child); + resource.addDependsOn(child); } if (cdk.Construct.isConstruct(child) && child.node.defaultChild && cdk.CfnResource.isCfnResource(child.node.defaultChild)) { - this.cfnFunction.addDependsOn(child.node.defaultChild); + resource.addDependsOn(child.node.defaultChild); } }); } - - prepare() { - // Because we're using a CfnResource to avoid a cyclic dependency on aws-lambda - // we lose the automatic application of tags. We have to add them again manually. - // This has to be done in the prepare phase, after Tag Aspects have applied. - if (this.tags.hasTags()) { - this.cfnFunction.addPropertyOverride('Tags', this.tags.renderTags()); - } - } } diff --git a/packages/@aws-cdk/core/lib/tag-manager.ts b/packages/@aws-cdk/core/lib/tag-manager.ts index cdc224500ef1b..bd3bc49f82461 100644 --- a/packages/@aws-cdk/core/lib/tag-manager.ts +++ b/packages/@aws-cdk/core/lib/tag-manager.ts @@ -1,5 +1,7 @@ import { TagType } from './cfn-resource'; import { CfnTag } from './cfn-tag'; +import { Lazy } from './lazy'; +import { IResolvable } from './resolvable'; interface Tag { key: string; @@ -228,7 +230,32 @@ export interface TagManagerOptions { } /** - * TagManager facilitates a common implementation of tagging for Constructs. + * TagManager facilitates a common implementation of tagging for Constructs + * + * Normally, you do not need to use this class, as the CloudFormation specification + * will indicate which resources are taggable. However, sometimes you will need this + * to make custom resources taggable. Used `tagManager.renderedTags` to obtain a + * value that will resolve to the tags at synthesis time. + * + * @example + * import * as cdk from '@aws-cdk/core'; + * + * class MyConstruct extends cdk.Resource implements cdk.ITaggable { + * public readonly tags = new cdk.TagManager(cdk.TagType.KEY_VALUE, 'Whatever::The::Type'); + * + * constructor(scope: cdk.Construct, id: string) { + * super(scope, id); + * + * new cdk.CfnResource(this, 'Resource', { + * type: 'Whatever::The::Type', + * properties: { + * // ... + * Tags: this.tags.renderedTags, + * }, + * }); + * } + * } + * */ export class TagManager { @@ -247,6 +274,14 @@ export class TagManager { */ public readonly tagPropertyName: string; + /** + * A lazy value that represents the rendered tags at synthesis time + * + * If you need to make a custom construct taggable, use the value of this + * property to pass to the `tags` property of the underlying construct. + */ + public readonly renderedTags: IResolvable; + private readonly tags = new Map(); private readonly priorities = new Map(); private readonly tagFormatter: ITagFormatter; @@ -260,6 +295,8 @@ export class TagManager { this._setTag(...this.tagFormatter.parseTags(tagStructure, this.initialTagPriority)); } this.tagPropertyName = options.tagPropertyName || 'tags'; + + this.renderedTags = Lazy.any({ produce: () => this.renderTags() }); } /** @@ -287,6 +324,11 @@ export class TagManager { /** * Renders tags into the proper format based on TagType + * + * This method will eagerly render the tags currently applied. In + * most cases, you should be using `tagManager.renderedTags` instead, + * which will return a `Lazy` value that will resolve to the correct + * tags at synthesis time. */ public renderTags(): any { return this.tagFormatter.formatTags(this.sortedTags);