diff --git a/packages/@aws-cdk/core/lib/cfn-mapping.ts b/packages/@aws-cdk/core/lib/cfn-mapping.ts index d8a5f6a972f5d..15be7e1054185 100644 --- a/packages/@aws-cdk/core/lib/cfn-mapping.ts +++ b/packages/@aws-cdk/core/lib/cfn-mapping.ts @@ -2,6 +2,8 @@ import { Construct } from 'constructs'; import { Annotations } from './annotations'; import { CfnRefElement } from './cfn-element'; import { Fn } from './cfn-fn'; +import { IResolvable, IResolveContext } from './resolvable'; +import { Stack } from './stack'; import { Token } from './token'; type Mapping = { [k1: string]: { [k2: string]: any } }; @@ -83,7 +85,8 @@ export class CfnMapping extends CfnRefElement { } else { this.lazyRender = true; } - return Fn.findInMap(this.logicalId, key1, key2); + + return new CfnMappingEmbedder(this, this.mapping, key1, key2).toString(); } /** @@ -123,3 +126,32 @@ export class CfnMapping extends CfnRefElement { } } } + +class CfnMappingEmbedder implements IResolvable { + readonly creationStack: string[] = []; + + constructor(private readonly cfnMapping: CfnMapping, readonly mapping: Mapping, private readonly key1: string, private readonly key2: string) { } + + public resolve(context: IResolveContext): string { + const consumingStack = Stack.of(context.scope); + if (consumingStack === Stack.of(this.cfnMapping)) { + return Fn.findInMap(this.cfnMapping.logicalId, this.key1, this.key2); + } + + const constructScope = consumingStack; + const constructId = `MappingCopy-${this.cfnMapping.node.id}-${this.cfnMapping.node.addr}`; + + let mappingCopy = constructScope.node.tryFindChild(constructId) as CfnMapping | undefined; + if (!mappingCopy) { + mappingCopy = new CfnMapping(constructScope, constructId, { + mapping: this.mapping, + }); + } + + return Fn.findInMap(mappingCopy.logicalId, this.key1, this.key2); + } + + public toString() { + return Token.asString(this); + } +} diff --git a/packages/@aws-cdk/core/test/mappings.test.ts b/packages/@aws-cdk/core/test/mappings.test.ts index 5d67aa0163e68..18b6c76ba6bed 100644 --- a/packages/@aws-cdk/core/test/mappings.test.ts +++ b/packages/@aws-cdk/core/test/mappings.test.ts @@ -1,6 +1,6 @@ import { ArtifactMetadataEntryType } from '@aws-cdk/cloud-assembly-schema'; import { CloudAssembly } from '@aws-cdk/cx-api'; -import { App, Aws, CfnMapping, CfnResource, Fn, Stack } from '../lib'; +import { App, Aws, CfnMapping, CfnResource, CfnOutput, Fn, Stack } from '../lib'; import { toCloudFormation } from './util'; describe('mappings', () => { @@ -149,6 +149,63 @@ describe('mappings', () => { }, })).toThrowError(/Attribute name 'us-east-1' must contain only alphanumeric characters./); }); + + test('using the value of a mapping in a different stack copies the mapping to the consuming stack', () => { + const app = new App(); + const creationStack = new Stack(app, 'creationStack'); + const consumingStack = new Stack(app, 'consumingStack'); + + const mapping = new CfnMapping(creationStack, 'MyMapping', { + mapping: { + boo: { + bah: 'foo', + }, + }, + }); + + new CfnOutput(consumingStack, 'Output', { + value: mapping.findInMap('boo', 'bah'), + }); + + const v1 = mapping.findInMap('boo', 'bah'); + let v2 = Fn.findInMap(mapping.logicalId, 'boo', 'bah'); + + const creationStackExpected = { 'Fn::FindInMap': ['MyMapping', 'boo', 'bah'] }; + expect(creationStack.resolve(v1)).toEqual(creationStackExpected); + expect(creationStack.resolve(v2)).toEqual(creationStackExpected); + expect(toCloudFormation(creationStack).Mappings).toEqual({ + MyMapping: { + boo: { + bah: 'foo', + }, + }, + }); + + const mappingCopyLogicalId = 'MappingCopyMyMappingc843c23de60b3672d919ab3e4cb2c14042794164d8'; + v2 = Fn.findInMap(mappingCopyLogicalId, 'boo', 'bah'); + const consumingStackExpected = { 'Fn::FindInMap': [mappingCopyLogicalId, 'boo', 'bah'] }; + + expect(consumingStack.resolve(v1)).toEqual(consumingStackExpected); + expect(consumingStack.resolve(v2)).toEqual(consumingStackExpected); + expect(toCloudFormation(consumingStack).Mappings).toEqual({ + [mappingCopyLogicalId]: { + boo: { + bah: 'foo', + }, + }, + }); + expect(toCloudFormation(consumingStack).Outputs).toEqual({ + Output: { + Value: { + 'Fn::FindInMap': [ + mappingCopyLogicalId, + 'boo', + 'bah', + ], + }, + }, + }); + }); }); describe('lazy mapping', () => {