Skip to content

Commit

Permalink
Merge branch 'main' into rmuller/lerna
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Jun 15, 2023
2 parents 077c4b4 + 49643d6 commit 569d9b4
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15236,7 +15236,7 @@
"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.",
"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.",
"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`",
"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`",
"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`",
"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.",
"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.",
"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* .",
Expand Down
37 changes: 23 additions & 14 deletions packages/aws-cdk-lib/aws-ecr/lib/repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {

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

/**
* Represents an ECR repository.
Expand Down Expand Up @@ -857,26 +858,34 @@ export class Repository extends RepositoryBase {
}

private enableAutoDeleteImages() {
// Use a iam policy to allow the custom resource to list & delete
// images in the repository and the ability to get all repositories to find the arn needed on delete.
const firstTime = Stack.of(this).node.tryFindChild(`${AUTO_DELETE_IMAGES_RESOURCE_TYPE}CustomResourceProvider`) === undefined;
const provider = CustomResourceProvider.getOrCreateProvider(this, AUTO_DELETE_IMAGES_RESOURCE_TYPE, {
codeDirectory: path.join(__dirname, 'auto-delete-images-handler'),
runtime: builtInCustomResourceProviderNodeRuntime(this),
description: `Lambda function for auto-deleting images in ${this.repositoryName} repository.`,
policyStatements: [
{
Effect: 'Allow',
Action: [
'ecr:BatchDeleteImage',
'ecr:DescribeRepositories',
'ecr:ListImages',
'ecr:ListTagsForResource',
],
Resource: [this._resource.attrArn],
},
],
});

if (firstTime) {
const repoArns = [this._resource.attrArn];
(provider as any)[REPO_ARN_SYMBOL] = repoArns;

// Use a iam policy to allow the custom resource to list & delete
// images in the repository and the ability to get all repositories to find the arn needed on delete.
// We lazily produce a list of repositories associated with this custom resource provider.
provider.addToRolePolicy({
Effect: 'Allow',
Action: [
'ecr:BatchDeleteImage',
'ecr:DescribeRepositories',
'ecr:ListImages',
'ecr:ListTagsForResource',
],
Resource: Lazy.list({ produce: () => repoArns }),
});
} else {
(provider as any)[REPO_ARN_SYMBOL].push(this._resource.attrArn);
}

const customResource = new CustomResource(this, 'AutoDeleteImagesCustomResource', {
resourceType: AUTO_DELETE_IMAGES_RESOURCE_TYPE,
serviceToken: provider.serviceToken,
Expand Down
60 changes: 60 additions & 0 deletions packages/aws-cdk-lib/aws-ecr/test/repository.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -976,4 +976,64 @@ describe('repository', () => {
});
});
});

describe('when auto delete images is set to true', () => {
test('permissions are correctly for multiple ecr repos', () => {
const stack = new cdk.Stack();
new ecr.Repository(stack, 'Repo1', {
autoDeleteImages: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
new ecr.Repository(stack, 'Repo2', {
autoDeleteImages: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});

Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
Policies: [
{
PolicyName: 'Inline',
PolicyDocument: {
Version: '2012-10-17',
Statement: [
{
Effect: 'Allow',
Action: [
'ecr:BatchDeleteImage',
'ecr:DescribeRepositories',
'ecr:ListImages',
'ecr:ListTagsForResource',
],
Resource: [
{
'Fn::GetAtt': [
'Repo1DBD717D9',
'Arn',
],
},
{
'Fn::GetAtt': [
'Repo2730A8200',
'Arn',
],
},
],
},
],
},
},
],
});
});

test('synth fails when removal policy is not DESTROY', () => {
const stack = new cdk.Stack();
expect(() => {
new ecr.Repository(stack, 'Repo', {
autoDeleteImages: true,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
}).toThrowError('Cannot use \'autoDeleteImages\' property on a repository without setting removal policy to \'DESTROY\'.');
});
});
});
14 changes: 14 additions & 0 deletions packages/aws-cdk-lib/core/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1207,6 +1207,20 @@ It's possible to synthesize the project with more Resources than the allowed (or

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

### Template Indentation

The AWS CloudFormation templates generated by CDK include indentation by default.
Indentation makes the templates more readable, but also increases their size,
and CloudFormation templates cannot exceed 1MB.

It's possible to reduce the size of your templates by suppressing indentation.

To do this for all templates, set the context key `@aws-cdk/core:suppressTemplateIndentation` to `true`.

To do this for a specific stack, add a `suppressTemplateIndentation: true` property to the
stack's `StackProps` parameter. You can also set this property to `false` to override
the context key setting.

## App Context

[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.
Expand Down
45 changes: 44 additions & 1 deletion packages/aws-cdk-lib/core/lib/stack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ const MY_STACK_CACHE = Symbol.for('@aws-cdk/core.Stack.myStack');

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

const SUPPRESS_TEMPLATE_INDENTATION_CONTEXT = '@aws-cdk/core:suppressTemplateIndentation';
const TEMPLATE_BODY_MAXIMUM_SIZE = 1_000_000;

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

const MAX_RESOURCES = 500;
Expand Down Expand Up @@ -172,6 +175,18 @@ export interface StackProps {
* @default - no permissions boundary is applied
*/
readonly permissionsBoundary?: PermissionsBoundary;

/**
* Enable this flag to suppress indentation in generated
* CloudFormation templates.
*
* If not specified, the value of the `@aws-cdk/core:suppressTemplateIndentation`
* context key will be used. If that is not specified, then the
* default value `false` will be used.
*
* @default - the value of `@aws-cdk/core:suppressTemplateIndentation`, or `false` if that is not set.
*/
readonly suppressTemplateIndentation?: boolean;
}

/**
Expand Down Expand Up @@ -359,6 +374,18 @@ export class Stack extends Construct implements ITaggable {

private readonly _stackName: string;

/**
* Enable this flag to suppress indentation in generated
* CloudFormation templates.
*
* If not specified, the value of the `@aws-cdk/core:suppressTemplateIndentation`
* context key will be used. If that is not specified, then the
* default value `false` will be used.
*
* @default - the value of `@aws-cdk/core:suppressTemplateIndentation`, or `false` if that is not set.
*/
private readonly _suppressTemplateIndentation: boolean;

/**
* Creates a new stack.
*
Expand All @@ -385,6 +412,7 @@ export class Stack extends Construct implements ITaggable {
this._stackDependencies = { };
this.templateOptions = { };
this._crossRegionReferences = !!props.crossRegionReferences;
this._suppressTemplateIndentation = props.suppressTemplateIndentation ?? this.node.tryGetContext(SUPPRESS_TEMPLATE_INDENTATION_CONTEXT) ?? false;

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

Expand Down Expand Up @@ -1047,7 +1075,22 @@ export class Stack extends Construct implements ITaggable {
Annotations.of(this).addInfo(`Number of resources: ${numberOfResources} is approaching allowed maximum of ${this.maxResources}`);
}
}
fs.writeFileSync(outPath, JSON.stringify(template, undefined, 1));

const indent = this._suppressTemplateIndentation ? undefined : 1;
const templateData = JSON.stringify(template, undefined, indent);

if (templateData.length > (TEMPLATE_BODY_MAXIMUM_SIZE * 0.8)) {
const verb = templateData.length > TEMPLATE_BODY_MAXIMUM_SIZE ? 'exceeds' : 'is approaching';
const advice = this._suppressTemplateIndentation ?
'Split resources into multiple stacks to reduce template size' :
'Split resources into multiple stacks or set suppressTemplateIndentation to reduce template size';

const message = `Template size ${verb} limit: ${templateData.length}/${TEMPLATE_BODY_MAXIMUM_SIZE}. ${advice}.`;

Annotations.of(this).addWarning(message);
}

fs.writeFileSync(outPath, templateData);

for (const ctx of this._missingContext) {
if (lookupRoleArn != null) {
Expand Down
44 changes: 44 additions & 0 deletions packages/aws-cdk-lib/core/test/stack.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as fs from 'fs';
import { testDeprecated } from '@aws-cdk/cdk-build-tools';
import { Construct, Node } from 'constructs';
import { toCloudFormation } from './util';
Expand Down Expand Up @@ -2104,6 +2105,49 @@ describe('stack', () => {
});
}).toThrowError('Region of stack environment must be a \'string\' but received \'number\'');
});

test('indent templates when suppressTemplateIndentation is not set', () => {
const app = new App();

const stack = new Stack(app, 'Stack');
new CfnResource(stack, 'MyResource', { type: 'MyResourceType' });

const assembly = app.synth();
const artifact = assembly.getStackArtifact(stack.artifactId);
const templateData = fs.readFileSync(artifact.templateFullPath, 'utf-8');

expect(templateData).toMatch(/^{\n \"Resources\": {\n \"MyResource\": {\n \"Type\": \"MyResourceType\"\n }\n }/);
});

test('do not indent templates when suppressTemplateIndentation is true', () => {
const app = new App();

const stack = new Stack(app, 'Stack', { suppressTemplateIndentation: true });
new CfnResource(stack, 'MyResource', { type: 'MyResourceType' });

const assembly = app.synth();
const artifact = assembly.getStackArtifact(stack.artifactId);
const templateData = fs.readFileSync(artifact.templateFullPath, 'utf-8');

expect(templateData).toMatch(/^{\"Resources\":{\"MyResource\":{\"Type\":\"MyResourceType\"}}/);
});

test('do not indent templates when @aws-cdk/core:suppressTemplateIndentation is true', () => {
const app = new App({
context: {
'@aws-cdk/core:suppressTemplateIndentation': true,
},
});

const stack = new Stack(app, 'Stack');
new CfnResource(stack, 'MyResource', { type: 'MyResourceType' });

const assembly = app.synth();
const artifact = assembly.getStackArtifact(stack.artifactId);
const templateData = fs.readFileSync(artifact.templateFullPath, 'utf-8');

expect(templateData).toMatch(/^{\"Resources\":{\"MyResource\":{\"Type\":\"MyResourceType\"}}/);
});
});

describe('permissions boundary', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,11 @@ test('Policy sizes do not exceed the maximum size', () => {
}
}

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

test('CodeBuild action role has the right AssumeRolePolicyDocument', () => {
Expand Down
10 changes: 10 additions & 0 deletions packages/aws-cdk/lib/context-providers/vpcs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export class VpcNetworkContextProviderPlugin implements ContextProviderPlugin {
if (type === undefined && subnet.MapPublicIpOnLaunch) { type = SubnetType.Public; }
if (type === undefined && routeTables.hasRouteToIgw(subnet.SubnetId)) { type = SubnetType.Public; }
if (type === undefined && routeTables.hasRouteToNatGateway(subnet.SubnetId)) { type = SubnetType.Private; }
if (type === undefined && routeTables.hasRouteToTransitGateway(subnet.SubnetId)) { type = SubnetType.Private; }
if (type === undefined) { type = SubnetType.Isolated; }

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

/**
* Whether the given subnet has a route to a Transit Gateway
*/
public hasRouteToTransitGateway(subnetId: string | undefined): boolean {
const table = this.tableForSubnet(subnetId) || this.mainRouteTable;

return !!table && !!table.Routes && table.Routes.some(route => !!route.TransitGatewayId && route.DestinationCidrBlock === '0.0.0.0/0');
}

/**
* Whether the given subnet has a route to an IGW
*/
Expand Down
Loading

0 comments on commit 569d9b4

Please sign in to comment.