From 05f34535561b91b8cc8b37ae296cd7a9323230ca Mon Sep 17 00:00:00 2001 From: Jane Chen <125300057+chenjane-dev@users.noreply.github.com> Date: Thu, 26 Oct 2023 12:04:30 -0400 Subject: [PATCH] feat(appconfig): support for CfnMonitorsProperty in environments (#27680) Adding support to associate our L1 monitors constructs (CfnMonitorsProperty) to a L2 environment construct. **BREAKING CHANGE** To define monitors under an environment, you now need to call a static method on `Monitor`. Example: ``` new Environment(this, 'MyEnv', { .... monitors: [ Monitor.fromCloudWatchAlarm(...), Monitor.fromCfnMonitorProperty(...), ], }); ``` ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../@aws-cdk/aws-appconfig-alpha/README.md | 6 +- .../aws-appconfig-alpha/lib/configuration.ts | 8 + .../aws-appconfig-alpha/lib/environment.ts | 62 ++++++-- .../aws-appconfig-alpha/lib/extension.ts | 8 + .../test/environment.test.ts | 141 ++++++++++++------ .../aws-appconfig-environment.assets.json | 4 +- .../aws-appconfig-environment.template.json | 31 ++++ .../manifest.json | 10 +- .../integ.environment.js.snapshot/tree.json | 57 +++++++ .../test/integ.environment.ts | 14 +- 10 files changed, 278 insertions(+), 63 deletions(-) diff --git a/packages/@aws-cdk/aws-appconfig-alpha/README.md b/packages/@aws-cdk/aws-appconfig-alpha/README.md index 2f29be7bba89e..e5247dd5c7e59 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/README.md +++ b/packages/@aws-cdk/aws-appconfig-alpha/README.md @@ -330,11 +330,13 @@ declare const alarm: cloudwatch.Alarm; new appconfig.Environment(this, 'MyEnvironment', { application, monitors: [ - {alarm}, - ] + appconfig.Monitor.fromCloudWatchAlarm(alarm), + ], }); ``` +Environment monitors also support L1 CfnEnvironment.MonitorsProperty constructs. However, this is not the recommended approach for CloudWatch alarms because a role will not be auto-generated if not provided. + ## Extension An extension augments your ability to inject logic or behavior at different points during the AWS AppConfig workflow of creating or deploying a configuration. diff --git a/packages/@aws-cdk/aws-appconfig-alpha/lib/configuration.ts b/packages/@aws-cdk/aws-appconfig-alpha/lib/configuration.ts index 51f36b406dd9c..de7f0668d6980 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/lib/configuration.ts +++ b/packages/@aws-cdk/aws-appconfig-alpha/lib/configuration.ts @@ -335,11 +335,15 @@ export interface HostedConfigurationOptions extends ConfigurationOptions { /** * The latest version number of the hosted configuration. + * + * @default - None. */ readonly latestVersionNumber?: number; /** * The version label of the hosted configuration. + * + * @default - None. */ readonly versionLabel?: string; } @@ -352,11 +356,15 @@ export interface HostedConfigurationProps extends ConfigurationProps { /** * The latest version number of the hosted configuration. + * + * @default - None. */ readonly latestVersionNumber?: number; /** * The version label of the hosted configuration. + * + * @default - None. */ readonly versionLabel?: string; } diff --git a/packages/@aws-cdk/aws-appconfig-alpha/lib/environment.ts b/packages/@aws-cdk/aws-appconfig-alpha/lib/environment.ts index fdcd7a29d92b6..cbe4c1ebbebb7 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/lib/environment.ts +++ b/packages/@aws-cdk/aws-appconfig-alpha/lib/environment.ts @@ -22,16 +22,22 @@ export interface EnvironmentAttributes { /** * The name of the environment. + * + * @default - None. */ readonly name?: string; /** * The description of the environment. + * + * @default - None. */ readonly description?: string; /** * The monitors for the environment. + * + * @default - None. */ readonly monitors?: Monitor[]; } @@ -238,8 +244,10 @@ export class Environment extends EnvironmentBase { description: this.description, monitors: this.monitors?.map((monitor, index) => { return { - alarmArn: monitor.alarm.alarmArn, - alarmRoleArn: monitor.alarmRole?.roleArn || this.createAlarmRole(monitor.alarm.alarmArn, index).roleArn, + alarmArn: monitor.alarmArn, + ...(monitor.monitorType === MonitorType.CLOUDWATCH + ? { alarmRoleArn: monitor.alarmRoleArn || this.createAlarmRole(monitor.alarmArn, index).roleArn } + : { alarmRoleArn: monitor.alarmRoleArn }), }; }), }); @@ -276,21 +284,57 @@ export class Environment extends EnvironmentBase { } } +export enum MonitorType { + CLOUDWATCH, + CFN_MONITORS_PROPERTY, +} + /** * Defines monitors that will be associated with an AWS AppConfig environment. */ -export interface Monitor { +export abstract class Monitor { /** - * The Amazon CloudWatch alarm. - */ - readonly alarm: cloudwatch.IAlarm; + * Creates a Monitor from a CloudWatch alarm. If the alarm role is not specified, a role will + * be generated. + * + * @param alarm The Amazon CloudWatch alarm. + * @param alarmRole The IAM role for AWS AppConfig to view the alarm state. + */ + public static fromCloudWatchAlarm(alarm: cloudwatch.IAlarm, alarmRole?: iam.IRole): Monitor { + return { + alarmArn: alarm.alarmArn, + alarmRoleArn: alarmRole?.roleArn, + monitorType: MonitorType.CLOUDWATCH, + }; + } /** - * The IAM role for AWS AppConfig to view the alarm state. + * Creates a Monitor from a CfnEnvironment.MonitorsProperty construct. * - * @default - A role is generated. + * @param monitorsProperty The monitors property. + */ + public static fromCfnMonitorsProperty(monitorsProperty: CfnEnvironment.MonitorsProperty): Monitor { + return { + alarmArn: monitorsProperty.alarmArn!, + alarmRoleArn: monitorsProperty.alarmRoleArn, + monitorType: MonitorType.CFN_MONITORS_PROPERTY, + }; + } + + /** + * The alarm ARN for AWS AppConfig to monitor. + */ + public abstract readonly alarmArn: string; + + /** + * The type of monitor. + */ + public abstract readonly monitorType: MonitorType; + + /** + * The IAM role ARN for AWS AppConfig to view the alarm state. */ - readonly alarmRole?: iam.IRole; + public abstract readonly alarmRoleArn?: string; } export interface IEnvironment extends IResource { diff --git a/packages/@aws-cdk/aws-appconfig-alpha/lib/extension.ts b/packages/@aws-cdk/aws-appconfig-alpha/lib/extension.ts index 307f4537bd407..d378214ffc799 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/lib/extension.ts +++ b/packages/@aws-cdk/aws-appconfig-alpha/lib/extension.ts @@ -290,21 +290,29 @@ export interface ExtensionAttributes { /** * The Amazon Resource Name (ARN) of the extension. + * + * @default - The extension ARN is generated. */ readonly extensionArn?: string; /** * The actions of the extension. + * + * @default - None. */ readonly actions?: Action[]; /** * The name of the extension. + * + * @default - None. */ readonly name?: string; /** * The description of the extension. + * + * @default - None. */ readonly description?: string; } diff --git a/packages/@aws-cdk/aws-appconfig-alpha/test/environment.test.ts b/packages/@aws-cdk/aws-appconfig-alpha/test/environment.test.ts index ffb8c092fa820..a7426b2e2e3e7 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/test/environment.test.ts +++ b/packages/@aws-cdk/aws-appconfig-alpha/test/environment.test.ts @@ -3,7 +3,7 @@ import { App } from 'aws-cdk-lib'; import { Template } from 'aws-cdk-lib/assertions'; import { Alarm, Metric } from 'aws-cdk-lib/aws-cloudwatch'; import * as iam from 'aws-cdk-lib/aws-iam'; -import { Application, Environment } from '../lib'; +import { Application, Environment, Monitor } from '../lib'; describe('environment', () => { test('default environment', () => { @@ -58,26 +58,23 @@ describe('environment', () => { test('environment with monitors with alarm and alarmRole', () => { 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 alarmRole = new iam.Role(stack, 'Role', { + assumedBy: new iam.ServicePrincipal('appconfig.amazonaws.com'), + }); const env = new Environment(stack, 'MyEnvironment', { name: 'TestEnv', application: app, - monitors: [ - { - alarm: new Alarm(stack, 'Alarm', { - threshold: 5, - evaluationPeriods: 5, - metric: new Metric( - { - namespace: 'aws', - metricName: 'myMetric', - }, - ), - }), - alarmRole: new iam.Role(stack, 'Role', { - assumedBy: new iam.ServicePrincipal('appconfig.amazonaws.com'), - }), - }, - ], + monitors: [Monitor.fromCloudWatchAlarm(alarm, alarmRole)], }); Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::Alarm', 1); @@ -123,11 +120,7 @@ describe('environment', () => { const env = new Environment(stack, 'MyEnvironment', { name: 'TestEnv', application: app, - monitors: [ - { - alarm, - }, - ], + monitors: [Monitor.fromCloudWatchAlarm(alarm)], }); expect(env).toBeDefined(); @@ -177,39 +170,97 @@ describe('environment', () => { }); }); - test('environment with monitors with two alarms', () => { + test('environment with CfnMonitorsProperty monitor', () => { const stack = new cdk.Stack(); const app = new Application(stack, 'MyAppConfig'); - new Environment(stack, 'MyEnvironment', { + const env = new Environment(stack, 'MyEnvironment', { name: 'TestEnv', application: app, monitors: [ + Monitor.fromCfnMonitorsProperty({ + alarmArn: 'thisismyalarm', + }), + ], + }); + + expect(env).toBeDefined(); + Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::Alarm', 0); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 0); + Template.fromStack(stack).hasResourceProperties('AWS::AppConfig::Environment', { + Name: 'TestEnv', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + Monitors: [ { - alarm: new Alarm(stack, 'Alarm1', { - threshold: 5, - evaluationPeriods: 5, - metric: new Metric( - { - namespace: 'aws', - metricName: 'myMetric', - }, - ), - }), + AlarmArn: 'thisismyalarm', }, + ], + }); + }); + + test('environment with CfnMonitorsProperty monitor with roleArn', () => { + const stack = new cdk.Stack(); + const app = new Application(stack, 'MyAppConfig'); + const env = new Environment(stack, 'MyEnvironment', { + name: 'TestEnv', + application: app, + monitors: [ + Monitor.fromCfnMonitorsProperty({ + alarmArn: 'thisismyalarm', + alarmRoleArn: 'thisismyalarmrolearn', + }), + ], + }); + + expect(env).toBeDefined(); + Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::Alarm', 0); + Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 0); + Template.fromStack(stack).hasResourceProperties('AWS::AppConfig::Environment', { + Name: 'TestEnv', + ApplicationId: { + Ref: 'MyAppConfigB4B63E75', + }, + Monitors: [ { - alarm: new Alarm(stack, 'Alarm2', { - threshold: 5, - evaluationPeriods: 5, - metric: new Metric( - { - namespace: 'aws', - metricName: 'myMetric', - }, - ), - }), + AlarmArn: 'thisismyalarm', + AlarmRoleArn: 'thisismyalarmrolearn', }, ], }); + }); + + test('environment with monitors with two alarms', () => { + const stack = new cdk.Stack(); + const app = new Application(stack, 'MyAppConfig'); + const alarm1 = new Alarm(stack, 'Alarm1', { + threshold: 5, + evaluationPeriods: 5, + metric: new Metric( + { + namespace: 'aws', + metricName: 'myMetric', + }, + ), + }); + const alarm2 = new Alarm(stack, 'Alarm2', { + threshold: 5, + evaluationPeriods: 5, + metric: new Metric( + { + namespace: 'aws', + metricName: 'myMetric', + }, + ), + }); + new Environment(stack, 'MyEnvironment', { + name: 'TestEnv', + application: app, + monitors: [ + Monitor.fromCloudWatchAlarm(alarm1), + Monitor.fromCloudWatchAlarm(alarm2), + ], + }); Template.fromStack(stack).resourceCountIs('AWS::CloudWatch::Alarm', 2); Template.fromStack(stack).resourceCountIs('AWS::IAM::Role', 2); diff --git a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.assets.json b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.assets.json index 8a91333b6e2d2..89f92e7bd5dc4 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.assets.json +++ b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.assets.json @@ -1,7 +1,7 @@ { "version": "34.0.0", "files": { - "c5abc7ef2e341477218fc19664c82ee2ea13e9509f2deaae6fc6200a619a5d1c": { + "ea755ac1ccc8e2c8816cf7e0a2b3789e472a166174037cb50eb45ec4ed621ba4": { "source": { "path": "aws-appconfig-environment.template.json", "packaging": "file" @@ -9,7 +9,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "c5abc7ef2e341477218fc19664c82ee2ea13e9509f2deaae6fc6200a619a5d1c.json", + "objectKey": "ea755ac1ccc8e2c8816cf7e0a2b3789e472a166174037cb50eb45ec4ed621ba4.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.template.json b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.template.json index e120019dcabe1..c3951447345b9 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.template.json +++ b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/aws-appconfig-environment.template.json @@ -18,6 +18,23 @@ "Threshold": 10 } }, + "MyRoleF48FFE04": { + "Type": "AWS::IAM::Role", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appconfig.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, "MyEnvironmentRole01C8C013F": { "Type": "AWS::IAM::Role", "Properties": { @@ -76,6 +93,20 @@ "Arn" ] } + }, + { + "AlarmArn": { + "Fn::GetAtt": [ + "MyAlarm696658B6", + "Arn" + ] + }, + "AlarmRoleArn": { + "Fn::GetAtt": [ + "MyRoleF48FFE04", + "Arn" + ] + } } ], "Name": "awsappconfigenvironment-MyEnvironment-C8813182" diff --git a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/manifest.json b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/manifest.json index 6f758fc4db657..4c038cd5bcd81 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/manifest.json +++ b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/manifest.json @@ -14,10 +14,11 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "aws-appconfig-environment.template.json", + "terminationProtection": false, "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}/c5abc7ef2e341477218fc19664c82ee2ea13e9509f2deaae6fc6200a619a5d1c.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/ea755ac1ccc8e2c8816cf7e0a2b3789e472a166174037cb50eb45ec4ed621ba4.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -45,6 +46,12 @@ "data": "MyAlarm696658B6" } ], + "/aws-appconfig-environment/MyRole/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyRoleF48FFE04" + } + ], "/aws-appconfig-environment/MyEnvironment/Role0/Resource": [ { "type": "aws:cdk:logicalId", @@ -85,6 +92,7 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "appconfigenvironmentDefaultTestDeployAssert75BD28E7.template.json", + "terminationProtection": false, "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}", diff --git a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/tree.json b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/tree.json index 730c4e7fb0302..9b1d723134bbf 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/tree.json +++ b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.js.snapshot/tree.json @@ -62,6 +62,49 @@ "version": "0.0.0" } }, + "MyRole": { + "id": "MyRole", + "path": "aws-appconfig-environment/MyRole", + "children": { + "ImportMyRole": { + "id": "ImportMyRole", + "path": "aws-appconfig-environment/MyRole/ImportMyRole", + "constructInfo": { + "fqn": "aws-cdk-lib.Resource", + "version": "0.0.0" + } + }, + "Resource": { + "id": "Resource", + "path": "aws-appconfig-environment/MyRole/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::IAM::Role", + "aws:cdk:cloudformation:props": { + "assumeRolePolicyDocument": { + "Statement": [ + { + "Action": "sts:AssumeRole", + "Effect": "Allow", + "Principal": { + "Service": "appconfig.amazonaws.com" + } + } + ], + "Version": "2012-10-17" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.CfnRole", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_iam.Role", + "version": "0.0.0" + } + }, "MyEnvironment": { "id": "MyEnvironment", "path": "aws-appconfig-environment/MyEnvironment", @@ -153,6 +196,20 @@ "Arn" ] } + }, + { + "alarmArn": { + "Fn::GetAtt": [ + "MyAlarm696658B6", + "Arn" + ] + }, + "alarmRoleArn": { + "Fn::GetAtt": [ + "MyRoleF48FFE04", + "Arn" + ] + } } ], "name": "awsappconfigenvironment-MyEnvironment-C8813182" diff --git a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.ts b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.ts index 2d19953e3d59c..3716997e2253e 100644 --- a/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.ts +++ b/packages/@aws-cdk/aws-appconfig-alpha/test/integ.environment.ts @@ -1,7 +1,8 @@ import { IntegTest } from '@aws-cdk/integ-tests-alpha'; import { App, Stack } from 'aws-cdk-lib'; -import { Application, Environment } from '../lib'; +import { Application, Environment, Monitor } from '../lib'; import { Alarm, Metric } from 'aws-cdk-lib/aws-cloudwatch'; +import { Role, ServicePrincipal } from 'aws-cdk-lib/aws-iam'; const app = new App(); @@ -19,15 +20,20 @@ const alarm = new Alarm(stack, 'MyAlarm', { evaluationPeriods: 5, threshold: 10, }); +const role = new Role(stack, 'MyRole', { + assumedBy: new ServicePrincipal('appconfig.amazonaws.com'), +}); // create environment with all props defined new Environment(stack, 'MyEnvironment', { application: appForEnv, description: 'This is the environment for integ testing', monitors: [ - { - alarm, - }, + Monitor.fromCloudWatchAlarm(alarm), + Monitor.fromCfnMonitorsProperty({ + alarmArn: alarm.alarmArn, + alarmRoleArn: role.roleArn, + }), ], });