Skip to content
8 changes: 8 additions & 0 deletions packages/@aws-cdk/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -843,3 +843,11 @@ IAM operator, we need it in the *key* of a `StringEquals` condition. JSON keys
*must be* strings, so to circumvent this limitation, we use `CfnJson`
to "delay" the rendition of this template section to deploy-time. This means
that the value of `StringEquals` in the template will be `{ "Fn::GetAtt": [ "ConditionJson", "Value" ] }`, and will only "expand" to the operator we synthesized during deployment.

### Stack Resource Limit

When deploying to AWS CloudFormation, it needs to keep in check the amount of resources being added inside a Stack. Currently it's possible to check the limits in the [AWS CloudFormation quotas](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cloudformation-limits.html) page.

It's possible to synthesize the project with more Resources than the allowed (or even reduce the number of Resources).

Set the context key `@aws-cdk/core:stackResourceLimit` with the proper value, being 0 for disable the limit of resources.
25 changes: 25 additions & 0 deletions packages/@aws-cdk/core/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ import { Construct as CoreConstruct } from './construct-compat';
const STACK_SYMBOL = Symbol.for('@aws-cdk/core.Stack');
const MY_STACK_CACHE = Symbol.for('@aws-cdk/core.Stack.myStack');

export const STACK_RESOURCE_LIMIT_CONTEXT = '@aws-cdk/core:stackResourceLimit';

const VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/;

const MAX_RESOURCES = 500;

export interface StackProps {
/**
* A description of the stack.
Expand Down Expand Up @@ -753,6 +757,17 @@ export class Stack extends CoreConstruct implements ITaggable {

// write the CloudFormation template as a JSON file
const outPath = path.join(builder.outdir, this.templateFile);

if (this.maxResources > 0) {
const resources = template.Resources || {};
const numberOfResources = Object.keys(resources).length;

if (numberOfResources > this.maxResources) {
throw new Error(`Number of resources: ${numberOfResources} is greater than allowed maximum of ${this.maxResources}`);
} else if (numberOfResources >= (this.maxResources * 0.8)) {
Annotations.of(this).addInfo(`Number of resources: ${numberOfResources} is approaching allowed maximum of ${this.maxResources}`);
}
}
fs.writeFileSync(outPath, JSON.stringify(template, undefined, 2));

for (const ctx of this._missingContext) {
Expand Down Expand Up @@ -907,6 +922,16 @@ export class Stack extends CoreConstruct implements ITaggable {
};
}

/**
* Maximum number of resources in the stack
*
* Set to 0 to mean "unlimited".
*/
private get maxResources(): number {
const contextLimit = this.node.tryGetContext(STACK_RESOURCE_LIMIT_CONTEXT);
return contextLimit !== undefined ? parseInt(contextLimit, 10) : MAX_RESOURCES;
}

/**
* Check whether this stack has a (transitive) dependency on another stack
*
Expand Down
62 changes: 62 additions & 0 deletions packages/@aws-cdk/core/test/stack.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,68 @@ nodeunitShim({
test.done();
},

'when stackResourceLimit is default, should give error'(test: Test) {
// GIVEN
const app = new App({});

const stack = new Stack(app, 'MyStack');

// WHEN
for (let index = 0; index < 1000; index++) {
new CfnResource(stack, `MyResource-${index}`, { type: 'MyResourceType' });
}

test.throws(() => {
app.synth();
}, 'Number of resources: 1000 is greater than allowed maximum of 500');

test.done();
},

'when stackResourceLimit is defined, should give the proper error'(test: Test) {
// GIVEN
const app = new App({
context: {
'@aws-cdk/core:stackResourceLimit': 100,
},
});

const stack = new Stack(app, 'MyStack');

// WHEN
for (let index = 0; index < 200; index++) {
new CfnResource(stack, `MyResource-${index}`, { type: 'MyResourceType' });
}

test.throws(() => {
app.synth();
}, 'Number of resources: 200 is greater than allowed maximum of 100');

test.done();
},

'when stackResourceLimit is 0, should not give error'(test: Test) {
// GIVEN
const app = new App({
context: {
'@aws-cdk/core:stackResourceLimit': 0,
},
});

const stack = new Stack(app, 'MyStack');

// WHEN
for (let index = 0; index < 1000; index++) {
new CfnResource(stack, `MyResource-${index}`, { type: 'MyResourceType' });
}

test.doesNotThrow(() => {
app.synth();
});

test.done();
},

'stack.templateOptions can be used to set template-level options'(test: Test) {
const stack = new Stack();

Expand Down