Skip to content

Commit

Permalink
Merge branch 'main' into corymhall/integ-tests/waiter
Browse files Browse the repository at this point in the history
  • Loading branch information
mergify[bot] committed Oct 4, 2022
2 parents 295e431 + aedee00 commit dfe4673
Show file tree
Hide file tree
Showing 64 changed files with 2,272 additions and 164 deletions.
1 change: 1 addition & 0 deletions .mergify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pull_request_rules:
- base=main
- -merged
- -closed
- changes-requested-reviews-by!=aws-cdk-automation
- name: if fails conventional commits
actions:
comment:
Expand Down
5 changes: 5 additions & 0 deletions packages/@aws-cdk/assertions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const templateJson = '{ "Resources": ... }'; /* The CloudFormation template as J
const template = Template.fromString(templateJson);
```

**Cyclical Resources Note**

If allowing cyclical references is desired, for example in the case of unprocessed Transform templates, supply TemplateParsingOptions and
set skipCyclicalDependenciesCheck to true. In all other cases, will fail on detecting cyclical dependencies.

## Full Template Match

The simplest assertion would be to assert that the template matches a given
Expand Down
40 changes: 31 additions & 9 deletions packages/@aws-cdk/assertions/lib/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,42 @@ export class Template {
/**
* Base your assertions on the CloudFormation template synthesized by a CDK `Stack`.
* @param stack the CDK Stack to run assertions on
* @param templateParsingOptions Optional param to configure template parsing behavior, such as disregarding circular
* dependencies.
*/
public static fromStack(stack: Stack): Template {
return new Template(toTemplate(stack));
public static fromStack(stack: Stack, templateParsingOptions?: TemplateParsingOptions): Template {
return new Template(toTemplate(stack), templateParsingOptions);
}

/**
* Base your assertions from an existing CloudFormation template formatted as an in-memory
* JSON object.
* @param template the CloudFormation template formatted as a nested set of records
* @param templateParsingOptions Optional param to configure template parsing behavior, such as disregarding circular
* dependencies.
*/
public static fromJSON(template: { [key: string] : any }): Template {
return new Template(template);
public static fromJSON(template: { [key: string] : any }, templateParsingOptions?: TemplateParsingOptions): Template {
return new Template(template, templateParsingOptions);
}

/**
* Base your assertions from an existing CloudFormation template formatted as a
* JSON string.
* @param template the CloudFormation template in
* @param templateParsingOptions Optional param to configure template parsing behavior, such as disregarding circular
* dependencies.
*/
public static fromString(template: string): Template {
return new Template(JSON.parse(template));
public static fromString(template: string, templateParsingOptions?: TemplateParsingOptions): Template {
return new Template(JSON.parse(template), templateParsingOptions);
}

private readonly template: TemplateType;

private constructor(template: { [key: string]: any }) {
private constructor(template: { [key: string]: any }, templateParsingOptions: TemplateParsingOptions = {}) {
this.template = template as TemplateType;
checkTemplateForCyclicDependencies(this.template);
if (!templateParsingOptions?.skipCyclicalDependenciesCheck ?? true) {
checkTemplateForCyclicDependencies(this.template);
}
}

/**
Expand Down Expand Up @@ -243,6 +251,20 @@ export class Template {
}
}

/**
* Options to configure template parsing behavior, such as disregarding circular
* dependencies.
*/
export interface TemplateParsingOptions {
/**
* If set to true, will skip checking for cyclical / circular dependencies. Should be set to false other than for
* templates that are valid despite containing cycles, such as unprocessed transform stacks.
*
* @default false
*/
readonly skipCyclicalDependenciesCheck?: boolean;
}

function toTemplate(stack: Stack): any {
const root = stack.node.root;
if (!Stage.isStage(root)) {
Expand All @@ -255,4 +277,4 @@ function toTemplate(stack: Stack): any {
return JSON.parse(fs.readFileSync(path.join(assembly.directory, stack.templateFile)).toString('utf-8'));
}
return assembly.getStackArtifact(stack.artifactId).template;
}
}
21 changes: 21 additions & 0 deletions packages/@aws-cdk/assertions/test/template.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1194,6 +1194,27 @@ describe('Template', () => {
});
}).toThrow(/dependency cycle/);
});

test('does not throw when given a template with cyclic dependencies if check is skipped', () => {
expect(() => {
Template.fromJSON({
Resources: {
Res1: {
Type: 'Foo',
Properties: {
Thing: { Ref: 'Res2' },
},
},
Res2: {
Type: 'Foo',
DependsOn: ['Res1'],
},
},
}, {
skipCyclicalDependenciesCheck: true,
});
}).not.toThrow(/dependency cycle/);
});
});

function expectToThrow(fn: () => void, msgs: (RegExp | string)[], done: jest.DoneCallback): void {
Expand Down
24 changes: 24 additions & 0 deletions packages/@aws-cdk/aws-cloudwatch/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,30 @@ new cloudwatch.CompositeAlarm(this, 'MyAwesomeCompositeAlarm', {
});
```

#### Actions Suppressor

If you want to disable actions of a Composite Alarm based on a certain condition, you can use [Actions Suppression](https://www.amazonaws.cn/en/new/2022/amazon-cloudwatch-supports-composite-alarm-actions-suppression/).

```ts
declare const childAlarm1: cloudwatch.Alarm;
declare const childAlarm2: cloudwatch.Alarm;
declare const onAlarmAction: cloudwatch.IAlarmAction;
declare const onOkAction: cloudwatch.IAlarmAction;
declare const actionsSuppressor: cloudwatch.Alarm;

const alarmRule = cloudwatch.AlarmRule.anyOf(alarm1, alarm2);

const myCompositeAlarm = new cloudwatch.CompositeAlarm(this, 'MyAwesomeCompositeAlarm', {
alarmRule,
actionsSuppressor,
});
myCompositeAlarm.addAlarmActions(onAlarmAction);
myComposireAlarm.addOkAction(onOkAction);
```

In the provided example, if `actionsSuppressor` is in `ALARM` state, `onAlarmAction` won't be triggered even if `myCompositeAlarm` goes into `ALARM` state.
Similar, if `actionsSuppressor` is in `ALARM` state and `myCompositeAlarm` goes from `ALARM` into `OK` state, `onOkAction` won't be triggered.

### A note on units

In CloudWatch, Metrics datums are emitted with units, such as `seconds` or
Expand Down
42 changes: 39 additions & 3 deletions packages/@aws-cdk/aws-cloudwatch/lib/composite-alarm.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ArnFormat, Lazy, Names, Stack } from '@aws-cdk/core';
import { ArnFormat, Lazy, Names, Stack, Duration } from '@aws-cdk/core';
import { Construct } from 'constructs';
import { AlarmBase, IAlarm, IAlarmRule } from './alarm-base';
import { CfnCompositeAlarm } from './cloudwatch.generated';
Expand All @@ -18,14 +18,14 @@ export interface CompositeAlarmProps {
/**
* Description for the alarm
*
* @default No description
* @default - No description.
*/
readonly alarmDescription?: string;

/**
* Name of the alarm
*
* @default Automatically generated name
* @default - Automatically generated name.
*/
readonly compositeAlarmName?: string;

Expand All @@ -34,6 +34,28 @@ export interface CompositeAlarmProps {
*/
readonly alarmRule: IAlarmRule;

/**
* Actions will be suppressed if the suppressor alarm is in the ALARM state.
*
* @default - alarm will not be suppressed.
*/
readonly actionsSuppressor?: IAlarm;

/**
* The maximum duration that the composite alarm waits after suppressor alarm goes out of the ALARM state.
* After this time, the composite alarm performs its actions.
*
* @default - 1 minute extension period will be set.
*/
readonly actionsSuppressorExtensionPeriod?: Duration;

/**
* The maximum duration that the composite alarm waits for the suppressor alarm to go into the ALARM state.
* After this time, the composite alarm performs its actions.
*
* @default - 1 minute wait period will be set.
*/
readonly actionsSuppressorWaitPeriod?: Duration;
}

/**
Expand Down Expand Up @@ -98,6 +120,17 @@ export class CompositeAlarm extends AlarmBase {
throw new Error('Alarm Rule expression cannot be greater than 10240 characters, please reduce the conditions in the Alarm Rule');
}

let extensionPeriod = props.actionsSuppressorExtensionPeriod;
let waitPeriod = props.actionsSuppressorWaitPeriod;
if (props.actionsSuppressor === undefined) {
if (extensionPeriod !== undefined || waitPeriod !== undefined) {
throw new Error('ActionsSuppressor Extension/Wait Periods require an ActionsSuppressor to be set.');
}
} else {
extensionPeriod = extensionPeriod ?? Duration.minutes(1);
waitPeriod = waitPeriod ?? Duration.minutes(1);
}

this.alarmRule = props.alarmRule.renderAlarmRule();

const alarm = new CfnCompositeAlarm(this, 'Resource', {
Expand All @@ -108,6 +141,9 @@ export class CompositeAlarm extends AlarmBase {
alarmActions: Lazy.list({ produce: () => this.alarmActionArns }),
insufficientDataActions: Lazy.list({ produce: (() => this.insufficientDataActionArns) }),
okActions: Lazy.list({ produce: () => this.okActionArns }),
actionsSuppressor: props.actionsSuppressor?.alarmArn,
actionsSuppressorExtensionPeriod: extensionPeriod?.toSeconds(),
actionsSuppressorWaitPeriod: waitPeriod?.toSeconds(),
});

this.alarmName = this.getResourceNameAttribute(alarm.ref);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"version": "20.0.0",
"version": "21.0.0",
"files": {
"1f1d7f1c425488b9245a0ff851dae7650c25e5558781cc88a972edb6a36be237": {
"ad8a5012407e26a8fc0b1b169b0ab2373b8466d955070ee91a90193c5c70d1a4": {
"source": {
"path": "CompositeAlarmIntegrationTest.template.json",
"packaging": "file"
},
"destinations": {
"current_account-current_region": {
"bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}",
"objectKey": "1f1d7f1c425488b9245a0ff851dae7650c25e5558781cc88a972edb6a36be237.json",
"objectKey": "ad8a5012407e26a8fc0b1b169b0ab2373b8466d955070ee91a90193c5c70d1a4.json",
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}"
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,15 @@
"\")))) OR FALSE)"
]
]
}
},
"ActionsSuppressor": {
"Fn::GetAtt": [
"Alarm548383B2F",
"Arn"
]
},
"ActionsSuppressorExtensionPeriod": 60,
"ActionsSuppressorWaitPeriod": 60
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version":"20.0.0"}
{"version":"21.0.0"}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "20.0.0",
"version": "21.0.0",
"testCases": {
"integ.composite-alarm": {
"stacks": [
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "20.0.0",
"version": "21.0.0",
"artifacts": {
"Tree": {
"type": "cdk:tree",
Expand All @@ -23,7 +23,7 @@
"validateOnSynth": false,
"assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}",
"cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/1f1d7f1c425488b9245a0ff851dae7650c25e5558781cc88a972edb6a36be237.json",
"stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/ad8a5012407e26a8fc0b1b169b0ab2373b8466d955070ee91a90193c5c70d1a4.json",
"requiresBootstrapStackVersion": 6,
"bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version",
"additionalDependencies": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"path": "Tree",
"constructInfo": {
"fqn": "constructs.Construct",
"version": "10.1.85"
"version": "10.1.108"
}
},
"CompositeAlarmIntegrationTest": {
Expand Down Expand Up @@ -220,7 +220,15 @@
"\")))) OR FALSE)"
]
]
}
},
"actionsSuppressor": {
"Fn::GetAtt": [
"Alarm548383B2F",
"Arn"
]
},
"actionsSuppressorExtensionPeriod": 60,
"actionsSuppressorWaitPeriod": 60
}
},
"constructInfo": {
Expand All @@ -236,14 +244,14 @@
}
},
"constructInfo": {
"fqn": "constructs.Construct",
"version": "10.1.85"
"fqn": "@aws-cdk/core.Stack",
"version": "0.0.0"
}
}
},
"constructInfo": {
"fqn": "constructs.Construct",
"version": "10.1.85"
"fqn": "@aws-cdk/core.App",
"version": "0.0.0"
}
}
}
Loading

0 comments on commit dfe4673

Please sign in to comment.