Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): CfnMapping values cannot be used in other stacks #20616

Merged
merged 14 commits into from
Jun 15, 2022
34 changes: 33 additions & 1 deletion packages/@aws-cdk/core/lib/cfn-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 } };
Expand Down Expand Up @@ -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();
}

/**
Expand Down Expand Up @@ -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);
}
}
59 changes: 58 additions & 1 deletion packages/@aws-cdk/core/test/mappings.test.ts
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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', () => {
Expand Down