Skip to content

Commit 673dd82

Browse files
authored
fix(diff): allow strings to be passed for boolean properties (#10378)
In CloudFormation, it's legal to pass as string where a boolean value is expected. Take that into account when performing diffs, and allow that case in `@aws-cdk/cloudformation-include`. ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 4751f42 commit 673dd82

File tree

5 files changed

+60
-10
lines changed

5 files changed

+60
-10
lines changed

packages/@aws-cdk/cloudformation-diff/lib/diff/util.ts

+6
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@
1515
*/
1616
export function deepEqual(lvalue: any, rvalue: any): boolean {
1717
if (lvalue === rvalue) { return true; }
18+
// CloudFormation allows passing strings into boolean-typed fields
19+
if (((typeof lvalue === 'string' && typeof rvalue === 'boolean') ||
20+
(typeof lvalue === 'boolean' && typeof rvalue === 'string')) &&
21+
lvalue.toString() === rvalue.toString()) {
22+
return true;
23+
}
1824
// allows a numeric 10 and a literal "10" to be equivalent;
1925
// this is consistent with CloudFormation.
2026
if (((typeof lvalue === 'string') || (typeof rvalue === 'string')) && (parseFloat(lvalue) === parseFloat(rvalue))) {

packages/@aws-cdk/cloudformation-diff/test/diff-template.test.ts

+34-1
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,6 @@ test('single element arrays are equivalent to the single element in DependsOn ex
403403
expect(differences.resources.differenceCount).toBe(0);
404404
});
405405

406-
407406
test('array equivalence is independent of element order in DependsOn expressions', () => {
408407
// GIVEN
409408
const currentTemplate = {
@@ -487,3 +486,37 @@ test('arrays that differ only in element order are considered unequal outside of
487486
differences = diffTemplate(newTemplate, currentTemplate);
488487
expect(differences.resources.differenceCount).toBe(1);
489488
});
489+
490+
test('boolean properties are considered equal with their stringified counterparts', () => {
491+
// GIVEN
492+
const currentTemplate = {
493+
Resources: {
494+
Bucket: {
495+
Type: 'AWS::S3::Bucket',
496+
Properties: {
497+
PublicAccessBlockConfiguration: {
498+
BlockPublicAcls: 'true',
499+
},
500+
},
501+
},
502+
},
503+
};
504+
const newTemplate = {
505+
Resources: {
506+
Bucket: {
507+
Type: 'AWS::S3::Bucket',
508+
Properties: {
509+
PublicAccessBlockConfiguration: {
510+
BlockPublicAcls: true,
511+
},
512+
},
513+
},
514+
},
515+
};
516+
517+
// WHEN
518+
const differences = diffTemplate(currentTemplate, newTemplate);
519+
520+
// THEN
521+
expect(differences.differenceCount).toBe(0);
522+
});

packages/@aws-cdk/cloudformation-include/test/test-templates/fn-sub-map-dotted-attributes.json

+1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
"AvailabilityZones": [
1515
"us-east-1a"
1616
],
17+
"CrossZone": "true",
1718
"Listeners": [{
1819
"LoadBalancerPort": "80",
1920
"InstancePort": "80",

packages/@aws-cdk/cloudformation-include/test/test-templates/resource-attribute-update-policy.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"BeforeAllowTrafficHook" : "Lambda2",
5454
"DeploymentGroupName" : { "Ref": "CodeDeployDg" }
5555
},
56-
"EnableVersionUpgrade": true,
56+
"EnableVersionUpgrade": "true",
5757
"UseOnlineResharding": false
5858
}
5959
}

packages/@aws-cdk/core/lib/cfn-parse.ts

+18-8
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,20 @@ export class FromCloudFormation {
3232
// nothing to for any but return it
3333
public static getAny(value: any) { return value; }
3434

35-
// nothing to do - if 'value' is not a boolean or a Token,
36-
// a validator should report that at runtime
37-
public static getBoolean(value: any): boolean | IResolvable { return value; }
35+
public static getBoolean(value: any): boolean | IResolvable {
36+
if (typeof value === 'string') {
37+
// CloudFormation allows passing strings as boolean
38+
switch (value) {
39+
case 'true': return true;
40+
case 'false': return false;
41+
default: throw new Error(`Expected 'true' or 'false' for boolean value, got: '${value}'`);
42+
}
43+
}
44+
45+
// in all other cases, just return the value,
46+
// and let a validator handle if it's not a boolean
47+
return value;
48+
}
3849

3950
public static getDate(value: any): Date | IResolvable {
4051
// if the date is a deploy-time value, just return it
@@ -80,9 +91,8 @@ export class FromCloudFormation {
8091
}
8192

8293
// return a number, if the input can be parsed as one
83-
let parsedValue;
8494
if (typeof value === 'string') {
85-
parsedValue = parseFloat(value);
95+
const parsedValue = parseFloat(value);
8696
if (!isNaN(parsedValue)) {
8797
return parsedValue;
8898
}
@@ -338,8 +348,8 @@ export class CfnParser {
338348
autoScalingRollingUpdate: parseAutoScalingRollingUpdate(policy.AutoScalingRollingUpdate),
339349
autoScalingScheduledAction: parseAutoScalingScheduledAction(policy.AutoScalingScheduledAction),
340350
codeDeployLambdaAliasUpdate: parseCodeDeployLambdaAliasUpdate(policy.CodeDeployLambdaAliasUpdate),
341-
enableVersionUpgrade: policy.EnableVersionUpgrade,
342-
useOnlineResharding: policy.UseOnlineResharding,
351+
enableVersionUpgrade: FromCloudFormation.getBoolean(policy.EnableVersionUpgrade),
352+
useOnlineResharding: FromCloudFormation.getBoolean(policy.UseOnlineResharding),
343353
});
344354

345355
function parseAutoScalingReplacingUpdate(p: any): CfnAutoScalingReplacingUpdate | undefined {
@@ -359,7 +369,7 @@ export class CfnParser {
359369
minSuccessfulInstancesPercent: FromCloudFormation.getNumber(p.MinSuccessfulInstancesPercent),
360370
pauseTime: FromCloudFormation.getString(p.PauseTime),
361371
suspendProcesses: FromCloudFormation.getStringArray(p.SuspendProcesses),
362-
waitOnResourceSignals: p.WaitOnResourceSignals,
372+
waitOnResourceSignals: FromCloudFormation.getBoolean(p.WaitOnResourceSignals),
363373
});
364374
}
365375

0 commit comments

Comments
 (0)