From 9facdb6f96b348c9878eb126988ca46dd241b979 Mon Sep 17 00:00:00 2001 From: Rico Huijbers Date: Mon, 16 Sep 2024 12:18:21 +0200 Subject: [PATCH] fix(stack): check stack tags for deploy-time values Stack tags are not rendered to the template, but instead are passed via API call. Verify that stack tags do not contain unresolved values, as they won't work. Closes #28017. --- .../core/lib/stack-synthesizers/_shared.ts | 18 +++++++++++++++--- packages/aws-cdk-lib/core/test/stack.test.ts | 15 +++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts b/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts index 1017f172a850e..05fa9c8b4f34f 100644 --- a/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts +++ b/packages/aws-cdk-lib/core/lib/stack-synthesizers/_shared.ts @@ -3,6 +3,7 @@ import { Node, IConstruct } from 'constructs'; import { ISynthesisSession } from './types'; import * as cxschema from '../../../cloud-assembly-schema'; import { Stack } from '../stack'; +import { Token } from '../token'; /** * Shared logic of writing stack artifact to the Cloud Assembly @@ -20,10 +21,21 @@ export function addStackArtifactToAssembly( stackProps: Partial, additionalStackDependencies: string[]) { + + const stackTags = stack.tags.tagValues(); + // nested stack tags are applied at the AWS::CloudFormation::Stack resource // level and are not needed in the cloud assembly. - if (stack.tags.hasTags()) { - stack.node.addMetadata(cxschema.ArtifactMetadataEntryType.STACK_TAGS, stack.tags.renderTags()); + if (Object.entries(stackTags).length > 0) { + stack.node.addMetadata( + cxschema.ArtifactMetadataEntryType.STACK_TAGS, + Object.entries(stackTags).map(([key, value]) => ({ Key: key, Value: value }))); + + for (const [k, v] of Object.entries(stackTags)) { + if (Token.isUnresolved(k) || Token.isUnresolved(v)) { + throw new Error(`Stack tags may not contain deploy-time values (tag: ${k}=${v}). Apply tags containing deploy-time values to resources only, avoid tagging stacks.`); + } + } } const deps = [ @@ -46,7 +58,7 @@ export function addStackArtifactToAssembly( const properties: cxschema.AwsCloudFormationStackProperties = { templateFile: stack.templateFile, terminationProtection: stack.terminationProtection, - tags: nonEmptyDict(stack.tags.tagValues()), + tags: nonEmptyDict(stackTags), validateOnSynth: session.validateOnSynth, ...stackProps, ...stackNameProperty, diff --git a/packages/aws-cdk-lib/core/test/stack.test.ts b/packages/aws-cdk-lib/core/test/stack.test.ts index 82be67b19499b..88e7e20f5d4f6 100644 --- a/packages/aws-cdk-lib/core/test/stack.test.ts +++ b/packages/aws-cdk-lib/core/test/stack.test.ts @@ -2075,6 +2075,21 @@ describe('stack', () => { expect(asm.getStackArtifact(stack2.artifactId).tags).toEqual(expected); }); + test('stack tags may not contain tokens', () => { + // GIVEN + const app = new App({ + stackTraces: false, + }); + + const stack = new Stack(app, 'stack1', { + tags: { + foo: Lazy.string({ produce: () => 'lazy' }), + }, + }); + + expect(() => app.synth()).toThrow(/Stack tags may not contain deploy-time values/); + }); + test('Termination Protection is reflected in Cloud Assembly artifact', () => { // if the root is an app, invoke "synth" to avoid double synthesis const app = new App();