From bf6744118be698f5a46d11a6a738d489dce74493 Mon Sep 17 00:00:00 2001 From: Adam Ruka Date: Fri, 12 Oct 2018 18:10:50 -0700 Subject: [PATCH] feat(aws-codedeploy): add support for setting CloudWatch alarms on a server Deployment Group. --- packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts | 6 ++ packages/@aws-cdk/aws-codedeploy/README.md | 7 ++ .../aws-codedeploy/lib/deployment-group.ts | 53 ++++++++++- packages/@aws-cdk/aws-codedeploy/package.json | 1 + .../test/integ.deployment-group.expected.json | 94 ++++++++++++------- .../test/integ.deployment-group.ts | 11 +++ .../test/test.deployment-group.ts | 32 +++++++ 7 files changed, 167 insertions(+), 37 deletions(-) diff --git a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts index 710b9bb0bef73..b88c1a9abdad5 100644 --- a/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts +++ b/packages/@aws-cdk/aws-cloudwatch/lib/alarm.ts @@ -121,6 +121,11 @@ export class Alarm extends Construct { */ public readonly alarmArn: string; + /** + * Name of this alarm. + */ + public readonly alarmName: string; + /** * The metric object this alarm was based on */ @@ -163,6 +168,7 @@ export class Alarm extends Construct { }); this.alarmArn = alarm.alarmArn; + this.alarmName = alarm.alarmName; this.metric = props.metric; this.annotation = { // tslint:disable-next-line:max-line-length diff --git a/packages/@aws-cdk/aws-codedeploy/README.md b/packages/@aws-cdk/aws-codedeploy/README.md index d832cc267c814..27f02ba6af705 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -53,6 +53,13 @@ const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'CodeDeployDe 'key2': ['v3'], }, ), + // CloudWatch alarms + alarms: [ + new cloudwatch.Alarm(/* ... */), + ], + // whether to ignore failure to fetch the status of alarms from CloudWatch + // default: false + ignorePollAlarmsFailure: false, }); ``` diff --git a/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts index e6c1eac4876f1..0d34e3692725a 100644 --- a/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/lib/deployment-group.ts @@ -1,4 +1,5 @@ import autoscaling = require("@aws-cdk/aws-autoscaling"); +import cloudwatch = require("@aws-cdk/aws-cloudwatch"); import codedeploylb = require("@aws-cdk/aws-codedeploy-api"); import ec2 = require("@aws-cdk/aws-ec2"); import iam = require('@aws-cdk/aws-iam'); @@ -167,6 +168,8 @@ export interface ServerDeploymentGroupProps { /** * The auto-scaling groups belonging to this Deployment Group. * + * Auto-scaling groups can also be added after the Deployment Group is created using the {@link #addAutoScalingGroup} method. + * * @default [] */ autoScalingGroups?: autoscaling.AutoScalingGroup[]; @@ -189,7 +192,7 @@ export interface ServerDeploymentGroupProps { */ loadBalancer?: codedeploylb.ILoadBalancer; - /* + /** * All EC2 instances matching the given set of tags when a deployment occurs will be added to this Deployment Group. * * @default no additional EC2 instances will be added to the Deployment Group @@ -202,6 +205,25 @@ export interface ServerDeploymentGroupProps { * @default no additional on-premise instances will be added to the Deployment Group */ onPremiseInstanceTags?: InstanceTagSet; + + /** + * The CloudWatch alarms associated with this Deployment Group. + * CodeDeploy will stop (and optionally roll back) + * a deployment if during it any of the alarms trigger. + * + * Alarms can also be added after the Deployment Group is created using the {@link #addAlarm} method. + * + * @default [] + * @see https://docs.aws.amazon.com/codedeploy/latest/userguide/monitoring-create-alarms.html + */ + alarms?: cloudwatch.Alarm[]; + + /** + * Whether to continue a deployment even if fetching the alarm status from CloudWatch failed. + * + * @default false + */ + ignorePollAlarmsFailure?: boolean; } /** @@ -216,6 +238,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef { private readonly _autoScalingGroups: autoscaling.AutoScalingGroup[]; private readonly installAgent: boolean; private readonly codeDeployBucket: s3.BucketRef; + private readonly alarms: cloudwatch.Alarm[]; constructor(parent: cdk.Construct, id: string, props: ServerDeploymentGroupProps = {}) { super(parent, id, props.deploymentConfig); @@ -237,6 +260,8 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef { this.addCodeDeployAgentInstallUserData(asg); } + this.alarms = props.alarms || []; + const resource = new cloudformation.DeploymentGroupResource(this, 'Resource', { applicationName: this.application.applicationName, deploymentGroupName: props.deploymentGroupName, @@ -255,6 +280,7 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef { }, ec2TagSet: this.ec2TagSet(props.ec2InstanceTags), onPremisesTagSet: this.onPremiseTagSet(props.onPremiseInstanceTags), + alarmConfiguration: new cdk.Token(() => this.renderAlarmConfiguration(props.ignorePollAlarmsFailure)), }); this.deploymentGroupName = resource.deploymentGroupName; @@ -262,11 +288,25 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef { this.deploymentGroupName); } + /** + * Adds an additional auto-scaling group to this Deployment Group. + * + * @param asg the auto-scaling group to add to this Deployment Group + */ public addAutoScalingGroup(asg: autoscaling.AutoScalingGroup): void { this._autoScalingGroups.push(asg); this.addCodeDeployAgentInstallUserData(asg); } + /** + * Associates an additional alarm with this Deployment Group. + * + * @param alarm the alarm to associate with this Deployment Group + */ + public addAlarm(alarm: cloudwatch.Alarm): void { + this.alarms.push(alarm); + } + public get autoScalingGroups(): autoscaling.AutoScalingGroup[] | undefined { return this._autoScalingGroups.slice(); } @@ -404,6 +444,17 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef { } return tagsInGroup; } + + private renderAlarmConfiguration(ignorePollAlarmFailure?: boolean): + cloudformation.DeploymentGroupResource.AlarmConfigurationProperty | undefined { + return this.alarms.length === 0 + ? undefined + : { + alarms: this.alarms.map(a => ({ name: a.alarmName })), + enabled: true, + ignorePollAlarmFailure, + }; + } } function deploymentGroupName2Arn(applicationName: string, deploymentGroupName: string): string { diff --git a/packages/@aws-cdk/aws-codedeploy/package.json b/packages/@aws-cdk/aws-codedeploy/package.json index daa021b9ca3f0..c37801876ae0d 100644 --- a/packages/@aws-cdk/aws-codedeploy/package.json +++ b/packages/@aws-cdk/aws-codedeploy/package.json @@ -65,6 +65,7 @@ "@aws-cdk/aws-autoscaling": "^0.12.0", "@aws-cdk/aws-codedeploy-api": "^0.12.0", "@aws-cdk/aws-codepipeline-api": "^0.12.0", + "@aws-cdk/aws-cloudwatch": "^0.12.0", "@aws-cdk/aws-iam": "^0.12.0", "@aws-cdk/aws-s3": "^0.12.0", "@aws-cdk/cdk": "^0.12.0" diff --git a/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json b/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json index cf70ed442b8c3..78d6d61b60339 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json +++ b/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.expected.json @@ -57,6 +57,18 @@ } } }, + "VPCPublicSubnet1DefaultRoute91CEF279": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet1RouteTableFEE4B781" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet1EIP6AD938E8": { "Type": "AWS::EC2::EIP", "Properties": { @@ -83,18 +95,6 @@ ] } }, - "VPCPublicSubnet1DefaultRoute91CEF279": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet1RouteTableFEE4B781" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet2Subnet74179F39": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -137,6 +137,18 @@ } } }, + "VPCPublicSubnet2DefaultRouteB7481BBA": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet2EIP4947BC00": { "Type": "AWS::EC2::EIP", "Properties": { @@ -163,18 +175,6 @@ ] } }, - "VPCPublicSubnet2DefaultRouteB7481BBA": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet2RouteTable6F1A15F1" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPublicSubnet3Subnet631C5E25": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -217,6 +217,18 @@ } } }, + "VPCPublicSubnet3DefaultRouteA0D29D46": { + "Type": "AWS::EC2::Route", + "Properties": { + "RouteTableId": { + "Ref": "VPCPublicSubnet3RouteTable98AE0E14" + }, + "DestinationCidrBlock": "0.0.0.0/0", + "GatewayId": { + "Ref": "VPCIGWB7E252D3" + } + } + }, "VPCPublicSubnet3EIPAD4BC883": { "Type": "AWS::EC2::EIP", "Properties": { @@ -243,18 +255,6 @@ ] } }, - "VPCPublicSubnet3DefaultRouteA0D29D46": { - "Type": "AWS::EC2::Route", - "Properties": { - "RouteTableId": { - "Ref": "VPCPublicSubnet3RouteTable98AE0E14" - }, - "DestinationCidrBlock": "0.0.0.0/0", - "GatewayId": { - "Ref": "VPCIGWB7E252D3" - } - } - }, "VPCPrivateSubnet1Subnet8BCA10E0": { "Type": "AWS::EC2::Subnet", "Properties": { @@ -712,6 +712,18 @@ ] } }, + "Alarm1F9009D71": { + "Type": "AWS::CloudWatch::Alarm", + "Properties": { + "ComparisonOperator": "GreaterThanOrEqualToThreshold", + "EvaluationPeriods": 1, + "MetricName": "Errors", + "Namespace": "my.namespace", + "Period": 300, + "Threshold": 1, + "Statistic": "Average" + } + }, "CodeDeployGroupApplication13EFBDA6": { "Type": "AWS::CodeDeploy::Application", "Properties": { @@ -750,6 +762,16 @@ "Arn" ] }, + "AlarmConfiguration": { + "Alarms": [ + { + "Name": { + "Ref": "Alarm1F9009D71" + } + } + ], + "Enabled": true + }, "AutoScalingGroups": [ { "Ref": "ASG46ED3070" diff --git a/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.ts index e62dd1b6ecc52..3035bc22aa089 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/integ.deployment-group.ts @@ -1,4 +1,5 @@ import autoscaling = require('@aws-cdk/aws-autoscaling'); +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import lb = require('@aws-cdk/aws-elasticloadbalancing'); import cdk = require('@aws-cdk/cdk'); @@ -25,6 +26,16 @@ new codedeploy.ServerDeploymentGroup(stack, 'CodeDeployGroup', { deploymentConfig: codedeploy.ServerDeploymentConfig.AllAtOnce, autoScalingGroups: [asg], loadBalancer: elb, + alarms: [ + new cloudwatch.Alarm(stack, 'Alarm1', { + metric: new cloudwatch.Metric({ + metricName: 'Errors', + namespace: 'my.namespace', + }), + threshold: 1, + evaluationPeriods: 1, + }), + ], }); app.run(); diff --git a/packages/@aws-cdk/aws-codedeploy/test/test.deployment-group.ts b/packages/@aws-cdk/aws-codedeploy/test/test.deployment-group.ts index a1d72b54e6834..9ce6a26947e3b 100644 --- a/packages/@aws-cdk/aws-codedeploy/test/test.deployment-group.ts +++ b/packages/@aws-cdk/aws-codedeploy/test/test.deployment-group.ts @@ -1,5 +1,6 @@ import { expect, haveResource } from '@aws-cdk/assert'; import autoscaling = require('@aws-cdk/aws-autoscaling'); +import cloudwatch = require('@aws-cdk/aws-cloudwatch'); import ec2 = require('@aws-cdk/aws-ec2'); import lbv2 = require('@aws-cdk/aws-elasticloadbalancingv2'); import cdk = require('@aws-cdk/cdk'); @@ -264,5 +265,36 @@ export = { test.done(); }, + + 'can have alarms added to it after being created'(test: Test) { + const stack = new cdk.Stack(); + + const alarm = new cloudwatch.Alarm(stack, 'Alarm1', { + metric: new cloudwatch.Metric({ + metricName: 'Errors', + namespace: 'my.namespace', + }), + threshold: 1, + evaluationPeriods: 1, + }); + + const deploymentGroup = new codedeploy.ServerDeploymentGroup(stack, 'DeploymentGroup'); + deploymentGroup.addAlarm(alarm); + + expect(stack).to(haveResource('AWS::CodeDeploy::DeploymentGroup', { + "AlarmConfiguration": { + "Alarms": [ + { + "Name": { + "Ref": "Alarm1F9009D71", + }, + }, + ], + "Enabled": true, + }, + })); + + test.done(); + }, }, };