Skip to content

Commit f2636e5

Browse files
author
Elad Ben-Israel
authored
fix(core): apply overrides after rendering properties (#2685)
Resource overrides (`addOverride` and `addPropertyOverride`) should be applied after rendering properties at the L1 level. Otherwise, validation and capitalization changes would be applied to overrides and this contradicts the idea of being able to specify arbitrary overrides as "patches" to the synthesized resource. The previous behavior had two adverse effects: 1. If a property was unknown, it would be omitted from the resource 2. Properties names would need to be capitalized in camel case instead of 1:1 with the CFN schema. Fixes #2677 BREAKING CHANGE: Properties passed to `addPropertyOverride` should match in capitalization to the CloudFormation schema (normally pascal case). For example, `addPropertyOverride('accessControl', 'xxx')` should now be `addPropertyOverride('AccessControl', 'xxx')`.
1 parent 50d71bf commit f2636e5

File tree

3 files changed

+135
-3
lines changed

3 files changed

+135
-3
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
// tests for the L1 escape hatches (overrides). those are in the IAM module
2+
// because we want to verify them end-to-end, as a complement to the unit
3+
// tests in the @aws-cdk/cdk module
4+
import { expect } from '@aws-cdk/assert';
5+
import { Stack } from '@aws-cdk/cdk';
6+
import { Test } from 'nodeunit';
7+
import iam = require('../lib');
8+
9+
// tslint:disable:object-literal-key-quotes
10+
11+
export = {
12+
'addPropertyOverride should allow overriding supported properties'(test: Test) {
13+
const stack = new Stack();
14+
const user = new iam.User(stack, 'user', {
15+
userName: 'MyUserName'
16+
});
17+
18+
const cfn = user.node.findChild('Resource') as iam.CfnUser;
19+
cfn.addPropertyOverride('UserName', 'OverriddenUserName');
20+
21+
expect(stack).toMatch({
22+
"Resources": {
23+
"user2C2B57AE": {
24+
"Type": "AWS::IAM::User",
25+
"Properties": {
26+
"UserName": "OverriddenUserName"
27+
}
28+
}
29+
}
30+
});
31+
test.done();
32+
},
33+
'addPropertyOverrides should allow specifying arbitrary properties'(test: Test) {
34+
// GIVEN
35+
const stack = new Stack();
36+
const user = new iam.User(stack, 'user', { userName: 'MyUserName' });
37+
const cfn = user.node.findChild('Resource') as iam.CfnUser;
38+
39+
// WHEN
40+
cfn.addPropertyOverride('Hello.World', 'Boom');
41+
42+
// THEN
43+
expect(stack).toMatch({
44+
"Resources": {
45+
"user2C2B57AE": {
46+
"Type": "AWS::IAM::User",
47+
"Properties": {
48+
"UserName": "MyUserName",
49+
"Hello": {
50+
"World": "Boom"
51+
}
52+
}
53+
}
54+
}
55+
});
56+
57+
test.done();
58+
},
59+
'addOverride should allow overriding properties'(test: Test) {
60+
// GIVEN
61+
const stack = new Stack();
62+
const user = new iam.User(stack, 'user', { userName: 'MyUserName' });
63+
const cfn = user.node.findChild('Resource') as iam.CfnUser;
64+
cfn.options.updatePolicy = { useOnlineResharding: true };
65+
66+
// WHEN
67+
cfn.addOverride('Properties.Hello.World', 'Bam');
68+
cfn.addOverride('Properties.UserName', 'HA!');
69+
cfn.addOverride('Joob.Jab', 'Jib');
70+
cfn.addOverride('Joob.Jab', 'Jib');
71+
cfn.addOverride('UpdatePolicy.UseOnlineResharding.Type', 'None');
72+
73+
// THEN
74+
expect(stack).toMatch({
75+
"Resources": {
76+
"user2C2B57AE": {
77+
"Type": "AWS::IAM::User",
78+
"Properties": {
79+
"UserName": "HA!",
80+
"Hello": {
81+
"World": "Bam"
82+
}
83+
},
84+
"Joob": {
85+
"Jab": "Jib"
86+
},
87+
"UpdatePolicy": {
88+
"UseOnlineResharding": {
89+
"Type": "None"
90+
}
91+
}
92+
}
93+
}
94+
});
95+
96+
test.done();
97+
}
98+
};

packages/@aws-cdk/cdk/lib/cfn-resource.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,11 @@ export class CfnResource extends CfnRefElement {
234234
Metadata: ignoreEmpty(this.options.metadata),
235235
Condition: this.options.condition && this.options.condition.logicalId
236236
}, props => {
237-
const r = deepMerge(props, this.rawOverrides);
238-
r.Properties = this.renderProperties(r.Properties);
239-
return r;
237+
// let derived classes to influence how properties are rendered (e.g. change capitalization)
238+
props.Properties = this.renderProperties(props.Properties);
239+
240+
// merge overrides *after* rendering
241+
return deepMerge(props, this.rawOverrides);
240242
})
241243
}
242244
};

packages/@aws-cdk/cdk/test/test.resource.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,38 @@ export = {
549549
test.done();
550550
},
551551

552+
'overrides are applied after render'(test: Test) {
553+
// GIVEN
554+
class MyResource extends CfnResource {
555+
public renderProperties() {
556+
return { Fixed: 123 };
557+
}
558+
}
559+
const stack = new Stack();
560+
const cfn = new MyResource(stack, 'rr', { type: 'AWS::Resource::Type' });
561+
562+
// WHEN
563+
cfn.addPropertyOverride('Boom', 'Hi');
564+
cfn.addOverride('Properties.Foo.Bar', 'Bar');
565+
566+
// THEN
567+
test.deepEqual(stack._toCloudFormation(), {
568+
Resources: {
569+
rr: {
570+
Type: 'AWS::Resource::Type',
571+
Properties: {
572+
Fixed: 123,
573+
Boom: 'Hi',
574+
Foo: {
575+
Bar: 'Bar'
576+
}
577+
}
578+
}
579+
}
580+
});
581+
test.done();
582+
},
583+
552584
'untypedPropertyOverrides': {
553585

554586
'can be used by derived classes to specify overrides before render()'(test: Test) {

0 commit comments

Comments
 (0)