Skip to content

Commit

Permalink
feat(appconfig-alpha): support for composite alarms (#28156)
Browse files Browse the repository at this point in the history
Supporting composite alarm role creation on the AppConfig environment.

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
  • Loading branch information
chenjane-dev committed Dec 5, 2023
1 parent 0abd0b5 commit d19640b
Show file tree
Hide file tree
Showing 11 changed files with 447 additions and 15 deletions.
2 changes: 2 additions & 0 deletions packages/@aws-cdk/aws-appconfig-alpha/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,11 +356,13 @@ Basic environment with monitors:
```ts
declare const application: appconfig.Application;
declare const alarm: cloudwatch.Alarm;
declare const compositeAlarm: cloudwatch.CompositeAlarm;

new appconfig.Environment(this, 'MyEnvironment', {
application,
monitors: [
appconfig.Monitor.fromCloudWatchAlarm(alarm),
appconfig.Monitor.fromCloudWatchAlarm(compositeAlarm),
],
});
```
Expand Down
25 changes: 22 additions & 3 deletions packages/@aws-cdk/aws-appconfig-alpha/lib/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ export class Environment extends EnvironmentBase {
return {
alarmArn: monitor.alarmArn,
...(monitor.monitorType === MonitorType.CLOUDWATCH
? { alarmRoleArn: monitor.alarmRoleArn || this.createAlarmRole(monitor.alarmArn, index).roleArn }
? { alarmRoleArn: monitor.alarmRoleArn || this.createAlarmRole(monitor, index).roleArn }
: { alarmRoleArn: monitor.alarmRoleArn }),
};
}),
Expand All @@ -274,7 +274,20 @@ export class Environment extends EnvironmentBase {
this.application.addExistingEnvironment(this);
}

private createAlarmRole(alarmArn: string, index: number): iam.IRole {
private createAlarmRole(monitor: Monitor, index: number): iam.IRole {
const logicalId = monitor.isCompositeAlarm ? 'RoleCompositeAlarm' : `Role${index}`;
const existingRole = this.node.tryFindChild(logicalId) as iam.IRole;
if (existingRole) {
return existingRole;
}
const alarmArn = monitor.isCompositeAlarm
? this.stack.formatArn({
service: 'cloudwatch',
resource: 'alarm',
resourceName: '*',
arnFormat: ArnFormat.COLON_RESOURCE_NAME,
})
: monitor.alarmArn;
const policy = new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ['cloudwatch:DescribeAlarms'],
Expand All @@ -283,7 +296,7 @@ export class Environment extends EnvironmentBase {
const document = new iam.PolicyDocument({
statements: [policy],
});
const role = new iam.Role(this, `Role${index}`, {
const role = new iam.Role(this, logicalId, {
roleName: PhysicalName.GENERATE_IF_NEEDED,
assumedBy: new iam.ServicePrincipal('appconfig.amazonaws.com'),
inlinePolicies: {
Expand Down Expand Up @@ -325,6 +338,7 @@ export abstract class Monitor {
alarmArn: alarm.alarmArn,
alarmRoleArn: alarmRole?.roleArn,
monitorType: MonitorType.CLOUDWATCH,
isCompositeAlarm: alarm instanceof cloudwatch.CompositeAlarm,
};
}

Expand Down Expand Up @@ -355,6 +369,11 @@ export abstract class Monitor {
* The IAM role ARN for AWS AppConfig to view the alarm state.
*/
public abstract readonly alarmRoleArn?: string;

/**
* Indicates whether a CloudWatch alarm is a composite alarm.
*/
public abstract readonly isCompositeAlarm?: boolean;
}

export interface IEnvironment extends IResource {
Expand Down
181 changes: 180 additions & 1 deletion packages/@aws-cdk/aws-appconfig-alpha/test/environment.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as cdk from 'aws-cdk-lib';
import { App } from 'aws-cdk-lib';
import { Template } from 'aws-cdk-lib/assertions';
import { Alarm, Metric } from 'aws-cdk-lib/aws-cloudwatch';
import { Alarm, CompositeAlarm, Metric } from 'aws-cdk-lib/aws-cloudwatch';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Application, Environment, Monitor } from '../lib';

Expand Down Expand Up @@ -230,6 +230,185 @@ describe('environment', () => {
});
});

test('environment with composite alarm', () => {
const stack = new cdk.Stack();
const app = new Application(stack, 'MyAppConfig');
const alarm = new Alarm(stack, 'Alarm', {
threshold: 5,
evaluationPeriods: 5,
metric: new Metric(
{
namespace: 'aws',
metricName: 'myMetric',
},
),
});
const compositeAlarm = new CompositeAlarm(stack, 'MyCompositeAlarm', {
alarmRule: alarm,
});
const env = new Environment(stack, 'MyEnvironment', {
name: 'TestEnv',
application: app,
monitors: [
Monitor.fromCloudWatchAlarm(compositeAlarm),
],
});

expect(env).toBeDefined();
Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::Alarm', 1);
Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::CompositeAlarm', 1);
Template.fromStack(stack).hasResourceProperties('AWS::AppConfig::Environment', {
Name: 'TestEnv',
ApplicationId: {
Ref: 'MyAppConfigB4B63E75',
},
Monitors: [
{
AlarmArn: {
'Fn::GetAtt': [
'MyCompositeAlarm0F045229',
'Arn',
],
},
AlarmRoleArn: {
'Fn::GetAtt': [
'MyEnvironmentRoleCompositeAlarm8C2A0542',
'Arn',
],
},
},
],
});
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
Policies: [
{
PolicyDocument: {
Statement: [
{
Effect: iam.Effect.ALLOW,
Resource: {
'Fn::Join': [
'',
[
'arn:',
{ Ref: 'AWS::Partition' },
':cloudwatch:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':alarm:*',
],
],
},
Action: 'cloudwatch:DescribeAlarms',
},
],
},
PolicyName: 'AllowAppConfigMonitorAlarmPolicy',
},
],
});
});

test('environment with two composite alarms', () => {
const stack = new cdk.Stack();
const app = new Application(stack, 'MyAppConfig');
const alarm = new Alarm(stack, 'Alarm', {
threshold: 5,
evaluationPeriods: 5,
metric: new Metric(
{
namespace: 'aws',
metricName: 'myMetric',
},
),
});
const compositeAlarm1 = new CompositeAlarm(stack, 'MyCompositeAlarm1', {
alarmRule: alarm,
});
const compositeAlarm2 = new CompositeAlarm(stack, 'MyCompositeAlarm2', {
alarmRule: alarm,
});
const env = new Environment(stack, 'MyEnvironment', {
name: 'TestEnv',
application: app,
monitors: [
Monitor.fromCloudWatchAlarm(compositeAlarm1),
Monitor.fromCloudWatchAlarm(compositeAlarm2),
],
});

expect(env).toBeDefined();
Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::Alarm', 1);
Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::CompositeAlarm', 2);
Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 1);
Template.fromStack(stack).hasResourceProperties('AWS::AppConfig::Environment', {
Name: 'TestEnv',
ApplicationId: {
Ref: 'MyAppConfigB4B63E75',
},
Monitors: [
{
AlarmArn: {
'Fn::GetAtt': [
'MyCompositeAlarm159A950D0',
'Arn',
],
},
AlarmRoleArn: {
'Fn::GetAtt': [
'MyEnvironmentRoleCompositeAlarm8C2A0542',
'Arn',
],
},
},
{
AlarmArn: {
'Fn::GetAtt': [
'MyCompositeAlarm2195BFA48',
'Arn',
],
},
AlarmRoleArn: {
'Fn::GetAtt': [
'MyEnvironmentRoleCompositeAlarm8C2A0542',
'Arn',
],
},
},
],
});
Template.fromStack(stack).hasResourceProperties('AWS::IAM::Role', {
Policies: [
{
PolicyDocument: {
Statement: [
{
Effect: iam.Effect.ALLOW,
Resource: {
'Fn::Join': [
'',
[
'arn:',
{ Ref: 'AWS::Partition' },
':cloudwatch:',
{ Ref: 'AWS::Region' },
':',
{ Ref: 'AWS::AccountId' },
':alarm:*',
],
],
},
Action: 'cloudwatch:DescribeAlarms',
},
],
},
PolicyName: 'AllowAppConfigMonitorAlarmPolicy',
},
],
});
});

test('environment with monitors with two alarms', () => {
const stack = new cdk.Stack();
const app = new Application(stack, 'MyAppConfig');
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit d19640b

Please sign in to comment.