From b0cdfd3fe1811eb614d4366c06beddafae48edcb 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 | 38 +++++++- 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, 152 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 c96de51f42279..c929d6cf6bc6b 100644 --- a/packages/@aws-cdk/aws-codedeploy/README.md +++ b/packages/@aws-cdk/aws-codedeploy/README.md @@ -59,6 +59,13 @@ const deploymentGroup = new codedeploy.ServerDeploymentGroup(this, 'CodeDeployDe stoppedDeployment: true, // default: true, deploymentInAlarm: true, // default: true, }, + // 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 a56e8780d049d..22b59e7c5e110 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'); @@ -216,7 +217,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 @@ -230,7 +231,26 @@ export interface ServerDeploymentGroupProps { */ onPremiseInstanceTags?: InstanceTagSet; + /** + * Auto-rollback configuration. + * + * @default roll back on all events + */ autoRollback?: IAutoRollbackConfig; + + /** + * The CloudWatch alarms associated with this Deployment Group. + * + * @default [] + */ + alarms?: cloudwatch.Alarm[]; + + /** + * Whether to continue a deployment even if fetching the alarm status from CloudWatch failed. + * + * @default false + */ + ignorePollAlarmsFailure?: boolean; } /** @@ -245,6 +265,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); @@ -266,6 +287,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, @@ -285,6 +308,15 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef { ec2TagSet: this.ec2TagSet(props.ec2InstanceTags), onPremisesTagSet: this.onPremiseTagSet(props.onPremiseInstanceTags), autoRollbackConfiguration: this.autoRollbackConfig(props.autoRollback || {}), + alarmConfiguration: new cdk.Token(() => + this.alarms.length === 0 + ? undefined + : { + alarms: this.alarms.map(a => ({ name: a.alarmName })), + enabled: true, + ignorePollAlarmsFailure: props.ignorePollAlarmsFailure, + }, + ), }); this.deploymentGroupName = resource.deploymentGroupName; @@ -297,6 +329,10 @@ export class ServerDeploymentGroup extends ServerDeploymentGroupRef { this.addCodeDeployAgentInstallUserData(asg); } + public addAlarm(alarm: cloudwatch.Alarm): void { + this.alarms.push(alarm); + } + public get autoScalingGroups(): autoscaling.AutoScalingGroup[] | undefined { return this._autoScalingGroups.slice(); } 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 d797af6327966..c53ccda17a415 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'); @@ -30,6 +31,16 @@ new codedeploy.ServerDeploymentGroup(stack, 'CodeDeployGroup', { stoppedDeployment: false, deploymentInAlarm: false, }, + 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(); + }, }, };