Skip to content

Commit 569d9b4

Browse files
authored
Merge branch 'main' into rmuller/lerna
2 parents 077c4b4 + 49643d6 commit 569d9b4

9 files changed

Lines changed: 266 additions & 18 deletions

File tree

packages/@aws-cdk/cfnspec/spec-source/cfn-docs/cfn-docs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15236,7 +15236,7 @@
1523615236
"IamInstanceProfile": "The name of an IAM instance profile. To create a new IAM instance profile, use the [AWS::IAM::InstanceProfile](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-iam-instanceprofile.html) resource.",
1523715237
"ImageId": "The ID of the AMI. An AMI ID is required to launch an instance and must be specified here or in a launch template.",
1523815238
"InstanceInitiatedShutdownBehavior": "Indicates whether an instance stops or terminates when you initiate shutdown from the instance (using the operating system command for system shutdown).\n\nDefault: `stop`",
15239-
"InstanceType": "The instance type. For more information, see [Instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html) in the *Amazon EC2 User Guide* .\n\nDefault: `m1.small`",
15239+
"InstanceType": "The instance type. For more information, see [Instance types](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html) in the *Amazon EC2 User Guide* .\n\nWhen you change your EBS-backed instance type, instance restart or replacement behavior depends on the instance type compatibility between the old and new types. An instance that's backed by an instance store volume is always replaced. For more information, see [Change the instance type](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-resize.html) in the *Amazon EC2 User Guide* .\n\nDefault: `m1.small`",
1524015240
"Ipv6AddressCount": "The number of IPv6 addresses to associate with the primary network interface. Amazon EC2 chooses the IPv6 addresses from the range of your subnet. You cannot specify this option and the option to assign specific IPv6 addresses in the same request. You can specify this option if you've specified a minimum number of instances to launch.\n\nYou cannot specify this option and the network interfaces option in the same request.",
1524115241
"Ipv6Addresses": "The IPv6 addresses from the range of the subnet to associate with the primary network interface. You cannot specify this option and the option to assign a number of IPv6 addresses in the same request. You cannot specify this option if you've specified a minimum number of instances to launch.\n\nYou cannot specify this option and the network interfaces option in the same request.",
1524215242
"KernelId": "The ID of the kernel.\n\n> We recommend that you use PV-GRUB instead of kernels and RAM disks. For more information, see [PV-GRUB](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/UserProvidedkernels.html) in the *Amazon EC2 User Guide* .",

packages/aws-cdk-lib/aws-ecr/lib/repository.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424

2525
const AUTO_DELETE_IMAGES_RESOURCE_TYPE = 'Custom::ECRAutoDeleteImages';
2626
const AUTO_DELETE_IMAGES_TAG = 'aws-cdk:auto-delete-images';
27+
const REPO_ARN_SYMBOL = Symbol.for('@aws-cdk/aws-ecr.RepoArns');
2728

2829
/**
2930
* Represents an ECR repository.
@@ -857,26 +858,34 @@ export class Repository extends RepositoryBase {
857858
}
858859

859860
private enableAutoDeleteImages() {
860-
// Use a iam policy to allow the custom resource to list & delete
861-
// images in the repository and the ability to get all repositories to find the arn needed on delete.
861+
const firstTime = Stack.of(this).node.tryFindChild(`${AUTO_DELETE_IMAGES_RESOURCE_TYPE}CustomResourceProvider`) === undefined;
862862
const provider = CustomResourceProvider.getOrCreateProvider(this, AUTO_DELETE_IMAGES_RESOURCE_TYPE, {
863863
codeDirectory: path.join(__dirname, 'auto-delete-images-handler'),
864864
runtime: builtInCustomResourceProviderNodeRuntime(this),
865865
description: `Lambda function for auto-deleting images in ${this.repositoryName} repository.`,
866-
policyStatements: [
867-
{
868-
Effect: 'Allow',
869-
Action: [
870-
'ecr:BatchDeleteImage',
871-
'ecr:DescribeRepositories',
872-
'ecr:ListImages',
873-
'ecr:ListTagsForResource',
874-
],
875-
Resource: [this._resource.attrArn],
876-
},
877-
],
878866
});
879867

868+
if (firstTime) {
869+
const repoArns = [this._resource.attrArn];
870+
(provider as any)[REPO_ARN_SYMBOL] = repoArns;
871+
872+
// Use a iam policy to allow the custom resource to list & delete
873+
// images in the repository and the ability to get all repositories to find the arn needed on delete.
874+
// We lazily produce a list of repositories associated with this custom resource provider.
875+
provider.addToRolePolicy({
876+
Effect: 'Allow',
877+
Action: [
878+
'ecr:BatchDeleteImage',
879+
'ecr:DescribeRepositories',
880+
'ecr:ListImages',
881+
'ecr:ListTagsForResource',
882+
],
883+
Resource: Lazy.list({ produce: () => repoArns }),
884+
});
885+
} else {
886+
(provider as any)[REPO_ARN_SYMBOL].push(this._resource.attrArn);
887+
}
888+
880889
const customResource = new CustomResource(this, 'AutoDeleteImagesCustomResource', {
881890
resourceType: AUTO_DELETE_IMAGES_RESOURCE_TYPE,
882891
serviceToken: provider.serviceToken,

packages/aws-cdk-lib/aws-ecr/test/repository.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -976,4 +976,64 @@ describe('repository', () => {
976976
});
977977
});
978978
});
979+
980+
describe('when auto delete images is set to true', () => {
981+
test('permissions are correctly for multiple ecr repos', () => {
982+
const stack = new cdk.Stack();
983+
new ecr.Repository(stack, 'Repo1', {
984+
autoDeleteImages: true,
985+
removalPolicy: cdk.RemovalPolicy.DESTROY,
986+
});
987+
new ecr.Repository(stack, 'Repo2', {
988+
autoDeleteImages: true,
989+
removalPolicy: cdk.RemovalPolicy.DESTROY,
990+
});
991+
992+
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
993+
Policies: [
994+
{
995+
PolicyName: 'Inline',
996+
PolicyDocument: {
997+
Version: '2012-10-17',
998+
Statement: [
999+
{
1000+
Effect: 'Allow',
1001+
Action: [
1002+
'ecr:BatchDeleteImage',
1003+
'ecr:DescribeRepositories',
1004+
'ecr:ListImages',
1005+
'ecr:ListTagsForResource',
1006+
],
1007+
Resource: [
1008+
{
1009+
'Fn::GetAtt': [
1010+
'Repo1DBD717D9',
1011+
'Arn',
1012+
],
1013+
},
1014+
{
1015+
'Fn::GetAtt': [
1016+
'Repo2730A8200',
1017+
'Arn',
1018+
],
1019+
},
1020+
],
1021+
},
1022+
],
1023+
},
1024+
},
1025+
],
1026+
});
1027+
});
1028+
1029+
test('synth fails when removal policy is not DESTROY', () => {
1030+
const stack = new cdk.Stack();
1031+
expect(() => {
1032+
new ecr.Repository(stack, 'Repo', {
1033+
autoDeleteImages: true,
1034+
removalPolicy: cdk.RemovalPolicy.RETAIN,
1035+
});
1036+
}).toThrowError('Cannot use \'autoDeleteImages\' property on a repository without setting removal policy to \'DESTROY\'.');
1037+
});
1038+
});
9791039
});

packages/aws-cdk-lib/core/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,20 @@ It's possible to synthesize the project with more Resources than the allowed (or
12071207

12081208
Set the context key `@aws-cdk/core:stackResourceLimit` with the proper value, being 0 for disable the limit of resources.
12091209

1210+
### Template Indentation
1211+
1212+
The AWS CloudFormation templates generated by CDK include indentation by default.
1213+
Indentation makes the templates more readable, but also increases their size,
1214+
and CloudFormation templates cannot exceed 1MB.
1215+
1216+
It's possible to reduce the size of your templates by suppressing indentation.
1217+
1218+
To do this for all templates, set the context key `@aws-cdk/core:suppressTemplateIndentation` to `true`.
1219+
1220+
To do this for a specific stack, add a `suppressTemplateIndentation: true` property to the
1221+
stack's `StackProps` parameter. You can also set this property to `false` to override
1222+
the context key setting.
1223+
12101224
## App Context
12111225

12121226
[Context values](https://docs.aws.amazon.com/cdk/v2/guide/context.html) are key-value pairs that can be associated with an app, stack, or construct.

packages/aws-cdk-lib/core/lib/stack.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ const MY_STACK_CACHE = Symbol.for('@aws-cdk/core.Stack.myStack');
2828

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

31+
const SUPPRESS_TEMPLATE_INDENTATION_CONTEXT = '@aws-cdk/core:suppressTemplateIndentation';
32+
const TEMPLATE_BODY_MAXIMUM_SIZE = 1_000_000;
33+
3134
const VALID_STACK_NAME_REGEX = /^[A-Za-z][A-Za-z0-9-]*$/;
3235

3336
const MAX_RESOURCES = 500;
@@ -172,6 +175,18 @@ export interface StackProps {
172175
* @default - no permissions boundary is applied
173176
*/
174177
readonly permissionsBoundary?: PermissionsBoundary;
178+
179+
/**
180+
* Enable this flag to suppress indentation in generated
181+
* CloudFormation templates.
182+
*
183+
* If not specified, the value of the `@aws-cdk/core:suppressTemplateIndentation`
184+
* context key will be used. If that is not specified, then the
185+
* default value `false` will be used.
186+
*
187+
* @default - the value of `@aws-cdk/core:suppressTemplateIndentation`, or `false` if that is not set.
188+
*/
189+
readonly suppressTemplateIndentation?: boolean;
175190
}
176191

177192
/**
@@ -359,6 +374,18 @@ export class Stack extends Construct implements ITaggable {
359374

360375
private readonly _stackName: string;
361376

377+
/**
378+
* Enable this flag to suppress indentation in generated
379+
* CloudFormation templates.
380+
*
381+
* If not specified, the value of the `@aws-cdk/core:suppressTemplateIndentation`
382+
* context key will be used. If that is not specified, then the
383+
* default value `false` will be used.
384+
*
385+
* @default - the value of `@aws-cdk/core:suppressTemplateIndentation`, or `false` if that is not set.
386+
*/
387+
private readonly _suppressTemplateIndentation: boolean;
388+
362389
/**
363390
* Creates a new stack.
364391
*
@@ -385,6 +412,7 @@ export class Stack extends Construct implements ITaggable {
385412
this._stackDependencies = { };
386413
this.templateOptions = { };
387414
this._crossRegionReferences = !!props.crossRegionReferences;
415+
this._suppressTemplateIndentation = props.suppressTemplateIndentation ?? this.node.tryGetContext(SUPPRESS_TEMPLATE_INDENTATION_CONTEXT) ?? false;
388416

389417
Object.defineProperty(this, STACK_SYMBOL, { value: true });
390418

@@ -1047,7 +1075,22 @@ export class Stack extends Construct implements ITaggable {
10471075
Annotations.of(this).addInfo(`Number of resources: ${numberOfResources} is approaching allowed maximum of ${this.maxResources}`);
10481076
}
10491077
}
1050-
fs.writeFileSync(outPath, JSON.stringify(template, undefined, 1));
1078+
1079+
const indent = this._suppressTemplateIndentation ? undefined : 1;
1080+
const templateData = JSON.stringify(template, undefined, indent);
1081+
1082+
if (templateData.length > (TEMPLATE_BODY_MAXIMUM_SIZE * 0.8)) {
1083+
const verb = templateData.length > TEMPLATE_BODY_MAXIMUM_SIZE ? 'exceeds' : 'is approaching';
1084+
const advice = this._suppressTemplateIndentation ?
1085+
'Split resources into multiple stacks to reduce template size' :
1086+
'Split resources into multiple stacks or set suppressTemplateIndentation to reduce template size';
1087+
1088+
const message = `Template size ${verb} limit: ${templateData.length}/${TEMPLATE_BODY_MAXIMUM_SIZE}. ${advice}.`;
1089+
1090+
Annotations.of(this).addWarning(message);
1091+
}
1092+
1093+
fs.writeFileSync(outPath, templateData);
10511094

10521095
for (const ctx of this._missingContext) {
10531096
if (lookupRoleArn != null) {

packages/aws-cdk-lib/core/test/stack.test.ts

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import * as fs from 'fs';
12
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
23
import { Construct, Node } from 'constructs';
34
import { toCloudFormation } from './util';
@@ -2104,6 +2105,49 @@ describe('stack', () => {
21042105
});
21052106
}).toThrowError('Region of stack environment must be a \'string\' but received \'number\'');
21062107
});
2108+
2109+
test('indent templates when suppressTemplateIndentation is not set', () => {
2110+
const app = new App();
2111+
2112+
const stack = new Stack(app, 'Stack');
2113+
new CfnResource(stack, 'MyResource', { type: 'MyResourceType' });
2114+
2115+
const assembly = app.synth();
2116+
const artifact = assembly.getStackArtifact(stack.artifactId);
2117+
const templateData = fs.readFileSync(artifact.templateFullPath, 'utf-8');
2118+
2119+
expect(templateData).toMatch(/^{\n \"Resources\": {\n \"MyResource\": {\n \"Type\": \"MyResourceType\"\n }\n }/);
2120+
});
2121+
2122+
test('do not indent templates when suppressTemplateIndentation is true', () => {
2123+
const app = new App();
2124+
2125+
const stack = new Stack(app, 'Stack', { suppressTemplateIndentation: true });
2126+
new CfnResource(stack, 'MyResource', { type: 'MyResourceType' });
2127+
2128+
const assembly = app.synth();
2129+
const artifact = assembly.getStackArtifact(stack.artifactId);
2130+
const templateData = fs.readFileSync(artifact.templateFullPath, 'utf-8');
2131+
2132+
expect(templateData).toMatch(/^{\"Resources\":{\"MyResource\":{\"Type\":\"MyResourceType\"}}/);
2133+
});
2134+
2135+
test('do not indent templates when @aws-cdk/core:suppressTemplateIndentation is true', () => {
2136+
const app = new App({
2137+
context: {
2138+
'@aws-cdk/core:suppressTemplateIndentation': true,
2139+
},
2140+
});
2141+
2142+
const stack = new Stack(app, 'Stack');
2143+
new CfnResource(stack, 'MyResource', { type: 'MyResourceType' });
2144+
2145+
const assembly = app.synth();
2146+
const artifact = assembly.getStackArtifact(stack.artifactId);
2147+
const templateData = fs.readFileSync(artifact.templateFullPath, 'utf-8');
2148+
2149+
expect(templateData).toMatch(/^{\"Resources\":{\"MyResource\":{\"Type\":\"MyResourceType\"}}/);
2150+
});
21072151
});
21082152

21092153
describe('permissions boundary', () => {

packages/aws-cdk-lib/pipelines/test/codepipeline/codepipeline.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,11 @@ test('Policy sizes do not exceed the maximum size', () => {
171171
}
172172
}
173173

174-
Annotations.fromStack(pipelineStack).hasNoWarning('*', Match.anyValue());
174+
// expect template size warning, but no other warnings
175+
const annotations = Annotations.fromStack(pipelineStack);
176+
annotations.hasWarning('*', Match.stringLikeRegexp('^Template size is approaching limit'));
177+
const warnings = annotations.findWarning('*', Match.anyValue());
178+
expect(warnings.length).toEqual(1);
175179
});
176180

177181
test('CodeBuild action role has the right AssumeRolePolicyDocument', () => {

packages/aws-cdk/lib/context-providers/vpcs.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin {
7474
if (type === undefined && subnet.MapPublicIpOnLaunch) { type = SubnetType.Public; }
7575
if (type === undefined && routeTables.hasRouteToIgw(subnet.SubnetId)) { type = SubnetType.Public; }
7676
if (type === undefined && routeTables.hasRouteToNatGateway(subnet.SubnetId)) { type = SubnetType.Private; }
77+
if (type === undefined && routeTables.hasRouteToTransitGateway(subnet.SubnetId)) { type = SubnetType.Private; }
7778
if (type === undefined) { type = SubnetType.Isolated; }
7879

7980
if (!isValidSubnetType(type)) {
@@ -176,6 +177,15 @@ class RouteTables {
176177
return !!table && !!table.Routes && table.Routes.some(route => !!route.NatGatewayId && route.DestinationCidrBlock === '0.0.0.0/0');
177178
}
178179

180+
/**
181+
* Whether the given subnet has a route to a Transit Gateway
182+
*/
183+
public hasRouteToTransitGateway(subnetId: string | undefined): boolean {
184+
const table = this.tableForSubnet(subnetId) || this.mainRouteTable;
185+
186+
return !!table && !!table.Routes && table.Routes.some(route => !!route.TransitGatewayId && route.DestinationCidrBlock === '0.0.0.0/0');
187+
}
188+
179189
/**
180190
* Whether the given subnet has a route to an IGW
181191
*/

0 commit comments

Comments
 (0)