Skip to content

Commit

Permalink
fix(core): CfnMapping values cannot be used in other stacks (#20616)
Browse files Browse the repository at this point in the history
Creating a `CfnMapping` in one stack and then using `findInMap()` in another stack will result in an error at deploy time because the `findInMap()` will turn into a `Fn::FindInMap` that searches for a mapping that does not exist. This change copies the mapping to any stack that references it, even if it is not defined.

Closes #18920.

----

### All Submissions:

* [x] Have you followed the guidelines in our [Contributing guide?](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md)

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md/#adding-new-unconventional-dependencies)

### New Features

* [ ] Have you added the new feature to an [integration test](https://github.com/aws/aws-cdk/blob/main/INTEGRATION_TESTS.md)?
	* [ ] Did you use `yarn integ` to deploy the infrastructure and generate the snapshot (i.e. `yarn integ` without `--dry-run`)?

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
comcalvi committed Jun 15, 2022
1 parent a8de0a1 commit f5c2284
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 2 deletions.
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

0 comments on commit f5c2284

Please sign in to comment.