Skip to content

Commit

Permalink
fix(cfn-include): detect a resource cycle in the included template (#…
Browse files Browse the repository at this point in the history
…19871)

Add code that detects when the CloudFormation template being included contains a cycle between any of its resources.
While that's not allowed in pure CloudFormation,
Serverless templates can unfortunately contain cycles before they are processed.

Fixes #16654

----

### All Submissions:

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

### Adding new Unconventional Dependencies:

* [ ] This PR adds new unconventional dependencies following the process described [here](https://github.com/aws/aws-cdk/blob/master/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/master/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
skinny85 authored Apr 12, 2022
1 parent 5472b11 commit 2c2bc0b
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 2 deletions.
9 changes: 7 additions & 2 deletions packages/@aws-cdk/cloudformation-include/lib/cfn-include.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,12 @@ export class CfnInclude extends core.CfnElement {
return cfnCondition;
}

private getOrCreateResource(logicalId: string): core.CfnResource {
private getOrCreateResource(logicalId: string, cycleChain: string[] = []): core.CfnResource {
cycleChain = cycleChain.concat([logicalId]);
if (cycleChain.length !== new Set(cycleChain).size) {
throw new Error(`Found a cycle between resources in the template: ${cycleChain.join(' depends on ')}`);
}

const ret = this.resources[logicalId];
if (ret) {
return ret;
Expand Down Expand Up @@ -618,7 +623,7 @@ export class CfnInclude extends core.CfnElement {
if (!(lId in (self.template.Resources || {}))) {
return undefined;
}
return self.getOrCreateResource(lId);
return self.getOrCreateResource(lId, cycleChain);
},

findRefTarget(elementName: string): core.CfnElement | undefined {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ describe('CDK Include', () => {
includeTestTemplate(stack, 'short-form-get-att-no-dot.yaml');
}).toThrow(/Short-form Fn::GetAtt must contain a '.' in its string argument, got: 'Bucket1Arn'/);
});

test('detects a cycle between resources in the template', () => {
expect(() => {
includeTestTemplate(stack, 'cycle-in-resources.json');
}).toThrow(/Found a cycle between resources in the template: Bucket1 depends on Bucket2 depends on Bucket1/);
});
});

function includeTestTemplate(scope: constructs.Construct, testTemplate: string): inc.CfnInclude {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"Resources": {
"Bucket1": {
"Type": "AWS::S3::Bucket",
"Properties": {
"BucketName": {
"Ref": "Bucket2"
}
}
},
"Bucket2": {
"Type": "AWS::S3::Bucket",
"DependsOn": "Bucket1"
}
}
}

1 comment on commit 2c2bc0b

@AlexanderADietrich
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

I may be wrong and missing an obvious usage of this change, but: it seems to me that while this makes the error more descriptive (rather than a raw stack overflow error, a message describing the cyclical issue) it doesn't solve the issue in full, as I believe the issue also requested an option to parse these templates like Cloudformation does (allowing cycles). Am I missing something in this commit that does enable this?

Thanks!
Alexander.

Please sign in to comment.