Skip to content

Commit

Permalink
Merge branch 'master' into cloudformation-integ-test
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] authored Feb 25, 2021
2 parents acd4395 + 8712b40 commit d2e4692
Show file tree
Hide file tree
Showing 25 changed files with 496 additions and 36 deletions.
1 change: 1 addition & 0 deletions packages/@aws-cdk/aws-cloudwatch/lib/private/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ function metricGraphJson(metric: IMetric, yAxis?: string, id?: string) {

withExpression(expr) {
options.expression = expr.expression;
if (expr.period && expr.period !== 300) { options.period = expr.period; }
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@
{
"Ref": "AWS::Region"
},
"\",\"metrics\":[[{\"label\":\"Total Messages\",\"expression\":\"m1+m2\"}],[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"",
"\",\"metrics\":[[{\"label\":\"Total Messages\",\"expression\":\"m1+m2\",\"period\":60}],[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"",
{
"Fn::GetAtt": [
"queue",
Expand Down Expand Up @@ -120,7 +120,7 @@
{
"Ref": "AWS::Region"
},
"\",\"metrics\":[[{\"label\":\"Total Messages\",\"expression\":\"m1+m2\"}],[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"",
"\",\"metrics\":[[{\"label\":\"Total Messages\",\"expression\":\"m1+m2\",\"period\":60}],[\"AWS/SQS\",\"ApproximateNumberOfMessagesVisible\",\"QueueName\",\"",
{
"Fn::GetAtt": [
"queue",
Expand Down
22 changes: 22 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/test/test.metric-math.ts
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,28 @@ export = {
test.done();
},

'top level period in a MathExpression is respected in its metrics'(test: Test) {
const graph = new GraphWidget({
left: [
a,
new MathExpression({
expression: 'a + b',
usingMetrics: { a, b },
period: Duration.minutes(1),
}),
],
});

// THEN
graphMetricsAre(test, graph, [
['Test', 'ACount'],
[{ label: 'a + b', expression: 'a + b', period: 60 }],
['Test', 'ACount', { visible: false, id: 'a', period: 60 }],
['Test', 'BCount', { visible: false, id: 'b', period: 60 }],
]);
test.done();
},

'MathExpression controls period of metrics transitively used in it'(test: Test) {
// Same as the previous test, but recursively

Expand Down
3 changes: 3 additions & 0 deletions packages/@aws-cdk/aws-elasticloadbalancingv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,9 @@ lb.addRedirect({

If you do not provide any options for this method, it redirects HTTP port 80 to HTTPS port 443.

By default all ingress traffic will be allowed on the source port. If you want to be more selective with your
ingress rules then set `open: false` and use the listener's `connections` object to selectively grant access to the listener.

## Defining a Network Load Balancer

Network Load Balancers are defined in a similar way to Application Load
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ export class ApplicationLoadBalancer extends BaseLoadBalancer implements IApplic
return this.addListener(`Redirect${sourcePort}To${targetPort}`, {
protocol: props.sourceProtocol ?? ApplicationProtocol.HTTP,
port: sourcePort,
open: true,
open: props.open ?? true,
defaultAction: ListenerAction.redirect({
port: targetPort,
protocol: props.targetProtocol ?? ApplicationProtocol.HTTPS,
Expand Down Expand Up @@ -665,4 +665,19 @@ export interface ApplicationLoadBalancerRedirectConfig {
*/
readonly targetPort?: number;

/**
* Allow anyone to connect to this listener
*
* If this is specified, the listener will be opened up to anyone who can reach it.
* For internal load balancers this is anyone in the same VPC. For public load
* balancers, this is anyone on the internet.
*
* If you want to be more selective about who can access this load
* balancer, set this to `false` and use the listener's `connections`
* object to selectively grant access to the listener.
*
* @default true
*/
readonly open?: boolean;

}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ export class ApplicationTargetGroup extends TargetGroupBase implements IApplicat

if (props) {
if (props.slowStart !== undefined) {
if (props.slowStart.toSeconds() < 30 || props.slowStart.toSeconds() > 900) {
throw new Error('Slow start duration value must be between 30 and 900 seconds.');
}
this.setAttribute('slow_start.duration_seconds', props.slowStart.toSeconds().toString());
}
if (props.stickinessCookieDuration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,31 @@ describe('tests', () => {
});
});

test('Can supress default ingress rules on a simple redirect response', () => {
// GIVEN
const stack = new cdk.Stack();
const vpc = new ec2.Vpc(stack, 'Stack');

const loadBalancer = new elbv2.ApplicationLoadBalancer(stack, 'LB', {
vpc,
});

// WHEN
loadBalancer.addRedirect({ open: false });

// THEN
expect(stack).not.toHaveResourceLike('AWS::EC2::SecurityGroup', {
SecurityGroupIngress: [
{
CidrIp: '0.0.0.0/0',
Description: 'Allow from anyone on port 80',
IpProtocol: 'tcp',
},
],
});

});

test('Can add simple redirect responses with custom values', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,4 +205,21 @@ describe('tests', () => {
});
}).toThrow(/Stickiness cookie duration value must be between 1 second and 7 days \(604800 seconds\)./);
});

test('Bad slow start duration value', () => {
// GIVEN
const app = new cdk.App();
const stack = new cdk.Stack(app, 'Stack');
const vpc = new ec2.Vpc(stack, 'VPC', {});

// THEN
[cdk.Duration.minutes(16), cdk.Duration.seconds(29)].forEach((badDuration, i) => {
expect(() => {
new elbv2.ApplicationTargetGroup(stack, `TargetGroup${i}`, {
slowStart: badDuration,
vpc,
});
}).toThrow(/Slow start duration value must be between 30 and 900 seconds./);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,10 @@
"Port": 80,
"Protocol": "HTTP",
"TargetGroupAttributes": [
{
"Key": "slow_start.duration_seconds",
"Value": "60"
},
{
"Key": "stickiness.enabled",
"Value": "true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const group2 = listener.addTargets('ConditionalTarget', {
targets: [new elbv2.IpTarget('10.0.128.5')],
stickinessCookieDuration: cdk.Duration.minutes(5),
stickinessCookieName: 'MyDeliciousCookie',
slowStart: cdk.Duration.minutes(1),
});

group1.metricTargetResponseTime().createAlarm(stack, 'ResponseTimeHigh1', {
Expand Down
3 changes: 2 additions & 1 deletion packages/@aws-cdk/aws-events-targets/lib/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,14 @@ export function singletonEventRole(scope: IConstruct, policyStatements: iam.Poli
export function addLambdaPermission(rule: events.IRule, handler: lambda.IFunction): void {
let scope: Construct | undefined;
let node: ConstructNode = handler.permissionsNode;
let permissionId = `AllowEventRule${Names.nodeUniqueId(rule.node)}`;
if (rule instanceof Construct) {
// Place the Permission resource in the same stack as Rule rather than the Function
// This is to reduce circular dependency when the lambda handler and the rule are across stacks.
scope = rule;
node = rule.node;
permissionId = `AllowEventRule${Names.nodeUniqueId(handler.node)}`;
}
const permissionId = `AllowEventRule${Names.nodeUniqueId(rule.node)}`;
if (!node.tryFindChild(permissionId)) {
handler.addPermission(permissionId, {
scope,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,26 @@
]
}
},
"ScheduleRuleAllowEventRuleawscdkawsapitargetintegScheduleRule51140722763E20C1": {
"ScheduleRuleAllowEventRuleawscdkawsapitargetintegScheduleRuleScheduleRuleTarget0HandlerF2C0C898874A4805": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
"FunctionName": {
"Fn::GetAtt": [
"AWSb4cf1abd4e4f4bc699441af7ccd9ec371511E620",
"Arn"
]
},
"Principal": "events.amazonaws.com",
"SourceArn": {
"Fn::GetAtt": [
"ScheduleRuleDA5BD877",
"Arn"
]
}
}
},
"ScheduleRuleAllowEventRuleawscdkawsapitargetintegScheduleRuleScheduleRuleTarget1Handler4688817C0179F894": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
Expand Down Expand Up @@ -198,7 +217,7 @@
]
}
},
"PatternRuleAllowEventRuleawscdkawsapitargetintegPatternRule3D388581AA4F776B": {
"PatternRuleAllowEventRuleawscdkawsapitargetintegPatternRulePatternRuleTarget0HandlerA0821464BB49C5D3": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@
]
}
},
"TimerAllowEventRulelambdaeventsTimer0E6AB6D890F582F4": {
"TimerAllowEventRulelambdaeventsMyFunc910E580F793D7BBB": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
Expand Down Expand Up @@ -105,7 +105,7 @@
]
}
},
"Timer2AllowEventRulelambdaeventsTimer27F866A1E50659689": {
"Timer2AllowEventRulelambdaeventsMyFunc910E580FCCD9CDCE": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:InvokeFunction",
Expand Down
25 changes: 23 additions & 2 deletions packages/@aws-cdk/aws-events-targets/test/lambda/lambda.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,27 @@ test('adding same lambda function as target mutiple times creates permission onl
expect(stack).toCountResources('AWS::Lambda::Permission', 1);
});

test('adding different lambda functions as target mutiple times creates multiple permissions', () => {
// GIVEN
const stack = new cdk.Stack();
const fn1 = newTestLambda(stack);
const fn2 = newTestLambda(stack, '2');
const rule = new events.Rule(stack, 'Rule', {
schedule: events.Schedule.rate(cdk.Duration.minutes(1)),
});

// WHEN
rule.addTarget(new targets.LambdaFunction(fn1, {
event: events.RuleTargetInput.fromObject({ key: 'value1' }),
}));
rule.addTarget(new targets.LambdaFunction(fn2, {
event: events.RuleTargetInput.fromObject({ key: 'value2' }),
}));

// THEN
expect(stack).toCountResources('AWS::Lambda::Permission', 2);
});

test('adding same singleton lambda function as target mutiple times creates permission only once', () => {
// GIVEN
const stack = new cdk.Stack();
Expand Down Expand Up @@ -126,8 +147,8 @@ test('lambda handler and cloudwatch event across stacks', () => {
expect(eventStack).toCountResources('AWS::Lambda::Permission', 1);
});

function newTestLambda(scope: constructs.Construct) {
return new lambda.Function(scope, 'MyLambda', {
function newTestLambda(scope: constructs.Construct, suffix = '') {
return new lambda.Function(scope, `MyLambda${suffix}`, {
code: new lambda.InlineCode('foo'),
handler: 'bar',
runtime: lambda.Runtime.PYTHON_2_7,
Expand Down
23 changes: 23 additions & 0 deletions packages/@aws-cdk/aws-events/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,26 @@ In this situation, the CDK will wire the 2 accounts together:

For more information, see the
[AWS documentation on cross-account events](https://docs.aws.amazon.com/eventbridge/latest/userguide/eventbridge-cross-account-event-delivery.html).

## Archiving

It is possible to archive all or some events sent to an event bus. It is then possible to [replay these events](https://aws.amazon.com/blogs/aws/new-archive-and-replay-events-with-amazon-eventbridge/).

```ts
import * as cdk from '@aws-cdk/core';

const stack = new stack();

const bus = new EventBus(stack, 'bus', {
eventBusName: 'MyCustomEventBus'
});

bus.archive('MyArchive', {
archiveName: 'MyCustomEventBusArchive',
description: 'MyCustomerEventBus Archive',
eventPattern: {
account: [stack.account],
},
retention: cdk.Duration.days(365),
});
```
77 changes: 77 additions & 0 deletions packages/@aws-cdk/aws-events/lib/archive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { Duration, Resource } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { IEventBus } from './event-bus';
import { EventPattern } from './event-pattern';
import { CfnArchive } from './events.generated';

/**
* The event archive base properties
*/
export interface BaseArchiveProps {
/**
* The name of the archive.
*
* @default - Automatically generated
*/
readonly archiveName?: string;
/**
* A description for the archive.
*
* @default - none
*/
readonly description?: string;
/**
* An event pattern to use to filter events sent to the archive.
*/
readonly eventPattern: EventPattern;
/**
* The number of days to retain events for. Default value is 0. If set to 0, events are retained indefinitely.
* @default - Infinite
*/
readonly retention?: Duration;
}


/**
* The event archive properties
*/
export interface ArchiveProps extends BaseArchiveProps {
/**
* The event source associated with the archive.
*/
readonly sourceEventBus: IEventBus;
}

/**
* Define an EventBridge Archive
*
* @resource AWS::Events::Archive
*/
export class Archive extends Resource {
/**
* The archive name.
* @attribute
*/
public readonly archiveName: string;

/**
* The ARN of the archive created.
* @attribute
*/
public readonly archiveArn: string;

constructor(scope: Construct, id: string, props: ArchiveProps) {
super(scope, id, { physicalName: props.archiveName });

let archive = new CfnArchive(this, 'Archive', {
sourceArn: props.sourceEventBus.eventBusArn,
description: props.description,
eventPattern: props.eventPattern,
retentionDays: props.retention?.toDays({ integral: true }) || 0,
archiveName: this.physicalName,
});

this.archiveArn = archive.attrArn;
this.archiveName = archive.attrArchiveName;
}
}
Loading

0 comments on commit d2e4692

Please sign in to comment.